diff --git a/.travis.yml b/.travis.yml index e7aa1ba1ee7..056fa03710c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,15 +9,4 @@ before_install: - echo "MAVEN_OPTS='-Xmx2g'" > ~/.mavenrc cache: directories: - - $HOME/.m2 -install: > - mvn - install - jacoco:prepare-agent - --define jacoco.skip=false - --define maven.javadoc.skip=true - --define skipTests=true - --batch-mode - --show-version -after_success: - - mvn jacoco:report jacoco:report-aggregate verify coveralls:report --define jacoco.skip=false + - $HOME/.m2 \ No newline at end of file diff --git a/Mage.Client/pom.xml b/Mage.Client/pom.xml index 0ff115a1879..3dbe81fc391 100644 --- a/Mage.Client/pom.xml +++ b/Mage.Client/pom.xml @@ -14,7 +14,6 @@ Mage Client - org.mage mage @@ -31,102 +30,97 @@ ${project.version} - com.googlecode.jspf - jspf-core - 0.9.1 - - - log4j - log4j - - - org.slf4j - slf4j-log4j12 + ${project.groupId} + mage-counter-plugin + 0.1 + runtime - + com.google.guava + guava + + + net.java.truevfs + truevfs-profile-base + + + org.unbescape + unbescape + + + + net.sf.trove4j trove4j 3.0.3 + + com.googlecode.jspf + jspf-core + 0.9.1 + + + + + com.mortennobel java-image-scaling 0.8.6 - com.google.guava - guava - - + + org.swinglabs swingx 1.6.1 - org.jetlang - jetlang - 0.2.23 - - - com.amazonaws - aws-java-sdk-s3 - 1.11.827 - - - com.jgoodies - forms - 1.2.1 - - - com.intellij - forms_rt - 7.0.3 - - - junit - junit - jar - test - - - ${project.groupId} - mage-counter-plugin - 0.1 - runtime - - - org.jdesktop - beansbinding - 1.2.1 - - + org.swinglabs swing-layout 1.0.3 - org.jsoup - jsoup - 1.14.2 + + org.jetlang + jetlang + 0.2.23 - truevfs-profile-base - net.java.truevfs - jar - 0.11.1 - - - truevfs-access-swing - net.java.truevfs - - - truecommons-key-swing - net.java.truecommons - - + + + com.amazonaws + aws-java-sdk-s3 + 1.12.78 + + + + + com.jgoodies + forms + 1.2.1 + + + + com.intellij + forms_rt + 7.0.3 + + + + + org.jdesktop + beansbinding + 1.2.1 + + + + org.jsoup + jsoup + 1.14.3 @@ -148,6 +142,7 @@ + net.java.balloontip balloontip 1.2.4.1 @@ -166,15 +161,11 @@ + org.ocpsoft.prettytime prettytime 4.0.6.Final - - org.unbescape - unbescape - 1.1.6.RELEASE - diff --git a/Mage.Client/src/main/java/mage/client/cards/CardArea.java b/Mage.Client/src/main/java/mage/client/cards/CardArea.java index ad8be6e3dee..4b73077031b 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardArea.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardArea.java @@ -186,14 +186,22 @@ public class CardArea extends JPanel implements CardEventProducer { card = tmp; } - CardIconRenderSettings customIconsRender = new CardIconRenderSettings() - .withDebugMode(true) - .withCustomPosition(customCardIconPosition) - .withCustomOrder(customCardIconOrder) - .withCustomColor(customCardIconColor) - .withCustomMaxVisibleCount(customCardIconsMaxVisibleCount) - .withCustomIconSizePercent(30); - MageCard cardPanel = Plugins.instance.getMageCard(card, bigCard, customIconsRender, cardDimension, gameId, true, true, + CardIconRenderSettings currentIconsRender; + if (this.customRenderMode >= 0) { + // debug + currentIconsRender = new CardIconRenderSettings() + .withDebugMode(true) + .withCustomPosition(customCardIconPosition) + .withCustomOrder(customCardIconOrder) + .withCustomColor(customCardIconColor) + .withCustomMaxVisibleCount(customCardIconsMaxVisibleCount) + .withCustomIconSizePercent(30); + } else { + // default + currentIconsRender = new CardIconRenderSettings(); + } + + MageCard cardPanel = Plugins.instance.getMageCard(card, bigCard, currentIconsRender, cardDimension, gameId, true, true, customRenderMode != -1 ? customRenderMode : PreferencesDialog.getRenderMode(), customNeedFullPermanentRender); cardPanel.setCardContainerRef(this); cardPanel.update(card); diff --git a/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java b/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java index 648c17bd622..855f8f13b43 100644 --- a/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java +++ b/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.*; /** - * Dialog for choosing abilities. + * GUI: Dialog for choosing abilities (list) * * @author nantuko, JayDi85 */ diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form index c2f5942521f..ebea76ef656 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form @@ -4378,11 +4378,11 @@ - + - + @@ -4416,8 +4416,8 @@ - - + + @@ -4453,7 +4453,7 @@ - + @@ -4469,7 +4469,7 @@ - + diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index 6efba3c286f..cfd05aa198c 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -81,7 +81,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_CARD_IMAGES_THREADS = "cardImagesThreads"; public static final String KEY_CARD_IMAGES_THREADS_DEFAULT = "3"; public static final String KEY_CARD_IMAGES_SAVE_TO_ZIP = "cardImagesSaveToZip"; - public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferedImageLaguage"; + public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferredImageLaguage"; public static final String KEY_CARD_RENDERING_FALLBACK = "cardRenderingFallback"; public static final String KEY_CARD_RENDERING_ICONS_FOR_ABILITIES = "cardRenderingIconsForAbilities"; @@ -400,7 +400,7 @@ public class PreferencesDialog extends javax.swing.JDialog { cbTheme.setModel(new DefaultComboBoxModel<>(ThemeType.values())); addAvatars(); - cbPreferedImageLanguage.setModel(new DefaultComboBoxModel<>(CardLanguage.toList())); + cbPreferredImageLanguage.setModel(new DefaultComboBoxModel<>(CardLanguage.toList())); cbNumberOfDownloadThreads.setModel(new DefaultComboBoxModel<>(new String[]{"10", "9", "8", "7", "6", "5", "4", "3", "2", "1"})); } @@ -509,8 +509,8 @@ public class PreferencesDialog extends javax.swing.JDialog { txtImageFolderPath = new javax.swing.JTextField(); btnBrowseImageLocation = new javax.swing.JButton(); cbSaveToZipFiles = new javax.swing.JCheckBox(); - cbPreferedImageLanguage = new javax.swing.JComboBox<>(); - labelPreferedImageLanguage = new javax.swing.JLabel(); + cbPreferredImageLanguage = new javax.swing.JComboBox<>(); + labelPreferredImageLanguage = new javax.swing.JLabel(); labelNumberOfDownloadThreads = new javax.swing.JLabel(); cbNumberOfDownloadThreads = new javax.swing.JComboBox(); labelHint1 = new javax.swing.JLabel(); @@ -1658,11 +1658,11 @@ public class PreferencesDialog extends javax.swing.JDialog { } }); - cbPreferedImageLanguage.setMaximumRowCount(20); - cbPreferedImageLanguage.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); + cbPreferredImageLanguage.setMaximumRowCount(20); + cbPreferredImageLanguage.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); - labelPreferedImageLanguage.setText("Default images language:"); - labelPreferedImageLanguage.setFocusable(false); + labelPreferredImageLanguage.setText("Default images language:"); + labelPreferredImageLanguage.setFocusable(false); labelNumberOfDownloadThreads.setText("Default download threads:"); @@ -1689,10 +1689,10 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(panelCardImagesLayout.createSequentialGroup() .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(labelNumberOfDownloadThreads) - .add(labelPreferedImageLanguage)) + .add(labelPreferredImageLanguage)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(cbPreferedImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(panelCardImagesLayout.createSequentialGroup() .add(cbNumberOfDownloadThreads, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) @@ -1716,8 +1716,8 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(labelHint1)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) - .add(labelPreferedImageLanguage) - .add(cbPreferedImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) + .add(labelPreferredImageLanguage) + .add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) ); panelCardStyles.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Card styles (restart xmage to apply new settings)")); @@ -2945,7 +2945,7 @@ public class PreferencesDialog extends javax.swing.JDialog { saveImagesPath(prefs); save(prefs, dialog.cbSaveToZipFiles, KEY_CARD_IMAGES_SAVE_TO_ZIP, "true", "false", UPDATE_CACHE_POLICY); save(prefs, dialog.cbNumberOfDownloadThreads, KEY_CARD_IMAGES_THREADS); - save(prefs, dialog.cbPreferedImageLanguage, KEY_CARD_IMAGES_PREF_LANGUAGE); + save(prefs, dialog.cbPreferredImageLanguage, KEY_CARD_IMAGES_PREF_LANGUAGE); save(prefs, dialog.cbUseDefaultBackground, KEY_BACKGROUND_IMAGE_DEFAULT, "true", "false", UPDATE_CACHE_POLICY); save(prefs, dialog.cbUseDefaultBattleImage, KEY_BATTLEFIELD_IMAGE_DEFAULT, "true", "false", UPDATE_CACHE_POLICY); @@ -3518,7 +3518,7 @@ public class PreferencesDialog extends javax.swing.JDialog { } load(prefs, dialog.cbSaveToZipFiles, KEY_CARD_IMAGES_SAVE_TO_ZIP, "true"); dialog.cbNumberOfDownloadThreads.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_THREADS, KEY_CARD_IMAGES_THREADS_DEFAULT)); - dialog.cbPreferedImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode())); + dialog.cbPreferredImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode())); // rendering settings load(prefs, dialog.cbCardRenderImageFallback, KEY_CARD_RENDERING_FALLBACK, "true", "false"); @@ -4073,7 +4073,7 @@ public class PreferencesDialog extends javax.swing.JDialog { private javax.swing.JComboBox cbNumberOfDownloadThreads; private javax.swing.JCheckBox cbPassPriorityActivation; private javax.swing.JCheckBox cbPassPriorityCast; - private javax.swing.JComboBox cbPreferedImageLanguage; + private javax.swing.JComboBox cbPreferredImageLanguage; private javax.swing.JComboBox cbProxyType; private javax.swing.JCheckBox cbSaveToZipFiles; private javax.swing.JCheckBox cbShowStormCounter; @@ -4176,7 +4176,7 @@ public class PreferencesDialog extends javax.swing.JDialog { private javax.swing.JLabel labelMainStep; private javax.swing.JLabel labelNextTurn; private javax.swing.JLabel labelNumberOfDownloadThreads; - private javax.swing.JLabel labelPreferedImageLanguage; + private javax.swing.JLabel labelPreferredImageLanguage; private javax.swing.JLabel labelPriorEnd; private javax.swing.JLabel labelSkipStep; private javax.swing.JLabel labelStackWidth; diff --git a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java index 5a2274d636d..13ff20959ac 100644 --- a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java @@ -14,6 +14,7 @@ import org.apache.log4j.Logger; import java.awt.*; import java.awt.event.ActionEvent; import java.io.Serializable; +import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.Executors; @@ -38,6 +39,7 @@ public class FeedbackPanel extends javax.swing.JPanel { private MageDialog connectedDialog; private ChatPanelBasic connectedChatPanel; private int lastMessageId; + private Map lastOptions = new HashMap<>(); private static final ScheduledExecutorService WORKER = Executors.newSingleThreadScheduledExecutor(); @@ -63,8 +65,8 @@ public class FeedbackPanel extends javax.swing.JPanel { private void setGUISize() { } - public void getFeedback(FeedbackMode mode, String message, boolean special, Map options, - int messageId, boolean gameNeedUserFeedback, TurnPhase gameTurnPhase) { + public void prepareFeedback(FeedbackMode mode, String message, boolean special, Map options, + int messageId, boolean gameNeedUserFeedback, TurnPhase gameTurnPhase) { synchronized (this) { if (messageId < this.lastMessageId) { // if too many warning messages here then look at GAME_REDRAW_GUI event logic @@ -72,13 +74,16 @@ public class FeedbackPanel extends javax.swing.JPanel { return; } this.lastMessageId = messageId; + this.lastOptions = options; + this.mode = mode; } + this.helper.setBasicMessage(message); this.helper.setOriginalId(null); // reference to the feedback causing ability String lblText = addAdditionalText(message, options); this.helper.setTextArea(lblText); - this.mode = mode; + switch (this.mode) { case INFORM: setButtonState("", "", mode); @@ -113,7 +118,7 @@ public class FeedbackPanel extends javax.swing.JPanel { } requestFocusIfPossible(); - handleOptions(options); + updateOptions(options); this.revalidate(); this.repaint(); @@ -167,29 +172,35 @@ public class FeedbackPanel extends javax.swing.JPanel { WORKER.schedule(task, 8, TimeUnit.SECONDS); } - private void handleOptions(Map options) { - // clear already opened dialog (second request) - if (connectedDialog != null) { - connectedDialog.removeDialog(); - connectedDialog = null; - } + public void updateOptions(Map options) { + this.lastOptions = options; - if (options != null) { - if (options.containsKey("UI.left.btn.text")) { - String text = (String) options.get("UI.left.btn.text"); + if (this.lastOptions != null) { + if (this.lastOptions.containsKey("UI.left.btn.text")) { + String text = (String) this.lastOptions.get("UI.left.btn.text"); this.btnLeft.setText(text); this.helper.setLeft(text, !text.isEmpty()); } - if (options.containsKey("UI.right.btn.text")) { - String text = (String) options.get("UI.right.btn.text"); + if (this.lastOptions.containsKey("UI.right.btn.text")) { + String text = (String) this.lastOptions.get("UI.right.btn.text"); this.btnRight.setText(text); this.helper.setRight(text, !text.isEmpty()); } - if (options.containsKey("dialog")) { - connectedDialog = (MageDialog) options.get("dialog"); - } - + updateConnectedDialog((MageDialog) this.lastOptions.getOrDefault("dialog", null)); this.helper.autoSizeButtonsAndFeedbackState(); + } else { + updateConnectedDialog(null); + } + } + + private void updateConnectedDialog(MageDialog newDialog) { + if (this.connectedDialog != null && this.connectedDialog != newDialog) { + // remove old + this.connectedDialog.removeDialog(); + } + this.connectedDialog = newDialog; + if (this.connectedDialog != null) { + this.connectedDialog.setVisible(true); } } @@ -244,10 +255,7 @@ public class FeedbackPanel extends javax.swing.JPanel { } private void btnRightActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRightActionPerformed - if (connectedDialog != null) { - connectedDialog.removeDialog(); - connectedDialog = null; - } + updateConnectedDialog(null); if (mode == FeedbackMode.SELECT && (evt.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) { SessionHandler.sendPlayerInteger(gameId, 0); } else if (mode == FeedbackMode.END) { diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 503fe93af96..f84b1c75270 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -274,8 +274,7 @@ public final class GamePanel extends javax.swing.JPanel { windowDialog.removeDialog(); } - clearPickTargetDialogs(); - clearPickPileDialogs(); + clearPickDialogs(); Plugins.instance.getActionCallback().hideOpenComponents(); try { @@ -288,18 +287,34 @@ public final class GamePanel extends javax.swing.JPanel { this.bigCard = null; } + private void hidePickDialogs() { + // temporary hide opened dialog on redraw/update + for (ShowCardsDialog dialog : this.pickTarget) { + dialog.setVisible(false); + } + for (PickPileDialog dialog : this.pickPile) { + dialog.setVisible(false); + } + } + + private void clearPickDialogs() { + // remove dialogs forever on clean or full update + clearPickTargetDialogs(); + clearPickPileDialogs(); + } + private void clearPickTargetDialogs() { - for (ShowCardsDialog pickTargetDialog : this.pickTarget) { - pickTargetDialog.cleanUp(); - pickTargetDialog.removeDialog(); + for (ShowCardsDialog dialog : this.pickTarget) { + dialog.cleanUp(); + dialog.removeDialog(); } this.pickTarget.clear(); } private void clearPickPileDialogs() { - for (PickPileDialog pickPileDialog : this.pickPile) { - pickPileDialog.cleanUp(); - pickPileDialog.removeDialog(); + for (PickPileDialog dialog : this.pickPile) { + dialog.cleanUp(); + dialog.removeDialog(); } this.pickPile.clear(); } @@ -929,6 +944,7 @@ public final class GamePanel extends javax.swing.JPanel { } feedbackPanel.disableUndo(); + feedbackPanel.updateOptions(lastGameData.options); this.revalidate(); this.repaint(); @@ -1344,7 +1360,7 @@ public final class GamePanel extends javax.swing.JPanel { public void ask(String question, GameView gameView, int messageId, Map options) { updateGame(gameView, false, options, null); - this.feedbackPanel.getFeedback(FeedbackMode.QUESTION, question, false, options, messageId, true, gameView.getPhase()); + this.feedbackPanel.prepareFeedback(FeedbackMode.QUESTION, question, false, options, messageId, true, gameView.getPhase()); } private void keepLastGameData(GameView game, boolean showPlayable, Map options, Set targets) { @@ -1604,11 +1620,16 @@ public final class GamePanel extends javax.swing.JPanel { * @param options * @param messageId */ - public void pickTarget(String message, CardsView cardsView, GameView gameView, Set targets, boolean required, Map options, int messageId) { + public void pickTarget(GameView gameView, Map options, String message, CardsView cardsView, Set targets, boolean required, int messageId) { + updateGame(gameView, false, options, targets); + hideAll(); + DialogManager.getManager(gameId).fadeOut(); + clearPickTargetDialogs(); + PopUpMenuType popupMenuType = null; - if (options != null) { + if (lastGameData.options != null) { if (options.containsKey("queryType")) { - PlayerQueryEvent.QueryType needType = (PlayerQueryEvent.QueryType) options.get("queryType"); + PlayerQueryEvent.QueryType needType = (PlayerQueryEvent.QueryType) lastGameData.options.get("queryType"); switch (needType) { case PICK_ABILITY: popupMenuType = PopUpMenuType.TRIGGER_ORDER; @@ -1622,17 +1643,13 @@ public final class GamePanel extends javax.swing.JPanel { } } - updateGame(gameView, false, options, targets); - - Map options0 = options == null ? new HashMap<>() : options; + Map options0 = lastGameData.options == null ? new HashMap<>() : lastGameData.options; ShowCardsDialog dialog = null; if (cardsView != null && !cardsView.isEmpty()) { - // clear old dialogs before the new - clearPickTargetDialogs(); - dialog = showCards(message, cardsView, required, options0, popupMenuType); + dialog = prepareCardsDialog(message, cardsView, required, options0, popupMenuType); options0.put("dialog", dialog); } - this.feedbackPanel.getFeedback(required ? FeedbackMode.INFORM : FeedbackMode.CANCEL, message, gameView.getSpecial(), options0, messageId, true, gameView.getPhase()); + this.feedbackPanel.prepareFeedback(required ? FeedbackMode.INFORM : FeedbackMode.CANCEL, message, gameView.getSpecial(), options0, messageId, true, gameView.getPhase()); if (dialog != null) { this.pickTarget.add(dialog); } @@ -1640,15 +1657,23 @@ public final class GamePanel extends javax.swing.JPanel { public void inform(String information, GameView gameView, int messageId) { updateGame(gameView); - this.feedbackPanel.getFeedback(FeedbackMode.INFORM, information, gameView.getSpecial(), null, messageId, false, gameView.getPhase()); + this.feedbackPanel.prepareFeedback(FeedbackMode.INFORM, information, gameView.getSpecial(), null, messageId, false, gameView.getPhase()); } - public void endMessage(String message, int messageId) { - this.feedbackPanel.getFeedback(FeedbackMode.END, message, false, null, messageId, true, null); + public void endMessage(GameView gameView, Map options, String message, int messageId) { + updateGame(gameView, false, options, null); + hideAll(); + DialogManager.getManager(gameId).fadeOut(); + + this.feedbackPanel.prepareFeedback(FeedbackMode.END, message, false, null, messageId, true, null); ArrowBuilder.getBuilder().removeAllArrows(gameId); } - public void select(String message, GameView gameView, int messageId, Map options) { + public void select(GameView gameView, Map options, String message, int messageId) { + updateGame(gameView, true, options, null); + hideAll(); + DialogManager.getManager(gameId).fadeOut(); + this.abilityPicker.setVisible(false); holdingPriority = false; @@ -1659,8 +1684,6 @@ public final class GamePanel extends javax.swing.JPanel { PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"), false); - updateGame(gameView, true, options, null); - boolean controllingPlayer = false; for (PlayerView playerView : gameView.getPlayers()) { if (playerView.getPlayerId().equals(playerId)) { @@ -1675,8 +1698,8 @@ public final class GamePanel extends javax.swing.JPanel { } Map panelOptions = new HashMap<>(); - if (options != null) { - panelOptions.putAll(options); + if (lastGameData.options != null) { + panelOptions.putAll(lastGameData.options); } panelOptions.put("your_turn", true); String activePlayerText; @@ -1690,39 +1713,45 @@ public final class GamePanel extends javax.swing.JPanel { priorityPlayerText = " / priority " + gameView.getPriorityPlayerName(); } String messageToDisplay = message + FeedbackPanel.getSmallText(activePlayerText + " / " + gameView.getStep().toString() + priorityPlayerText); - this.feedbackPanel.getFeedback(FeedbackMode.SELECT, messageToDisplay, gameView.getSpecial(), panelOptions, messageId, true, gameView.getPhase()); + this.feedbackPanel.prepareFeedback(FeedbackMode.SELECT, messageToDisplay, gameView.getSpecial(), panelOptions, messageId, true, gameView.getPhase()); } - public void playMana(String message, GameView gameView, Map options, int messageId) { + public void playMana(GameView gameView, Map options, String message, int messageId) { updateGame(gameView, true, options, null); + hideAll(); DialogManager.getManager(gameId).fadeOut(); - this.feedbackPanel.getFeedback(FeedbackMode.CANCEL, message, gameView.getSpecial(), options, messageId, true, gameView.getPhase()); + + this.feedbackPanel.prepareFeedback(FeedbackMode.CANCEL, message, gameView.getSpecial(), options, messageId, true, gameView.getPhase()); } - public void playXMana(String message, GameView gameView, int messageId) { - updateGame(gameView, true, null, null); + public void playXMana(GameView gameView, Map options, String message, int messageId) { + updateGame(gameView, true, options, null); + hideAll(); DialogManager.getManager(gameId).fadeOut(); - this.feedbackPanel.getFeedback(FeedbackMode.CONFIRM, message, gameView.getSpecial(), null, messageId, true, gameView.getPhase()); + + this.feedbackPanel.prepareFeedback(FeedbackMode.CONFIRM, message, gameView.getSpecial(), null, messageId, true, gameView.getPhase()); } public void replayMessage(String message) { //TODO: implement this } - public void pickAbility(AbilityPickerView choices) { + public void pickAbility(GameView gameView, Map options, AbilityPickerView choices) { + updateGame(gameView, false, options, null); hideAll(); DialogManager.getManager(gameId).fadeOut(); + this.abilityPicker.show(choices, MageFrame.getDesktop().getMousePosition()); } private void hideAll() { + hidePickDialogs(); this.abilityPicker.setVisible(false); ActionCallback callback = Plugins.instance.getActionCallback(); ((MageActionCallback) callback).hideGameUpdate(gameId); } - private ShowCardsDialog showCards(String title, CardsView cards, boolean required, Map options, PopUpMenuType popupMenuType) { - hideAll(); + private ShowCardsDialog prepareCardsDialog(String title, CardsView cards, boolean required, Map options, PopUpMenuType popupMenuType) { ShowCardsDialog showCards = new ShowCardsDialog(); JPopupMenu popupMenu = null; if (PopUpMenuType.TRIGGER_ORDER == popupMenuType) { @@ -1732,7 +1761,11 @@ public final class GamePanel extends javax.swing.JPanel { return showCards; } - public void getAmount(int min, int max, String message) { + public void getAmount(GameView gameView, Map options, int min, int max, String message) { + updateGame(gameView, false, options, null); + hideAll(); + DialogManager.getManager(gameId).fadeOut(); + pickNumber.showDialog(min, max, message); if (pickNumber.isCancel()) { SessionHandler.sendPlayerBoolean(gameId, false); @@ -1741,13 +1774,20 @@ public final class GamePanel extends javax.swing.JPanel { } } - public void getMultiAmount(List messages, int min, int max, Map options) { - pickMultiNumber.showDialog(messages, min, max, options); + public void getMultiAmount(List messages, GameView gameView, Map options, int min, int max) { + updateGame(gameView, false, options, null); + hideAll(); + DialogManager.getManager(gameId).fadeOut(); + + pickMultiNumber.showDialog(messages, min, max, lastGameData.options); SessionHandler.sendPlayerString(gameId, pickMultiNumber.getMultiAmount()); } - public void getChoice(Choice choice, UUID objectId) { + public void getChoice(GameView gameView, Map options, Choice choice, UUID objectId) { + updateGame(gameView, false, options, null); hideAll(); + DialogManager.getManager(gameId).fadeOut(); + // TODO: remember last choices and search incremental for same events? PickChoiceDialog pickChoice = new PickChoiceDialog(); pickChoice.showDialog(choice, null, objectId, choiceWindowState, bigCard); @@ -1769,8 +1809,10 @@ public final class GamePanel extends javax.swing.JPanel { pickChoice.removeDialog(); } - public void pickPile(String message, CardsView pile1, CardsView pile2) { + public void pickPile(GameView gameView, Map options, String message, CardsView pile1, CardsView pile2) { + updateGame(gameView, false, options, null); hideAll(); + DialogManager.getManager(gameId).fadeOut(); // remove old dialogs before the new clearPickPileDialogs(); diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index e256f7307c2..59fcb99ce2b 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -157,7 +157,7 @@ public class CallbackClientImpl implements CallbackClient { case REPLAY_DONE: { GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { - panel.endMessage((String) callback.getData(), callback.getMessageId()); + panel.endMessage(null, null, (String) callback.getData(), callback.getMessageId()); } break; } @@ -180,16 +180,17 @@ public class CallbackClientImpl implements CallbackClient { } case GAME_OVER: { + GameClientMessage message = (GameClientMessage) callback.getData(); GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { Session session = SessionHandler.getSession(); if (session.isJsonLogActive()) { - appendJsonEvent("GAME_OVER", callback.getObjectId(), callback.getData()); - ActionData actionData = appendJsonEvent("GAME_OVER", callback.getObjectId(), callback.getData()); - String logFileName = "game-" + actionData.gameId + ".json"; - S3Uploader.upload(logFileName, actionData.gameId.toString()); + UUID gameId = callback.getObjectId(); + appendJsonEvent("GAME_OVER", callback.getObjectId(), message); + String logFileName = "game-" + gameId + ".json"; + S3Uploader.upload(logFileName, gameId.toString()); } - panel.endMessage((String) callback.getData(), callback.getMessageId()); + panel.endMessage(message.getGameView(), message.getOptions(), message.getMessage(), callback.getMessageId()); } break; } @@ -209,35 +210,34 @@ public class CallbackClientImpl implements CallbackClient { break; } - case GAME_TARGET: // e.g. Pick triggered ability - { + case GAME_TARGET: { + // e.g. Pick triggered ability GameClientMessage message = (GameClientMessage) callback.getData(); - GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { appendJsonEvent("GAME_TARGET", callback.getObjectId(), message); - panel.pickTarget(message.getMessage(), message.getCardsView(), message.getGameView(), - message.getTargets(), message.isFlag(), message.getOptions(), callback.getMessageId()); + panel.pickTarget(message.getGameView(), message.getOptions(), message.getMessage(), + message.getCardsView1(), message.getTargets(), message.isFlag(), callback.getMessageId()); } break; } case GAME_SELECT: { GameClientMessage message = (GameClientMessage) callback.getData(); - GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { appendJsonEvent("GAME_SELECT", callback.getObjectId(), message); - panel.select(message.getMessage(), message.getGameView(), callback.getMessageId(), message.getOptions()); + panel.select(message.getGameView(), message.getOptions(), message.getMessage(), callback.getMessageId()); } break; } case GAME_CHOOSE_ABILITY: { + AbilityPickerView abilityPickerView = (AbilityPickerView) callback.getData(); GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { appendJsonEvent("GAME_CHOOSE_ABILITY", callback.getObjectId(), callback.getData()); - panel.pickAbility((AbilityPickerView) callback.getData()); + panel.pickAbility(abilityPickerView.getGameView(), null, abilityPickerView); } break; } @@ -247,19 +247,17 @@ public class CallbackClientImpl implements CallbackClient { GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { appendJsonEvent("GAME_CHOOSE_PILE", callback.getObjectId(), message); - panel.pickPile(message.getMessage(), message.getPile1(), message.getPile2()); + panel.pickPile(message.getGameView(), message.getOptions(), message.getMessage(), message.getCardsView1(), message.getCardsView2()); } break; } case GAME_CHOOSE_CHOICE: { GameClientMessage message = (GameClientMessage) callback.getData(); - GamePanel panel = MageFrame.getGame(callback.getObjectId()); - if (panel != null) { appendJsonEvent("GAME_CHOOSE_CHOICE", callback.getObjectId(), message); - panel.getChoice(message.getChoice(), callback.getObjectId()); + panel.getChoice(message.getGameView(), message.getOptions(), message.getChoice(), callback.getObjectId()); } break; } @@ -269,53 +267,48 @@ public class CallbackClientImpl implements CallbackClient { GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { appendJsonEvent("GAME_PLAY_MANA", callback.getObjectId(), message); - panel.playMana(message.getMessage(), message.getGameView(), message.getOptions(), callback.getMessageId()); + panel.playMana(message.getGameView(), message.getOptions(), message.getMessage(), callback.getMessageId()); } break; } case GAME_PLAY_XMANA: { GameClientMessage message = (GameClientMessage) callback.getData(); - GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { appendJsonEvent("GAME_PLAY_XMANA", callback.getObjectId(), message); - panel.playXMana(message.getMessage(), message.getGameView(), callback.getMessageId()); + panel.playXMana(message.getGameView(), message.getOptions(), message.getMessage(), callback.getMessageId()); } break; } case GAME_GET_AMOUNT: { GameClientMessage message = (GameClientMessage) callback.getData(); - GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { appendJsonEvent("GAME_GET_AMOUNT", callback.getObjectId(), message); - panel.getAmount(message.getMin(), message.getMax(), message.getMessage()); + panel.getAmount(message.getGameView(), message.getOptions(), message.getMin(), message.getMax(), message.getMessage()); } break; } case GAME_GET_MULTI_AMOUNT: { GameClientMessage message = (GameClientMessage) callback.getData(); - GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { appendJsonEvent("GAME_GET_MULTI_AMOUNT", callback.getObjectId(), message); - panel.getMultiAmount(message.getMessages(), message.getMin(), message.getMax(), message.getOptions()); + panel.getMultiAmount(message.getMessages(), message.getGameView(), message.getOptions(), message.getMin(), message.getMax()); } break; } case GAME_UPDATE: { GamePanel panel = MageFrame.getGame(callback.getObjectId()); - if (panel != null) { appendJsonEvent("GAME_UPDATE", callback.getObjectId(), callback.getData()); - - panel.updateGame((GameView) callback.getData(), true, null, null); // update after undo + panel.updateGame((GameView) callback.getData(), true, null, null); // update after undo wtf?! } break; } diff --git a/Mage.Client/src/main/java/mage/client/util/audio/LinePool.java b/Mage.Client/src/main/java/mage/client/util/audio/LinePool.java index 633b677a350..cab0b04e7da 100644 --- a/Mage.Client/src/main/java/mage/client/util/audio/LinePool.java +++ b/Mage.Client/src/main/java/mage/client/util/audio/LinePool.java @@ -16,14 +16,13 @@ import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.SourceDataLine; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.log4j.Logger; import mage.utils.ThreadUtils; public class LinePool { - private final Logger log = LoggerFactory.getLogger(getClass()); + private final org.apache.log4j.Logger logger = Logger.getLogger(LinePool.class); private static final int LINE_CLEANUP_INTERVAL = 30000; private final Queue freeLines = new ArrayDeque<>(); @@ -55,7 +54,7 @@ public class LinePool { SourceDataLine line = (SourceDataLine) mixer.getLine(lineInfo); freeLines.add(line); } catch (LineUnavailableException e) { - log.warn("Failed to get line from mixer", e); + logger.warn("Failed to get line from mixer", e); } } new Timer("Line cleanup", true).scheduleAtFixedRate(new TimerTask() { @@ -65,7 +64,7 @@ public class LinePool { for (SourceDataLine sourceDataLine : freeLines) { if (sourceDataLine.isOpen()) { sourceDataLine.close(); - log.debug("Closed line {}", sourceDataLine); + logger.debug("Closed line " + sourceDataLine); } } } @@ -96,13 +95,13 @@ public class LinePool { public void playSound(final MageClip mageClip) { final SourceDataLine line; synchronized (LinePool.this) { - log.debug("Playing {}", mageClip.getFilename()); + logger.debug("Playing: " + mageClip.getFilename()); logLineStats(); line = borrowLine(); if (line == null) { // no lines available, queue sound to play it when a line is available queue.add(mageClip); - log.debug("Sound {} queued.", mageClip.getFilename()); + logger.debug("Sound queued: " + mageClip.getFilename()); return; } logLineStats(); @@ -113,19 +112,19 @@ public class LinePool { if (!line.isOpen()) { line.open(); line.addLineListener(event -> { - log.debug("Event: {}", event); + logger.debug("Event: " + event); if (event.getType() != Type.STOP) { return; } synchronized (LinePool.this) { - log.debug("Before stop on line {}", line); + logger.debug("Before stop on line " + line); logLineStats(); returnLine(line); - log.debug("After stop on line {}", line); + logger.debug("After stop on line " + line); logLineStats(); MageClip queuedSound = queue.poll(); if (queuedSound != null) { - log.debug("Playing queued sound {}", queuedSound); + logger.debug("Playing queued sound " + queuedSound); playSound(queuedSound); } } @@ -133,19 +132,21 @@ public class LinePool { } line.start(); } catch (LineUnavailableException e) { - log.warn("Failed to open line", e); + logger.warn("Failed to open line", e); } } byte[] buffer = mageClip.getBuffer(); - log.debug("Before write to line {}", line); + logger.debug("Before write to line " + line); line.write(buffer, 0, buffer.length); line.drain(); line.stop(); - log.debug("Line completed: {}", line); + logger.debug("Line completed: " + line); }); } private void logLineStats() { - log.debug("Free lines: {} Active: {} Busy: {}", freeLines.size(), activeLines.size(), busyLines.size()); + logger.debug(String.format("Free lines: %d; Active: %d; Busy: %d", + freeLines.size(), activeLines.size(), busyLines.size() + )); } } diff --git a/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java b/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java index 34105d8f145..06609df4ca8 100644 --- a/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java +++ b/Mage.Client/src/main/java/mage/client/util/comparators/CardViewEDHPowerLevelComparator.java @@ -315,203 +315,280 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator { } if (card.isPlanesWalker()) { - if (card.getName().toLowerCase(Locale.ENGLISH).equals("jace, the mind sculptor")) { - thisMaxPower = Math.max(thisMaxPower, 6); - } - if (card.getName().toLowerCase(Locale.ENGLISH).equals("ugin, the spirit dragon")) { - thisMaxPower = Math.max(thisMaxPower, 5); - } - thisMaxPower = Math.max(thisMaxPower, 4); + thisMaxPower = Math.max(thisMaxPower, 6); } String cn = card.getName().toLowerCase(Locale.ENGLISH); - if (cn.equals("ancient tomb") + if (cn.equals("acid rain") + || cn.equals("agent of treachery") || cn.equals("anafenza, the foremost") + || cn.equals("ancient tomb") + || cn.equals("animar, soul of element") + || cn.equals("animate artifact") + || cn.equals("apocalypse") + || cn.equals("archaeomancer") || cn.equals("arcum dagsson") || cn.equals("armageddon") + || cn.equals("ashnod's altar") + || cn.equals("atraxa, praetors' voice") + || cn.equals("aura flux") || cn.equals("aura shards") + || cn.equals("avacyn, angel of hope") || cn.equals("azami, lady of scrolls") || cn.equals("azusa, lost but seeking") || cn.equals("back to basics") || cn.equals("bane of progress") || cn.equals("basalt monolith") + || cn.equals("bend or break") || cn.equals("blightsteel collossus") + || cn.equals("blightsteel colossus") || cn.equals("blood moon") + || cn.equals("boil") + || cn.equals("boiling seas") + || cn.equals("brago, king eternal") || cn.equals("braids, cabal minion") + || cn.equals("bribery") + || cn.equals("burning sands") || cn.equals("cabal coffers") + || cn.equals("candelabra of tawnos") || cn.equals("captain sisay") + || cn.equals("card view") + || cn.equals("cataclysm") + || cn.equals("catastrophe") || cn.equals("celestial dawn") + || cn.equals("cephalid aristocrat") + || cn.equals("cephalid illusionist") + || cn.equals("changeling berserker") || cn.equals("child of alara") + || cn.equals("chulane, teller of tales") + || cn.equals("cinderhaze wretch") || cn.equals("coalition relic") + || cn.equals("confusion in the ranks") + || cn.equals("consecrated sphinx") + || cn.equals("contamination") || cn.equals("craterhoof behemoth") + || cn.equals("cryptic gateway") + || cn.equals("cyclonic rift") + || cn.equals("deadeye navigator") + || cn.equals("death cloud") + || cn.equals("decree of annihilation") + || cn.equals("decree of silence") || cn.equals("deepglow skate") + || cn.equals("demonic consultation") || cn.equals("derevi, empyrial tactician") + || cn.equals("devastation") || cn.equals("dig through time") + || cn.equals("divine intervention") + || cn.equals("dockside extortionist") + || cn.equals("doomsday") + || cn.equals("doubling season") + || cn.equals("drannith magistrate") + || cn.equals("dross scorpion") + || cn.equals("earthcraft") || cn.equals("edric, spymaster of trest") || cn.equals("elesh norn, grand cenobite") + || cn.equals("embargo") + || cn.equals("emrakul, the promised end") + || cn.equals("enter the infinite") || cn.equals("entomb") - || cn.equals("force of will") + || cn.equals("epicenter") + || cn.equals("erratic portal") + || cn.equals("expropriate") + || cn.equals("exquisite blood") + || cn.equals("fall of the thran") + || cn.equals("fierce guardianship") || cn.equals("food chain") + || cn.equals("force of negation") + || cn.equals("force of will") + || cn.equals("future sight") || cn.equals("gaddock teeg") || cn.equals("gaea's cradle") + || cn.equals("genesis chamber") + || cn.equals("ghave, guru of spores") + || cn.equals("gilded drake") + || cn.equals("glenn, the voice of calm") + || cn.equals("global ruin") + || cn.equals("golos, tireless pilgrim") || cn.equals("grand arbiter augustin iv") + || cn.equals("grave pact") + || cn.equals("grave titan") + || cn.equals("great whale") || cn.equals("grim monolith") + || cn.equals("grip of chaos") + || cn.equals("gush") + || cn.equals("hellkite charger") || cn.equals("hermit druid") || cn.equals("hokori, dust drinker") || cn.equals("humility") + || cn.equals("impending disaster") || cn.equals("imperial seal") + || cn.equals("intruder alarm") + || cn.equals("invoke prejudice") || cn.equals("iona, shield of emeria") || cn.equals("jin-gitaxias, core augur") + || cn.equals("jokulhaups") + || cn.equals("kaalia of the vast") || cn.equals("karador, ghost chieftain") || cn.equals("karakas") + || cn.equals("karn, silver golem") || cn.equals("kataki, war's wage") + || cn.equals("keldon firebombers") + || cn.equals("kiki-jiki, mirror breaker") + || cn.equals("kinnan, bonder prodigy") || cn.equals("knowledge pool") + || cn.equals("kozilek, butcher of truth") + || cn.equals("krark-clan ironworks") + || cn.equals("krenko, mob boss") + || cn.equals("krosan restorer") + || cn.equals("laboratory maniac") + || cn.equals("land equilibrium") + || cn.equals("leonin relic-warder") + || cn.equals("leovold, emissary of trest") + || cn.equals("leyline of the void") || cn.equals("linvala, keeper of silence") || cn.equals("living death") || cn.equals("llawan, cephalid empress") || cn.equals("loyal retainers") || cn.equals("maelstrom wanderer") + || cn.equals("magister sphinx") || cn.equals("malfegor") - || cn.equals("master of cruelties") + || cn.equals("mana breach") || cn.equals("mana crypt") || cn.equals("mana drain") || cn.equals("mana vault") + || cn.equals("mana vortex") + || cn.equals("master of cruelties") + || cn.equals("memnarch") + || cn.equals("meren of clan nel toth") || cn.equals("michiko konda, truth seeker") + || cn.equals("mikaeus the unhallowed") + || cn.equals("mikaeus, the unhallowed") + || cn.equals("mindcrank") + || cn.equals("mindslaver") + || cn.equals("minion reflector") + || cn.equals("mycosynth lattice") + || cn.equals("myr turbine") + || cn.equals("narset, enlightened master") + || cn.equals("narset, parter of veils") || cn.equals("nath of the gilt-leaf") || cn.equals("natural order") || cn.equals("necrotic ooze") + || cn.equals("negan, the cold-blooded") + || cn.equals("nekusar, the mindrazer") + || cn.equals("nether void") + || cn.equals("nexus of fate") || cn.equals("nicol bolas") + || cn.equals("norin the wary") + || cn.equals("notion thief") || cn.equals("numot, the devastator") || cn.equals("oath of druids") + || cn.equals("obliterate") + || cn.equals("oko, thief of crowns") + || cn.equals("oloro, ageless ascetic") + || cn.equals("omniscience") + || cn.equals("opalescence") + || cn.equals("opposition agent") + || cn.equals("oppression") + || cn.equals("ornithopter") + || cn.equals("overwhelming splendor") + || cn.equals("palinchron") + || cn.equals("paradox engine") || cn.equals("pattern of rebirth") + || cn.equals("peregrine drake") + || cn.equals("planar portal") + || cn.equals("possessed portal") + || cn.equals("power artifact") + || cn.equals("price of glory") + || cn.equals("prossh, skyraider of kher") || cn.equals("protean hulk") || cn.equals("purphoros, god of the forge") || cn.equals("ravages of war") || cn.equals("reclamation sage") + || cn.equals("rhystic study") + || cn.equals("rick, steadfast leader") + || cn.equals("rings of brighthearth") + || cn.equals("rising waters") + || cn.equals("rite of replication") + || cn.equals("ruination") + || cn.equals("sanguine bond") + || cn.equals("scrambleverse") + || cn.equals("seedborn muse") || cn.equals("sen triplets") + || cn.equals("sensei's divining top") || cn.equals("serra's sanctum") || cn.equals("sheoldred, whispering one") + || cn.equals("sire of insanity") + || cn.equals("skithiryx, the blight dragon") + || cn.equals("smokestack") + || cn.equals("smothering tithe") || cn.equals("sol ring") + || cn.equals("sorin markov") + || cn.equals("splinter twin") || cn.equals("spore frog") || cn.equals("stasis") + || cn.equals("static orb") + || cn.equals("stony silence") + || cn.equals("storage matrix") + || cn.equals("storm cauldron") || cn.equals("strip mine") - || cn.equals("the tabernacle at pendrell vale") - || cn.equals("tinker") - || cn.equals("treasure cruise") - || cn.equals("urabrask the hidden") - || cn.equals("vorinclex, voice of hunger") - || cn.equals("winter orb") - || cn.equals("zur the enchanter")) { - thisMaxPower = Math.max(thisMaxPower, 5); - } - - // Parts of infinite combos - if (cn.equals("animate artifact") || cn.equals("animar, soul of element") - || cn.equals("archaeomancer") - || cn.equals("ashnod's altar") || cn.equals("azami, lady of scrolls") - || cn.equals("aura flux") - || cn.equals("basalt monolith") || cn.equals("brago, king eternal") - || cn.equals("candelabra of tawnos") || cn.equals("cephalid aristocrat") - || cn.equals("cephalid illusionist") || cn.equals("changeling berserker") - || cn.equals("consecrated sphinx") - || cn.equals("cyclonic rift") - || cn.equals("the chain veil") - || cn.equals("cinderhaze wretch") || cn.equals("cryptic gateway") - || cn.equals("deadeye navigator") || cn.equals("derevi, empyrial tactician") - || cn.equals("doubling season") || cn.equals("dross scorpion") - || cn.equals("earthcraft") || cn.equals("erratic portal") - || cn.equals("enter the infinite") || cn.equals("omniscience") - || cn.equals("exquisite blood") || cn.equals("future sight") - || cn.equals("genesis chamber") - || cn.equals("ghave, guru of spores") - || cn.equals("grave pact") - || cn.equals("grave titan") || cn.equals("great whale") - || cn.equals("grim monolith") || cn.equals("gush") - || cn.equals("hellkite charger") || cn.equals("intruder alarm") - || cn.equals("hermit druid") - || cn.equals("humility") - || cn.equals("iona, shield of emeria") - || cn.equals("karn, silver golem") || cn.equals("kiki-jiki, mirror breaker") - || cn.equals("krark-clan ironworks") || cn.equals("krenko, mob boss") - || cn.equals("krosan restorer") || cn.equals("laboratory maniac") - || cn.equals("leovold, emissary of trest") - || cn.equals("leonin relic-warder") || cn.equals("leyline of the void") - || cn.equals("memnarch") - || cn.equals("meren of clan nel toth") || cn.equals("mikaeus, the unhallowed") - || cn.equals("mindcrank") || cn.equals("mindslaver") - || cn.equals("minion reflector") || cn.equals("mycosynth lattice") - || cn.equals("myr turbine") || cn.equals("narset, enlightened master") - || cn.equals("nekusar, the mindrazer") || cn.equals("norin the wary") - || cn.equals("notion thief") - || cn.equals("opalescence") || cn.equals("ornithopter") - || cn.equals("paradox engine") - || cn.equals("purphoros, god of the forge") - || cn.equals("peregrine drake") || cn.equals("palinchron") - || cn.equals("planar portal") || cn.equals("power artifact") - || cn.equals("rings of brighthearth") || cn.equals("rite of replication") - || cn.equals("sanguine bond") || cn.equals("sensei's divining top") - || cn.equals("splinter twin") || cn.equals("stony silence") || cn.equals("sunder") - || cn.equals("storm cauldron") || cn.equals("teferi's puzzle box") + || cn.equals("survival of the fittest") + || cn.equals("table view") + || cn.equals("tainted aether") || cn.equals("tangle wire") + || cn.equals("tectonic break") + || cn.equals("teferi's protection") + || cn.equals("teferi's puzzle box") || cn.equals("teferi, mage of zhalfir") - || cn.equals("tezzeret the seeker") || cn.equals("time stretch") - || cn.equals("time warp") || cn.equals("training grounds") - || cn.equals("triskelavus") || cn.equals("triskelion") - || cn.equals("turnabout") || cn.equals("umbral mantle") - || cn.equals("uyo, silent prophet") || cn.equals("voltaic key") - || cn.equals("workhorse") || cn.equals("worldgorger dragon") - || cn.equals("worthy cause") || cn.equals("yawgmoth's will") - || cn.equals("zealous conscripts")) { - thisMaxPower = Math.max(thisMaxPower, 12); - } - - if (cn.equals("animar, soul of element") - || cn.equals("azami, lady of scrolls") - || cn.equals("braids, cabal minion") - || cn.equals("child of alara") - || cn.equals("derevi, empyrial tactician") - || cn.equals("edric, spymaster of trest") - || cn.equals("gaddock teeg") - || cn.equals("grand arbiter augustin iv") - || cn.equals("hokori, dust drinker") - || cn.equals("iona, shield of emeria") - || cn.equals("jin-gitaxias, core augur") - || cn.equals("kaalia of the vast") - || cn.equals("karador, ghost chieftain") - || cn.equals("leovold, emissary of trest") - || cn.equals("linvala, keeper of silence") - || cn.equals("llawan, cephalid empress") - || cn.equals("memnarch") - || cn.equals("meren of clan nel toth") - || cn.equals("michiko konda, truth seeker") - || cn.equals("narset, enlightened master") - || cn.equals("nekusar, the mindrazer") - || cn.equals("norin the wary") - || cn.equals("numot, the devastator") - || cn.equals("sheoldred, whispering one") - || cn.equals("teferi, mage of zhalfir") - || cn.equals("zur the enchanter")) { - thisMaxPower = Math.max(thisMaxPower, 12); - } - - if (cn.equals("anafenza, the foremost") - || cn.equals("arcum dagsson") - || cn.equals("azusa, lost but seeking") - || cn.equals("brago, king eternal") - || cn.equals("captain sisay") - || cn.equals("elesh norn, grand cenobite") - || cn.equals("malfegor") - || cn.equals("maelstrom wanderer") - || cn.equals("mikaeus the unhallowed") - || cn.equals("nath of the gilt-leaf") - || cn.equals("prossh, skyraider of kher") - || cn.equals("purphoros, god of the forge") - || cn.equals("sen triplets") + || cn.equals("teferi, master of time") + || cn.equals("teferi, time raveler") + || cn.equals("temporal manipulation") + || cn.equals("tergrid, god of fright") + || cn.equals("text view") + || cn.equals("tezzeret the seeker") + || cn.equals("thassa's oracle") + || cn.equals("the chain veil") + || cn.equals("the tabernacle at pendrell vale") + || cn.equals("thieves' auction") + || cn.equals("thoughts of ruin") + || cn.equals("thrasios, triton hero") + || cn.equals("time stretch") + || cn.equals("time warp") + || cn.equals("tinker") + || cn.equals("tooth and nail") + || cn.equals("torment of hailfire") + || cn.equals("torpor orb") + || cn.equals("training grounds") + || cn.equals("treasure cruise") + || cn.equals("triskelavus") + || cn.equals("triskelion") + || cn.equals("triumph of the hordes") + || cn.equals("turnabout") + || cn.equals("ugin, the spirit dragon") + || cn.equals("ulamog, the ceaseless hunger") + || cn.equals("ulamog, the infinite gyre") + || cn.equals("umbral mantle") || cn.equals("urabrask the hidden") - || cn.equals("vorinclex, voice of hunger")) { - thisMaxPower = Math.max(thisMaxPower, 10); - } + || cn.equals("urza, lord high artificer") + || cn.equals("uyo, silent prophet") + || cn.equals("void winnower") + || cn.equals("voltaic key") + || cn.equals("vorinclex, voice of hunger") + || cn.equals("wake of destruction") + || cn.equals("warp world") + || cn.equals("winter orb") + || cn.equals("workhorse") + || cn.equals("worldgorger dragon") + || cn.equals("worthy cause") + || cn.equals("xanathar, guild kingpin") + || cn.equals("yawgmoth's will") + || cn.equals("zealous conscripts") + || cn.equals("zur the enchanter")) { + thisMaxPower = Math.max(thisMaxPower, 12); + } return thisMaxPower; } + } + + diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java index 5a2e2870d1d..92f9d625ce4 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java @@ -225,8 +225,9 @@ public abstract class CardRenderer { // Call the template methods drawBorder(g); drawBackground(g); + lessOpaqueRulesTextBox = false; drawArt(g); - drawFrame(g, attribs, image); + drawFrame(g, attribs, image, lessOpaqueRulesTextBox); if (!cardView.isAbility()) { drawOverlays(g); drawCounters(g); @@ -241,7 +242,7 @@ public abstract class CardRenderer { protected abstract void drawArt(Graphics2D g); - protected abstract void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image); + protected abstract void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox); // Template methods that are possible to override, but unlikely to be // overridden. @@ -318,7 +319,8 @@ public abstract class CardRenderer { } } - protected void drawFaceArtIntoRect(Graphics2D g, int x, int y, int w, int h, Rectangle2D artRect, boolean shouldPreserveAspect) { + private boolean lessOpaqueRulesTextBox = false; + protected void drawFaceArtIntoRect(Graphics2D g, int x, int y, int w, int h, int alternate_h, Rectangle2D artRect, boolean shouldPreserveAspect) { // Perform a process to make sure that the art is scaled uniformly to fill the frame, cutting // off the minimum amount necessary to make it completely fill the frame without "squashing" it. double fullCardImgWidth = faceArtImage.getWidth(); @@ -346,10 +348,18 @@ public abstract class CardRenderer { RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.setRenderingHints(rh); - g.drawImage(faceArtImage, - x, y, - (int) targetWidth, (int) targetHeight, - null); + if (fullCardImgWidth > fullCardImgHeight) { + g.drawImage(faceArtImage, + x, y, + (int) targetWidth, (int) targetHeight, + null); + } else { + g.drawImage(faceArtImage, + x, y, + (int) targetWidth, alternate_h, // alernate_h is roughly (targetWidth / 0.74) + null); + lessOpaqueRulesTextBox = true; + } } catch (RasterFormatException e) { // At very small card sizes we may encounter a problem with rounding error making the rect not fit System.out.println(e); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java index 7d72a9d4688..f39cc96b467 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java @@ -450,9 +450,11 @@ public class ModernCardRenderer extends CardRenderer { // Normal drawing of art from a source part of the card frame into the rect if (useFaceArt) { + int alternate_height = cardHeight - boxHeight * 2 - totalContentInset; drawFaceArtIntoRect(g, totalContentInset + 1, totalContentInset + boxHeight, contentWidth - 2, typeLineY - totalContentInset - boxHeight, + alternate_height, sourceRect, shouldPreserveAspect); } else if (!isZendikarFullArtLand()) { drawArtIntoRect(g, @@ -464,14 +466,14 @@ public class ModernCardRenderer extends CardRenderer { } @Override - protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image) { + protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) { // Get the card colors to base the frame on ObjectColor frameColors = getFrameObjectColor(); // Get the border paint Color boxColor = getBoxColor(frameColors, cardView.getCardTypes(), attribs.isTransformed); Color additionalBoxColor = getAdditionalBoxColor(frameColors, cardView.getCardTypes(), attribs.isTransformed); - Paint textboxPaint = getTextboxPaint(frameColors, cardView.getCardTypes(), cardWidth); + Paint textboxPaint = getTextboxPaint(frameColors, cardView.getCardTypes(), cardWidth, lessOpaqueRulesTextBox); Paint borderPaint = getBorderPaint(frameColors, cardView.getCardTypes(), cardWidth); // Special colors @@ -1765,21 +1767,29 @@ public class ModernCardRenderer extends CardRenderer { } } + private static Color getLessOpaqueColor(Color color, boolean lessOpaqueRulesTextBox) { + if (lessOpaqueRulesTextBox) { + Color lessOpaque = new Color (color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() - 50); + return lessOpaque; + } + return color; + } + // Determine the border paint to use, based on an ObjectColors - protected static Paint getTextboxPaint(ObjectColor colors, Collection types, int width) { + protected static Paint getTextboxPaint(ObjectColor colors, Collection types, int width, boolean lessOpaqueRulesTextBox) { if (colors.isMulticolored()) { if (colors.getColorCount() == 2) { List twoColors = colors.getColors(); Color[] translatedColors; if (types.contains(CardType.LAND)) { translatedColors = new Color[]{ - getLandTextboxColor(twoColors.get(0)), - getLandTextboxColor(twoColors.get(1)) + getLessOpaqueColor(getLandTextboxColor(twoColors.get(0)), lessOpaqueRulesTextBox), + getLessOpaqueColor(getLandTextboxColor(twoColors.get(1)), lessOpaqueRulesTextBox) }; } else { translatedColors = new Color[]{ - getTextboxColor(twoColors.get(0)), - getTextboxColor(twoColors.get(1)) + getLessOpaqueColor(getTextboxColor(twoColors.get(0)), lessOpaqueRulesTextBox), + getLessOpaqueColor(getTextboxColor(twoColors.get(1)), lessOpaqueRulesTextBox) }; } @@ -1789,20 +1799,20 @@ public class ModernCardRenderer extends CardRenderer { new float[]{0.4f, 0.6f}, translatedColors); } else if (types.contains(CardType.LAND)) { - return LAND_TEXTBOX_GOLD; + return getLessOpaqueColor(LAND_TEXTBOX_GOLD, lessOpaqueRulesTextBox); } else { - return TEXTBOX_GOLD; + return getLessOpaqueColor(TEXTBOX_GOLD, lessOpaqueRulesTextBox); } } else if (colors.isColorless()) { if (types.contains(CardType.LAND)) { - return TEXTBOX_LAND; + return getLessOpaqueColor(TEXTBOX_LAND, lessOpaqueRulesTextBox); } else { - return TEXTBOX_COLORLESS; + return getLessOpaqueColor(TEXTBOX_COLORLESS, lessOpaqueRulesTextBox); } } else if (types.contains(CardType.LAND)) { - return getLandTextboxColor(colors); + return getLessOpaqueColor(getLandTextboxColor(colors), lessOpaqueRulesTextBox); } else { - return getTextboxColor(colors); + return getLessOpaqueColor(getTextboxColor(colors), lessOpaqueRulesTextBox); } } } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java index 03f29d8fe52..1d56ecde616 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java @@ -223,7 +223,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { protected void drawSplitHalfFrame(Graphics2D g, CardPanelAttributes attribs, HalfCardProps half, int typeLineY) { // Get the border paint Color boxColor = getBoxColor(half.color, cardView.getCardTypes(), attribs.isTransformed); - Paint textboxPaint = getTextboxPaint(half.color, cardView.getCardTypes(), cardWidth); + Paint textboxPaint = getTextboxPaint(half.color, cardView.getCardTypes(), cardWidth, false); Paint borderPaint = getBorderPaint(half.color, cardView.getCardTypes(), cardWidth); // Draw main frame @@ -299,7 +299,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { } @Override - protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image) { + protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) { if (isAftermath()) { drawSplitHalfFrame(getUnmodifiedHalfContext(g), attribs, leftHalf, (int) (leftHalf.ch * TYPE_LINE_Y_FRAC)); drawSplitHalfFrame(getAftermathHalfContext(g), attribs, rightHalf, (rightHalf.ch - boxHeight) / 2); @@ -309,7 +309,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { if (isFuse()) { Graphics2D g2 = getRightHalfContext(g); int totalFuseBoxWidth = rightHalf.cw * 2 + 2 * borderWidth + dividerSize; - Paint boxColor = getTextboxPaint(cardView.getColor(), ONLY_LAND_TYPE, totalFuseBoxWidth); + Paint boxColor = getTextboxPaint(cardView.getColor(), ONLY_LAND_TYPE, totalFuseBoxWidth, false); Paint borderPaint = getBorderPaint(cardView.getColor(), ONLY_LAND_TYPE, totalFuseBoxWidth); CardRendererUtils.drawRoundedBox(g2, -borderWidth, rightHalf.ch, diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java index 96fcb74f8df..31688a61c3f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java @@ -1,11 +1,11 @@ package org.mage.plugins.card.dl.sources; import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import mage.MageException; import mage.client.util.CardLanguage; +import mage.util.JsonUtil; import org.apache.log4j.Logger; import org.mage.plugins.card.dl.DownloadServiceInfo; import org.mage.plugins.card.images.CardDownloadData; @@ -173,17 +173,19 @@ public enum ScryfallImageSource implements CardImageSource { } // OK, found card data, parse it - JsonParser jp = new JsonParser(); - JsonElement root = jp.parse(new InputStreamReader(jsonStream)); - JsonObject jsonCard = root.getAsJsonObject(); - if (!jsonCard.has("card_faces")) { + JsonObject jsonCard = JsonParser.parseReader(new InputStreamReader(jsonStream)).getAsJsonObject(); + JsonArray jsonFaces = JsonUtil.getAsArray(jsonCard, "card_faces"); + if (jsonFaces == null) { throw new MageException("Couldn't find card_faces in card's JSON data: " + jsonUrl); } - JsonArray jsonCardFaces = jsonCard.getAsJsonArray("card_faces"); - JsonObject jsonCardFace = jsonCardFaces.get(card.isSecondSide() ? 1 : 0).getAsJsonObject(); - JsonObject jsonImageUris = jsonCardFace.getAsJsonObject("image_uris"); - return jsonImageUris.get("large").getAsString(); + JsonObject jsonFace = jsonFaces.get(card.isSecondSide() ? 1 : 0).getAsJsonObject(); + JsonObject jsonImages = JsonUtil.getAsObject(jsonFace, "image_uris"); + if (jsonImages == null) { + throw new MageException("Couldn't find image_uris in card's JSON data: " + jsonUrl); + } + + return JsonUtil.getAsString(jsonImages, "large"); } @Override diff --git a/Mage.Common/pom.xml b/Mage.Common/pom.xml index c6d83d8238f..14fa512b144 100644 --- a/Mage.Common/pom.xml +++ b/Mage.Common/pom.xml @@ -20,6 +20,7 @@ mage ${project.version} + com.googlecode.jspf jspf-core @@ -41,37 +42,26 @@ jboss-serialization 4.2.2.GA + concurrent concurrent 1.3.4 + trove trove 1.0.2 - - com.google.code.gson - gson - 2.8.6 - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - + org.apache.commons commons-lang3 test + diff --git a/Mage.Common/src/main/java/mage/remote/SessionImpl.java b/Mage.Common/src/main/java/mage/remote/SessionImpl.java index 4b730261dd6..c8b3f7f1a32 100644 --- a/Mage.Common/src/main/java/mage/remote/SessionImpl.java +++ b/Mage.Common/src/main/java/mage/remote/SessionImpl.java @@ -122,11 +122,23 @@ public class SessionImpl implements Session { client.showMessage("Remote task error. " + message); } - private boolean doRemoteWorkAndHandleErrors(RemotingTask remoting) { + private boolean doRemoteWorkAndHandleErrors(boolean closeConnectionOnFinish, boolean mustWaitServerMessageOnFail, + RemotingTask remoting) { // execute remote task and wait result, can be canceled lastRemotingTask = remoting; try { - return remoting.doWork(); + boolean res = remoting.doWork(); + if (!res && mustWaitServerMessageOnFail) { + // server send detail error as separate message by existing connection, + // so you need wait some time before disconnect + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + logger.fatal("waiting of error message had failed", e); + Thread.currentThread().interrupt(); + } + } + return res; } catch (InterruptedException | CancellationException t) { // was canceled by user, nothing to show } catch (MalformedURLException ex) { @@ -180,13 +192,16 @@ public class SessionImpl implements Session { } } finally { lastRemotingTask = null; + if (closeConnectionOnFinish) { + disconnect(false); // it's ok on mutiple calls + } } return false; } @Override public synchronized boolean register(final Connection connection) { - return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() { + return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(true, true, new RemotingTask() { @Override public boolean work() throws Throwable { logger.info("Registration: username " + getUserName() + " for email " + getEmail()); @@ -199,7 +214,7 @@ public class SessionImpl implements Session { @Override public synchronized boolean emailAuthToken(final Connection connection) { - return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() { + return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(true, true, new RemotingTask() { @Override public boolean work() throws Throwable { logger.info("Auth request: requesting auth token for username " + getUserName() + " to email " + getEmail()); @@ -212,12 +227,12 @@ public class SessionImpl implements Session { @Override public synchronized boolean resetPassword(final Connection connection) { - return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() { + return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(true, true, new RemotingTask() { @Override public boolean work() throws Throwable { logger.info("Password reset: reseting password for username " + getUserName()); boolean result = server.resetPassword(sessionId, connection.getEmail(), connection.getAuthToken(), connection.getPassword()); - logger.info("Password reset: " + (result ? "DONE, check your email for new password" : "FAIL")); + logger.info("Password reset: " + (result ? "DONE, now you can login with new password" : "FAIL")); return result; } }); @@ -225,7 +240,7 @@ public class SessionImpl implements Session { @Override public synchronized boolean connect(final Connection connection) { - return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() { + return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(false, true, new RemotingTask() { @Override public boolean work() throws Throwable { setLastError(""); @@ -258,7 +273,6 @@ public class SessionImpl implements Session { } logger.info("Logging: FAIL"); - disconnect(false); return false; } }); @@ -437,7 +451,7 @@ public class SessionImpl implements Session { boolean result; try { - result = doRemoteWorkAndHandleErrors(lastRemotingTask); + result = doRemoteWorkAndHandleErrors(false, false, lastRemotingTask); } finally { lastRemotingTask = null; } @@ -529,6 +543,7 @@ public class SessionImpl implements Session { if (sessionState == SessionState.DISCONNECTING || sessionState == SessionState.CONNECTING) { sessionState = SessionState.DISCONNECTED; + serverState = null; logger.info("Disconnecting DONE"); if (askForReconnect) { client.showError("Network error. You have been disconnected from " + connection.getHost()); @@ -1654,7 +1669,10 @@ public class SessionImpl implements Session { @Override public boolean ping() { try { - if (isConnected() && sessionId != null) { + // ping must work after login only, all other actions are single call (example: register new user) + // sessionId fills on connection + // serverState fills on good login + if (isConnected() && sessionId != null && serverState != null) { long startTime = System.nanoTime(); if (!server.ping(sessionId, pingInfo)) { logger.error("Ping failed: " + this.getUserName() + " Session: " + sessionId + " to MAGE server at " + connection.getHost() + ':' + connection.getPort()); diff --git a/Mage.Common/src/main/java/mage/view/AbilityPickerView.java b/Mage.Common/src/main/java/mage/view/AbilityPickerView.java index d4ae6a0c231..7b4141294cd 100644 --- a/Mage.Common/src/main/java/mage/view/AbilityPickerView.java +++ b/Mage.Common/src/main/java/mage/view/AbilityPickerView.java @@ -17,9 +17,11 @@ public class AbilityPickerView implements Serializable { private static final long serialVersionUID = 1L; private Map choices = new LinkedHashMap<>(); - private String message = null; + private String message; + private GameView gameView; - public AbilityPickerView(String objectName, List abilities, String message) { + public AbilityPickerView(GameView gameView, String objectName, List abilities, String message) { + this.gameView = gameView; this.message = message; int num = 0; @@ -44,6 +46,12 @@ public class AbilityPickerView implements Serializable { } } + public AbilityPickerView(GameView gameView, Map modes, String message) { + this.gameView = gameView; + this.choices = modes; + this.message = message; + } + private String getAbilityRules(Ability ability, String objectName) { String rule = ability.getRule(objectName); if (rule.isEmpty()) { @@ -55,11 +63,6 @@ public class AbilityPickerView implements Serializable { return rule; } - public AbilityPickerView(Map modes, String message) { - this.choices = modes; - this.message = message; - } - public Map getChoices() { return choices; } @@ -67,4 +70,8 @@ public class AbilityPickerView implements Serializable { public String getMessage() { return message; } + + public GameView getGameView() { + return gameView; + } } diff --git a/Mage.Common/src/main/java/mage/view/GameClientMessage.java b/Mage.Common/src/main/java/mage/view/GameClientMessage.java index 092079bf7cc..64a4c43e379 100644 --- a/Mage.Common/src/main/java/mage/view/GameClientMessage.java +++ b/Mage.Common/src/main/java/mage/view/GameClientMessage.java @@ -24,7 +24,7 @@ public class GameClientMessage implements Serializable { @Expose private GameView gameView; @Expose - private CardsView cardsView; + private CardsView cardsView1; @Expose private CardsView cardsView2; @Expose @@ -32,8 +32,6 @@ public class GameClientMessage implements Serializable { @Expose private boolean flag; @Expose - private String[] strings; - @Expose private Set targets; @Expose private int min; @@ -46,64 +44,53 @@ public class GameClientMessage implements Serializable { @Expose private List messages; - public GameClientMessage(GameView gameView) { + public GameClientMessage(GameView gameView, Map options) { this.gameView = gameView; - } - - public GameClientMessage(GameView gameView, String message) { - this.gameView = gameView; - this.message = message; - } - - public GameClientMessage(GameView gameView, String message, Map options) { - this.gameView = gameView; - this.message = message; this.options = options; } - private GameClientMessage(GameView gameView, String question, CardsView cardView, Set targets, boolean required) { + public GameClientMessage(GameView gameView, Map options, String message) { this.gameView = gameView; - this.message = question; - this.cardsView = cardView; + this.options = options; + this.message = message; + } + + public GameClientMessage(GameView gameView, Map options, String message, CardsView cardsView1, Set targets, boolean required) { + this.gameView = gameView; + this.options = options; + this.message = message; + this.cardsView1 = cardsView1; this.targets = targets; this.flag = required; } - public GameClientMessage(GameView gameView, String question, CardsView cardView, Set targets, boolean required, Map options) { - this(gameView, question, cardView, targets, required); + public GameClientMessage(GameView gameView, Map options, String message, int min, int max) { + this.gameView = gameView; this.options = options; - } - - public GameClientMessage(String[] choices, String message) { - this.strings = choices; - this.message = message; - } - - public GameClientMessage(String message, int min, int max) { this.message = message; this.min = min; this.max = max; } - public GameClientMessage(String message, CardsView pile1, CardsView pile2) { + public GameClientMessage(GameView gameView, Map options, String message, CardsView pile1, CardsView pile2) { + this.gameView = gameView; + this.options = options; this.message = message; - this.cardsView = pile1; + this.cardsView1 = pile1; this.cardsView2 = pile2; } - public GameClientMessage(CardsView cardView, String name) { - this.cardsView = cardView; - this.message = name; - } - - public GameClientMessage(List messages, int min, int max, Map options) { + public GameClientMessage(GameView gameView, Map options, List messages, int min, int max) { + this.gameView = gameView; + this.options = options; this.messages = messages; this.min = min; this.max = max; - this.options = options; } - public GameClientMessage(Choice choice) { + public GameClientMessage(GameView gameView, Map options, Choice choice) { + this.gameView = gameView; + this.options = options; this.choice = choice; } @@ -111,8 +98,12 @@ public class GameClientMessage implements Serializable { return gameView; } - public CardsView getCardsView() { - return cardsView; + public CardsView getCardsView1() { + return cardsView1; + } + + public CardsView getCardsView2() { + return cardsView2; } public String getMessage() { @@ -123,22 +114,10 @@ public class GameClientMessage implements Serializable { return flag; } - public String[] getStrings() { - return strings; - } - public Set getTargets() { return targets; } - public CardsView getPile1() { - return cardsView; - } - - public CardsView getPile2() { - return cardsView2; - } - public int getMin() { return min; } diff --git a/Mage.Plugins/Mage.Counter.Plugin/pom.xml b/Mage.Plugins/Mage.Counter.Plugin/pom.xml index 483cdf4e6a0..a68381e5a00 100644 --- a/Mage.Plugins/Mage.Counter.Plugin/pom.xml +++ b/Mage.Plugins/Mage.Counter.Plugin/pom.xml @@ -22,11 +22,6 @@ mage-common ${mage-version} - - log4j - log4j - provided - diff --git a/Mage.Server.Console/pom.xml b/Mage.Server.Console/pom.xml index dfe03c7fe4b..4628afb0de0 100644 --- a/Mage.Server.Console/pom.xml +++ b/Mage.Server.Console/pom.xml @@ -24,11 +24,6 @@ swingx 1.6.1 - - junit - junit - test - diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AusHighlander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AusHighlander.java index f7d76283ca4..99355992206 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AusHighlander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AusHighlander.java @@ -22,7 +22,7 @@ public class AusHighlander extends Constructed { pointMap.put("Black Lotus", 4); pointMap.put("Time Vault", 4); pointMap.put("Demonic Tutor", 3); - pointMap.put("Imperial Seal", 3); + pointMap.put("Mana Crypt", 3); pointMap.put("Mox Emerald", 3); pointMap.put("Mox Jet", 3); pointMap.put("Mox Pearl", 3); @@ -30,11 +30,12 @@ public class AusHighlander extends Constructed { pointMap.put("Mox Sapphire", 3); pointMap.put("Sol Ring", 3); pointMap.put("Thassa's Oracle", 3); + pointMap.put("Underworld Breach", 3); pointMap.put("Vampiric Tutor", 3); pointMap.put("Channel", 2); pointMap.put("Dig Through Time", 2); pointMap.put("Flash", 2); - pointMap.put("Mana Crypt", 2); + pointMap.put("Imperial Seal", 2); pointMap.put("Mind Twist", 2); pointMap.put("Mystical Tutor", 2); pointMap.put("Oko, Thief of Crowns", 2); @@ -46,11 +47,12 @@ public class AusHighlander extends Constructed { pointMap.put("Balance", 1); pointMap.put("Birthing Pod", 1); pointMap.put("Crop Rotation", 1); - pointMap.put("Dark Petition", 1); + pointMap.put("Deathrite Shaman", 1); pointMap.put("Doomsday", 1); pointMap.put("Enlightened Tutor", 1); pointMap.put("Fastbond", 1); pointMap.put("Force of Will", 1); + pointMap.put("Gifts Ungiven", 1); pointMap.put("Green Sun's Zenith", 1); pointMap.put("Hermit Druid", 1); pointMap.put("Intuition", 1); @@ -77,7 +79,7 @@ public class AusHighlander extends Constructed { pointMap.put("Timetwister", 1); pointMap.put("Tolarian Academy", 1); pointMap.put("Umezawa's Jitte", 1); - pointMap.put("Underworld Breach", 1); + pointMap.put("Uro, Titan of Nature's Wrath", 1); pointMap.put("Wasteland", 1); pointMap.put("Wishclaw Talisman", 1); pointMap.put("Wrenn and Six", 1); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/CanadianHighlander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/CanadianHighlander.java index ca967a3244e..d5feec40142 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/CanadianHighlander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/CanadianHighlander.java @@ -40,15 +40,16 @@ public class CanadianHighlander extends Constructed { pointMap.put("Mox Ruby", 3); pointMap.put("Mox Sapphire", 3); pointMap.put("Mystical Tutor", 2); - pointMap.put("Natural Order", 4); + pointMap.put("Natural Order", 3); pointMap.put("Price of Progress", 1); - pointMap.put("Protean Hulk", 3); + pointMap.put("Protean Hulk", 2); pointMap.put("Sol Ring", 4); pointMap.put("Spellseeker", 2); pointMap.put("Strip Mine", 3); pointMap.put("Summoner's Pact", 1); pointMap.put("Survival of the Fittest", 2); pointMap.put("Tainted Pact", 1); + pointMap.put("Thassa's Oracle", 2); pointMap.put("Time Vault", 7); pointMap.put("Time Walk", 7); pointMap.put("Tinker", 3); @@ -56,8 +57,8 @@ public class CanadianHighlander extends Constructed { pointMap.put("Transmute Artifact", 1); pointMap.put("Treasure Cruise", 1); pointMap.put("True-Name Nemesis", 1); - pointMap.put("Umezawa's Jitte", 2); - pointMap.put("Underworld Breach", 1); + pointMap.put("Umezawa's Jitte", 1); + pointMap.put("Underworld Breach", 2); pointMap.put("Vampiric Tutor", 2); pointMap.put("Wishclaw Talisman", 1); pointMap.put("Yawgmoth's Will", 2); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java index b732e7faac5..9dccf0fde5f 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java @@ -46,6 +46,7 @@ public class Commander extends Constructed { banned.add("Fastbond"); banned.add("Flash"); banned.add("Gifts Ungiven"); + banned.add("Golos, Tireless Pilgrim"); banned.add("Griselbrand"); banned.add("Hullbreacher"); banned.add("Iona, Shield of Emeria"); @@ -74,7 +75,6 @@ public class Commander extends Constructed { banned.add("Tolarian Academy"); banned.add("Trade Secrets"); banned.add("Upheaval"); - banned.add("Worldfire"); banned.add("Yawgmoth's Bargain"); } @@ -586,13 +586,7 @@ public class Commander extends Constructed { } if (card.isPlaneswalker()) { - if (card.getName().toLowerCase(Locale.ENGLISH).equals("jace, the mind sculptor")) { - thisMaxPower = Math.max(thisMaxPower, 6); - } - if (card.getName().toLowerCase(Locale.ENGLISH).equals("ugin, the spirit dragon")) { - thisMaxPower = Math.max(thisMaxPower, 5); - } - thisMaxPower = Math.max(thisMaxPower, 4); + thisMaxPower = Math.max(thisMaxPower, 6); } String cn = card.getName().toLowerCase(Locale.ENGLISH); @@ -673,7 +667,7 @@ public class Commander extends Constructed { || cn.equals("vorinclex, voice of hunger") || cn.equals("winter orb") || cn.equals("zur the enchanter")) { - thisMaxPower = Math.max(thisMaxPower, 5); + thisMaxPower = Math.max(thisMaxPower, 12); } // Parts of infinite combos @@ -734,9 +728,151 @@ public class Commander extends Constructed { || cn.equals("workhorse") || cn.equals("worldgorger dragon") || cn.equals("worthy cause") || cn.equals("yawgmoth's will") || cn.equals("zealous conscripts")) { - thisMaxPower = Math.max(thisMaxPower, 12); + thisMaxPower = Math.max(thisMaxPower, 15); numberInfinitePieces++; } + + // Saltiest cards (edhrec) + if (cn.equals("acid rain") + || cn.equals("agent of treachery") + || cn.equals("apocalypse") + || cn.equals("armageddon") + || cn.equals("atraxa, praetors' voice") + || cn.equals("aura shards") + || cn.equals("avacyn, angel of hope") + || cn.equals("back to basics") + || cn.equals("bend or break") + || cn.equals("blightsteel colossus") + || cn.equals("blood moon") + || cn.equals("boil") + || cn.equals("boiling seas") + || cn.equals("bribery") + || cn.equals("burning sands") + || cn.equals("card view") + || cn.equals("cataclysm") + || cn.equals("catastrophe") + || cn.equals("chulane, teller of tales") + || cn.equals("confusion in the ranks") + || cn.equals("consecrated sphinx") + || cn.equals("contamination") + || cn.equals("craterhoof behemoth") + || cn.equals("cyclonic rift") + || cn.equals("death cloud") + || cn.equals("decree of annihilation") + || cn.equals("decree of silence") + || cn.equals("demonic consultation") + || cn.equals("derevi, empyrial tactician") + || cn.equals("devastation") + || cn.equals("divine intervention") + || cn.equals("dockside extortionist") + || cn.equals("doomsday") + || cn.equals("doubling season") + || cn.equals("drannith magistrate") + || cn.equals("elesh norn, grand cenobite") + || cn.equals("embargo") + || cn.equals("emrakul, the promised end") + || cn.equals("epicenter") + || cn.equals("expropriate") + || cn.equals("fall of the thran") + || cn.equals("fierce guardianship") + || cn.equals("food chain") + || cn.equals("force of negation") + || cn.equals("force of will") + || cn.equals("gaddock teeg") + || cn.equals("gaea's cradle") + || cn.equals("gilded drake") + || cn.equals("glenn, the voice of calm") + || cn.equals("global ruin") + || cn.equals("golos, tireless pilgrim") + || cn.equals("grand arbiter augustin iv") + || cn.equals("grip of chaos") + || cn.equals("hokori, dust drinker") + || cn.equals("humility") + || cn.equals("impending disaster") + || cn.equals("invoke prejudice") + || cn.equals("iona, shield of emeria") + || cn.equals("jin-gitaxias, core augur") + || cn.equals("jokulhaups") + || cn.equals("keldon firebombers") + || cn.equals("kinnan, bonder prodigy") + || cn.equals("kozilek, butcher of truth") + || cn.equals("land equilibrium") + || cn.equals("linvala, keeper of silence") + || cn.equals("magister sphinx") + || cn.equals("mana breach") + || cn.equals("mana crypt") + || cn.equals("mana drain") + || cn.equals("mana vortex") + || cn.equals("mindslaver") + || cn.equals("narset, enlightened master") + || cn.equals("narset, parter of veils") + || cn.equals("negan, the cold-blooded") + || cn.equals("nether void") + || cn.equals("nexus of fate") + || cn.equals("notion thief") + || cn.equals("obliterate") + || cn.equals("oko, thief of crowns") + || cn.equals("oloro, ageless ascetic") + || cn.equals("omniscience") + || cn.equals("opposition agent") + || cn.equals("oppression") + || cn.equals("overwhelming splendor") + || cn.equals("palinchron") + || cn.equals("paradox engine") + || cn.equals("possessed portal") + || cn.equals("price of glory") + || cn.equals("protean hulk") + || cn.equals("ravages of war") + || cn.equals("rhystic study") + || cn.equals("rick, steadfast leader") + || cn.equals("rising waters") + || cn.equals("ruination") + || cn.equals("scrambleverse") + || cn.equals("seedborn muse") + || cn.equals("sen triplets") + || cn.equals("sire of insanity") + || cn.equals("skithiryx, the blight dragon") + || cn.equals("smokestack") + || cn.equals("smothering tithe") + || cn.equals("sorin markov") + || cn.equals("stasis") + || cn.equals("static orb") + || cn.equals("storage matrix") + || cn.equals("sunder") + || cn.equals("survival of the fittest") + || cn.equals("table view") + || cn.equals("tainted aether") + || cn.equals("tectonic break") + || cn.equals("teferi's protection") + || cn.equals("teferi, master of time") + || cn.equals("teferi, time raveler") + || cn.equals("temporal manipulation") + || cn.equals("tergrid, god of fright") + || cn.equals("text view") + || cn.equals("thassa's oracle") + || cn.equals("the tabernacle at pendrell vale") + || cn.equals("thieves' auction") + || cn.equals("thoughts of ruin") + || cn.equals("thrasios, triton hero") + || cn.equals("time stretch") + || cn.equals("time warp") + || cn.equals("tooth and nail") + || cn.equals("torment of hailfire") + || cn.equals("torpor orb") + || cn.equals("triumph of the hordes") + || cn.equals("ugin, the spirit dragon") + || cn.equals("ulamog, the ceaseless hunger") + || cn.equals("ulamog, the infinite gyre") + || cn.equals("urza, lord high artificer") + || cn.equals("void winnower") + || cn.equals("vorinclex, voice of hunger") + || cn.equals("wake of destruction") + || cn.equals("warp world") + || cn.equals("winter orb") + || cn.equals("xanathar, guild kingpin") + || cn.equals("zur the enchanter")) { + thisMaxPower = Math.max(thisMaxPower, 15); + } edhPowerLevel += thisMaxPower; } @@ -769,11 +905,17 @@ public class Commander extends Constructed { // Least fun commanders if (cn.equals("animar, soul of element") + || cn.equals("anafenza, the foremost") + || cn.equals("arcum dagsson") || cn.equals("azami, lady of scrolls") + || cn.equals("azusa, lost but seeking") + || cn.equals("brago, king eternal") || cn.equals("braids, cabal minion") + || cn.equals("captain sisay") || cn.equals("child of alara") || cn.equals("derevi, empyrial tactician") || cn.equals("edric, spymaster of trest") + || cn.equals("elesh norn, grand cenobite") || cn.equals("gaddock teeg") || cn.equals("grand arbiter augustin iv") || cn.equals("hokori, dust drinker") @@ -784,41 +926,67 @@ public class Commander extends Constructed { || cn.equals("leovold, emissary of trest") || cn.equals("linvala, keeper of silence") || cn.equals("llawan, cephalid empress") + || cn.equals("maelstrom wanderer") + || cn.equals("malfegor") || cn.equals("memnarch") || cn.equals("meren of clan nel toth") || cn.equals("michiko konda, truth seeker") + || cn.equals("mikaeus the unhallowed") || cn.equals("narset, enlightened master") + || cn.equals("nath of the gilt-leaf") || cn.equals("nekusar, the mindrazer") || cn.equals("norin the wary") || cn.equals("numot, the devastator") + || cn.equals("prossh, skyraider of kher") + || cn.equals("purphoros, god of the forge") + || cn.equals("sen triplets") || cn.equals("sheoldred, whispering one") || cn.equals("teferi, mage of zhalfir") + || cn.equals("urabrask the hidden") + || cn.equals("vorinclex, voice of hunger") || cn.equals("zur the enchanter")) { thisMaxPower = Math.max(thisMaxPower, 25); } - // Next least fun commanders - if (cn.equals("anafenza, the foremost") - || cn.equals("arcum dagsson") - || cn.equals("azusa, lost but seeking") - || cn.equals("brago, king eternal") - || cn.equals("captain sisay") + // Saltiest commanders + if (cn.equals("atraxa, praetors' voice") + || cn.equals("avacyn, angel of hope") + || cn.equals("chulane, teller of tales") + || cn.equals("derevi, empyrial tactician") || cn.equals("elesh norn, grand cenobite") - || cn.equals("malfegor") - || cn.equals("maelstrom wanderer") - || cn.equals("mikaeus the unhallowed") - || cn.equals("nath of the gilt-leaf") - || cn.equals("prossh, skyraider of kher") - || cn.equals("purphoros, god of the forge") + || cn.equals("emrakul, the promised end") + || cn.equals("gaddock teeg") + || cn.equals("glenn, the voice of calm") + || cn.equals("golos, tireless pilgrim") + || cn.equals("grand arbiter augustin iv") + || cn.equals("hokori, dust drinker") + || cn.equals("iona, shield of emeria") + || cn.equals("jin-gitaxias, core augur") + || cn.equals("kinnan, bonder prodigy") + || cn.equals("kozilek, butcher of truth") + || cn.equals("linvala, keeper of silence") + || cn.equals("narset, enlightened master") + || cn.equals("negan, the cold-blooded") + || cn.equals("oko, thief of crowns") + || cn.equals("oloro, ageless ascetic") + || cn.equals("rick, steadfast leader") || cn.equals("sen triplets") - || cn.equals("urabrask the hidden") - || cn.equals("vorinclex, voice of hunger")) { - thisMaxPower = Math.max(thisMaxPower, 15); + || cn.equals("skithiryx, the blight dragon") + || cn.equals("teferi, master of time") + || cn.equals("teferi, time raveler") + || cn.equals("thrasios, triton hero") + || cn.equals("ulamog, the ceaseless hunger") + || cn.equals("ulamog, the infinite gyre") + || cn.equals("urza, lord high artificer") + || cn.equals("vorinclex, voice of hunger") + || cn.equals("xanathar, guild kingpin") + || cn.equals("zur the enchanter")) { + thisMaxPower = Math.max(thisMaxPower, 20); } edhPowerLevel += thisMaxPower; } - edhPowerLevel += numberInfinitePieces * 12; + edhPowerLevel += numberInfinitePieces * 18; edhPowerLevel = Math.round(edhPowerLevel / 10); if (edhPowerLevel >= 100) { edhPowerLevel = 99; diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java index 786f19ce042..629758796b8 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java @@ -36,8 +36,9 @@ public class DuelCommander extends Commander { banned.add("Karakas"); banned.add("Library of Alexandria"); banned.add("Lion's Eye Diamond"); + banned.add("Lutri, The Spellchaser"); banned.add("Loyal Retainers"); - banned.add("Lutri, the Spellchaser"); + banned.add("Maddening Hex"); banned.add("Mana Crypt"); banned.add("Mana Drain"); banned.add("Mana Vault"); @@ -77,6 +78,7 @@ public class DuelCommander extends Commander { bannedCommander.add("Akiri, Line-Slinger"); bannedCommander.add("Arahbo, Roar of the World"); bannedCommander.add("Ardenn, Intrepid Archaeologist"); + bannedCommander.add("Asmoranomardicadaistinaculdacar"); bannedCommander.add("Baral, Chief of Compliance"); bannedCommander.add("Breya, Etherium Shaper"); bannedCommander.add("Bruse Tarl, Boorish Herder"); @@ -106,6 +108,7 @@ public class DuelCommander extends Commander { bannedCommander.add("Tymna, the Weaver"); bannedCommander.add("Urza, Lord High Artificer"); bannedCommander.add("Vial Smasher the Fierce"); + bannedCommander.add("Winota, Joiner of Forces"); bannedCommander.add("Yuriko, the Tiger's Shadow"); bannedCommander.add("Zurgo Bellstriker"); } diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Oathbreaker.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Oathbreaker.java index fa01ecfdd58..9a86f632844 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Oathbreaker.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Oathbreaker.java @@ -25,7 +25,6 @@ public class Oathbreaker extends Vintage { setName("Oathbreaker"); // banned = vintage + oathbreaker's list: https://oathbreakermtg.org/banned-list/ - // last updated 4/24/20 - Dark Ritual banned banned.add("Ad Nauseam"); banned.add("Ancestral Recall"); banned.add("Balance"); @@ -77,7 +76,6 @@ public class Oathbreaker extends Vintage { banned.add("Tooth and Nail"); banned.add("Trade Secrets"); banned.add("Upheaval"); - banned.add("Worldfire"); banned.add("Yawgmoth's Bargain"); } diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java index 902dc2e6ca3..9ab606410b4 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java @@ -5,7 +5,11 @@ import mage.cards.Sets; import mage.cards.decks.Constructed; import mage.constants.SetType; -import java.util.*; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.stream.Collectors; /** * @author BetaSteward_at_googlemail.com @@ -17,61 +21,39 @@ public class Standard extends Constructed { setCodes.addAll(makeLegalSets()); - banned.add("Agent of Treachery"); - banned.add("Cauldron Familiar"); - banned.add("Escape to the Wilds"); - banned.add("Field of the Dead"); - banned.add("Fires of Invention"); - banned.add("Growth Spiral"); - banned.add("Lucky Clover"); - banned.add("Oko, Thief of Crowns"); banned.add("Omnath, Locus of Creation"); - banned.add("Once Upon a Time"); - banned.add("Teferi, Time Raveler"); - banned.add("Uro, Titan of Nature's Wrath"); - banned.add("Wilderness Reclamation"); - banned.add("Veil of Summer"); } private static boolean isFallSet(ExpansionSet set) { Calendar cal = Calendar.getInstance(); cal.setTime(set.getReleaseDate()); - // Fall sets are normally released during or after September - return set.getSetType() == SetType.EXPANSION && (cal.get(Calendar.MONTH) > 7); + // Fall sets are normally released during or after September and before November + return set.getSetType() == SetType.EXPANSION + && Calendar.SEPTEMBER <= cal.get(Calendar.MONTH) + && cal.get(Calendar.MONTH) < Calendar.NOVEMBER; } static List makeLegalSets() { - List codes = new ArrayList<>(); GregorianCalendar current = new GregorianCalendar(); - List sets = new ArrayList(Sets.getInstance().values()); - Collections.sort(sets, new Comparator() { - @Override - public int compare(final ExpansionSet lhs, ExpansionSet rhs) { - return lhs.getReleaseDate().after(rhs.getReleaseDate()) ? -1 : 1; - } - }); - int fallSetsAdded = 0; - Date earliestDate = null; // Get the second most recent fall set that's been released. - for (ExpansionSet set : sets) { - if (set.getReleaseDate().after(current.getTime())) { - continue; - } - if (isFallSet(set)) { - fallSetsAdded++; - if (fallSetsAdded == 2) { - earliestDate = set.getReleaseDate(); - break; - } - } - } - - for (ExpansionSet set : sets) { - boolean isDateCompatible = earliestDate != null && !set.getReleaseDate().before(earliestDate) /*!set.getReleaseDate().after(current.getTime())*/; // no after date restrict for early tests and beta - if (set.getSetType().isStandardLegal() && isDateCompatible) { - codes.add(set.getCode()); - } - } - return codes; + Date earliestDate = Sets + .getInstance() + .values() + .stream() + .filter(set -> !set.getReleaseDate().after(current.getTime())) + .filter(Standard::isFallSet) + .sorted(ExpansionSet.getComparator()) + .skip(1) + .findFirst() + .get() + .getReleaseDate(); + return Sets.getInstance() + .values() + .stream() + .filter(set -> set.getSetType().isStandardLegal()) + .filter(set -> !set.getReleaseDate().before(earliestDate)) +// .filter(set -> !set.getReleaseDate().after(current.getTime())) // no after date restrict for early tests and beta + .map(ExpansionSet::getCode) + .collect(Collectors.toList()); } } diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/TinyLeaders.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/TinyLeaders.java index 4e2c3907fcb..92f45d511ba 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/TinyLeaders.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/TinyLeaders.java @@ -28,12 +28,12 @@ public class TinyLeaders extends Constructed { } } //Banned list from tinyleaders.blodspot.ca/p/ban-list.html - //Ban list updated as of 11/08/14 banned.add("Ancestral Recall"); banned.add("Balance"); banned.add("Black Lotus"); banned.add("Black Vise"); banned.add("Channel"); + banned.add("Codie, Vociferous Codex"); banned.add("Counterbalance"); banned.add("Demonic Tutor"); banned.add("Earthcraft"); @@ -57,14 +57,16 @@ public class TinyLeaders extends Constructed { banned.add("Mox Pearl"); banned.add("Mox Ruby"); banned.add("Mox Sapphire"); - banned.add("Najeela, the Blade Blossom"); + banned.add("Najeela, the Blade-Blossom"); banned.add("Necropotence"); banned.add("Shahrazad"); + banned.add("Sisay, Weatherlight Captain"); banned.add("Skullclamp"); banned.add("Sol Ring"); banned.add("Strip Mine"); banned.add("Survival of the Fittest"); banned.add("Sword of Body and Mind"); + banned.add("Thassa's Oracle"); banned.add("The Tabernacle at Pendrell Vale"); banned.add("Time Vault"); banned.add("Time Walk"); diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index 9b13c4809ae..5b347cb2a92 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -162,13 +162,12 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { boolean usedStack = false; while (actions.peek() != null) { Ability ability = actions.poll(); - // log example: ===> Act [PlayerA] Action: Cast Blessings of Nature (target 1; target 2) - logger.info(new StringBuilder("===> Act [") - .append(game.getPlayer(playerId).getName()) - .append("] Action: ") - .append(ability.toString()) - .append(listTargets(game, ability.getTargets(), " (targeting %s)", "")) - .toString()); + // example: ===> SELECTED ACTION for PlayerA: Play Swamp + logger.info(String.format("===> SELECTED ACTION for %s: %s", + getName(), + ability.toString() + + listTargets(game, ability.getTargets(), " (targeting %s)", "") + )); if (!ability.getTargets().isEmpty()) { for (Target target : ability.getTargets()) { for (UUID id : target.getTargets()) { @@ -391,6 +390,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { protected void resolve(SimulationNode2 node, int depth, Game game) { StackObject stackObject = game.getStack().getFirst(); if (stackObject instanceof StackAbility) { + // AI hint for search effects (calc all possible cards for best score) SearchEffect effect = getSearchEffect((StackAbility) stackObject); if (effect != null && stackObject.getControllerId().equals(playerId)) { @@ -477,15 +477,24 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { SimulationNode2 bestNode = null; List allActions = currentPlayer.simulatePriority(game); optimize(game, allActions); + int startedScore = GameStateEvaluator2.evaluate(this.getId(), node.getGame()).getTotalScore(); if (logger.isInfoEnabled() && !allActions.isEmpty() && depth == maxDepth) { - logger.info("ADDED ACTIONS (" + allActions.size() + ") " + ' ' + allActions); + logger.info(String.format("POSSIBLE ACTIONS for %s (%d, started score: %d)%s", + getName(), + allActions.size(), + startedScore, + (actions.isEmpty() ? "" : ":") + )); + for (int i = 0; i < allActions.size(); i++) { + logger.info(String.format("-> #%d (%s)", i + 1, allActions.get(i))); + } } - int counter = 0; + int actionNumber = 0; int bestValSubNodes = Integer.MIN_VALUE; for (Ability action : allActions) { - counter++; + actionNumber++; if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.interrupted()) { Thread.currentThread().interrupt(); @@ -503,7 +512,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } if (!sim.checkIfGameIsOver() && (action.isUsesStack() || action instanceof PassAbility)) { - // only pass if the last action uses the stack + // skip priority for opponents before stack resolve UUID nextPlayerId = sim.getPlayerList().get(); do { sim.getPlayer(nextPlayerId).pass(game); @@ -512,48 +521,73 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } SimulationNode2 newNode = new SimulationNode2(node, sim, action, depth, currentPlayer.getId()); sim.checkStateAndTriggered(); - int val; + int actionScore; if (action instanceof PassAbility && sim.getStack().isEmpty()) { - // Stop to simulate deeper if PassAbility and stack is empty - val = GameStateEvaluator2.evaluate(this.getId(), sim).getTotalScore(); + // no more next actions, it's a final score + actionScore = GameStateEvaluator2.evaluate(this.getId(), sim).getTotalScore(); } else { - val = addActions(newNode, depth - 1, alpha, beta); + // resolve current action and calc all next actions to find best score (return max possible score) + actionScore = addActions(newNode, depth - 1, alpha, beta); } - logger.debug("Sim Prio " + BLANKS.substring(0, 2 + (maxDepth - depth) * 3) + '[' + depth + "]#" + counter + " <" + val + "> - (" + action + ") "); + logger.debug("Sim Prio " + BLANKS.substring(0, 2 + (maxDepth - depth) * 3) + '[' + depth + "]#" + actionNumber + " <" + actionScore + "> - (" + action + ") "); + + // Hints on data: + // * node - started game with executed command (pay and put on stack) + // * newNode - resolved game with resolved command (resolve stack) + // * node.children - rewrites to store only best tree (e.g. contains only final data) + // * node.score - rewrites to store max score (e.g. contains only final data) if (logger.isInfoEnabled() && depth >= maxDepth) { - StringBuilder sb = new StringBuilder("Sim Prio [").append(depth).append("] #").append(counter) - .append(" <").append(val).append("> (").append(action) - .append(action.isModal() ? " Mode = " + action.getModes().getMode().toString() : "") - .append(listTargets(game, action.getTargets(), " (targeting %s)", "")).append(')') - .append(logger.isTraceEnabled() ? " #" + newNode.hashCode() : ""); + // show calculated actions and score + // example: Sim Prio [6] #1 <605> (Play Swamp) + int currentActionScore = GameStateEvaluator2.evaluate(this.getId(), newNode.getGame()).getTotalScore(); + int diffCurrentAction = currentActionScore - startedScore; + int diffNextActions = actionScore - startedScore - diffCurrentAction; + logger.info(String.format("Sim Prio [%d] #%d (%s)", + depth, + actionNumber, + printDiffScore(diffCurrentAction), + printDiffScore(diffNextActions), + action + + (action.isModal() ? " Mode = " + action.getModes().getMode().toString() : "") + + listTargets(game, action.getTargets(), " (targeting %s)", "") + + (logger.isTraceEnabled() ? " #" + newNode.hashCode() : "") + )); + // collect childs info (next actions chain) SimulationNode2 logNode = newNode; while (logNode.getChildren() != null && !logNode.getChildren().isEmpty()) { logNode = logNode.getChildren().get(0); if (logNode.getAbilities() != null && !logNode.getAbilities().isEmpty()) { - sb.append(" -> [").append(logNode.getDepth()).append(']').append(logNode.getAbilities().toString()).append('<').append(logNode.getScore()).append('>'); + int logCurrentScore = GameStateEvaluator2.evaluate(this.getId(), logNode.getGame()).getTotalScore(); + int logPrevScore = GameStateEvaluator2.evaluate(this.getId(), logNode.getParent().getGame()).getTotalScore(); + logger.info(String.format("Sim Prio [%d] -> next action: [%d]%s ", + depth, + logNode.getDepth(), + logNode.getAbilities().toString(), + printDiffScore(logCurrentScore - logPrevScore), + printDiffScore(actionScore - logCurrentScore) + )); } } - logger.info(sb); } if (currentPlayer.getId().equals(playerId)) { - if (val > bestValSubNodes) { - bestValSubNodes = val; + if (actionScore > bestValSubNodes) { + bestValSubNodes = actionScore; } if (depth == maxDepth && action instanceof PassAbility) { - val = val - PASSIVITY_PENALTY; // passivity penalty + actionScore = actionScore - PASSIVITY_PENALTY; // passivity penalty } - if (val > alpha + if (actionScore > alpha || (depth == maxDepth - && val == alpha + && actionScore == alpha && RandomUtil.nextBoolean())) { // Adding random for equal value to get change sometimes - alpha = val; + alpha = actionScore; bestNode = newNode; - bestNode.setScore(val); + bestNode.setScore(actionScore); if (!newNode.getChildren().isEmpty()) { bestNode.setCombat(newNode.getChildren().get(0).getCombat()); } @@ -564,7 +598,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { .stream() .map(a -> a.toString() + listTargets(game, a.getTargets(), " (targeting %s)", "")) .collect(Collectors.joining("; ")); - logger.info("Sim Prio [" + depth + "] -- Saved best node yet <" + bestNode.getScore() + scoreInfo + "> " + abilitiesInfo); + logger.info("Sim Prio [" + depth + "] >> BEST action chain found <" + bestNode.getScore() + scoreInfo + "> " + abilitiesInfo); node.children.clear(); node.children.add(bestNode); node.setScore(bestNode.getScore()); @@ -572,22 +606,22 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } // no need to check other actions - if (val == GameStateEvaluator2.WIN_GAME_SCORE) { + if (actionScore == GameStateEvaluator2.WIN_GAME_SCORE) { logger.debug("Sim Prio -- win - break"); break; } } else { - if (val < beta) { - beta = val; + if (actionScore < beta) { + beta = actionScore; bestNode = newNode; - bestNode.setScore(val); + bestNode.setScore(actionScore); if (!newNode.getChildren().isEmpty()) { bestNode.setCombat(newNode.getChildren().get(0).getCombat()); } } // no need to check other actions - if (val == GameStateEvaluator2.LOSE_GAME_SCORE) { + if (actionScore == GameStateEvaluator2.LOSE_GAME_SCORE) { logger.debug("Sim Prio -- lose - break"); break; } @@ -622,6 +656,14 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } } + private String printDiffScore(int score) { + if (score >= 0) { + return "+" + score; + } else { + return "" + score; + } + } + /** * Various AI optimizations for actions. * diff --git a/Mage.Server.Plugins/Mage.Player.AI/pom.xml b/Mage.Server.Plugins/Mage.Player.AI/pom.xml index b6b558e6e81..db3e907f0ce 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI/pom.xml @@ -15,11 +15,6 @@ Mage Player AI - - log4j - log4j - jar - ${project.groupId} mage diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index 311fa30f694..d7deb819b41 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -540,6 +540,58 @@ public class ComputerPlayer extends PlayerImpl implements Player { return setTargetPlayer(outcome, target, source, sourceId, abilityControllerId, randomOpponentId, game, required); } + // Angel of Serenity trigger + if (target.getOriginalTarget() instanceof TargetCardInGraveyardOrBattlefield) { + Cards cards = new CardsImpl(possibleTargets); + List possibleCards = new ArrayList<>(cards.getCards(game)); + for (Card card : possibleCards) { + // check permanents first; they have more intrinsic worth + if (card instanceof Permanent) { + Permanent p = ((Permanent) card); + if (outcome.isGood() + && p.isControlledBy(abilityControllerId)) { + if (target.canTarget(abilityControllerId, p.getId(), source, game)) { + if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { + break; + } + target.addTarget(p.getId(), source, game); + } + } + if (!outcome.isGood() + && !p.isControlledBy(abilityControllerId)) { + if (target.canTarget(abilityControllerId, p.getId(), source, game)) { + if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { + break; + } + target.addTarget(p.getId(), source, game); + } + } + } + // check the graveyards last + if (game.getState().getZone(card.getId()) == Zone.GRAVEYARD) { + if (outcome.isGood() + && card.isOwnedBy(abilityControllerId)) { + if (target.canTarget(abilityControllerId, card.getId(), source, game)) { + if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { + break; + } + target.addTarget(card.getId(), source, game); + } + } + if (!outcome.isGood() + && !card.isOwnedBy(abilityControllerId)) { + if (target.canTarget(abilityControllerId, card.getId(), source, game)) { + if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { + break; + } + target.addTarget(card.getId(), source, game); + } + } + } + } + return target.isChosen(); + } + if (target.getOriginalTarget() instanceof TargetDiscard || target.getOriginalTarget() instanceof TargetCardInHand) { if (outcome.isGood()) { @@ -2841,27 +2893,38 @@ public class ComputerPlayer extends PlayerImpl implements Player { } /** - * Sets a possible target player + * Sets a possible target player. Depends on bad/good outcome + * + * @param source null on choose and non-null on chooseTarget */ private boolean setTargetPlayer(Outcome outcome, Target target, Ability source, UUID sourceId, UUID abilityControllerId, UUID randomOpponentId, Game game, boolean required) { + Outcome affectedOutcome; + if (abilityControllerId == this.playerId) { + // selects for itself + affectedOutcome = outcome; + } else { + // selects for another player + affectedOutcome = Outcome.inverse(outcome); + } + if (target.getOriginalTarget() instanceof TargetOpponent) { if (source == null) { if (target.canTarget(randomOpponentId, game)) { target.add(randomOpponentId, game); return true; } - } else if (target.canTarget(randomOpponentId, source, game)) { - target.add(randomOpponentId, game); + } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { + target.addTarget(randomOpponentId, source, game); return true; } - for (UUID currentId : game.getOpponents(abilityControllerId)) { + for (UUID possibleOpponentId : game.getOpponents(abilityControllerId)) { if (source == null) { - if (target.canTarget(currentId, game)) { - target.add(currentId, game); + if (target.canTarget(possibleOpponentId, game)) { + target.add(possibleOpponentId, game); return true; } - } else if (target.canTarget(currentId, source, game)) { - target.add(currentId, game); + } else if (target.canTarget(abilityControllerId, possibleOpponentId, source, game)) { + target.addTarget(possibleOpponentId, source, game); return true; } } @@ -2869,8 +2932,9 @@ public class ComputerPlayer extends PlayerImpl implements Player { } if (target.getOriginalTarget() instanceof TargetPlayer) { - if (outcome.isGood()) { + if (affectedOutcome.isGood()) { if (source == null) { + // good if (target.canTarget(abilityControllerId, game)) { target.add(abilityControllerId, game); return true; @@ -2882,19 +2946,20 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } } else { + // good if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { - target.addTarget(playerId, source, game); + target.addTarget(abilityControllerId, source, game); return true; } if (target.isRequired(sourceId, game)) { - if (target.canTarget(randomOpponentId, game)) { - target.add(randomOpponentId, game); + if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { + target.addTarget(randomOpponentId, source, game); return true; } } } - } else if (source == null) { + // bad if (target.canTarget(randomOpponentId, game)) { target.add(randomOpponentId, game); return true; @@ -2906,13 +2971,14 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } } else { - if (target.canTarget(randomOpponentId, game)) { - target.add(randomOpponentId, game); + // bad + if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { + target.addTarget(randomOpponentId, source, game); return true; } if (required) { - if (target.canTarget(abilityControllerId, game)) { - target.add(abilityControllerId, game); + if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { + target.addTarget(abilityControllerId, source, game); return true; } } @@ -2950,7 +3016,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { @Override public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { - Map useable = PlayerImpl.getSpellAbilities(this.getId(), card, game.getState().getZone(card.getId()), game); + Map useable = PlayerImpl.getCastableSpellAbilities(game, this.getId(), card, game.getState().getZone(card.getId()), noMana); return (SpellAbility) useable.values().stream().findFirst().orElse(null); } diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml index 8435f3ea3ed..10e62a3e731 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml @@ -15,11 +15,6 @@ Mage Player AI MCTS - - log4j - log4j - jar - ${project.groupId} mage diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml b/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml index 3b773ba2ea1..8b878dfacc3 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/pom.xml @@ -15,11 +15,6 @@ Mage Player AI Minimax - - log4j - log4j - jar - ${project.groupId} mage diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java index 6feb0d0eeca..097c08ef3b7 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java @@ -351,6 +351,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player { } protected int simulatePriority(SimulationNode node, Game game, int alpha, int beta) { + // NOT USED in real AI, see ComputerPlayer6 if (Thread.interrupted()) { Thread.currentThread().interrupt(); logger.debug(indent(node.depth) + "interrupted"); diff --git a/Mage.Server.Plugins/Mage.Player.Human/pom.xml b/Mage.Server.Plugins/Mage.Player.Human/pom.xml index c4d102c8920..6aed2472706 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.Human/pom.xml @@ -25,11 +25,6 @@ mage-common ${project.version} - - log4j - log4j - provided - diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 6312f27ab0a..352147acc40 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -2174,7 +2174,7 @@ public class HumanPlayer extends PlayerImpl { } @Override - public SpellAbility chooseAbilityForCast(Card card, Game game, boolean nonMana) { + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { if (gameInCheckPlayableState(game)) { return null; } @@ -2186,8 +2186,8 @@ public class HumanPlayer extends PlayerImpl { MageObject object = game.getObject(card.getId()); // must be object to find real abilities (example: commander) if (object != null) { - String message = "Choose ability to cast" + (nonMana ? " for FREE" : "") + "
" + object.getLogName(); - LinkedHashMap useableAbilities = getSpellAbilities(playerId, object, game.getState().getZone(object.getId()), game); + String message = "Choose ability to cast" + (noMana ? " for FREE" : "") + "
" + object.getLogName(); + LinkedHashMap useableAbilities = PlayerImpl.getCastableSpellAbilities(game, playerId, object, game.getState().getZone(object.getId()), noMana); if (useableAbilities != null && useableAbilities.size() == 1) { return (SpellAbility) useableAbilities.values().iterator().next(); diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index 79e1f17a73c..f951213ed28 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -19,11 +19,6 @@ mage ${mage-version}
- - org.apache.commons - commons-lang3 - 3.11 - ${project.groupId} mage-common @@ -34,30 +29,6 @@ mage-sets ${mage-version} - - junit - junit - test - - - com.sun.xml.bind - jaxb-impl - 2.3.3 - - - - org.glassfish.jaxb - jaxb-runtime - 3.0.2 - - - log4j - log4j - - - org.slf4j - slf4j-log4j12 - ${project.groupId} mage-player-ai @@ -88,12 +59,6 @@ ${project.version} runtime - - org.apache.commons - commons-compress - [1.19,) - - ${project.groupId} mage-game-commanderfreeforall @@ -184,7 +149,6 @@ ${project.version} runtime - ${project.groupId} mage-game-freeformcommanderfreeforall @@ -203,7 +167,6 @@ ${project.version} runtime - ${project.groupId} mage-game-oathbreakerduel @@ -216,7 +179,6 @@ ${project.version} runtime - ${project.groupId} mage-game-momirduel @@ -229,41 +191,57 @@ ${project.version} runtime + + + org.apache.commons + commons-lang3 + 3.11 + + + com.sun.xml.bind + jaxb-impl + 3.0.2 + + + + org.glassfish.jaxb + jaxb-runtime + 3.0.2 + + + org.apache.commons + commons-compress + [1.19,) + org.apache.shiro shiro-core 1.8.0 - jar com.google.api-client google-api-client 1.31.1 - jar com.google.apis google-api-services-gmail v1-rev20210614-1.32.1 - jar com.google.oauth-client google-oauth-client-java6 1.31.0 - jar com.google.oauth-client google-oauth-client-jetty 1.31.2 - jar javax.mail mail 1.5.0-b01 - jar com.sun.jersey @@ -281,21 +259,15 @@ 1.19.4 + org.xerial sqlite-jdbc 3.32.3.2 - - - - - org.junit.jupiter - junit-jupiter - test + runtime - org.assertj - assertj-core - test + org.unbescape + unbescape
diff --git a/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java b/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java index f1915e72f11..2f817d7db03 100644 --- a/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java +++ b/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java @@ -22,9 +22,7 @@ import java.io.File; import java.sql.SQLException; import java.util.List; -public enum AuthorizedUserRepository { - - instance; +public class AuthorizedUserRepository { private static final String JDBC_URL = "jdbc:h2:file:./db/authorized_user.h2;AUTO_SERVER=TRUE"; private static final String VERSION_ENTITY_NAME = "authorized_user"; @@ -32,15 +30,20 @@ public enum AuthorizedUserRepository { private static final long DB_VERSION = 2; private static final RandomNumberGenerator rng = new SecureRandomNumberGenerator(); + private static final AuthorizedUserRepository instance; + static { + instance = new AuthorizedUserRepository(JDBC_URL); + } + private Dao dao; - AuthorizedUserRepository() { + public AuthorizedUserRepository(String connectionString) { File file = new File("db"); if (!file.exists()) { file.mkdirs(); } try { - ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL); + ConnectionSource connectionSource = new JdbcConnectionSource(connectionString); TableUtils.createTableIfNotExists(connectionSource, AuthorizedUser.class); dao = DaoManager.createDao(connectionSource, AuthorizedUser.class); } catch (SQLException ex) { @@ -48,6 +51,10 @@ public enum AuthorizedUserRepository { } } + public static AuthorizedUserRepository getInstance() { + return instance; + } + public void add(final String userName, final String password, final String email) { try { Hash hash = new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, rng.nextBytes(), 1024); diff --git a/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java index 1bd283e03eb..dbdbb00dd5f 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java @@ -128,7 +128,7 @@ public class ChatManagerImpl implements ChatManager { Matcher matchPattern = cardNamePattern.matcher(message); while (matchPattern.find()) { String cardName = matchPattern.group(1); - CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(cardName, true); + CardInfo cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardName, true); if (cardInfo != null) { String colour = "silver"; if (cardInfo.getCard().getColor(null).isMulticolored()) { @@ -270,7 +270,7 @@ public class ChatManagerImpl implements ChatManager { Matcher matchPattern = getCardTextPattern.matcher(message.toLowerCase(Locale.ENGLISH)); if (matchPattern.find()) { String cardName = matchPattern.group(1); - CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(cardName, true); + CardInfo cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardName, true); if (cardInfo != null) { cardInfo.getRules(); message = "" + cardInfo.getName() + ": Cost:" + cardInfo.getManaCosts(CardInfo.ManaCostSide.ALL).toString() + ", Types:" + cardInfo.getTypes().toString() + ", "; diff --git a/Mage.Server/src/main/java/mage/server/MageServerImpl.java b/Mage.Server/src/main/java/mage/server/MageServerImpl.java index 0992ab6cc0d..19608585bed 100644 --- a/Mage.Server/src/main/java/mage/server/MageServerImpl.java +++ b/Mage.Server/src/main/java/mage/server/MageServerImpl.java @@ -34,8 +34,8 @@ import mage.server.util.SystemUtil; import mage.utils.*; import mage.view.*; import mage.view.ChatMessage.MessageColor; -import org.apache.commons.lang3.StringEscapeUtils; import org.apache.log4j.Logger; +import org.unbescape.html.HtmlEscape; import javax.management.timer.Timer; import java.security.SecureRandom; @@ -83,15 +83,17 @@ public class MageServerImpl implements MageServer { @Override public boolean emailAuthToken(String sessionId, String email) throws MageException { if (!managerFactory.configSettings().isAuthenticationActivated()) { - sendErrorMessageToClient(sessionId, "Registration is disabled by the server config"); + sendErrorMessageToClient(sessionId, Session.REGISTRATION_DISABLED_MESSAGE); return false; } - AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByEmail(email); + + AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByEmail(email); if (authorizedUser == null) { sendErrorMessageToClient(sessionId, "No user was found with the email address " + email); logger.info("Auth token is requested for " + email + " but there's no such user in DB"); return false; } + String authToken = generateAuthToken(); activeAuthTokens.put(email, authToken); String subject = "XMage Password Reset Auth Token"; @@ -113,23 +115,31 @@ public class MageServerImpl implements MageServer { @Override public boolean resetPassword(String sessionId, String email, String authToken, String password) throws MageException { if (!managerFactory.configSettings().isAuthenticationActivated()) { - sendErrorMessageToClient(sessionId, "Registration is disabled by the server config"); + sendErrorMessageToClient(sessionId, Session.REGISTRATION_DISABLED_MESSAGE); return false; } + + // multi-step reset: + // - send auth token + // - check auth token to confirm reset + String storedAuthToken = activeAuthTokens.get(email); if (storedAuthToken == null || !storedAuthToken.equals(authToken)) { sendErrorMessageToClient(sessionId, "Invalid auth token"); logger.info("Invalid auth token " + authToken + " is sent for " + email); return false; } - AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByEmail(email); + + AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByEmail(email); if (authorizedUser == null) { - sendErrorMessageToClient(sessionId, "The user is no longer in the DB"); + sendErrorMessageToClient(sessionId, "User with that email doesn't exists"); logger.info("Auth token is valid, but the user with email address " + email + " is no longer in the DB"); return false; } - AuthorizedUserRepository.instance.remove(authorizedUser.getName()); - AuthorizedUserRepository.instance.add(authorizedUser.getName(), password, email); + + // recreate user with new password + AuthorizedUserRepository.getInstance().remove(authorizedUser.getName()); + AuthorizedUserRepository.getInstance().add(authorizedUser.getName(), password, email); activeAuthTokens.remove(email); return true; } @@ -479,7 +489,7 @@ public class MageServerImpl implements MageServer { public void sendChatMessage(final UUID chatId, final String userName, final String message) throws MageException { try { callExecutor.execute( - () -> managerFactory.chatManager().broadcast(chatId, userName, StringEscapeUtils.escapeHtml4(message), MessageColor.BLUE, true, null, ChatMessage.MessageType.TALK, null) + () -> managerFactory.chatManager().broadcast(chatId, userName, HtmlEscape.escapeHtml4(message), MessageColor.BLUE, true, null, ChatMessage.MessageType.TALK, null) ); } catch (Exception ex) { handleException(ex); @@ -1042,7 +1052,7 @@ public class MageServerImpl implements MageServer { @Override public void setActivation(final String sessionId, final String userName, boolean active) throws MageException { execute("setActivation", sessionId, () -> { - AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByName(userName); + AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByName(userName); Optional u = managerFactory.userManager().getUserByName(userName); if (u.isPresent()) { User user = u.get(); diff --git a/Mage.Server/src/main/java/mage/server/Main.java b/Mage.Server/src/main/java/mage/server/Main.java index e78807b1623..2c967fb7d01 100644 --- a/Mage.Server/src/main/java/mage/server/Main.java +++ b/Mage.Server/src/main/java/mage/server/Main.java @@ -66,7 +66,15 @@ public final class Main { public static final PluginClassLoader classLoader = new PluginClassLoader(); private static TransporterServer server; + + // special test mode: + // - fast game buttons; + // - cheat commands; + // - no deck validation; + // - simplified registration and login (no password check); + // - debug main menu for GUI and rendering testing; private static boolean testMode; + private static boolean fastDbMode; /** @@ -98,7 +106,7 @@ public final class Main { if (config.isAuthenticationActivated()) { logger.info("Check authorized user DB version ..."); - if (!AuthorizedUserRepository.instance.checkAlterAndMigrateAuthorizedUser()) { + if (!AuthorizedUserRepository.getInstance().checkAlterAndMigrateAuthorizedUser()) { logger.fatal("Failed to start server."); return; } diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index 237a27ec62b..90bc5bc8879 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -35,6 +35,8 @@ public class Session { private static final Pattern alphabetsPattern = Pattern.compile("[a-zA-Z]"); private static final Pattern digitsPattern = Pattern.compile("[0-9]"); + public static final String REGISTRATION_DISABLED_MESSAGE = "Registration has been disabled on the server. You can use any name and empty password to login."; + private final ManagerFactory managerFactory; private final String sessionId; private UUID userId; @@ -60,30 +62,36 @@ public class Session { public String registerUser(String userName, String password, String email) throws MageException { if (!managerFactory.configSettings().isAuthenticationActivated()) { - String returnMessage = "Registration is disabled by the server config"; + String returnMessage = REGISTRATION_DISABLED_MESSAGE; sendErrorMessageToClient(returnMessage); return returnMessage; } - synchronized (AuthorizedUserRepository.instance) { + synchronized (AuthorizedUserRepository.getInstance()) { + // name String returnMessage = validateUserName(userName); if (returnMessage != null) { sendErrorMessageToClient(returnMessage); return returnMessage; } + // auto-generated password RandomString randomString = new RandomString(10); password = randomString.nextString(); returnMessage = validatePassword(password, userName); if (returnMessage != null) { - sendErrorMessageToClient(returnMessage); + sendErrorMessageToClient("Auto-generated password fail, try again: " + returnMessage); return returnMessage; } + + // email returnMessage = validateEmail(email); if (returnMessage != null) { sendErrorMessageToClient(returnMessage); return returnMessage; } - AuthorizedUserRepository.instance.add(userName, password, email); + + // create + AuthorizedUserRepository.getInstance().add(userName, password, email); String text = "You are successfully registered as " + userName + '.'; text += " Your initial, generated password is: " + password; @@ -95,15 +103,15 @@ public class Session { success = managerFactory.mailgunClient().sendMessage(email, subject, text); } if (success) { - String ok = "Sent a registration confirmation / initial password email to " + email + " for " + userName; + String ok = "Email with initial password sent to " + email + " for a user " + userName; logger.info(ok); sendInfoMessageToClient(ok); } else if (Main.isTestMode()) { - String ok = "Server is in test mode. Your account is registered with a password of " + password + " for " + userName; + String ok = "Email sending failed. Server is in test mode. Your account registered with a password " + password + " for a user " + userName; logger.info(ok); sendInfoMessageToClient(ok); } else { - String err = "Failed sending a registration confirmation / initial password email to " + email + " for " + userName; + String err = "Email sending failed. Try use another email address or service. Or reset password by email " + email + " for a user " + userName; logger.error(err); sendErrorMessageToClient(err); return err; @@ -113,9 +121,13 @@ public class Session { } private String validateUserName(String userName) { + // return error message or null on good name + if (userName.equals("Admin")) { + // virtual user for admin console return "User name Admin already in use"; } + ConfigSettings config = managerFactory.configSettings(); if (userName.length() < config.getMinUserNameLength()) { return "User name may not be shorter than " + config.getMinUserNameLength() + " characters"; @@ -123,15 +135,19 @@ public class Session { if (userName.length() > config.getMaxUserNameLength()) { return "User name may not be longer than " + config.getMaxUserNameLength() + " characters"; } + Pattern invalidUserNamePattern = Pattern.compile(managerFactory.configSettings().getInvalidUserNamePattern(), Pattern.CASE_INSENSITIVE); Matcher m = invalidUserNamePattern.matcher(userName); if (m.find()) { return "User name '" + userName + "' includes not allowed characters: use a-z, A-Z and 0-9"; } - AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByName(userName); + + AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByName(userName); if (authorizedUser != null) { return "User name '" + userName + "' already in use"; } + + // all fine return null; } @@ -159,7 +175,7 @@ public class Session { if (email == null || email.isEmpty()) { return "Email address cannot be blank"; } - AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByEmail(email); + AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByEmail(email); if (authorizedUser != null) { return "Email address '" + email + "' is associated with another user"; } @@ -182,8 +198,8 @@ public class Session { this.isAdmin = false; AuthorizedUser authorizedUser = null; if (managerFactory.configSettings().isAuthenticationActivated()) { - authorizedUser = AuthorizedUserRepository.instance.getByName(userName); - String errorMsg = "Wrong username or password. In case you haven't, please register your account first."; + authorizedUser = AuthorizedUserRepository.getInstance().getByName(userName); + String errorMsg = "Wrong username or password. You must register your account first."; if (authorizedUser == null) { return errorMsg; } @@ -193,16 +209,16 @@ public class Session { } if (!authorizedUser.active) { - return "Your profile is deactivated, you can't sign on."; + return "Your profile has been deactivated by admin."; } if (authorizedUser.lockedUntil != null) { if (authorizedUser.lockedUntil.compareTo(Calendar.getInstance().getTime()) > 0) { - return "Your profile is deactivated until " + SystemUtil.dateFormat.format(authorizedUser.lockedUntil); + return "Your profile has need deactivated by admin until " + SystemUtil.dateFormat.format(authorizedUser.lockedUntil); } else { + // unlock on timeout end managerFactory.userManager().createUser(userName, host, authorizedUser).ifPresent(user -> user.setLockedUntil(null) ); - } } } diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index bf7f025da4a..41e0378c96b 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -817,7 +817,7 @@ public class User { authorizedUser.chatLockedUntil = this.chatLockedUntil; authorizedUser.lockedUntil = this.lockedUntil; authorizedUser.active = this.active; - AuthorizedUserRepository.instance.update(authorizedUser); + AuthorizedUserRepository.getInstance().update(authorizedUser); } } } diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index d2b0cbac495..e839e151864 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -810,7 +810,7 @@ public class GameController implements GameCallback { } private synchronized void chooseAbility(UUID playerId, final String objectName, final List choices, String message) throws MageException { - perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(objectName, choices, message))); + perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(getGameView(playerId), objectName, choices, message))); } private synchronized void choosePile(UUID playerId, final String message, final List pile1, final List pile2) throws MageException { @@ -818,7 +818,7 @@ public class GameController implements GameCallback { } private synchronized void chooseMode(UUID playerId, final Map modes, final String message) throws MageException { - perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(modes, message))); + perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(getGameView(playerId), modes, message))); } private synchronized void chooseChoice(UUID playerId, final Choice choice) throws MageException { diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java index dfa6bf947fa..714572fdc49 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java @@ -47,7 +47,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void ask(final String question, final Map options) { if (!killed) { - userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ASK, game.getId(), new GameClientMessage(getGameView(), question, options))) + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ASK, game.getId(), new GameClientMessage(getGameView(), options, question))) ); } } @@ -55,7 +55,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void target(final String question, final CardsView cardView, final Set targets, final boolean required, final Map options) { if (!killed) { userManager.getUser(userId).ifPresent(user -> { - user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_TARGET, game.getId(), new GameClientMessage(getGameView(), question, cardView, targets, required, options))); + user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_TARGET, game.getId(), new GameClientMessage(getGameView(), options, question, cardView, targets, required))); }); } @@ -63,7 +63,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void select(final String message, final Map options) { if (!killed) { - userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_SELECT, game.getId(), new GameClientMessage(getGameView(), message, options)))); + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_SELECT, game.getId(), new GameClientMessage(getGameView(), options, message)))); } } @@ -78,7 +78,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void choosePile(final String message, final CardsView pile1, final CardsView pile2) { if (!killed) { userManager.getUser(userId).ifPresent(user - -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_PILE, game.getId(), new GameClientMessage(message, pile1, pile2)))); + -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_PILE, game.getId(), new GameClientMessage(getGameView(), null, message, pile1, pile2)))); } } @@ -86,7 +86,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void chooseChoice(final Choice choice) { if (!killed) { userManager.getUser(userId).ifPresent(user - -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_CHOICE, game.getId(), new GameClientMessage(choice)))); + -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_CHOICE, game.getId(), new GameClientMessage(getGameView(), null, choice)))); } } @@ -94,14 +94,14 @@ public class GameSessionPlayer extends GameSessionWatcher { public void playMana(final String message, final Map options) { if (!killed) { userManager.getUser(userId).ifPresent(user - -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_MANA, game.getId(), new GameClientMessage(getGameView(), message, options)))); + -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_MANA, game.getId(), new GameClientMessage(getGameView(), options, message)))); } } public void playXMana(final String message) { if (!killed) { userManager.getUser(userId).ifPresent(user - -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_XMANA, game.getId(), new GameClientMessage(getGameView(), message)))); + -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_XMANA, game.getId(), new GameClientMessage(getGameView(), null, message)))); } } @@ -109,7 +109,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void getAmount(final String message, final int min, final int max) { if (!killed) { userManager.getUser(userId).ifPresent(user -> { - user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_AMOUNT, game.getId(), new GameClientMessage(message, min, max))); + user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_AMOUNT, game.getId(), new GameClientMessage(getGameView(), null, message, min, max))); }); } } @@ -117,7 +117,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public void getMultiAmount(final List messages, final int min, final int max, final Map options) { if (!killed) { userManager.getUser(userId).ifPresent(user - -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_MULTI_AMOUNT, game.getId(), new GameClientMessage(messages, min, max, options)))); + -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_MULTI_AMOUNT, game.getId(), new GameClientMessage(getGameView(), options, messages, min, max)))); } } diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java index 4bcd69d57c9..452e948abe3 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java @@ -58,14 +58,14 @@ public class GameSessionWatcher { public void inform(final String message) { if (!killed) { - userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM, game.getId(), new GameClientMessage(getGameView(), message)))); + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM, game.getId(), new GameClientMessage(getGameView(), null, message)))); } } public void informPersonal(final String message) { if (!killed) { - userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM_PERSONAL, game.getId(), new GameClientMessage(getGameView(), message)))); + userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM_PERSONAL, game.getId(), new GameClientMessage(getGameView(), null, message)))); } } @@ -74,7 +74,7 @@ public class GameSessionWatcher { if (!killed) { userManager.getUser(userId).ifPresent(user -> { user.removeGameWatchInfo(game.getId()); - user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_OVER, game.getId(), message)); + user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_OVER, game.getId(), new GameClientMessage(getGameView(), null, message))); }); } } @@ -89,7 +89,6 @@ public class GameSessionWatcher { public void gameError(final String message) { if (!killed) { userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ERROR, game.getId(), message))); - } } diff --git a/Mage.Server/src/test/resources/config_error.xml b/Mage.Server/src/test/data/config_error.xml similarity index 100% rename from Mage.Server/src/test/resources/config_error.xml rename to Mage.Server/src/test/data/config_error.xml diff --git a/Mage.Server/src/test/java/mage/server/util/ConfigFactoryTest.java b/Mage.Server/src/test/java/mage/server/util/ConfigFactoryTest.java index 3563287fad1..c487037479b 100644 --- a/Mage.Server/src/test/java/mage/server/util/ConfigFactoryTest.java +++ b/Mage.Server/src/test/java/mage/server/util/ConfigFactoryTest.java @@ -24,7 +24,7 @@ public class ConfigFactoryTest { @DisplayName("should fail if config is malformed") void failOnMalformed() { assertThatExceptionOfType(ConfigurationException.class) - .isThrownBy(() -> ConfigFactory.loadFromFile(Paths.get("src", "test", "resources", "config_error.xml").toString())); + .isThrownBy(() -> ConfigFactory.loadFromFile(Paths.get("src", "test", "data", "config_error.xml").toString())); } @Test diff --git a/Mage.Sets/pom.xml b/Mage.Sets/pom.xml index 4dac5f304e8..b14b92ca739 100644 --- a/Mage.Sets/pom.xml +++ b/Mage.Sets/pom.xml @@ -21,17 +21,6 @@ mage ${mage-version}
- - log4j - log4j - jar - - - - junit - junit - test -
diff --git a/Mage.Sets/src/mage/cards/a/AbandonThePost.java b/Mage.Sets/src/mage/cards/a/AbandonThePost.java index 267cc544add..f32e342fc9e 100644 --- a/Mage.Sets/src/mage/cards/a/AbandonThePost.java +++ b/Mage.Sets/src/mage/cards/a/AbandonThePost.java @@ -25,7 +25,7 @@ public final class AbandonThePost extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2)); // Flashback {3}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{R}"))); } private AbandonThePost(final AbandonThePost card) { diff --git a/Mage.Sets/src/mage/cards/a/AccursedWitch.java b/Mage.Sets/src/mage/cards/a/AccursedWitch.java index 1275d59be45..d157b752a25 100644 --- a/Mage.Sets/src/mage/cards/a/AccursedWitch.java +++ b/Mage.Sets/src/mage/cards/a/AccursedWitch.java @@ -59,7 +59,7 @@ class AccursedWitchReturnTransformedEffect extends OneShotEffect { AccursedWitchReturnTransformedEffect() { super(Outcome.PutCardInPlay); - this.staticText = "Put {this} from your graveyard onto the battlefield transformed under your control attached to target opponent"; + this.staticText = "return it to the battlefield transformed under your control attached to target opponent"; } private AccursedWitchReturnTransformedEffect(final AccursedWitchReturnTransformedEffect effect) { @@ -78,15 +78,17 @@ class AccursedWitchReturnTransformedEffect extends OneShotEffect { if (controller == null || !(game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) || attachTo == null) { return false; } - game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE); - UUID secondFaceId = game.getCard(source.getSourceId()).getSecondCardFace().getId(); - game.getState().setValue("attachTo:" + secondFaceId, attachTo.getId()); - //note: should check for null after game.getCard + Card card = game.getCard(source.getSourceId()); - if (card != null) { - if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { - attachTo.addAttachment(card.getId(), source, game); - } + if (card == null) { + return false; + } + + game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE); + UUID secondFaceId = card.getSecondCardFace().getId(); + game.getState().setValue("attachTo:" + secondFaceId, attachTo.getId()); + if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { + attachTo.addAttachment(card.getId(), source, game); } return true; } diff --git a/Mage.Sets/src/mage/cards/a/AcornHarvest.java b/Mage.Sets/src/mage/cards/a/AcornHarvest.java index c52ef3374c4..ccd4e55a212 100644 --- a/Mage.Sets/src/mage/cards/a/AcornHarvest.java +++ b/Mage.Sets/src/mage/cards/a/AcornHarvest.java @@ -26,7 +26,7 @@ public final class AcornHarvest extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new SquirrelToken(), 2)); // Flashback-{1}{G} - Pay 3 life. - FlashbackAbility ability = new FlashbackAbility(new ManaCostsImpl("{1}{G}"), TimingRule.SORCERY); + FlashbackAbility ability = new FlashbackAbility(this, new ManaCostsImpl("{1}{G}")); ability.addCost(new PayLifeCost(3)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/a/AdelineResplendentCathar.java b/Mage.Sets/src/mage/cards/a/AdelineResplendentCathar.java new file mode 100644 index 00000000000..7af6c796092 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AdelineResplendentCathar.java @@ -0,0 +1,79 @@ +package mage.cards.a; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.SetPowerSourceEffect; +import mage.abilities.hint.common.CreaturesYouControlHint; +import mage.constants.*; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.game.Game; +import mage.game.permanent.token.HumanToken; + +/** + * + * @author weirddan455 + */ +public final class AdelineResplendentCathar extends CardImpl { + + public AdelineResplendentCathar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Adeline, Resplendent Cathar's power is equal to the number of creatures you control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetPowerSourceEffect( + CreaturesYouControlCount.instance, Duration.EndOfGame)).addHint(CreaturesYouControlHint.instance) + ); + + // Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control. + this.addAbility(new AttacksWithCreaturesTriggeredAbility(new AdelineResplendentCatharEffect(), 1)); + } + + private AdelineResplendentCathar(final AdelineResplendentCathar card) { + super(card); + } + + @Override + public AdelineResplendentCathar copy() { + return new AdelineResplendentCathar(this); + } +} + +class AdelineResplendentCatharEffect extends OneShotEffect { + + public AdelineResplendentCatharEffect() { + super(Outcome.Benefit); + staticText = "for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control"; + } + + private AdelineResplendentCatharEffect(final AdelineResplendentCatharEffect effect) { + super(effect); + } + + @Override + public AdelineResplendentCatharEffect copy() { + return new AdelineResplendentCatharEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + new HumanToken().putOntoBattlefield(1, game, source, source.getControllerId(), true, true, opponentId); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AetherInspector.java b/Mage.Sets/src/mage/cards/a/AetherInspector.java index 9347c671e49..6ba31ee9731 100644 --- a/Mage.Sets/src/mage/cards/a/AetherInspector.java +++ b/Mage.Sets/src/mage/cards/a/AetherInspector.java @@ -1,4 +1,3 @@ - package mage.cards.a; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/a/AkiriFearlessVoyager.java b/Mage.Sets/src/mage/cards/a/AkiriFearlessVoyager.java index cf08f48cc2c..a7583635ab4 100644 --- a/Mage.Sets/src/mage/cards/a/AkiriFearlessVoyager.java +++ b/Mage.Sets/src/mage/cards/a/AkiriFearlessVoyager.java @@ -14,8 +14,8 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.common.FilterEquipmentPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.events.DefenderAttackedEvent; import mage.game.events.GameEvent; @@ -103,11 +103,11 @@ class AkiriFearlessVoyagerTriggeredAbility extends TriggeredAbilityImpl { class AkiriFearlessVoyagerEffect extends OneShotEffect { - private static enum AkiriFearlessVoyagerPredicate implements ObjectPlayerPredicate> { + private static enum AkiriFearlessVoyagerPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { return game.getPermanent(input.getObject().getAttachedTo()) != null && game.getControllerId(input.getObject().getAttachedTo()).equals(input.getPlayerId()); } diff --git a/Mage.Sets/src/mage/cards/a/AkromaVisionOfIxidor.java b/Mage.Sets/src/mage/cards/a/AkromaVisionOfIxidor.java index 5e5ad8c2cd2..c859b98ff03 100644 --- a/Mage.Sets/src/mage/cards/a/AkromaVisionOfIxidor.java +++ b/Mage.Sets/src/mage/cards/a/AkromaVisionOfIxidor.java @@ -1,6 +1,5 @@ package mage.cards.a; -import com.google.common.collect.Sets; import mage.MageInt; import mage.abilities.Abilities; import mage.abilities.Ability; @@ -17,6 +16,8 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; +import java.util.Arrays; +import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -66,7 +67,7 @@ public final class AkromaVisionOfIxidor extends CardImpl { class AkromaVisionOfIxidorEffect extends OneShotEffect { - private static final Set> classes = Sets.newHashSet( + private static final Set> classes = new HashSet<>(Arrays.asList( FlyingAbility.class, FirstStrikeAbility.class, DoubleStrikeAbility.class, @@ -81,7 +82,7 @@ class AkromaVisionOfIxidorEffect extends OneShotEffect { TrampleAbility.class, VigilanceAbility.class, PartnerAbility.class - ); + )); AkromaVisionOfIxidorEffect() { super(Outcome.Benefit); diff --git a/Mage.Sets/src/mage/cards/a/AleshaWhoSmilesAtDeath.java b/Mage.Sets/src/mage/cards/a/AleshaWhoSmilesAtDeath.java index 5821347dc85..9f76c02a339 100644 --- a/Mage.Sets/src/mage/cards/a/AleshaWhoSmilesAtDeath.java +++ b/Mage.Sets/src/mage/cards/a/AleshaWhoSmilesAtDeath.java @@ -1,38 +1,39 @@ - package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.abilities.keyword.FirstStrikeAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.mageobject.PowerPredicate; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class AleshaWhoSmilesAtDeath extends CardImpl { - private static final FilterCreatureCard filter = new FilterCreatureCard("creature card with power 2 or less"); + private static final FilterCard filter + = new FilterCreatureCard("creature card with power 2 or less from your graveyard"); static { filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 3)); } public AleshaWhoSmilesAtDeath(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WARRIOR); @@ -43,7 +44,10 @@ public final class AleshaWhoSmilesAtDeath extends CardImpl { this.addAbility(FirstStrikeAbility.getInstance()); // Whenever Alesha, Who Smiles at Death attacks, you may pay {W/B}{W/B}. If you do, return target creature card with power 2 or less from your graveyard to the battlefield tapped and attacking. - Ability ability = new AttacksTriggeredAbility(new DoIfCostPaid(new AleshaWhoSmilesAtDeathEffect(), new ManaCostsImpl("{W/B}{W/B}")), false); + Ability ability = new AttacksTriggeredAbility(new DoIfCostPaid( + new ReturnFromGraveyardToBattlefieldTargetEffect(true, true), + new ManaCostsImpl<>("{W/B}{W/B}") + ), false); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } @@ -57,38 +61,3 @@ public final class AleshaWhoSmilesAtDeath extends CardImpl { return new AleshaWhoSmilesAtDeath(this); } } - -class AleshaWhoSmilesAtDeathEffect extends OneShotEffect { - - public AleshaWhoSmilesAtDeathEffect() { - super(Outcome.PutCreatureInPlay); - this.staticText = "return target creature card with power 2 or less from your graveyard to the battlefield tapped and attacking"; - } - - public AleshaWhoSmilesAtDeathEffect(final AleshaWhoSmilesAtDeathEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - - if (controller != null) { - Card card = game.getCard(getTargetPointer().getFirst(game, source)); - if (card != null) { - if (controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null)) { - game.getCombat().addAttackingCreature(card.getId(), game); - } - } - return true; - - } - return false; - } - - @Override - public AleshaWhoSmilesAtDeathEffect copy() { - return new AleshaWhoSmilesAtDeathEffect(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java b/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java new file mode 100644 index 00000000000..9070dbea35b --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java @@ -0,0 +1,63 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.CovenCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.hint.common.CovenHint; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AmbitiousFarmhand extends CardImpl { + + private static final FilterCard filter = new FilterCard("basic Plains card"); + + static { + filter.add(SuperType.BASIC.getPredicate()); + filter.add(SubType.PLAINS.getPredicate()); + } + + public AmbitiousFarmhand(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + this.transformable = true; + this.secondSideCardClazz = mage.cards.s.SeasonedCathar.class; + + // When Ambitious Farmhand enters the battlefield, you may search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter)), true + )); + + // Coven—{1}{W}{W}: Transform Ambitious Farmhand. Activate only if you control three or more creatures with different powers. + this.addAbility(new TransformAbility()); + this.addAbility(new ActivateIfConditionActivatedAbility( + Zone.BATTLEFIELD, new TransformSourceEffect(true), + new ManaCostsImpl<>("{1}{W}{W}"), CovenCondition.instance + ).setAbilityWord(AbilityWord.COVEN).addHint(CovenHint.instance)); + } + + private AmbitiousFarmhand(final AmbitiousFarmhand card) { + super(card); + } + + @Override + public AmbitiousFarmhand copy() { + return new AmbitiousFarmhand(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AncestralTribute.java b/Mage.Sets/src/mage/cards/a/AncestralTribute.java index 28bde5e75a6..ba9b95b70cb 100644 --- a/Mage.Sets/src/mage/cards/a/AncestralTribute.java +++ b/Mage.Sets/src/mage/cards/a/AncestralTribute.java @@ -27,7 +27,7 @@ public final class AncestralTribute extends CardImpl { this.getSpellAbility().addEffect(new GainLifeEffect((new CardsInControllerGraveyardCount(new FilterCard(), 2)))); // Flashback {9}{W}{W}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{9}{W}{W}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{9}{W}{W}{W}"))); } diff --git a/Mage.Sets/src/mage/cards/a/AncientGrudge.java b/Mage.Sets/src/mage/cards/a/AncientGrudge.java index 5503f190d37..733954463f0 100644 --- a/Mage.Sets/src/mage/cards/a/AncientGrudge.java +++ b/Mage.Sets/src/mage/cards/a/AncientGrudge.java @@ -23,7 +23,7 @@ public final class AncientGrudge extends CardImpl { this.getSpellAbility().addTarget(new TargetArtifactPermanent()); // Flashback {G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{G}"))); } private AncientGrudge(final AncientGrudge card) { diff --git a/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java b/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java index 9c03356a167..57d4f570450 100644 --- a/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java +++ b/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java @@ -12,13 +12,12 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterPermanent; -import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInGraveyardOrBattlefield; import java.util.UUID; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; /** * @author LevelX2 @@ -27,10 +26,13 @@ public final class AngelOfSerenity extends CardImpl { private static final String rule = "you may exile up to three other target creatures " + "from the battlefield and/or creature cards from graveyards."; - private static final FilterPermanent filter = new FilterCreaturePermanent("other target creatures"); - + + private static final FilterCreatureCard filterCreatureCard = new FilterCreatureCard("creature card in a graveyard"); + + private static final FilterCreaturePermanent filterCreaturePermanent = new FilterCreaturePermanent("other target creature"); + static { - filter.add(AnotherPredicate.instance); + filterCreaturePermanent.add(AnotherPredicate.instance); } public AngelOfSerenity(UUID ownerId, CardSetInfo setInfo) { @@ -48,7 +50,7 @@ public final class AngelOfSerenity extends CardImpl { new ExileTargetForSourceEffect().setText(rule), true ); ability.addTarget(new TargetCardInGraveyardOrBattlefield( - 0, 3, StaticFilters.FILTER_CARD_CREATURE, filter + 0, 3, filterCreatureCard, filterCreaturePermanent )); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/a/AngelfireIgnition.java b/Mage.Sets/src/mage/cards/a/AngelfireIgnition.java index b7c091c891a..84d0f7c284b 100644 --- a/Mage.Sets/src/mage/cards/a/AngelfireIgnition.java +++ b/Mage.Sets/src/mage/cards/a/AngelfireIgnition.java @@ -42,7 +42,7 @@ public final class AngelfireIgnition extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {2}{R}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{2}{R}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{2}{R}{W}"))); } private AngelfireIgnition(final AngelfireIgnition card) { diff --git a/Mage.Sets/src/mage/cards/a/ArcaneInfusion.java b/Mage.Sets/src/mage/cards/a/ArcaneInfusion.java index 5bdc4825d76..33cb5d8ca39 100644 --- a/Mage.Sets/src/mage/cards/a/ArcaneInfusion.java +++ b/Mage.Sets/src/mage/cards/a/ArcaneInfusion.java @@ -32,7 +32,7 @@ public final class ArcaneInfusion extends CardImpl { "Put the rest on the bottom of your library in a random order.")); // Flashback {3}{U}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{U}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{U}{R}"))); } private ArcaneInfusion(final ArcaneInfusion card) { diff --git a/Mage.Sets/src/mage/cards/a/ArchmagesCharm.java b/Mage.Sets/src/mage/cards/a/ArchmagesCharm.java index d74e8e6f61a..51509613010 100644 --- a/Mage.Sets/src/mage/cards/a/ArchmagesCharm.java +++ b/Mage.Sets/src/mage/cards/a/ArchmagesCharm.java @@ -44,7 +44,7 @@ public final class ArchmagesCharm extends CardImpl { this.getSpellAbility().addMode(mode); // • Gain control of target nonland permanent with converted mana cost 1 or less. - mode = new Mode(new GainControlTargetEffect(Duration.Custom, true)); + mode = new Mode(new GainControlTargetEffect(Duration.EndOfGame, true)); mode.addTarget(new TargetPermanent(filter)); this.getSpellAbility().addMode(mode); } diff --git a/Mage.Sets/src/mage/cards/a/ArlinnTheMoonsFury.java b/Mage.Sets/src/mage/cards/a/ArlinnTheMoonsFury.java new file mode 100644 index 00000000000..98e064a041b --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArlinnTheMoonsFury.java @@ -0,0 +1,119 @@ +package mage.cards.a; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.NightboundAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ArlinnTheMoonsFury extends CardImpl { + + public ArlinnTheMoonsFury(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, ""); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ARLINN); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + this.color.setRed(true); + this.color.setGreen(true); + this.transformable = true; + this.nightCard = true; + + // Nightbound + this.addAbility(new NightboundAbility()); + + // +2: Add {R}{G}. + this.addAbility(new LoyaltyAbility(new BasicManaEffect(new Mana( + 0, 0, 0, 1, 1, 0, 0, 0 + )), 2)); + + // 0: Until end of turn, Arlinn, the Moon's Fury becomes a 5/5 Werewolf creature with trample, indestructible, and haste. + this.addAbility(new LoyaltyAbility(new ArlinnTheMoonsFuryEffect(), 0)); + } + + private ArlinnTheMoonsFury(final ArlinnTheMoonsFury card) { + super(card); + } + + @Override + public ArlinnTheMoonsFury copy() { + return new ArlinnTheMoonsFury(this); + } +} + +class ArlinnTheMoonsFuryEffect extends ContinuousEffectImpl { + + ArlinnTheMoonsFuryEffect() { + super(Duration.EndOfTurn, Outcome.Benefit); + staticText = "until end of turn, {this} becomes a 5/5 Werewolf creature with trample, indestructible, and haste"; + } + + private ArlinnTheMoonsFuryEffect(final ArlinnTheMoonsFuryEffect effect) { + super(effect); + } + + @Override + public ArlinnTheMoonsFuryEffect copy() { + return new ArlinnTheMoonsFuryEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + discard(); + return false; + } + switch (layer) { + case TypeChangingEffects_4: + permanent.removeAllCardTypes(game); + permanent.addCardType(game, CardType.CREATURE); + permanent.removeAllCreatureTypes(game); + permanent.addSubType(game, SubType.WEREWOLF); + return true; + case AbilityAddingRemovingEffects_6: + permanent.addAbility(TrampleAbility.getInstance(), source.getSourceId(), game); + permanent.addAbility(IndestructibleAbility.getInstance(), source.getSourceId(), game); + permanent.addAbility(HasteAbility.getInstance(), source.getSourceId(), game); + return true; + case PTChangingEffects_7: + if (sublayer == SubLayer.SetPT_7b) { + permanent.getPower().setValue(5); + permanent.getToughness().setValue(5); + return true; + } + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + switch (layer) { + case TypeChangingEffects_4: + case AbilityAddingRemovingEffects_6: + case PTChangingEffects_7: + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArlinnThePacksHope.java b/Mage.Sets/src/mage/cards/a/ArlinnThePacksHope.java new file mode 100644 index 00000000000..ee4f826916a --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArlinnThePacksHope.java @@ -0,0 +1,106 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashAllEffect; +import mage.abilities.keyword.DayboundAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.WolfToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ArlinnThePacksHope extends CardImpl { + + private static final FilterCard filter = new FilterCreatureCard("creature spells"); + + public ArlinnThePacksHope(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{G}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ARLINN); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + this.transformable = true; + this.secondSideCardClazz = mage.cards.a.ArlinnTheMoonsFury.class; + + // Daybound + this.addAbility(new TransformAbility()); + this.addAbility(new DayboundAbility()); + + // +1: Until your next turn, you may cast creature spells as though they had flash, and each creature you control enters the battlefield with an additional +1/+1 counter on it. + Ability ability = new LoyaltyAbility(new CastAsThoughItHadFlashAllEffect( + Duration.UntilYourNextTurn, filter + ).setText("until your next turn, you may cast creature spells as though they had flash"), 1); + ability.addEffect(new ArlinnThePacksHopeEffect()); + this.addAbility(ability); + + // −3: Create two 2/2 green Wolf creature tokens. + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new WolfToken(), 2), -3)); + } + + private ArlinnThePacksHope(final ArlinnThePacksHope card) { + super(card); + } + + @Override + public ArlinnThePacksHope copy() { + return new ArlinnThePacksHope(this); + } +} + +class ArlinnThePacksHopeEffect extends ReplacementEffectImpl { + + ArlinnThePacksHopeEffect() { + super(Duration.UntilYourNextTurn, Outcome.BoostCreature); + this.staticText = ", and each creature you control enters the battlefield with an additional +1/+1 counter on it"; + } + + private ArlinnThePacksHopeEffect(ArlinnThePacksHopeEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget(); + return permanent != null && permanent.isControlledBy(source.getControllerId()) && permanent.isCreature(game); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent target = ((EntersTheBattlefieldEvent) event).getTarget(); + if (target != null) { + target.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game, event.getAppliedEffects()); + } + return false; + } + + @Override + public ArlinnThePacksHopeEffect copy() { + return new ArlinnThePacksHopeEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArmyOfTheDamned.java b/Mage.Sets/src/mage/cards/a/ArmyOfTheDamned.java index 08285b4742f..a220f66c4e4 100644 --- a/Mage.Sets/src/mage/cards/a/ArmyOfTheDamned.java +++ b/Mage.Sets/src/mage/cards/a/ArmyOfTheDamned.java @@ -23,7 +23,7 @@ public final class ArmyOfTheDamned extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieToken(), 13, true, false)); // Flashback {7}{B}{B}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{7}{B}{B}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{7}{B}{B}{B}"))); } private ArmyOfTheDamned(final ArmyOfTheDamned card) { diff --git a/Mage.Sets/src/mage/cards/a/ArtfulDodge.java b/Mage.Sets/src/mage/cards/a/ArtfulDodge.java index 7f31e547ad5..c2d89265238 100644 --- a/Mage.Sets/src/mage/cards/a/ArtfulDodge.java +++ b/Mage.Sets/src/mage/cards/a/ArtfulDodge.java @@ -25,7 +25,7 @@ public final class ArtfulDodge extends CardImpl { this.getSpellAbility().addEffect(new CantBeBlockedTargetEffect()); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{U}"))); } private ArtfulDodge(final ArtfulDodge card) { diff --git a/Mage.Sets/src/mage/cards/a/AshmouthDragon.java b/Mage.Sets/src/mage/cards/a/AshmouthDragon.java new file mode 100644 index 00000000000..aa7fae2f965 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AshmouthDragon.java @@ -0,0 +1,51 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +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 mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AshmouthDragon extends CardImpl { + + public AshmouthDragon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.color.setRed(true); + this.transformable = true; + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast an instant or sorcery spell, Ashmouth Dragon deals 2 damage to any target. + Ability ability = new SpellCastControllerTriggeredAbility( + new DamageTargetEffect(2), StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false + ); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + } + + private AshmouthDragon(final AshmouthDragon card) { + super(card); + } + + @Override + public AshmouthDragon copy() { + return new AshmouthDragon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AuraGraft.java b/Mage.Sets/src/mage/cards/a/AuraGraft.java index 26fdd13056d..27fad23790d 100644 --- a/Mage.Sets/src/mage/cards/a/AuraGraft.java +++ b/Mage.Sets/src/mage/cards/a/AuraGraft.java @@ -14,8 +14,8 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.filter.Filter; import mage.filter.FilterPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -54,19 +54,19 @@ public final class AuraGraft extends CardImpl { } } -class AttachedToPermanentPredicate implements ObjectPlayerPredicate> { +class AttachedToPermanentPredicate implements ObjectSourcePlayerPredicate { public AttachedToPermanentPredicate() { super(); } - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent attached = input.getObject(); return attached != null && game.getPermanent(attached.getAttachedTo()) != null; } } -class PermanentCanBeAttachedToPredicate implements ObjectPlayerPredicate> { +class PermanentCanBeAttachedToPredicate implements ObjectSourcePlayerPredicate { protected Permanent aura; @@ -76,7 +76,7 @@ class PermanentCanBeAttachedToPredicate implements ObjectPlayerPredicate input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent potentialAttachment = input.getObject(); for (TargetAddress addr : TargetAddress.walk(aura)) { Target target = addr.getTarget(aura); diff --git a/Mage.Sets/src/mage/cards/a/AwokenDemon.java b/Mage.Sets/src/mage/cards/a/AwokenDemon.java new file mode 100644 index 00000000000..d7b9e1c6edc --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AwokenDemon.java @@ -0,0 +1,35 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AwokenDemon extends CardImpl { + + public AwokenDemon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.DEMON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.color.setBlack(true); + this.transformable = true; + this.nightCard = true; + } + + private AwokenDemon(final AwokenDemon card) { + super(card); + } + + @Override + public AwokenDemon copy() { + return new AwokenDemon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BackdraftHellkite.java b/Mage.Sets/src/mage/cards/b/BackdraftHellkite.java index e500f89c212..712d08a1044 100644 --- a/Mage.Sets/src/mage/cards/b/BackdraftHellkite.java +++ b/Mage.Sets/src/mage/cards/b/BackdraftHellkite.java @@ -93,15 +93,7 @@ class BackdraftHellkiteEffect extends ContinuousEffectImpl { if (card == null) { return; } - FlashbackAbility ability = null; - if (card.isInstant(game)) { - ability = new FlashbackAbility(card.getManaCost(), TimingRule.INSTANT); - } else if (card.isSorcery(game)) { - ability = new FlashbackAbility(card.getManaCost(), TimingRule.SORCERY); - } - if (ability == null) { - return; - } + FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); ability.setSourceId(cardId); ability.setControllerId(card.getOwnerId()); game.getState().addOtherAbility(card, ability); diff --git a/Mage.Sets/src/mage/cards/b/BaithookAngler.java b/Mage.Sets/src/mage/cards/b/BaithookAngler.java index 2549bd9dd13..200ccb38c7a 100644 --- a/Mage.Sets/src/mage/cards/b/BaithookAngler.java +++ b/Mage.Sets/src/mage/cards/b/BaithookAngler.java @@ -3,6 +3,7 @@ package mage.cards.b; import mage.MageInt; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -26,6 +27,7 @@ public final class BaithookAngler extends CardImpl { this.secondSideCardClazz = mage.cards.h.HookHauntDrifter.class; // Disturb {1}{U} + this.addAbility(new TransformAbility()); this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{1}{U}"))); } diff --git a/Mage.Sets/src/mage/cards/b/BarteredCow.java b/Mage.Sets/src/mage/cards/b/BarteredCow.java index 9675188862e..707e63fa07a 100644 --- a/Mage.Sets/src/mage/cards/b/BarteredCow.java +++ b/Mage.Sets/src/mage/cards/b/BarteredCow.java @@ -56,7 +56,7 @@ public final class BarteredCow extends CardImpl { } } -enum BarteredCowPredicate implements ObjectSourcePlayerPredicate> { +enum BarteredCowPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/b/BashToBits.java b/Mage.Sets/src/mage/cards/b/BashToBits.java index 07a23d0c605..5f1db4474e4 100644 --- a/Mage.Sets/src/mage/cards/b/BashToBits.java +++ b/Mage.Sets/src/mage/cards/b/BashToBits.java @@ -27,7 +27,7 @@ public final class BashToBits extends CardImpl { Target target = new TargetArtifactPermanent(); this.getSpellAbility().addTarget(target); // Flashback {4}{R}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{R}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{R}{R}"))); } private BashToBits(final BashToBits card) { diff --git a/Mage.Sets/src/mage/cards/b/BattleScreech.java b/Mage.Sets/src/mage/cards/b/BattleScreech.java index 3922c90a5c1..feb673697ce 100644 --- a/Mage.Sets/src/mage/cards/b/BattleScreech.java +++ b/Mage.Sets/src/mage/cards/b/BattleScreech.java @@ -37,7 +37,7 @@ public final class BattleScreech extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new BirdToken(), 2)); // Flashback-Tap three untapped white creatures you control. - this.addAbility(new FlashbackAbility(new TapTargetCost(new TargetControlledCreaturePermanent(3,3, filter, true)), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new TapTargetCost(new TargetControlledCreaturePermanent(3,3, filter, true)))); } private BattleScreech(final BattleScreech card) { diff --git a/Mage.Sets/src/mage/cards/b/BeastAttack.java b/Mage.Sets/src/mage/cards/b/BeastAttack.java index b33e287feed..3f90496f984 100644 --- a/Mage.Sets/src/mage/cards/b/BeastAttack.java +++ b/Mage.Sets/src/mage/cards/b/BeastAttack.java @@ -24,7 +24,7 @@ public final class BeastAttack extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new BeastToken2())); // Flashback {2}{G}{G}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{G}{G}{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{G}{G}{G}"))); } private BeastAttack(final BeastAttack card) { diff --git a/Mage.Sets/src/mage/cards/b/BenevolentGeist.java b/Mage.Sets/src/mage/cards/b/BenevolentGeist.java new file mode 100644 index 00000000000..f54295208ea --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BenevolentGeist.java @@ -0,0 +1,54 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CantBeCounteredControlledEffect; +import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BenevolentGeist extends CardImpl { + + public BenevolentGeist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.color.setBlue(true); + this.transformable = true; + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Noncreature spells you control can't be countered. + this.addAbility(new SimpleStaticAbility(new CantBeCounteredControlledEffect( + StaticFilters.FILTER_SPELLS_NON_CREATURE, null, Duration.WhileOnBattlefield + ))); + + // If Benevolent Geist would be put into a graveyard from anywhere, exile it instead. + this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + } + + private BenevolentGeist(final BenevolentGeist card) { + super(card); + } + + @Override + public BenevolentGeist copy() { + return new BenevolentGeist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BereavedSurvivor.java b/Mage.Sets/src/mage/cards/b/BereavedSurvivor.java new file mode 100644 index 00000000000..453f517c4fd --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BereavedSurvivor.java @@ -0,0 +1,46 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +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 BereavedSurvivor extends CardImpl { + + public BereavedSurvivor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + this.transformable = true; + this.secondSideCardClazz = mage.cards.d.DauntlessAvenger.class; + + // When another creature you control dies, transform Bereaved Survivor. + this.addAbility(new TransformAbility()); + this.addAbility(new DiesCreatureTriggeredAbility( + new TransformSourceEffect(true), false, + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE + )); + } + + private BereavedSurvivor(final BereavedSurvivor card) { + super(card); + } + + @Override + public BereavedSurvivor copy() { + return new BereavedSurvivor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/Bioshift.java b/Mage.Sets/src/mage/cards/b/Bioshift.java index 9725347d4c7..d8b750d9ac7 100644 --- a/Mage.Sets/src/mage/cards/b/Bioshift.java +++ b/Mage.Sets/src/mage/cards/b/Bioshift.java @@ -100,7 +100,7 @@ class MoveCounterFromTargetToTargetEffect extends OneShotEffect { } } -class SameControllerPredicate implements ObjectSourcePlayerPredicate> { +class SameControllerPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/b/BirdAdmirer.java b/Mage.Sets/src/mage/cards/b/BirdAdmirer.java index 23cd8f80e6c..3ee4bc0bd70 100644 --- a/Mage.Sets/src/mage/cards/b/BirdAdmirer.java +++ b/Mage.Sets/src/mage/cards/b/BirdAdmirer.java @@ -32,7 +32,7 @@ public final class BirdAdmirer extends CardImpl { // Daybound this.addAbility(new TransformAbility()); - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new DayboundAbility()); } private BirdAdmirer(final BirdAdmirer card) { diff --git a/Mage.Sets/src/mage/cards/b/Bladebrand.java b/Mage.Sets/src/mage/cards/b/Bladebrand.java index 472d4cc8092..737bd290b20 100644 --- a/Mage.Sets/src/mage/cards/b/Bladebrand.java +++ b/Mage.Sets/src/mage/cards/b/Bladebrand.java @@ -26,7 +26,7 @@ public final class Bladebrand extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Draw a card. - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); } private Bladebrand(final Bladebrand card) { diff --git a/Mage.Sets/src/mage/cards/b/BlastFromThePast.java b/Mage.Sets/src/mage/cards/b/BlastFromThePast.java index 72af5d013e7..97bdd6bc46a 100644 --- a/Mage.Sets/src/mage/cards/b/BlastFromThePast.java +++ b/Mage.Sets/src/mage/cards/b/BlastFromThePast.java @@ -35,7 +35,7 @@ public final class BlastFromThePast extends CardImpl { // Kicker {2}{R} this.addAbility(new KickerAbility("{2}{R}")); // Flashback {3}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{R}"))); // Buyback {4}{R} this.addAbility(new BuybackAbility("{4}{R}")); diff --git a/Mage.Sets/src/mage/cards/b/BoldDefense.java b/Mage.Sets/src/mage/cards/b/BoldDefense.java index 182b91a3c25..8cdac1833c0 100644 --- a/Mage.Sets/src/mage/cards/b/BoldDefense.java +++ b/Mage.Sets/src/mage/cards/b/BoldDefense.java @@ -1,12 +1,9 @@ - package mage.cards.b; -import java.util.UUID; -import mage.abilities.condition.LockedInCondition; +import mage.abilities.Ability; import mage.abilities.condition.common.KickedCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; -import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.KickerAbility; @@ -14,7 +11,11 @@ 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 java.util.UUID; /** * @author nantuko, Loki @@ -28,12 +29,7 @@ public final class BoldDefense extends CardImpl { this.addAbility(new KickerAbility("{3}{W}")); // Creatures you control get +1/+1 until end of turn. If Bold Defense was kicked, instead creatures you control get +2/+2 and gain first strike until end of turn. - this.getSpellAbility().addEffect(new ConditionalContinuousEffect(new BoostControlledEffect(2, 2, Duration.EndOfTurn), - new BoostTargetEffect(1, 1, Duration.EndOfTurn), new LockedInCondition(KickedCondition.instance), - "Creatures you control get +1/+1 until end of turn. If this spell was kicked, instead creatures you control get +2/+2")); - this.getSpellAbility().addEffect(new ConditionalContinuousEffect(new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE, false), - null, new LockedInCondition(KickedCondition.instance), - "and gain first strike until end of turn")); + this.getSpellAbility().addEffect(new BoldDefenseEffect()); } private BoldDefense(final BoldDefense card) { @@ -45,3 +41,35 @@ public final class BoldDefense extends CardImpl { return new BoldDefense(this); } } + +class BoldDefenseEffect extends OneShotEffect { + + BoldDefenseEffect() { + super(Outcome.Benefit); + staticText = "Creatures you control get +1/+1 until end of turn. If this spell was kicked, " + + "instead creatures you control get +2/+2 and gain first strike until end of turn."; + } + + private BoldDefenseEffect(final BoldDefenseEffect effect) { + super(effect); + } + + @Override + public BoldDefenseEffect copy() { + return new BoldDefenseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (KickedCondition.instance.apply(game, source)) { + game.addEffect(new BoostControlledEffect(2, 2, Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect( + FirstStrikeAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ), source); + } else { + game.addEffect(new BoostControlledEffect(1, 1, Duration.EndOfTurn), source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BoreasCharger.java b/Mage.Sets/src/mage/cards/b/BoreasCharger.java index fcc80133551..7169a5d1240 100644 --- a/Mage.Sets/src/mage/cards/b/BoreasCharger.java +++ b/Mage.Sets/src/mage/cards/b/BoreasCharger.java @@ -138,7 +138,7 @@ class BoreasChargerEffect extends OneShotEffect { } } -class BoreasChargerPredicate implements ObjectSourcePlayerPredicate> { +class BoreasChargerPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/b/BrilliantUltimatum.java b/Mage.Sets/src/mage/cards/b/BrilliantUltimatum.java index aa914cfbe47..158bfc6d391 100644 --- a/Mage.Sets/src/mage/cards/b/BrilliantUltimatum.java +++ b/Mage.Sets/src/mage/cards/b/BrilliantUltimatum.java @@ -110,8 +110,7 @@ class BrilliantUltimatumEffect extends OneShotEffect { TargetCard targetExiledCard = new TargetCard(Zone.EXILED, new FilterCard()); if (controller.chooseTarget(Outcome.PlayForFree, selectedPile, targetExiledCard, source, game)) { Card card = selectedPile.get(targetExiledCard.getFirstTarget(), game); - controller.canPlayLand(); - if (controller.playCard(card, game, true, true, new ApprovingObject(source, game))) { + if (controller.playCard(card, game, true, new ApprovingObject(source, game))) { selectedPileCards.remove(card); selectedPile.remove(card); } diff --git a/Mage.Sets/src/mage/cards/b/BrutalCathar.java b/Mage.Sets/src/mage/cards/b/BrutalCathar.java new file mode 100644 index 00000000000..902e0277d02 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrutalCathar.java @@ -0,0 +1,99 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.delayed.OnLeaveReturnExiledToBattlefieldAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.keyword.DayboundAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BrutalCathar extends CardImpl { + + public BrutalCathar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.subtype.add(SubType.WEREWOLF); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.transformable = true; + this.secondSideCardClazz = mage.cards.m.MoonrageBrute.class; + + // When this creature enters the battlefield or transforms into Brutal Cathar, exile target creature an opponent controls until this creature leaves the battlefield. + this.addAbility(new BrutalCatharTriggeredAbility()); + + // Daybound + this.addAbility(new TransformAbility()); + this.addAbility(new DayboundAbility()); + } + + private BrutalCathar(final BrutalCathar card) { + super(card); + } + + @Override + public BrutalCathar copy() { + return new BrutalCathar(this); + } +} + +class BrutalCatharTriggeredAbility extends TriggeredAbilityImpl { + + public BrutalCatharTriggeredAbility() { + super(Zone.BATTLEFIELD, new ExileUntilSourceLeavesEffect("creature an opponent controls"), false); + this.addTarget(new TargetOpponentsCreaturePermanent()); + this.addEffect(new CreateDelayedTriggeredAbilityEffect(new OnLeaveReturnExiledToBattlefieldAbility())); + } + + public BrutalCatharTriggeredAbility(final BrutalCatharTriggeredAbility ability) { + super(ability); + } + + @Override + public BrutalCatharTriggeredAbility copy() { + return new BrutalCatharTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TRANSFORMED + || event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!event.getTargetId().equals(this.getSourceId())) { + return false; + } + switch (event.getType()) { + case TRANSFORMED: + Permanent permanent = getSourcePermanentIfItStillExists(game); + return permanent != null && !permanent.isTransformed(); + case ENTERS_THE_BATTLEFIELD: + return true; + } + return false; + } + + @Override + public String getRule() { + return "When this creature enters the battlefield or transforms into {this}, " + + "exile target creature an opponent controls until this creature leaves the battlefield."; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BumpInTheNight.java b/Mage.Sets/src/mage/cards/b/BumpInTheNight.java index 70072d56b65..4bb42bf6392 100644 --- a/Mage.Sets/src/mage/cards/b/BumpInTheNight.java +++ b/Mage.Sets/src/mage/cards/b/BumpInTheNight.java @@ -24,7 +24,7 @@ public final class BumpInTheNight extends CardImpl { this.getSpellAbility().addTarget(new TargetOpponent()); // Flashback {5}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{R}"))); } private BumpInTheNight(final BumpInTheNight card) { diff --git a/Mage.Sets/src/mage/cards/b/BurlyBreaker.java b/Mage.Sets/src/mage/cards/b/BurlyBreaker.java index e81de9308fb..4f327728240 100644 --- a/Mage.Sets/src/mage/cards/b/BurlyBreaker.java +++ b/Mage.Sets/src/mage/cards/b/BurlyBreaker.java @@ -32,7 +32,7 @@ public final class BurlyBreaker extends CardImpl { // Daybound this.addAbility(new TransformAbility()); - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new DayboundAbility()); } private BurlyBreaker(final BurlyBreaker card) { diff --git a/Mage.Sets/src/mage/cards/b/BurnTheAccursed.java b/Mage.Sets/src/mage/cards/b/BurnTheAccursed.java new file mode 100644 index 00000000000..f8a73c59a94 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BurnTheAccursed.java @@ -0,0 +1,70 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetIfDiesEffect; +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 TheElk801 + */ +public final class BurnTheAccursed extends CardImpl { + + public BurnTheAccursed(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}"); + + // Burn the Accused deals 5 damage to target creature and 2 damage to that creature's controller. If that creature would die this turn, exile it instead. + this.getSpellAbility().addEffect(new BurnTheAccursedEffect()); + this.getSpellAbility().addEffect(new ExileTargetIfDiesEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private BurnTheAccursed(final BurnTheAccursed card) { + super(card); + } + + @Override + public BurnTheAccursed copy() { + return new BurnTheAccursed(this); + } +} + +class BurnTheAccursedEffect extends OneShotEffect { + + BurnTheAccursedEffect() { + super(Outcome.Benefit); + staticText = "{this} deals 5 damage to target creature and 2 damage to that creature's controller."; + } + + private BurnTheAccursedEffect(final BurnTheAccursedEffect effect) { + super(effect); + } + + @Override + public BurnTheAccursedEffect copy() { + return new BurnTheAccursedEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + permanent.damage(5, source.getSourceId(), source, game); + Player player = game.getPlayer(permanent.getControllerId()); + if (player != null) { + player.damage(2, source.getSourceId(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BurningOil.java b/Mage.Sets/src/mage/cards/b/BurningOil.java index 0eeaa8c0cd0..2106bcf3a7b 100644 --- a/Mage.Sets/src/mage/cards/b/BurningOil.java +++ b/Mage.Sets/src/mage/cards/b/BurningOil.java @@ -25,7 +25,7 @@ public final class BurningOil extends CardImpl { this.getSpellAbility().addTarget(new TargetAttackingOrBlockingCreature()); this.getSpellAbility().addEffect(new DamageTargetEffect(3)); // Flashback {3}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{W}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{W}"))); } private BurningOil(final BurningOil card) { diff --git a/Mage.Sets/src/mage/cards/c/CabalTherapy.java b/Mage.Sets/src/mage/cards/c/CabalTherapy.java index 597a6da161d..59cd97eef5f 100644 --- a/Mage.Sets/src/mage/cards/c/CabalTherapy.java +++ b/Mage.Sets/src/mage/cards/c/CabalTherapy.java @@ -12,7 +12,6 @@ import mage.cards.CardSetInfo; import mage.cards.Cards; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.TimingRule; import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; @@ -36,9 +35,9 @@ public final class CabalTherapy extends CardImpl { this.getSpellAbility().addEffect(new CabalTherapyEffect()); // Flashback-Sacrifice a creature. - this.addAbility(new FlashbackAbility( - new SacrificeTargetCost(new TargetControlledCreaturePermanent(1, 1, StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT, true)), - TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new SacrificeTargetCost( + new TargetControlledCreaturePermanent(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT) + ))); } private CabalTherapy(final CabalTherapy card) { diff --git a/Mage.Sets/src/mage/cards/c/CacklingCounterpart.java b/Mage.Sets/src/mage/cards/c/CacklingCounterpart.java index 92121d3c46f..290d6ae92c4 100644 --- a/Mage.Sets/src/mage/cards/c/CacklingCounterpart.java +++ b/Mage.Sets/src/mage/cards/c/CacklingCounterpart.java @@ -24,7 +24,7 @@ public final class CacklingCounterpart extends CardImpl { this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); // Flashback {5}{U}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{U}{U}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{U}{U}"))); } private CacklingCounterpart(final CacklingCounterpart card) { diff --git a/Mage.Sets/src/mage/cards/c/CalibratedBlast.java b/Mage.Sets/src/mage/cards/c/CalibratedBlast.java index 320cb6f805a..adf7d89564f 100644 --- a/Mage.Sets/src/mage/cards/c/CalibratedBlast.java +++ b/Mage.Sets/src/mage/cards/c/CalibratedBlast.java @@ -28,7 +28,7 @@ public final class CalibratedBlast extends CardImpl { this.getSpellAbility().addEffect(new CalibratedBlastEffect()); // Flashback {3}{R}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{R}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{R}{R}"))); } private CalibratedBlast(final CalibratedBlast card) { diff --git a/Mage.Sets/src/mage/cards/c/CalixDestinysHand.java b/Mage.Sets/src/mage/cards/c/CalixDestinysHand.java index 767f3873d6e..689f1617f5e 100644 --- a/Mage.Sets/src/mage/cards/c/CalixDestinysHand.java +++ b/Mage.Sets/src/mage/cards/c/CalixDestinysHand.java @@ -29,6 +29,7 @@ import java.util.UUID; import java.util.stream.Collectors; import static mage.constants.Outcome.Benefit; +import mage.util.CardUtil; /** * @author TheElk801 @@ -105,21 +106,24 @@ class CalixDestinysHandExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); + Player controller = game.getPlayer(source.getControllerId()); if (source.getTargets().size() > 2) { return false; } source.getTargets(); Permanent theirPerm = game.getPermanent(source.getTargets().get(0).getFirstTarget()); Permanent myPerm = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - if (player == null || theirPerm == null || myPerm == null) { + if (controller == null + || theirPerm == null + || myPerm == null) { return false; } MageObjectReference theirMor = new MageObjectReference( theirPerm.getId(), theirPerm.getZoneChangeCounter(game) + 1, game ); MageObjectReference myMor = new MageObjectReference(myPerm, game); - player.moveCards(theirPerm, Zone.EXILED, source, game); + UUID exileId = CardUtil.getExileZoneId(game, source); + controller.moveCardsToExile(theirPerm, source, game, true, exileId, myPerm.getLogName()); game.addDelayedTriggeredAbility(new CalixDestinysHandDelayedTriggeredAbility(theirMor, myMor), source); return true; } diff --git a/Mage.Sets/src/mage/cards/c/CallOfTheHerd.java b/Mage.Sets/src/mage/cards/c/CallOfTheHerd.java index 7a3b3cb7398..f5973d64a60 100644 --- a/Mage.Sets/src/mage/cards/c/CallOfTheHerd.java +++ b/Mage.Sets/src/mage/cards/c/CallOfTheHerd.java @@ -24,7 +24,7 @@ public final class CallOfTheHerd extends CardImpl { // Create a 3/3 green Elephant creature token. this.getSpellAbility().addEffect(new CreateTokenEffect(new ElephantToken())); // Flashback {3}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{G}"))); } private CallOfTheHerd(final CallOfTheHerd card) { diff --git a/Mage.Sets/src/mage/cards/c/CanopyClaws.java b/Mage.Sets/src/mage/cards/c/CanopyClaws.java index 1fb66de4464..5fe1dae8e80 100644 --- a/Mage.Sets/src/mage/cards/c/CanopyClaws.java +++ b/Mage.Sets/src/mage/cards/c/CanopyClaws.java @@ -26,7 +26,7 @@ public final class CanopyClaws extends CardImpl { this.getSpellAbility().addEffect(new LoseAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{G}"))); } private CanopyClaws(final CanopyClaws card) { diff --git a/Mage.Sets/src/mage/cards/c/CantStayAway.java b/Mage.Sets/src/mage/cards/c/CantStayAway.java index 0d72ab474c0..813896ec60c 100644 --- a/Mage.Sets/src/mage/cards/c/CantStayAway.java +++ b/Mage.Sets/src/mage/cards/c/CantStayAway.java @@ -44,7 +44,7 @@ public final class CantStayAway extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter)); // Flashback {3}{W}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{W}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{W}{B}"))); } private CantStayAway(final CantStayAway card) { diff --git a/Mage.Sets/src/mage/cards/c/CatharCommando.java b/Mage.Sets/src/mage/cards/c/CatharCommando.java index 7e29b383c96..72cb8a11621 100644 --- a/Mage.Sets/src/mage/cards/c/CatharCommando.java +++ b/Mage.Sets/src/mage/cards/c/CatharCommando.java @@ -30,7 +30,7 @@ public final class CatharCommando extends CardImpl { this.addAbility(FlashAbility.getInstance()); // {1}, Sacrifice Cathar Commando: Destroy target artifact or enchantment. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), new ManaCostsImpl("{1}{G}")); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), new ManaCostsImpl("{1}")); ability.addCost(new SacrificeSourceCost()); ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/CatharsCall.java b/Mage.Sets/src/mage/cards/c/CatharsCall.java new file mode 100644 index 00000000000..0dcbf8b25df --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CatharsCall.java @@ -0,0 +1,56 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.permanent.token.HumanToken; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CatharsCall extends CardImpl { + + public CatharsCall(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature has vigilance and "At the beginning of your end step, create a 1/1 white Human creature token." + ability = new SimpleStaticAbility(new GainAbilityAttachedEffect(VigilanceAbility.getInstance(), AttachmentType.AURA)); + ability.addEffect(new GainAbilityAttachedEffect( + new BeginningOfEndStepTriggeredAbility( + new CreateTokenEffect(new HumanToken()), + TargetController.YOU, false + ), AttachmentType.AURA + ).setText("and \"At the beginning of your end step, create a 1/1 white Human creature token.\"")); + this.addAbility(ability); + } + + private CatharsCall(final CatharsCall card) { + super(card); + } + + @Override + public CatharsCall copy() { + return new CatharsCall(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CelebrateTheHarvest.java b/Mage.Sets/src/mage/cards/c/CelebrateTheHarvest.java new file mode 100644 index 00000000000..47fcbcb28f7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CelebrateTheHarvest.java @@ -0,0 +1,96 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.common.CovenHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CelebrateTheHarvest extends CardImpl { + + public CelebrateTheHarvest(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); + + // Search your library for up to X basic land cards, where X is the number of different powers among creatures you control. Put those cards onto the battlefield tapped, then shuffle. + this.getSpellAbility().addEffect(new CelebrateTheHarvestEffect()); + this.getSpellAbility().addHint(CovenHint.instance); + } + + private CelebrateTheHarvest(final CelebrateTheHarvest card) { + super(card); + } + + @Override + public CelebrateTheHarvest copy() { + return new CelebrateTheHarvest(this); + } +} + +class CelebrateTheHarvestEffect extends OneShotEffect { + + CelebrateTheHarvestEffect() { + super(Outcome.Benefit); + staticText = "search your library for up to X basic land cards, where X is the number of different powers " + + "among creatures you control. Put those cards onto the battlefield tapped, then shuffle"; + } + + private CelebrateTheHarvestEffect(final CelebrateTheHarvestEffect effect) { + super(effect); + } + + @Override + public CelebrateTheHarvestEffect copy() { + return new CelebrateTheHarvestEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int powerCount = game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + source.getControllerId(), source.getSourceId(), game + ) + .stream() + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .distinct() + .map(x -> 1) + .sum(); + TargetCardInLibrary target = new TargetCardInLibrary(0, powerCount, StaticFilters.FILTER_CARD_BASIC_LAND); + player.searchLibrary(target, source, game); + Cards cards = new CardsImpl(); + target.getTargets() + .stream() + .map(cardId -> player.getLibrary().getCard(cardId, game)) + .forEach(cards::add); + player.moveCards( + cards.getCards(game), Zone.BATTLEFIELD, source, game, + true, false, true, null + ); + player.shuffleLibrary(source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CelestialJudgment.java b/Mage.Sets/src/mage/cards/c/CelestialJudgment.java new file mode 100644 index 00000000000..5ac6057ad24 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CelestialJudgment.java @@ -0,0 +1,102 @@ +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.ComparisonType; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class CelestialJudgment extends CardImpl { + + public CelestialJudgment(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{W}{W}"); + + // For each different power among creatures on the battlefield, choose a creature with that power. Destroy each creature not chosen this way. + this.getSpellAbility().addEffect(new CelestialJudgmentEffect()); + } + + private CelestialJudgment(final CelestialJudgment card) { + super(card); + } + + @Override + public CelestialJudgment copy() { + return new CelestialJudgment(this); + } +} + +class CelestialJudgmentEffect extends OneShotEffect { + + CelestialJudgmentEffect() { + super(Outcome.Benefit); + staticText = "for each different power among creatures on the battlefield, " + + "choose a creature with that power. Destroy each creature not chosen this way"; + } + + private CelestialJudgmentEffect(final CelestialJudgmentEffect effect) { + super(effect); + } + + @Override + public CelestialJudgmentEffect copy() { + return new CelestialJudgmentEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + List permanents = game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, + source.getControllerId(), source.getSourceId(), game + ); + Map> powerMap = permanents + .stream() + .collect(Collectors.toMap( + permanent -> permanent.getPower().getValue(), + permanent -> Arrays.asList(permanent), + (a1, a2) -> { + a1.addAll(a2); + return a1; + })); + Set toKeep = new HashSet<>(); + for (Map.Entry> entry : powerMap.entrySet()) { + if (entry.getValue().size() == 1) { + toKeep.add(entry.getValue().get(0).getId()); + continue; + } + FilterPermanent filter = new FilterCreaturePermanent("creature with power " + entry.getKey() + " to save"); + filter.add(new PowerPredicate(ComparisonType.EQUAL_TO, entry.getKey())); + TargetPermanent target = new TargetPermanent(filter); + target.setNotTarget(true); + player.choose(outcome, target, source.getSourceId(), game); + toKeep.add(target.getFirstTarget()); + } + for (Permanent permanent : permanents) { + if (!toKeep.contains(permanent.getId())) { + permanent.destroy(source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CeruleanDrake.java b/Mage.Sets/src/mage/cards/c/CeruleanDrake.java index 00cbafc41fa..c65cdd08337 100644 --- a/Mage.Sets/src/mage/cards/c/CeruleanDrake.java +++ b/Mage.Sets/src/mage/cards/c/CeruleanDrake.java @@ -13,8 +13,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterSpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.stack.StackObject; import mage.target.TargetSpell; @@ -61,11 +61,11 @@ public final class CeruleanDrake extends CardImpl { } } -enum CeruleanDrakePredicate implements ObjectPlayerPredicate> { +enum CeruleanDrakePredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { if (input.getPlayerId() == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/ChainersEdict.java b/Mage.Sets/src/mage/cards/c/ChainersEdict.java index b4956b0b7d8..c9b34236619 100644 --- a/Mage.Sets/src/mage/cards/c/ChainersEdict.java +++ b/Mage.Sets/src/mage/cards/c/ChainersEdict.java @@ -26,7 +26,7 @@ public final class ChainersEdict extends CardImpl { this.getSpellAbility().addTarget(new TargetPlayer()); // Flashback {5}{B}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{B}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{B}{B}"))); } private ChainersEdict(final ChainersEdict card) { diff --git a/Mage.Sets/src/mage/cards/c/ChandraAblaze.java b/Mage.Sets/src/mage/cards/c/ChandraAblaze.java index 20518c65b7b..2e665564dff 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraAblaze.java +++ b/Mage.Sets/src/mage/cards/c/ChandraAblaze.java @@ -171,7 +171,10 @@ class ChandraAblazeEffect5 extends OneShotEffect { if (player.choose(outcome, target, source.getSourceId(), game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { - player.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + player.cast(player.chooseAbilityForCast(card, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); + cards.remove(card); } } diff --git a/Mage.Sets/src/mage/cards/c/ChaplainOfAlms.java b/Mage.Sets/src/mage/cards/c/ChaplainOfAlms.java index 371ec588518..73105c7bf2f 100644 --- a/Mage.Sets/src/mage/cards/c/ChaplainOfAlms.java +++ b/Mage.Sets/src/mage/cards/c/ChaplainOfAlms.java @@ -4,6 +4,7 @@ import mage.MageInt; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.TransformAbility; import mage.abilities.keyword.WardAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -34,6 +35,7 @@ public final class ChaplainOfAlms extends CardImpl { this.addAbility(new WardAbility(new ManaCostsImpl<>("{1}"))); // Disturb {3}{W} + this.addAbility(new TransformAbility()); this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{3}{W}"))); } diff --git a/Mage.Sets/src/mage/cards/c/ChatterOfTheSquirrel.java b/Mage.Sets/src/mage/cards/c/ChatterOfTheSquirrel.java index 0357a1e387f..f0ab9129de7 100644 --- a/Mage.Sets/src/mage/cards/c/ChatterOfTheSquirrel.java +++ b/Mage.Sets/src/mage/cards/c/ChatterOfTheSquirrel.java @@ -25,7 +25,7 @@ public final class ChatterOfTheSquirrel extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new SquirrelToken())); // Flashback {1}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{G}"))); } private ChatterOfTheSquirrel(final ChatterOfTheSquirrel card) { diff --git a/Mage.Sets/src/mage/cards/c/ChillOfForeboding.java b/Mage.Sets/src/mage/cards/c/ChillOfForeboding.java index aa52573d3a2..ed7c2f47ebc 100644 --- a/Mage.Sets/src/mage/cards/c/ChillOfForeboding.java +++ b/Mage.Sets/src/mage/cards/c/ChillOfForeboding.java @@ -23,7 +23,7 @@ public final class ChillOfForeboding extends CardImpl { this.getSpellAbility().addEffect(new MillCardsEachPlayerEffect(5, TargetController.ANY)); // Flashback {7}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{7}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{7}{U}"))); } private ChillOfForeboding(final ChillOfForeboding card) { diff --git a/Mage.Sets/src/mage/cards/c/ChillingChronicle.java b/Mage.Sets/src/mage/cards/c/ChillingChronicle.java new file mode 100644 index 00000000000..27b55de43e1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChillingChronicle.java @@ -0,0 +1,44 @@ +package mage.cards.c; + +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.abilities.effects.common.TransformSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChillingChronicle extends CardImpl { + + public ChillingChronicle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); + + this.color.setBlue(true); + this.transformable = true; + this.nightCard = true; + + // {1}, {T}: Tap target nonland permanent. Transform Chilling Chronicle. + Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + ability.addEffect(new TransformSourceEffect(false)); + ability.addTarget(new TargetNonlandPermanent()); + this.addAbility(ability); + } + + private ChillingChronicle(final ChillingChronicle card) { + super(card); + } + + @Override + public ChillingChronicle copy() { + return new ChillingChronicle(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CleaverSkaab.java b/Mage.Sets/src/mage/cards/c/CleaverSkaab.java new file mode 100644 index 00000000000..b5c78518df0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CleaverSkaab.java @@ -0,0 +1,95 @@ +package mage.cards.c; + +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.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.Collection; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CleaverSkaab extends CardImpl { + + private static final FilterControlledPermanent filter + = new FilterControlledPermanent(SubType.ZOMBIE, "another Zombie"); + + static { + filter.add(AnotherPredicate.instance); + } + + public CleaverSkaab(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // {3}, {T}, Sacrifice another Zombie: Create two tokens that are copies of the sacrificed creature. + Ability ability = new SimpleActivatedAbility(new CleaverSkaabEffect(), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(filter)); + this.addAbility(ability); + } + + private CleaverSkaab(final CleaverSkaab card) { + super(card); + } + + @Override + public CleaverSkaab copy() { + return new CleaverSkaab(this); + } +} + +class CleaverSkaabEffect extends OneShotEffect { + + CleaverSkaabEffect() { + super(Outcome.Benefit); + staticText = "create two tokens that are copies of the sacrificed creature"; + } + + private CleaverSkaabEffect(final CleaverSkaabEffect effect) { + super(effect); + } + + @Override + public CleaverSkaabEffect copy() { + return new CleaverSkaabEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = CardUtil.castStream( + source.getCosts().stream(), SacrificeTargetCost.class + ) + .map(SacrificeTargetCost::getPermanents) + .flatMap(Collection::stream) + .findFirst() + .orElse(null); + if (permanent == null) { + return false; + } + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(); + effect.setSavedPermanent(permanent); + effect.setNumber(2); + return effect.apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ClockworkAvian.java b/Mage.Sets/src/mage/cards/c/ClockworkAvian.java index ca67107e045..1b7c5f3ea57 100644 --- a/Mage.Sets/src/mage/cards/c/ClockworkAvian.java +++ b/Mage.Sets/src/mage/cards/c/ClockworkAvian.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EndOfCombatTriggeredAbility; @@ -12,26 +10,23 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.decorator.ConditionalActivatedAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.counters.Counter; +import mage.constants.*; import mage.counters.CounterType; -import mage.counters.Counters; import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; import mage.watchers.common.AttackedOrBlockedThisCombatWatcher; +import java.util.UUID; + /** - * - * @author MarcoMarin + * @author TheElk801 */ public final class ClockworkAvian extends CardImpl { @@ -52,23 +47,16 @@ public final class ClockworkAvian extends CardImpl { // At end of combat, if Clockwork Avian attacked or blocked this combat, remove a +1/+0 counter from it. this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new EndOfCombatTriggeredAbility(new RemoveCounterSourceEffect(CounterType.P1P0.createInstance()), false), - AttackedOrBlockedThisCombatSourceCondition.instance, - "At end of combat, if {this} attacked or blocked this combat, remove a +1/+0 counter from it."), - new AttackedOrBlockedThisCombatWatcher() - ); + new EndOfCombatTriggeredAbility( + new RemoveCounterSourceEffect(CounterType.P1P0.createInstance()), false + ), AttackedOrBlockedThisCombatSourceCondition.instance, "At end of combat, " + + "if {this} attacked or blocked this combat, remove a +1/+0 counter from it." + ), new AttackedOrBlockedThisCombatWatcher()); // {X}, {tap}: Put up to X +1/+0 counters on Clockwork Avian. This ability can't cause the total number of +1/+0 counters on Clockwork Avian to be greater than four. Activate this ability only during your upkeep. Ability ability = new ConditionalActivatedAbility( - Zone.BATTLEFIELD, - new AvianAddCountersSourceEffect( - CounterType.P1P0.createInstance(), - ManacostVariableValue.REGULAR, - true, true - ), - new ManaCostsImpl("{X}"), - new IsStepCondition(PhaseStep.UPKEEP), - null + Zone.BATTLEFIELD, new ClockworkAvianEffect(), + new ManaCostsImpl<>("{X}"), new IsStepCondition(PhaseStep.UPKEEP) ); ability.addCost(new TapSourceCost()); this.addAbility(ability); @@ -84,24 +72,42 @@ public final class ClockworkAvian extends CardImpl { } } -class AvianAddCountersSourceEffect extends AddCountersSourceEffect { +class ClockworkAvianEffect extends OneShotEffect { - public AvianAddCountersSourceEffect(Counter counter, DynamicValue amount, boolean informPlayers, boolean putOnCard) { - super(counter, amount, informPlayers, putOnCard); - staticText = "Put up to X +1/+0 counters on {this}. This ability can't cause the total number of +1/+0 counters on {this} to be greater than four."; + ClockworkAvianEffect() { + super(Outcome.Benefit); + staticText = "put up to X +1/+0 counters on {this}. This ability can't cause " + + "the total number of +1/+0 counters on {this} to be greater than four"; + } + + private ClockworkAvianEffect(final ClockworkAvianEffect effect) { + super(effect); + } + + @Override + public ClockworkAvianEffect copy() { + return new ClockworkAvianEffect(this); } @Override public boolean apply(Game game, Ability source) { - //record how many counters - Counters permCounters = game.getPermanent(source.getSourceId()).getCounters(game); - int countersWas = permCounters.getCount(CounterType.P1P0); - if (countersWas < 4) { - super.apply(game, source); - if (permCounters.getCount(CounterType.P1P0) > 4) { - permCounters.removeCounter(CounterType.P1P0, permCounters.getCount(CounterType.P1P0) - 4); - }//if countersWas < 4 then counter is min(current,4); there is no setCounters function tho - }//else this is a rare case of an Avian getting boosted by outside sources :) Which is the sole purpose of this if, for the benefit of this rare but not impossible case :p - return true; + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (player == null || permanent == null) { + return false; + } + int maxCounters = Integer.min( + 4 - permanent.getCounters(game).getCount(CounterType.P1P0), source.getManaCostsToPay().getX() + ); + if (maxCounters < 1) { + return false; + } + int toAdd = player.getAmount( + 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game + ); + return toAdd > 0 && permanent.addCounters( + CounterType.P1P0.createInstance(toAdd), source.getControllerId(), + source, game, null, true, 4 + ); } } diff --git a/Mage.Sets/src/mage/cards/c/ClockworkBeast.java b/Mage.Sets/src/mage/cards/c/ClockworkBeast.java index 48e257ada37..4d0c2b8bdff 100644 --- a/Mage.Sets/src/mage/cards/c/ClockworkBeast.java +++ b/Mage.Sets/src/mage/cards/c/ClockworkBeast.java @@ -1,7 +1,6 @@ package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EndOfCombatTriggeredAbility; @@ -12,25 +11,22 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.decorator.ConditionalActivatedAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.counters.Counter; +import mage.constants.*; import mage.counters.CounterType; -import mage.counters.Counters; import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; import mage.watchers.common.AttackedOrBlockedThisCombatWatcher; +import java.util.UUID; + /** - * - * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + * @author TheElk801 */ public final class ClockworkBeast extends CardImpl { @@ -48,23 +44,16 @@ public final class ClockworkBeast extends CardImpl { // At end of combat, if Clockwork Beast attacked or blocked this combat, remove a +1/+0 counter from it. this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new EndOfCombatTriggeredAbility(new RemoveCounterSourceEffect(CounterType.P1P0.createInstance()), false), - AttackedOrBlockedThisCombatSourceCondition.instance, - "At end of combat, if {this} attacked or blocked this combat, remove a +1/+0 counter from it."), - new AttackedOrBlockedThisCombatWatcher() - ); + new EndOfCombatTriggeredAbility( + new RemoveCounterSourceEffect(CounterType.P1P0.createInstance()), false + ), AttackedOrBlockedThisCombatSourceCondition.instance, "At end of combat, " + + "if {this} attacked or blocked this combat, remove a +1/+0 counter from it." + ), new AttackedOrBlockedThisCombatWatcher()); // {X}, {tap}: Put up to X +1/+0 counters on Clockwork Beast. This ability can't cause the total number of +1/+0 counters on Clockwork Beast to be greater than seven. Activate this ability only during your upkeep. Ability ability = new ConditionalActivatedAbility( - Zone.BATTLEFIELD, - new BeastAddCountersSourceEffect( - CounterType.P1P0.createInstance(), - ManacostVariableValue.REGULAR, - true, true - ), - new ManaCostsImpl("{X}"), - new IsStepCondition(PhaseStep.UPKEEP), - null + Zone.BATTLEFIELD, new ClockworkBeastEffect(), + new ManaCostsImpl<>("{X}"), new IsStepCondition(PhaseStep.UPKEEP) ); ability.addCost(new TapSourceCost()); this.addAbility(ability); @@ -80,32 +69,42 @@ public final class ClockworkBeast extends CardImpl { } } -class BeastAddCountersSourceEffect extends AddCountersSourceEffect { +class ClockworkBeastEffect extends OneShotEffect { - public BeastAddCountersSourceEffect(Counter counter, DynamicValue amount, boolean informPlayers, boolean putOnCard) { - super(counter, amount, informPlayers, putOnCard); - staticText = "Put up to X +1/+0 counters on {this}. This ability can't cause the total number of +1/+0 counters on {this} to be greater than seven."; + ClockworkBeastEffect() { + super(Outcome.Benefit); + staticText = "put up to X +1/+0 counters on {this}. This ability can't cause " + + "the total number of +1/+0 counters on {this} to be greater than seven"; } - @Override - public boolean apply(Game game, Ability source) { - Counters permCounters = game.getPermanent(source.getSourceId()).getCounters(game); - int countersWas = permCounters.getCount(CounterType.P1P0); - if (countersWas < 7) { - super.apply(game, source); - if (permCounters.getCount(CounterType.P1P0) > 7) { - permCounters.removeCounter(CounterType.P1P0, permCounters.getCount(CounterType.P1P0) - 7); - }//if countersWas < 7 then counter is min(current,7); there is no setCounters function though - }//else this is a rare case of a Beast getting boosted by outside sources. Which is the sole purpose of this if, for the benefit of this rare but not impossible case - return true; - } - - public BeastAddCountersSourceEffect(final BeastAddCountersSourceEffect effect) { + private ClockworkBeastEffect(final ClockworkBeastEffect effect) { super(effect); } @Override - public BeastAddCountersSourceEffect copy() { - return new BeastAddCountersSourceEffect(this); + public ClockworkBeastEffect copy() { + return new ClockworkBeastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (player == null || permanent == null) { + return false; + } + int maxCounters = Integer.min( + 7 - permanent.getCounters(game).getCount(CounterType.P1P0), source.getManaCostsToPay().getX() + ); + if (maxCounters < 1) { + return false; + } + int toAdd = player.getAmount( + 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game + ); + return toAdd > 0 && permanent.addCounters( + CounterType.P1P0.createInstance(toAdd), source.getControllerId(), + source, game, null, true, 7 + ); } } diff --git a/Mage.Sets/src/mage/cards/c/ClockworkSteed.java b/Mage.Sets/src/mage/cards/c/ClockworkSteed.java index dac9760b5e7..7ab435ef583 100644 --- a/Mage.Sets/src/mage/cards/c/ClockworkSteed.java +++ b/Mage.Sets/src/mage/cards/c/ClockworkSteed.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EndOfCombatTriggeredAbility; @@ -13,28 +11,24 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.decorator.ConditionalActivatedAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.counters.Counter; +import mage.constants.*; import mage.counters.CounterType; -import mage.counters.Counters; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; import mage.watchers.common.AttackedOrBlockedThisCombatWatcher; +import java.util.UUID; + /** - * - * @author escplan9, MarcoMarin & L_J + * @author TheElk801 */ public final class ClockworkSteed extends CardImpl { @@ -58,23 +52,16 @@ public final class ClockworkSteed extends CardImpl { // At end of combat, if Clockwork Steed attacked or blocked this combat, remove a +1/+0 counter from it. this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new EndOfCombatTriggeredAbility(new RemoveCounterSourceEffect(CounterType.P1P0.createInstance()), false), - AttackedOrBlockedThisCombatSourceCondition.instance, - "At end of combat, if {this} attacked or blocked this combat, remove a +1/+0 counter from it."), - new AttackedOrBlockedThisCombatWatcher() - ); + new EndOfCombatTriggeredAbility( + new RemoveCounterSourceEffect(CounterType.P1P0.createInstance()), false + ), AttackedOrBlockedThisCombatSourceCondition.instance, "At end of combat, " + + "if {this} attacked or blocked this combat, remove a +1/+0 counter from it." + ), new AttackedOrBlockedThisCombatWatcher()); // {X}, {tap}: Put up to X +1/+0 counters on Clockwork Steed. This ability can't cause the total number of +1/+0 counters on Clockwork Steed to be greater than four. Activate this ability only during your upkeep. Ability ability = new ConditionalActivatedAbility( - Zone.BATTLEFIELD, - new ClockworkSteedAddCountersSourceEffect( - CounterType.P1P0.createInstance(), - ManacostVariableValue.REGULAR, - true, true - ), - new ManaCostsImpl("{X}"), - new IsStepCondition(PhaseStep.UPKEEP), - null + Zone.BATTLEFIELD, new ClockworkSteedEffect(), + new ManaCostsImpl<>("{X}"), new IsStepCondition(PhaseStep.UPKEEP) ); ability.addCost(new TapSourceCost()); this.addAbility(ability); @@ -90,24 +77,42 @@ public final class ClockworkSteed extends CardImpl { } } -class ClockworkSteedAddCountersSourceEffect extends AddCountersSourceEffect { +class ClockworkSteedEffect extends OneShotEffect { - public ClockworkSteedAddCountersSourceEffect(Counter counter, DynamicValue amount, boolean informPlayers, boolean putOnCard) { - super(counter, amount, informPlayers, putOnCard); - staticText = "Put up to X +1/+0 counters on {this}. This ability can't cause the total number of +1/+0 counters on {this} to be greater than four."; + ClockworkSteedEffect() { + super(Outcome.Benefit); + staticText = "put up to X +1/+0 counters on {this}. This ability can't cause " + + "the total number of +1/+0 counters on {this} to be greater than four"; + } + + private ClockworkSteedEffect(final ClockworkSteedEffect effect) { + super(effect); + } + + @Override + public ClockworkSteedEffect copy() { + return new ClockworkSteedEffect(this); } @Override public boolean apply(Game game, Ability source) { - //record how many counters - Counters permCounters = game.getPermanent(source.getSourceId()).getCounters(game); - int countersWas = permCounters.getCount(CounterType.P1P0); - if (countersWas < 4) { - super.apply(game, source); - if (permCounters.getCount(CounterType.P1P0) > 4) { - permCounters.removeCounter(CounterType.P1P0, permCounters.getCount(CounterType.P1P0) - 4); - }//if countersWas < 4 then counter is min(current,4); there is no setCounters function tho - }//else this is a rare case of a Steed getting boosted by outside sources :) Which is the sole purpose of this if, for the benefit of this rare but not impossible case :p - return true; + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (player == null || permanent == null) { + return false; + } + int maxCounters = Integer.min( + 4 - permanent.getCounters(game).getCount(CounterType.P1P0), source.getManaCostsToPay().getX() + ); + if (maxCounters < 1) { + return false; + } + int toAdd = player.getAmount( + 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game + ); + return toAdd > 0 && permanent.addCounters( + CounterType.P1P0.createInstance(toAdd), source.getControllerId(), + source, game, null, true, 4 + ); } } diff --git a/Mage.Sets/src/mage/cards/c/ClockworkSwarm.java b/Mage.Sets/src/mage/cards/c/ClockworkSwarm.java index 02e88b6d084..2e8c15776d8 100644 --- a/Mage.Sets/src/mage/cards/c/ClockworkSwarm.java +++ b/Mage.Sets/src/mage/cards/c/ClockworkSwarm.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EndOfCombatTriggeredAbility; @@ -13,27 +11,23 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.decorator.ConditionalActivatedAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.counters.Counter; +import mage.constants.*; import mage.counters.CounterType; -import mage.counters.Counters; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; import mage.watchers.common.AttackedOrBlockedThisCombatWatcher; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class ClockworkSwarm extends CardImpl { @@ -62,22 +56,16 @@ public final class ClockworkSwarm extends CardImpl { // At end of combat, if Clockwork Swarm attacked or blocked this combat, remove a +1/+0 counter from it. this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new EndOfCombatTriggeredAbility(new RemoveCounterSourceEffect(CounterType.P1P0.createInstance()), false), - AttackedOrBlockedThisCombatSourceCondition.instance, - "At end of combat, if {this} attacked or blocked this combat, remove a +1/+0 counter from it."), - new AttackedOrBlockedThisCombatWatcher()); + new EndOfCombatTriggeredAbility( + new RemoveCounterSourceEffect(CounterType.P1P0.createInstance()), false + ), AttackedOrBlockedThisCombatSourceCondition.instance, "At end of combat, " + + "if {this} attacked or blocked this combat, remove a +1/+0 counter from it." + ), new AttackedOrBlockedThisCombatWatcher()); // {X}, {tap}: Put up to X +1/+0 counters on Clockwork Swarm. This ability can't cause the total number of +1/+0 counters on Clockwork Swarm to be greater than four. Activate this ability only during your upkeep. Ability ability = new ConditionalActivatedAbility( - Zone.BATTLEFIELD, - new SwarmAddCountersSourceEffect( - CounterType.P1P0.createInstance(), - ManacostVariableValue.REGULAR, - true, true - ), - new ManaCostsImpl("{X}"), - new IsStepCondition(PhaseStep.UPKEEP), - null + Zone.BATTLEFIELD, new ClockworkSwarmEffect(), + new ManaCostsImpl<>("{X}"), new IsStepCondition(PhaseStep.UPKEEP) ); ability.addCost(new TapSourceCost()); this.addAbility(ability); @@ -93,32 +81,42 @@ public final class ClockworkSwarm extends CardImpl { } } -class SwarmAddCountersSourceEffect extends AddCountersSourceEffect { +class ClockworkSwarmEffect extends OneShotEffect { - public SwarmAddCountersSourceEffect(Counter counter, DynamicValue amount, boolean informPlayers, boolean putOnCard) { - super(counter, amount, informPlayers, putOnCard); - staticText = "Put up to X +1/+0 counters on {this}. This ability can't cause the total number of +1/+0 counters on {this} to be greater than four."; + ClockworkSwarmEffect() { + super(Outcome.Benefit); + staticText = "put up to X +1/+0 counters on {this}. This ability can't cause " + + "the total number of +1/+0 counters on {this} to be greater than four"; } - @Override - public boolean apply(Game game, Ability source) { - Counters permCounters = game.getPermanent(source.getSourceId()).getCounters(game); - int countersWas = permCounters.getCount(CounterType.P1P0); - if (countersWas < 4) { - super.apply(game, source); - if (permCounters.getCount(CounterType.P1P0) > 4) { - permCounters.removeCounter(CounterType.P1P0, permCounters.getCount(CounterType.P1P0) - 4); - }//if countersWas < 4 then counter is min(current,4); there is no setCounters function though - }//else this is a rare case of a Beast getting boosted by outside sources. Which is the sole purpose of this if, for the benefit of this rare but not impossible case - return true; - } - - public SwarmAddCountersSourceEffect(final SwarmAddCountersSourceEffect effect) { + private ClockworkSwarmEffect(final ClockworkSwarmEffect effect) { super(effect); } @Override - public SwarmAddCountersSourceEffect copy() { - return new SwarmAddCountersSourceEffect(this); + public ClockworkSwarmEffect copy() { + return new ClockworkSwarmEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (player == null || permanent == null) { + return false; + } + int maxCounters = Integer.min( + 4 - permanent.getCounters(game).getCount(CounterType.P1P0), source.getManaCostsToPay().getX() + ); + if (maxCounters < 1) { + return false; + } + int toAdd = player.getAmount( + 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game + ); + return toAdd > 0 && permanent.addCounters( + CounterType.P1P0.createInstance(toAdd), source.getControllerId(), + source, game, null, true, 4 + ); } } diff --git a/Mage.Sets/src/mage/cards/c/CoffinPurge.java b/Mage.Sets/src/mage/cards/c/CoffinPurge.java index 8bd9edf701b..60cd3162de0 100644 --- a/Mage.Sets/src/mage/cards/c/CoffinPurge.java +++ b/Mage.Sets/src/mage/cards/c/CoffinPurge.java @@ -26,7 +26,7 @@ public final class CoffinPurge extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInGraveyard()); // Flashback {B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{B}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{B}"))); } private CoffinPurge(final CoffinPurge card) { diff --git a/Mage.Sets/src/mage/cards/c/ComponentCollector.java b/Mage.Sets/src/mage/cards/c/ComponentCollector.java new file mode 100644 index 00000000000..d744368d1ac --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ComponentCollector.java @@ -0,0 +1,45 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomeDayAsEntersAbility; +import mage.abilities.common.BecomesDayOrNightTriggeredAbility; +import mage.abilities.effects.common.MayTapOrUntapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ComponentCollector extends CardImpl { + + public ComponentCollector(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HOMUNCULUS); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // If it's neither day nor night, it becomes day as Component Collector enters the battlefield. + this.addAbility(new BecomeDayAsEntersAbility()); + + // Whenever day becomes night or night becomes day, you may tap or untap target nonland permanent. + Ability ability = new BecomesDayOrNightTriggeredAbility(new MayTapOrUntapTargetEffect()); + ability.addTarget(new TargetNonlandPermanent()); + this.addAbility(ability); + } + + private ComponentCollector(final ComponentCollector card) { + super(card); + } + + @Override + public ComponentCollector copy() { + return new ComponentCollector(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ConduitOfRuin.java b/Mage.Sets/src/mage/cards/c/ConduitOfRuin.java index b08ee2cff9b..f9e79a610da 100644 --- a/Mage.Sets/src/mage/cards/c/ConduitOfRuin.java +++ b/Mage.Sets/src/mage/cards/c/ConduitOfRuin.java @@ -19,8 +19,8 @@ import mage.constants.ComparisonType; import mage.constants.WatcherScope; import mage.constants.Zone; import mage.filter.common.FilterCreatureCard; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.mageobject.ColorlessPredicate; import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.game.Controllable; @@ -100,10 +100,10 @@ class ConduitOfRuinWatcher extends Watcher { } } -class FirstCastCreatureSpellPredicate implements ObjectPlayerPredicate> { +class FirstCastCreatureSpellPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { if (input.getObject() instanceof Card && ((Card) input.getObject()).isCreature(game)) { ConduitOfRuinWatcher watcher = game.getState().getWatcher(ConduitOfRuinWatcher.class); diff --git a/Mage.Sets/src/mage/cards/c/Conflagrate.java b/Mage.Sets/src/mage/cards/c/Conflagrate.java index 0c9ed5d6fb7..f988a7b82d4 100644 --- a/Mage.Sets/src/mage/cards/c/Conflagrate.java +++ b/Mage.Sets/src/mage/cards/c/Conflagrate.java @@ -33,7 +33,7 @@ public final class Conflagrate extends CardImpl { this.getSpellAbility().addTarget(new TargetAnyTargetAmount(xValue)); // Flashback-{R}{R}, Discard X cards. - Ability ability = new FlashbackAbility(new ManaCostsImpl("{R}{R}"), TimingRule.SORCERY); + Ability ability = new FlashbackAbility(this, new ManaCostsImpl("{R}{R}")); ability.addCost(new DiscardXTargetCost(new FilterCard("cards"))); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/ConfrontThePast.java b/Mage.Sets/src/mage/cards/c/ConfrontThePast.java index 19b1507a8f4..8cafe8f05d2 100644 --- a/Mage.Sets/src/mage/cards/c/ConfrontThePast.java +++ b/Mage.Sets/src/mage/cards/c/ConfrontThePast.java @@ -1,6 +1,5 @@ package mage.cards.c; -import com.google.common.collect.Iterables; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; @@ -65,7 +64,8 @@ enum ConfrontThePastAdjuster implements TargetAdjuster { @Override public void adjustTargets(Ability ability, Game game) { - if (Iterables.getOnlyElement(ability.getEffects()) instanceof ReturnFromGraveyardToBattlefieldTargetEffect) { + if (ability.getEffects().size() == 1 + && ability.getEffects().get(0) instanceof ReturnFromGraveyardToBattlefieldTargetEffect) { int xValue = ability.getManaCostsToPay().getX(); ability.getTargets().clear(); FilterPermanentCard filter = new FilterPermanentCard("planeswalker card with mana value X or less"); diff --git a/Mage.Sets/src/mage/cards/c/ConsumingBlob.java b/Mage.Sets/src/mage/cards/c/ConsumingBlob.java new file mode 100644 index 00000000000..b49efca1438 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ConsumingBlob.java @@ -0,0 +1,78 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.CardTypesInGraveyardHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.token.ConsumingBlobToken; + +import java.util.UUID; + +/** + * @author ciaccona007 + */ +public final class ConsumingBlob extends CardImpl { + + public ConsumingBlob(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); + + this.subtype.add(SubType.OOZE); + this.power = new MageInt(0); + this.toughness = new MageInt(1); + + // Consuming Blob's power is equal to the number of card types among cards in your graveyard and its toughness is equal to that number plus 1. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConsumingBlobEffect()).addHint(CardTypesInGraveyardHint.YOU)); + + // At the beginning of your end step, create a green Ooze creature token with "This creature's power is equal to the number of card types among cards in your graveyard and its toughness is equal to that number plus 1". + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new CreateTokenEffect(new ConsumingBlobToken()), TargetController.YOU, false) + ); + } + + private ConsumingBlob(final ConsumingBlob card) { + super(card); + } + + @Override + public ConsumingBlob copy() { + return new ConsumingBlob(this); + } +} + +class ConsumingBlobEffect extends ContinuousEffectImpl { + + public ConsumingBlobEffect() { + super(Duration.EndOfGame, Layer.PTChangingEffects_7, SubLayer.CharacteristicDefining_7a, Outcome.BoostCreature); + staticText = "{this}'s power is equal to the number of card types among cards in your graveyard and its toughness is equal to that number plus 1"; + } + + public ConsumingBlobEffect(final ConsumingBlobEffect effect) { + super(effect); + } + + @Override + public ConsumingBlobEffect copy() { + return new ConsumingBlobEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject target = source.getSourceObject(game); + if (target == null) { + return false; + } + int number = CardTypesInGraveyardCount.YOU.calculate(game, source, this); + target.getPower().setValue(number); + target.getToughness().setValue(number + 1); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CorpseCobble.java b/Mage.Sets/src/mage/cards/c/CorpseCobble.java index 8cd490110b9..91d695c606d 100644 --- a/Mage.Sets/src/mage/cards/c/CorpseCobble.java +++ b/Mage.Sets/src/mage/cards/c/CorpseCobble.java @@ -38,7 +38,7 @@ public final class CorpseCobble extends CardImpl { this.getSpellAbility().addEffect(new CorpseCobbleEffect()); // Flashback {3}{U}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{U}{B}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{U}{B}"))); } private CorpseCobble(final CorpseCobble card) { diff --git a/Mage.Sets/src/mage/cards/c/CovertCutpurse.java b/Mage.Sets/src/mage/cards/c/CovertCutpurse.java new file mode 100644 index 00000000000..4659de478d1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CovertCutpurse.java @@ -0,0 +1,63 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.WasDealtDamageThisTurnPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CovertCutpurse extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("creature you don't control that was dealt damage this turn"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(WasDealtDamageThisTurnPredicate.instance); + } + + public CovertCutpurse(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(2); + this.toughness = new MageInt(1); + this.transformable = true; + this.secondSideCardClazz = mage.cards.c.CovetousGeist.class; + + // When Covert Cutpurse enters the battlefield, destroy target creature you don't control that was dealt damage this turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // Disturb {4}{B} + this.addAbility(new TransformAbility()); + this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{4}{B}"))); + } + + private CovertCutpurse(final CovertCutpurse card) { + super(card); + } + + @Override + public CovertCutpurse copy() { + return new CovertCutpurse(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CovetousCastaway.java b/Mage.Sets/src/mage/cards/c/CovetousCastaway.java new file mode 100644 index 00000000000..9f1a2180606 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CovetousCastaway.java @@ -0,0 +1,46 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.TransformAbility; +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 CovetousCastaway extends CardImpl { + + public CovetousCastaway(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.HUMAN); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + this.transformable = true; + this.secondSideCardClazz = mage.cards.g.GhostlyCastigator.class; + + // When Covetous Castaway dies, mill three cards. + this.addAbility(new DiesSourceTriggeredAbility(new MillCardsControllerEffect(3))); + + // Disturb {3}{U}{U} + this.addAbility(new TransformAbility()); + this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{3}{U}{U}"))); + } + + private CovetousCastaway(final CovetousCastaway card) { + super(card); + } + + @Override + public CovetousCastaway copy() { + return new CovetousCastaway(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CovetousGeist.java b/Mage.Sets/src/mage/cards/c/CovetousGeist.java new file mode 100644 index 00000000000..7f1be068826 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CovetousGeist.java @@ -0,0 +1,49 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; +import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CovetousGeist extends CardImpl { + + public CovetousGeist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.color.setBlack(true); + this.transformable = true; + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // If Covetous Geist would be put into a graveyard from anywhere, exile it instead. + this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + } + + private CovetousGeist(final CovetousGeist card) { + super(card); + } + + @Override + public CovetousGeist copy() { + return new CovetousGeist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CrawlFromTheCellar.java b/Mage.Sets/src/mage/cards/c/CrawlFromTheCellar.java new file mode 100644 index 00000000000..84d2f9f38d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrawlFromTheCellar.java @@ -0,0 +1,51 @@ +package mage.cards.c; + +import java.util.UUID; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TimingRule; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +/** + * + * @author weirddan455 + */ +public final class CrawlFromTheCellar extends CardImpl { + + private static final FilterControlledCreaturePermanent filter + = new FilterControlledCreaturePermanent(SubType.ZOMBIE); + + public CrawlFromTheCellar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}"); + + // Return target creature card from your graveyard to your hand. Put a +1/+1 counter on up to one target Zombie you control. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance()).setTargetPointer(SecondTargetPointer.getInstance())); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(0, 1, filter, false)); + + // Flashback {3}{B} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{B}"))); + } + + private CrawlFromTheCellar(final CrawlFromTheCellar card) { + super(card); + } + + @Override + public CrawlFromTheCellar copy() { + return new CrawlFromTheCellar(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CreepingInn.java b/Mage.Sets/src/mage/cards/c/CreepingInn.java new file mode 100644 index 00000000000..791bfedba22 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CreepingInn.java @@ -0,0 +1,111 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PhaseOutSourceEffect; +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.ExileZone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author LePwnerer + */ +public final class CreepingInn extends CardImpl { + + public CreepingInn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, ""); + + this.subtype.add(SubType.HORROR); + this.subtype.add(SubType.CONSTRUCT); + this.power = new MageInt(3); + this.toughness = new MageInt(7); + this.color.setBlack(true); + this.transformable = true; + this.nightCard = true; + + // Whenever Creeping Inn attacks, you may exile a creature card from your graveyard. + // If you do, each opponent loses X life and you gain X life, + // where X is the number of creature cards exiled with Creeping Inn. + this.addAbility(new AttacksTriggeredAbility(new CreepingInnEffect())); + + // {4}: Creeping Inn phases out. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new PhaseOutSourceEffect(), new ManaCostsImpl("{4}"))); + } + + private CreepingInn(final CreepingInn card) { + super(card); + } + + @Override + public CreepingInn copy() { + return new CreepingInn(this); + } +} + +class CreepingInnEffect extends OneShotEffect { + + public CreepingInnEffect() { + super(Outcome.Exile); + this.staticText = "you may exile a creature card from your graveyard. " + + "If you do, each opponent loses X life and you gain X life, " + + "where X is the number of creature cards exiled with Creeping Inn."; + } + + public CreepingInnEffect(final CreepingInnEffect effect) { + super(effect); + } + + @Override + public CreepingInnEffect copy() { + return new CreepingInnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (player != null && permanent != null) { + UUID exileId = CardUtil.getExileZoneId(game, source); + TargetCardInGraveyard target = new TargetCardInGraveyard(0, 1, StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD); + target.setNotTarget(true); + if (target.canChoose(source.getSourceId(), player.getId(), game)) { + if (player.choose(Outcome.Exile, target, source.getId(), game)) { + Card cardChosen = game.getCard(target.getFirstTarget()); + if (cardChosen != null) { + int lifeAmount = 0; + player.moveCardsToExile(cardChosen, source, game, true, exileId, permanent.getName()); + ExileZone exile = game.getExile().getExileZone(exileId); + if (exile != null) { + for (UUID cardId : exile) { + lifeAmount++; + } + } + for (UUID playerId : game.getOpponents(source.getControllerId())) { + game.getPlayer(playerId).loseLife(lifeAmount, game, source, false); + } + player.gainLife(lifeAmount, game, source); + } + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CreepingRenaissance.java b/Mage.Sets/src/mage/cards/c/CreepingRenaissance.java index 7edb1ee3f15..10f09e1f1d3 100644 --- a/Mage.Sets/src/mage/cards/c/CreepingRenaissance.java +++ b/Mage.Sets/src/mage/cards/c/CreepingRenaissance.java @@ -33,7 +33,7 @@ public final class CreepingRenaissance extends CardImpl { this.getSpellAbility().addEffect(new CreepingRenaissanceEffect()); // Flashback {5}{G}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{G}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{G}{G}"))); } private CreepingRenaissance(final CreepingRenaissance card) { diff --git a/Mage.Sets/src/mage/cards/c/CripplingFatigue.java b/Mage.Sets/src/mage/cards/c/CripplingFatigue.java index ac4a7e05f6a..5a8a8e0b1ca 100644 --- a/Mage.Sets/src/mage/cards/c/CripplingFatigue.java +++ b/Mage.Sets/src/mage/cards/c/CripplingFatigue.java @@ -28,7 +28,7 @@ public final class CripplingFatigue extends CardImpl { this.getSpellAbility().addEffect(new BoostTargetEffect(-2, -2, Duration.EndOfTurn)); // Flashback-{1}{B}, Pay 3 life - Ability ability = new FlashbackAbility(new ManaCostsImpl("{1}{B}"), TimingRule.SORCERY); + Ability ability = new FlashbackAbility(this, new ManaCostsImpl("{1}{B}")); ability.addCost(new PayLifeCost(3)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/c/CroakingCounterpart.java b/Mage.Sets/src/mage/cards/c/CroakingCounterpart.java index 5fb85942c33..a7172e58211 100644 --- a/Mage.Sets/src/mage/cards/c/CroakingCounterpart.java +++ b/Mage.Sets/src/mage/cards/c/CroakingCounterpart.java @@ -42,7 +42,7 @@ public final class CroakingCounterpart extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); // Flashback {3}{G}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{G}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{G}{U}"))); } private CroakingCounterpart(final CroakingCounterpart card) { diff --git a/Mage.Sets/src/mage/cards/c/CrossroadsCandleguide.java b/Mage.Sets/src/mage/cards/c/CrossroadsCandleguide.java new file mode 100644 index 00000000000..acc41ef4ced --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrossroadsCandleguide.java @@ -0,0 +1,46 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CrossroadsCandleguide extends CardImpl { + + public CrossroadsCandleguide(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}"); + + this.subtype.add(SubType.SCARECROW); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When Crossroads Candleguide enters the battlefield, exile up to one target card from a graveyard. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect()); + ability.addTarget(new TargetCardInGraveyard(0, 1)); + this.addAbility(ability); + + // {2}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility(new GenericManaCost(2))); + } + + private CrossroadsCandleguide(final CrossroadsCandleguide card) { + super(card); + } + + @Override + public CrossroadsCandleguide copy() { + return new CrossroadsCandleguide(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CrowdedCrypt.java b/Mage.Sets/src/mage/cards/c/CrowdedCrypt.java new file mode 100644 index 00000000000..e5bc3fcd83b --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrowdedCrypt.java @@ -0,0 +1,61 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.mana.BlackManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.ZombieDecayedToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CrowdedCrypt extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(CounterType.CORPSE); + + public CrowdedCrypt(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{B}"); + + // {T}: Add {B}. + this.addAbility(new BlackManaAbility()); + + // Whenever a creature you control dies, put a corpse counter on Crowded Crypt. + this.addAbility(new DiesCreatureTriggeredAbility( + new AddCountersSourceEffect(CounterType.CORPSE.createInstance()), + false, StaticFilters.FILTER_CONTROLLED_A_CREATURE + )); + + // {4}{B}{B}, {T}, Sacrifice Crowded Crypt: Create a 2/2 black Zombie creature token with decayed for each corpse counter on Crowded Crypt. + Ability ability = new SimpleActivatedAbility( + new CreateTokenEffect(new ZombieDecayedToken(), xValue) + .setText("create a 2/2 black Zombie creature token with decayed for each corpse counter on {this}"), + new ManaCostsImpl<>("{4}{B}{B}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private CrowdedCrypt(final CrowdedCrypt card) { + super(card); + } + + @Override + public CrowdedCrypt copy() { + return new CrowdedCrypt(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CrownOfDoom.java b/Mage.Sets/src/mage/cards/c/CrownOfDoom.java index 295c2a27e2b..6d6b2669340 100644 --- a/Mage.Sets/src/mage/cards/c/CrownOfDoom.java +++ b/Mage.Sets/src/mage/cards/c/CrownOfDoom.java @@ -72,7 +72,7 @@ public final class CrownOfDoom extends CardImpl { } } -enum CrownOfDoomPredicate implements ObjectSourcePlayerPredicate> { +enum CrownOfDoomPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/c/CruelReality.java b/Mage.Sets/src/mage/cards/c/CruelReality.java index 7f354276433..e3249e40188 100644 --- a/Mage.Sets/src/mage/cards/c/CruelReality.java +++ b/Mage.Sets/src/mage/cards/c/CruelReality.java @@ -1,7 +1,7 @@ package mage.cards.c; import mage.abilities.Ability; -import mage.abilities.common.BeginningOfUpkeepAttachedTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.keyword.EnchantAbility; @@ -10,6 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.Predicates; @@ -38,7 +39,7 @@ public final class CruelReality extends CardImpl { this.addAbility(new EnchantAbility(auraTarget.getTargetName())); //At the beginning of enchanted player's upkeep, that player sacrifices a creature or planeswalker. If the player can't, they lose 5 life. - this.addAbility(new BeginningOfUpkeepAttachedTriggeredAbility(new CruelRealityEffect())); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new CruelRealityEffect(), TargetController.ENCHANTED, false)); } private CruelReality(final CruelReality card) { diff --git a/Mage.Sets/src/mage/cards/c/CrushOfWurms.java b/Mage.Sets/src/mage/cards/c/CrushOfWurms.java index 208402f61b7..68b48a02f94 100644 --- a/Mage.Sets/src/mage/cards/c/CrushOfWurms.java +++ b/Mage.Sets/src/mage/cards/c/CrushOfWurms.java @@ -23,7 +23,7 @@ public final class CrushOfWurms extends CardImpl { // Put three 6/6 green Wurm creature tokens onto the battlefield. this.getSpellAbility().addEffect(new CreateTokenEffect(new WurmToken(), 3)); // Flashback {9}{G}{G}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{9}{G}{G}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{9}{G}{G}{G}"))); } private CrushOfWurms(final CrushOfWurms card) { diff --git a/Mage.Sets/src/mage/cards/c/CullingScales.java b/Mage.Sets/src/mage/cards/c/CullingScales.java index 0d3922d4a01..9bddf1d536c 100644 --- a/Mage.Sets/src/mage/cards/c/CullingScales.java +++ b/Mage.Sets/src/mage/cards/c/CullingScales.java @@ -52,7 +52,7 @@ public final class CullingScales extends CardImpl { } -class HasLowestCMCAmongstNonlandPermanentsPredicate implements ObjectSourcePlayerPredicate> { +class HasLowestCMCAmongstNonlandPermanentsPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/c/CurseOfClingingWebs.java b/Mage.Sets/src/mage/cards/c/CurseOfClingingWebs.java new file mode 100644 index 00000000000..42f3e572803 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CurseOfClingingWebs.java @@ -0,0 +1,65 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.SpiderToken; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CurseOfClingingWebs extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("a nontoken creature enchanted player controls"); + + static { + filter.add(TokenPredicate.FALSE); + filter.add(TargetController.ENCHANTED.getControllerPredicate()); + } + + public CurseOfClingingWebs(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); + + this.subtype.add(SubType.AURA); + this.subtype.add(SubType.CURSE); + + // Enchant player + TargetPlayer auraTarget = new TargetPlayer(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget.getTargetName())); + + // Whenever a nontoken creature enchanted player controls dies, exile it and you create a 1/2 green Spider creature token with reach. + Ability ability = new DiesCreatureTriggeredAbility( + new ExileTargetEffect().setText("exile it"), + false, filter, true + ); + ability.addEffect(new CreateTokenEffect(new SpiderToken()).concatBy("and you")); + this.addAbility(ability); + } + + private CurseOfClingingWebs(final CurseOfClingingWebs card) { + super(card); + } + + @Override + public CurseOfClingingWebs copy() { + return new CurseOfClingingWebs(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CurseOfConformity.java b/Mage.Sets/src/mage/cards/c/CurseOfConformity.java new file mode 100644 index 00000000000..41d8c9d9d19 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CurseOfConformity.java @@ -0,0 +1,103 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CurseOfConformity extends CardImpl { + + public CurseOfConformity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{W}"); + + this.subtype.add(SubType.AURA); + this.subtype.add(SubType.CURSE); + + // Enchant player + TargetPlayer auraTarget = new TargetPlayer(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget.getTargetName())); + + // Nonlegendary creatures enchanted player controls have base power and toughness 3/3 and lose all creature types. + this.addAbility(new SimpleStaticAbility(new CurseOfConformityEffect())); + } + + private CurseOfConformity(final CurseOfConformity card) { + super(card); + } + + @Override + public CurseOfConformity copy() { + return new CurseOfConformity(this); + } +} + +class CurseOfConformityEffect extends ContinuousEffectImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + filter.add(TargetController.ENCHANTED.getControllerPredicate()); + } + + CurseOfConformityEffect() { + super(Duration.WhileOnBattlefield, Outcome.LoseAbility); + staticText = "nonlegendary creatures enchanted player controls " + + "have base power and toughness 3/3 and lose all creature types"; + } + + private CurseOfConformityEffect(final CurseOfConformityEffect effect) { + super(effect); + } + + @Override + public CurseOfConformityEffect copy() { + return new CurseOfConformityEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + filter, source.getControllerId(), source.getSourceId(), game + )) { + switch (layer) { + case TypeChangingEffects_4: + permanent.removeAllCreatureTypes(game); + break; + case PTChangingEffects_7: + if (sublayer == SubLayer.SetPT_7b) { + permanent.getPower().setValue(3); + permanent.getToughness().setValue(3); + } + } + } + return true; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.TypeChangingEffects_4 || layer == Layer.PTChangingEffects_7; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CurseOfDeathsHold.java b/Mage.Sets/src/mage/cards/c/CurseOfDeathsHold.java index 48b612e216a..ab293524c59 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfDeathsHold.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfDeathsHold.java @@ -1,28 +1,29 @@ - package mage.cards.c; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.keyword.EnchantAbility; 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.filter.common.FilterCreaturePermanent; import mage.target.TargetPlayer; import java.util.UUID; /** - * * @author BetaSteward */ public final class CurseOfDeathsHold extends CardImpl { + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("creatures enchanted player controls"); + + static { + filter.add(TargetController.ENCHANTED.getControllerPredicate()); + } + public CurseOfDeathsHold(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}{B}"); this.subtype.add(SubType.AURA, SubType.CURSE); @@ -34,7 +35,9 @@ public final class CurseOfDeathsHold extends CardImpl { this.addAbility(new EnchantAbility(auraTarget.getTargetName())); // Creatures enchanted player controls get -1/-1. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CurseOfDeathsHoldEffect())); + this.addAbility(new SimpleStaticAbility(new BoostAllEffect( + -1, -1, Duration.WhileOnBattlefield, filter, false + ))); } private CurseOfDeathsHold(final CurseOfDeathsHold card) { @@ -46,36 +49,3 @@ public final class CurseOfDeathsHold extends CardImpl { return new CurseOfDeathsHold(this); } } - -class CurseOfDeathsHoldEffect extends ContinuousEffectImpl { - - public CurseOfDeathsHoldEffect() { - super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.ModifyPT_7c, Outcome.UnboostCreature); - staticText = "Creatures enchanted player controls get -1/-1"; - } - - public CurseOfDeathsHoldEffect(final CurseOfDeathsHoldEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent enchantment = game.getPermanent(source.getSourceId()); - if (enchantment != null && enchantment.getAttachedTo() != null) { - Player player = game.getPlayer(enchantment.getAttachedTo()); - if (player != null) { - for (Permanent perm : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, player.getId(), game)) { - perm.addPower(-1); - perm.addToughness(-1); - } - return true; - } - } - return false; - } - - @Override - public CurseOfDeathsHoldEffect copy() { - return new CurseOfDeathsHoldEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/c/CurseOfOblivion.java b/Mage.Sets/src/mage/cards/c/CurseOfOblivion.java index 62eec2c2e5c..fad2c782192 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfOblivion.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfOblivion.java @@ -1,16 +1,13 @@ package mage.cards.c; import mage.abilities.Ability; -import mage.abilities.common.BeginningOfUpkeepAttachedTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.ExileFromZoneTargetEffect; import mage.abilities.keyword.EnchantAbility; 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.constants.*; import mage.filter.StaticFilters; import mage.target.TargetPlayer; @@ -33,9 +30,9 @@ public final class CurseOfOblivion extends CardImpl { this.addAbility(ability); // At the beginning of enchanted player's upkeep, that player exiles two cards from their graveyard. - this.addAbility(new BeginningOfUpkeepAttachedTriggeredAbility(new ExileFromZoneTargetEffect( + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new ExileFromZoneTargetEffect( Zone.GRAVEYARD, StaticFilters.FILTER_CARD_CARDS, 2, false - ).setText("that player exiles two cards from their graveyard"))); + ).setText("that player exiles two cards from their graveyard"), TargetController.ENCHANTED, false)); } private CurseOfOblivion(final CurseOfOblivion card) { diff --git a/Mage.Sets/src/mage/cards/c/CurseOfObsession.java b/Mage.Sets/src/mage/cards/c/CurseOfObsession.java new file mode 100644 index 00000000000..38c9340b9d1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CurseOfObsession.java @@ -0,0 +1,60 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfDrawTriggeredAbility; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.discard.DiscardHandTargetEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CurseOfObsession extends CardImpl { + + public CurseOfObsession(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{R}"); + + this.subtype.add(SubType.AURA); + this.subtype.add(SubType.CURSE); + + // Enchant player + TargetPlayer auraTarget = new TargetPlayer(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // At the beginning of enchanted player's draw step, that player draws two additional cards. + this.addAbility(new BeginningOfDrawTriggeredAbility( + new DrawCardTargetEffect(2) + .setText("that player draws two additional cards"), + TargetController.ENCHANTED, false + )); + + // At the beginning of enchanted player's end step, that player discards their hand. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new DiscardHandTargetEffect("that player"), + TargetController.ENCHANTED, false + )); + } + + private CurseOfObsession(final CurseOfObsession card) { + super(card); + } + + @Override + public CurseOfObsession copy() { + return new CurseOfObsession(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CurseOfSurveillance.java b/Mage.Sets/src/mage/cards/c/CurseOfSurveillance.java index f95f7fa7ce1..0d5f1620c81 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfSurveillance.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfSurveillance.java @@ -1,19 +1,18 @@ package mage.cards.c; -import java.util.UUID; - -import mage.abilities.common.BeginningOfUpkeepAttachedTriggeredAbility; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; -import mage.abilities.effects.common.DrawCardTargetEffect; -import mage.constants.SubType; -import mage.abilities.Ability; import mage.abilities.effects.common.AttachEffect; -import mage.constants.Outcome; +import mage.abilities.effects.common.DrawCardTargetEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterPlayer; import mage.filter.predicate.Predicates; import mage.filter.predicate.other.PlayerIdPredicate; @@ -22,8 +21,9 @@ import mage.game.permanent.Permanent; import mage.target.TargetPlayer; import mage.target.targetadjustment.TargetAdjuster; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class CurseOfSurveillance extends CardImpl { @@ -42,11 +42,11 @@ public final class CurseOfSurveillance extends CardImpl { this.addAbility(ability); // At the beginning of enchanted player's upkeep, any number of target players other than that player each draw cards equal to the number of Curses attached to that player. - ability = new BeginningOfUpkeepAttachedTriggeredAbility( + ability = new BeginningOfUpkeepTriggeredAbility( new DrawCardTargetEffect(CurseOfSurveillanceValue.instance).setText( "any number of target players other than that player each draw cards equal to the number of Curses attached to that player" ), - false, false + TargetController.ENCHANTED, false ); ability.setTargetAdjuster(CurseOfSurveillanceTargetAdjuster.instance); ability.addTarget(new TargetPlayer(0, Integer.MAX_VALUE, false)); @@ -68,13 +68,12 @@ enum CurseOfSurveillanceValue implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - UUID enchantedPlayerId = (UUID) effect.getValue("enchantedPlayer"); int curses = 0; - if (enchantedPlayerId != null) { - for (Permanent permanent : game.getBattlefield().getAllActivePermanents()) { - if (permanent != null && permanent.hasSubtype(SubType.CURSE, game) && permanent.isAttachedTo(enchantedPlayerId)) { - curses++; - } + for (Permanent permanent : game.getBattlefield().getAllActivePermanents()) { + if (permanent != null + && permanent.hasSubtype(SubType.CURSE, game) + && permanent.isAttachedTo(game.getActivePlayerId())) { + curses++; } } return curses; diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheBloodyTome.java b/Mage.Sets/src/mage/cards/c/CurseOfTheBloodyTome.java index 9803b34953e..99b5e5b31ce 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfTheBloodyTome.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheBloodyTome.java @@ -1,7 +1,7 @@ package mage.cards.c; import mage.abilities.Ability; -import mage.abilities.common.BeginningOfUpkeepAttachedTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.PutLibraryIntoGraveTargetEffect; import mage.abilities.keyword.EnchantAbility; @@ -10,6 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.TargetController; import mage.target.TargetPlayer; import java.util.UUID; @@ -31,8 +32,10 @@ public final class CurseOfTheBloodyTome extends CardImpl { this.addAbility(ability); // At the beginning of enchanted player's upkeep, that player puts the top two cards of their library into their graveyard. - this.addAbility(new BeginningOfUpkeepAttachedTriggeredAbility( - new PutLibraryIntoGraveTargetEffect(2).setText("that player mills two cards") + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new PutLibraryIntoGraveTargetEffect(2) + .setText("that player mills two cards"), + TargetController.ENCHANTED, false )); } diff --git a/Mage.Sets/src/mage/cards/c/CurseOfThePiercedHeart.java b/Mage.Sets/src/mage/cards/c/CurseOfThePiercedHeart.java index d12b08f91ae..1841f5cac30 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfThePiercedHeart.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfThePiercedHeart.java @@ -1,7 +1,7 @@ package mage.cards.c; import mage.abilities.Ability; -import mage.abilities.common.BeginningOfUpkeepAttachedTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.keyword.EnchantAbility; @@ -10,6 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterPlaneswalkerPermanent; @@ -39,7 +40,9 @@ public final class CurseOfThePiercedHeart extends CardImpl { this.addAbility(ability); // At the beginning of enchanted player's upkeep, Curse of the Pierced Heart deals 1 damage to that player. - this.addAbility(new BeginningOfUpkeepAttachedTriggeredAbility(new CurseOfThePiercedHeartEffect())); + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new CurseOfThePiercedHeartEffect(), TargetController.ENCHANTED, false + )); } private CurseOfThePiercedHeart(final CurseOfThePiercedHeart card) { diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheRestlessDead.java b/Mage.Sets/src/mage/cards/c/CurseOfTheRestlessDead.java new file mode 100644 index 00000000000..f1fc1cc57ef --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheRestlessDead.java @@ -0,0 +1,61 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterLandPermanent; +import mage.game.permanent.token.ZombieDecayedToken; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CurseOfTheRestlessDead extends CardImpl { + + private static final FilterPermanent filter = new FilterLandPermanent(); + + static { + filter.add(TargetController.ENCHANTED.getControllerPredicate()); + } + + public CurseOfTheRestlessDead(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); + + this.subtype.add(SubType.AURA); + this.subtype.add(SubType.CURSE); + + // Enchant player + TargetPlayer auraTarget = new TargetPlayer(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Whenever a land enters the battlefield under enchanted player's control, you create a 2/2 black Zombie creature token with decayed. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new CreateTokenEffect(new ZombieDecayedToken()), + filter, "Whenever a land enters the battlefield under enchanted player's control, " + + "you create a 2/2 black Zombie creature token with decayed." + )); + } + + private CurseOfTheRestlessDead(final CurseOfTheRestlessDead card) { + super(card); + } + + @Override + public CurseOfTheRestlessDead copy() { + return new CurseOfTheRestlessDead(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CurseOfThirst.java b/Mage.Sets/src/mage/cards/c/CurseOfThirst.java index bffb0a1ea66..3ee3cc73375 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfThirst.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfThirst.java @@ -1,7 +1,7 @@ package mage.cards.c; import mage.abilities.Ability; -import mage.abilities.common.BeginningOfUpkeepAttachedTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.AttachEffect; @@ -12,6 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.TargetController; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; @@ -35,9 +36,10 @@ public final class CurseOfThirst extends CardImpl { this.addAbility(new EnchantAbility(auraTarget.getTargetName())); // At the beginning of enchanted player's upkeep, Curse of Thirst deals damage to that player equal to the number of Curses attached to them. - this.addAbility(new BeginningOfUpkeepAttachedTriggeredAbility( + this.addAbility(new BeginningOfUpkeepTriggeredAbility( new DamageTargetEffect(CursesAttachedCount.instance) - .setText("{this} deals damage to that player equal to the number of Curses attached to them") + .setText("{this} deals damage to that player equal to the number of Curses attached to them"), + TargetController.ENCHANTED, false )); } diff --git a/Mage.Sets/src/mage/cards/c/CurseOfUnbinding.java b/Mage.Sets/src/mage/cards/c/CurseOfUnbinding.java new file mode 100644 index 00000000000..2cc4c2494ae --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CurseOfUnbinding.java @@ -0,0 +1,92 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.*; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CurseOfUnbinding extends CardImpl { + + public CurseOfUnbinding(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{6}{U}"); + + this.subtype.add(SubType.AURA); + this.subtype.add(SubType.CURSE); + + // Enchant player + TargetPlayer auraTarget = new TargetPlayer(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // At the beginning of enchanted player's upkeep, that player reveals cards from the top of their library until they reveal a creature card. Put that card onto the battlefield under your control. That player puts the rest of the revealed cards into their graveyard. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new CurseOfConformityEffect(), TargetController.ENCHANTED, false + )); + } + + private CurseOfUnbinding(final CurseOfUnbinding card) { + super(card); + } + + @Override + public CurseOfUnbinding copy() { + return new CurseOfUnbinding(this); + } +} + +class CurseOfUnbindingEffect extends OneShotEffect { + + CurseOfUnbindingEffect() { + super(Outcome.Benefit); + staticText = "that player reveals cards from the top of their library " + + "until they reveal a creature card. Put that card onto the battlefield under your control. " + + "That player puts the rest of the revealed cards into their graveyard"; + } + + private CurseOfUnbindingEffect(final CurseOfUnbindingEffect effect) { + super(effect); + } + + @Override + public CurseOfUnbindingEffect copy() { + return new CurseOfUnbindingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(game.getActivePlayerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(); + Card toHand = null; + for (Card card : player.getLibrary().getCards(game)) { + cards.add(card); + if (card.isCreature(game)) { + toHand = card; + break; + } + } + player.revealCards(source, cards, game); + Player controller = game.getPlayer(source.getControllerId()); + if (toHand != null && controller != null) { + controller.moveCards(toHand, Zone.BATTLEFIELD, source, game); + } + cards.retainZone(Zone.LIBRARY, game); + player.moveCards(cards, Zone.GRAVEYARD, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DarigaazReincarnated.java b/Mage.Sets/src/mage/cards/d/DarigaazReincarnated.java index a4363062da4..5a7a0756ea5 100644 --- a/Mage.Sets/src/mage/cards/d/DarigaazReincarnated.java +++ b/Mage.Sets/src/mage/cards/d/DarigaazReincarnated.java @@ -1,7 +1,5 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -18,22 +16,18 @@ import mage.abilities.keyword.TrampleAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; 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.util.CardUtil; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class DarigaazReincarnated extends CardImpl { @@ -92,15 +86,11 @@ class DarigaazReincarnatedDiesEffect extends ReplacementEffectImpl { public boolean replaceEvent(GameEvent event, Ability source, Game game) { Permanent permanent = ((ZoneChangeEvent) event).getTarget(); Player controller = game.getPlayer(source.getControllerId()); - if (controller != null && permanent != null) { - Card permCard = game.getCard(permanent.getId()); - if (permCard == null) { - return false; - } - return controller.moveCardToExileWithInfo(permanent, null, null, source, game, Zone.BATTLEFIELD, true) - && permCard.addCounters(CounterType.EGG.createInstance(3), source.getControllerId(), source, game); + if (permanent == null || controller == null) { + return false; } - return false; + + return CardUtil.moveCardWithCounter(game, source, controller, permanent, Zone.EXILED, CounterType.EGG.createInstance(3)); } @Override @@ -125,7 +115,7 @@ class DarigaazReincarnatedInterveningIfTriggeredAbility extends ConditionalInter super(new BeginningOfUpkeepTriggeredAbility(Zone.EXILED, new DarigaazReincarnatedReturnEffect(), TargetController.YOU, false), DarigaazReincarnatedCondition.instance, "At the beginning of your upkeep, if {this} is exiled with an egg counter on it, " - + "remove an egg counter from it. Then if {this} has no egg counters on it, return it to the battlefield"); + + "remove an egg counter from it. Then if {this} has no egg counters on it, return it to the battlefield"); } public DarigaazReincarnatedInterveningIfTriggeredAbility(final DarigaazReincarnatedInterveningIfTriggeredAbility effect) { @@ -181,10 +171,8 @@ enum DarigaazReincarnatedCondition implements Condition { public boolean apply(Game game, Ability source) { Card card = game.getCard(source.getSourceId()); if (card != null) { - if (game.getState().getZone(card.getId()) == Zone.EXILED - && card.getCounters(game).getCount(CounterType.EGG) > 0) { - return true; - } + return game.getState().getZone(card.getId()) == Zone.EXILED + && card.getCounters(game).getCount(CounterType.EGG) > 0; } return false; } diff --git a/Mage.Sets/src/mage/cards/d/DauntlessAvenger.java b/Mage.Sets/src/mage/cards/d/DauntlessAvenger.java new file mode 100644 index 00000000000..d7004ce3498 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DauntlessAvenger.java @@ -0,0 +1,56 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DauntlessAvenger extends CardImpl { + + private static final FilterCard filter + = new FilterCreatureCard("creature card with mana value 2 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 3)); + } + + public DauntlessAvenger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + this.color.setWhite(true); + this.transformable = true; + this.nightCard = true; + + // Whenever Dauntless Avenger attacks, return target creature card with mana value 2 or less from your graveyard to the battlefield tapped and attacking. + Ability ability = new AttacksTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(true, true)); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + } + + private DauntlessAvenger(final DauntlessAvenger card) { + super(card); + } + + @Override + public DauntlessAvenger copy() { + return new DauntlessAvenger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DauthiVoidwalker.java b/Mage.Sets/src/mage/cards/d/DauthiVoidwalker.java index 45e6a97c655..9ab5d97c7af 100644 --- a/Mage.Sets/src/mage/cards/d/DauthiVoidwalker.java +++ b/Mage.Sets/src/mage/cards/d/DauthiVoidwalker.java @@ -22,6 +22,7 @@ import mage.game.events.ZoneChangeEvent; import mage.players.Player; import mage.target.common.TargetCardInExile; import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; import java.util.UUID; @@ -84,17 +85,12 @@ class DauthiVoidwalkerReplacementEffect extends ReplacementEffectImpl { if (card == null) { card = game.getCard(event.getTargetId()); } - if (controller == null - || card == null) { + + if (controller == null || card == null) { return false; } - controller.moveCards(card, Zone.EXILED, source, game); - // okay, not sure why this needs to be done to work correctly with creature permanents - Card cardFromObject = (Card) game.getObject(card.getId()); - if (cardFromObject != null) { - return cardFromObject.addCounters(CounterType.VOID.createInstance(), source.getControllerId(), source, game); - } - return false; + CardUtil.moveCardWithCounter(game, source, controller, card, Zone.EXILED, CounterType.VOID.createInstance()); + return true; } @Override diff --git a/Mage.Sets/src/mage/cards/d/DawnCharm.java b/Mage.Sets/src/mage/cards/d/DawnCharm.java index 25e194a01b2..76e15531189 100644 --- a/Mage.Sets/src/mage/cards/d/DawnCharm.java +++ b/Mage.Sets/src/mage/cards/d/DawnCharm.java @@ -11,8 +11,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.filter.FilterSpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.stack.StackObject; import mage.target.Target; @@ -59,10 +59,10 @@ public final class DawnCharm extends CardImpl { } } -class DawnCharmPredicate implements ObjectPlayerPredicate> { +class DawnCharmPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { UUID controllerId = input.getPlayerId(); if (controllerId == null) { return false; diff --git a/Mage.Sets/src/mage/cards/d/DawnhartMentor.java b/Mage.Sets/src/mage/cards/d/DawnhartMentor.java new file mode 100644 index 00000000000..6f3341802c6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DawnhartMentor.java @@ -0,0 +1,59 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.CovenCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.hint.common.CovenHint; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.permanent.token.HumanToken; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DawnhartMentor extends CardImpl { + + public DawnhartMentor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // When Dawnhart Mentor enters the battlefield, create a 1/1 white Human creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HumanToken()))); + + // Coven — {5}{G}: Target creature you control gets +3/+3 and gains trample until end of turn. Activate only if you control three or more creatures with different powers. + Ability ability = new ActivateIfConditionActivatedAbility( + Zone.BATTLEFIELD, new BoostTargetEffect(3, 3) + .setText("target creature you control gets +3/+3"), + new ManaCostsImpl<>("{5}{G}"), CovenCondition.instance + ); + ability.addEffect(new GainAbilityTargetEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains trample until end of turn")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability.addHint(CovenHint.instance).setAbilityWord(AbilityWord.COVEN)); + } + + private DawnhartMentor(final DawnhartMentor card) { + super(card); + } + + @Override + public DawnhartMentor copy() { + return new DawnhartMentor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DeadlyAllure.java b/Mage.Sets/src/mage/cards/d/DeadlyAllure.java index 3beea7c54c2..a7550779d91 100644 --- a/Mage.Sets/src/mage/cards/d/DeadlyAllure.java +++ b/Mage.Sets/src/mage/cards/d/DeadlyAllure.java @@ -33,7 +33,7 @@ public final class DeadlyAllure extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{G}"))); } diff --git a/Mage.Sets/src/mage/cards/d/DeathbonnetHulk.java b/Mage.Sets/src/mage/cards/d/DeathbonnetHulk.java new file mode 100644 index 00000000000..99b151931c9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeathbonnetHulk.java @@ -0,0 +1,93 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +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.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DeathbonnetHulk extends CardImpl { + + public DeathbonnetHulk(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.FUNGUS); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + this.color.setGreen(true); + this.transformable = true; + this.nightCard = true; + + // At the beginning of your upkeep, you may exile a card from a graveyard. If a creature card was exiled this way, put a +1/+1 counter on Deathbonnet Hulk. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new DeathbonnetHulkEffect(), TargetController.YOU, false + )); + } + + private DeathbonnetHulk(final DeathbonnetHulk card) { + super(card); + } + + @Override + public DeathbonnetHulk copy() { + return new DeathbonnetHulk(this); + } +} + +class DeathbonnetHulkEffect extends OneShotEffect { + + DeathbonnetHulkEffect() { + super(Outcome.Benefit); + staticText = "you may exile a card from a graveyard. " + + "If a creature card was exiled this way, put a +1/+1 counter on {this}"; + } + + private DeathbonnetHulkEffect(final DeathbonnetHulkEffect effect) { + super(effect); + } + + @Override + public DeathbonnetHulkEffect copy() { + return new DeathbonnetHulkEffect(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.setNotTarget(true); + player.choose(outcome, target, source.getSourceId(), 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 permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent != null) { + permanent.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DeathbonnetSprout.java b/Mage.Sets/src/mage/cards/d/DeathbonnetSprout.java new file mode 100644 index 00000000000..1dca8bc1abc --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeathbonnetSprout.java @@ -0,0 +1,62 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.TransformAbility; +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 DeathbonnetSprout extends CardImpl { + + private static final Condition condition + = new CardsInControllerGraveyardCondition(3, StaticFilters.FILTER_CARD_CREATURE); + private static final Hint hint = new ValueHint( + "Creature cards in your graveyard", + new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE) + ); + + public DeathbonnetSprout(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.FUNGUS); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + this.transformable = true; + this.secondSideCardClazz = mage.cards.d.DeathbonnetHulk.class; + + // At the beginning of your upkeep, mill a card. Then if there are three or more creature cards in your graveyard, transform Deathbonnet Sprout. + this.addAbility(new TransformAbility()); + Ability ability = new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(1)); + ability.addEffect(new ConditionalOneShotEffect( + new TransformSourceEffect(true), condition, + "Then if there are three or more creature cards in your graveyard, transform {this}" + )); + this.addAbility(ability.addHint(hint)); + } + + private DeathbonnetSprout(final DeathbonnetSprout card) { + super(card); + } + + @Override + public DeathbonnetSprout copy() { + return new DeathbonnetSprout(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DeepAnalysis.java b/Mage.Sets/src/mage/cards/d/DeepAnalysis.java index 8b9341d4ace..b9b51f2bd22 100644 --- a/Mage.Sets/src/mage/cards/d/DeepAnalysis.java +++ b/Mage.Sets/src/mage/cards/d/DeepAnalysis.java @@ -26,7 +26,7 @@ public final class DeepAnalysis extends CardImpl { this.getSpellAbility().addTarget(new TargetPlayer()); // Flashback-{1}{U}, Pay 3 life. - FlashbackAbility ability = new FlashbackAbility(new ManaCostsImpl("{1}{U}"), TimingRule.SORCERY); + FlashbackAbility ability = new FlashbackAbility(this, new ManaCostsImpl("{1}{U}")); ability.addCost(new PayLifeCost(3)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DeepReconnaissance.java b/Mage.Sets/src/mage/cards/d/DeepReconnaissance.java index 651eed5e88f..14c9c7c6ce5 100644 --- a/Mage.Sets/src/mage/cards/d/DeepReconnaissance.java +++ b/Mage.Sets/src/mage/cards/d/DeepReconnaissance.java @@ -26,7 +26,7 @@ public final class DeepReconnaissance extends CardImpl { // Search your library for a basic land card and put that card onto the battlefield tapped. Then shuffle your library. this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true, true)); // Flashback {4}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{G}"))); } private DeepReconnaissance(final DeepReconnaissance card) { diff --git a/Mage.Sets/src/mage/cards/d/DefyGravity.java b/Mage.Sets/src/mage/cards/d/DefyGravity.java index 468aa3f8293..7c94505a3d0 100644 --- a/Mage.Sets/src/mage/cards/d/DefyGravity.java +++ b/Mage.Sets/src/mage/cards/d/DefyGravity.java @@ -26,7 +26,7 @@ public final class DefyGravity extends CardImpl { this.getSpellAbility().addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{U}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{U}"))); } private DefyGravity(final DefyGravity card) { diff --git a/Mage.Sets/src/mage/cards/d/Dematerialize.java b/Mage.Sets/src/mage/cards/d/Dematerialize.java index 289d9aacf16..8a42a17c84c 100644 --- a/Mage.Sets/src/mage/cards/d/Dematerialize.java +++ b/Mage.Sets/src/mage/cards/d/Dematerialize.java @@ -25,7 +25,7 @@ public final class Dematerialize extends CardImpl { this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); this.getSpellAbility().addTarget(new TargetPermanent()); // Flashback {5}{U}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{U}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{U}{U}"))); } private Dematerialize(final Dematerialize card) { diff --git a/Mage.Sets/src/mage/cards/d/DennickPiousApparition.java b/Mage.Sets/src/mage/cards/d/DennickPiousApparition.java new file mode 100644 index 00000000000..ec7c305155d --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DennickPiousApparition.java @@ -0,0 +1,53 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.PutCardIntoGraveFromAnywhereAllTriggeredAbility; +import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; +import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; + +import java.util.UUID; + +/** + * @author LePwnerer + */ +public final class DennickPiousApparition extends CardImpl { + + public DennickPiousApparition(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + this.color.setWhite(true); + this.color.setBlue(true); + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever one or more creature cards are put into graveyards from anywhere, investigate. This ability triggers only once each turn. + this.addAbility(new PutCardIntoGraveFromAnywhereAllTriggeredAbility(new InvestigateEffect(1), false, TargetController.ANY).setTriggersOnce(true)); + + // If Dennick, Pious Apparition would be put into a graveyard from anywhere, exile it instead. + this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + } + + private DennickPiousApparition(final DennickPiousApparition card) { + super(card); + } + + @Override + public DennickPiousApparition copy() { + return new DennickPiousApparition(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DennickPiousApprentice.java b/Mage.Sets/src/mage/cards/d/DennickPiousApprentice.java new file mode 100644 index 00000000000..20107974ca3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DennickPiousApprentice.java @@ -0,0 +1,55 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CantBeTargetedCardsGraveyardsEffect; +import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author LePwnerer + */ +public final class DennickPiousApprentice extends CardImpl { + + public DennickPiousApprentice(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + this.transformable = true; + this.secondSideCardClazz = mage.cards.d.DennickPiousApparition.class; + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Cards in graveyards can't be the targets of spells or abilities. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantBeTargetedCardsGraveyardsEffect())); + + // Disturb {2}{W}{U} + this.addAbility(new TransformAbility()); + this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{2}{W}{U}"))); + + } + + private DennickPiousApprentice(final DennickPiousApprentice card) { + super(card); + } + + @Override + public DennickPiousApprentice copy() { + return new DennickPiousApprentice(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DepartedSoulkeeper.java b/Mage.Sets/src/mage/cards/d/DepartedSoulkeeper.java new file mode 100644 index 00000000000..91e19c92f0f --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DepartedSoulkeeper.java @@ -0,0 +1,49 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.CanBlockOnlyFlyingAbility; +import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; +import mage.abilities.effects.common.ExileSourceEffect; +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 DepartedSoulkeeper extends CardImpl { + + public DepartedSoulkeeper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + this.color.setWhite(true); + this.color.setBlue(true); + this.transformable = true; + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Departed Soulkeeper can block only creatures with flying. + this.addAbility(new CanBlockOnlyFlyingAbility()); + + // If Departed Soulkeeper would be put into a graveyard from anywhere, exile it instead. + this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + } + + private DepartedSoulkeeper(final DepartedSoulkeeper card) { + super(card); + } + + @Override + public DepartedSoulkeeper copy() { + return new DepartedSoulkeeper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DesperateRavings.java b/Mage.Sets/src/mage/cards/d/DesperateRavings.java index b36ff25a4d9..868562aad8c 100644 --- a/Mage.Sets/src/mage/cards/d/DesperateRavings.java +++ b/Mage.Sets/src/mage/cards/d/DesperateRavings.java @@ -29,7 +29,7 @@ public final class DesperateRavings extends CardImpl { // Draw two cards, then discard a card at random. this.getSpellAbility().addEffect(new DesperateRavingsEffect()); // Flashback {2}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{U}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{U}"))); } private DesperateRavings(final DesperateRavings card) { diff --git a/Mage.Sets/src/mage/cards/d/DevilsPlay.java b/Mage.Sets/src/mage/cards/d/DevilsPlay.java index a6b959ccbd9..15b3150a291 100644 --- a/Mage.Sets/src/mage/cards/d/DevilsPlay.java +++ b/Mage.Sets/src/mage/cards/d/DevilsPlay.java @@ -26,7 +26,7 @@ public final class DevilsPlay extends CardImpl { this.getSpellAbility().addEffect(new DamageTargetEffect(ManacostVariableValue.REGULAR)); this.getSpellAbility().addTarget(new TargetAnyTarget()); // Flashback {X}{R}{R}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{X}{R}{R}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{X}{R}{R}{R}"))); } private DevilsPlay(final DevilsPlay card) { diff --git a/Mage.Sets/src/mage/cards/d/DevotedGrafkeeper.java b/Mage.Sets/src/mage/cards/d/DevotedGrafkeeper.java new file mode 100644 index 00000000000..667d86340c4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DevotedGrafkeeper.java @@ -0,0 +1,89 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.TransformAbility; +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.game.events.GameEvent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DevotedGrafkeeper extends CardImpl { + + public DevotedGrafkeeper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + this.transformable = true; + this.secondSideCardClazz = mage.cards.d.DepartedSoulkeeper.class; + + // When Devoted Grafkeeper enters the battlefield, mill two cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(2))); + + // Whenever you cast a spell from your graveyard, tap target creature you don't control. + this.addAbility(new DevotedGrafkeeperTriggeredAbility()); + + // Disturb {1}{W}{U} + this.addAbility(new TransformAbility()); + this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{1}{W}{U}"))); + } + + private DevotedGrafkeeper(final DevotedGrafkeeper card) { + super(card); + } + + @Override + public DevotedGrafkeeper copy() { + return new DevotedGrafkeeper(this); + } +} + +class DevotedGrafkeeperTriggeredAbility extends TriggeredAbilityImpl { + + DevotedGrafkeeperTriggeredAbility() { + super(Zone.BATTLEFIELD, new TapTargetEffect(), false); + this.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); + } + + private DevotedGrafkeeperTriggeredAbility(DevotedGrafkeeperTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return isControlledBy(event.getPlayerId()) && event.getZone() == Zone.GRAVEYARD; + } + + @Override + public DevotedGrafkeeperTriggeredAbility copy() { + return new DevotedGrafkeeperTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever you cast a spell from your graveyard, tap target creature you don't control."; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DevoutHarpist.java b/Mage.Sets/src/mage/cards/d/DevoutHarpist.java index 20a3a427220..0f6c9a2ca2d 100644 --- a/Mage.Sets/src/mage/cards/d/DevoutHarpist.java +++ b/Mage.Sets/src/mage/cards/d/DevoutHarpist.java @@ -13,8 +13,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterEnchantmentPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; @@ -56,9 +56,9 @@ public final class DevoutHarpist extends CardImpl { } -class DevoutHarpistPredicate implements ObjectPlayerPredicate> { +class DevoutHarpistPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent attachment = input.getObject(); if (attachment != null) { Permanent permanent = game.getPermanent(attachment.getAttachedTo()); diff --git a/Mage.Sets/src/mage/cards/d/DiamondKnight.java b/Mage.Sets/src/mage/cards/d/DiamondKnight.java index 406854098e5..d1e089376ba 100644 --- a/Mage.Sets/src/mage/cards/d/DiamondKnight.java +++ b/Mage.Sets/src/mage/cards/d/DiamondKnight.java @@ -59,7 +59,7 @@ public final class DiamondKnight extends CardImpl { } } -enum DiamondKnightPredicate implements ObjectSourcePlayerPredicate> { +enum DiamondKnightPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/d/DireStrainBrawler.java b/Mage.Sets/src/mage/cards/d/DireStrainBrawler.java index f6d61205137..c7116d20e7f 100644 --- a/Mage.Sets/src/mage/cards/d/DireStrainBrawler.java +++ b/Mage.Sets/src/mage/cards/d/DireStrainBrawler.java @@ -29,7 +29,7 @@ public final class DireStrainBrawler extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private DireStrainBrawler(final DireStrainBrawler card) { diff --git a/Mage.Sets/src/mage/cards/d/DireStrainDemolisher.java b/Mage.Sets/src/mage/cards/d/DireStrainDemolisher.java index 40ee7cbd748..51fcdcc7077 100644 --- a/Mage.Sets/src/mage/cards/d/DireStrainDemolisher.java +++ b/Mage.Sets/src/mage/cards/d/DireStrainDemolisher.java @@ -30,7 +30,7 @@ public final class DireStrainDemolisher extends CardImpl { this.addAbility(new WardAbility(new ManaCostsImpl<>("{3}"))); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private DireStrainDemolisher(final DireStrainDemolisher card) { diff --git a/Mage.Sets/src/mage/cards/d/DireStrainRampage.java b/Mage.Sets/src/mage/cards/d/DireStrainRampage.java index 5b4e90df050..1b9f3feaeab 100644 --- a/Mage.Sets/src/mage/cards/d/DireStrainRampage.java +++ b/Mage.Sets/src/mage/cards/d/DireStrainRampage.java @@ -50,7 +50,7 @@ public final class DireStrainRampage extends CardImpl { this.getSpellAbility().addTarget(new TargetPermanent(filter)); // Flashback {3}{R}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{R}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{R}{G}"))); } private DireStrainRampage(final DireStrainRampage card) { diff --git a/Mage.Sets/src/mage/cards/d/DiregrafRebirth.java b/Mage.Sets/src/mage/cards/d/DiregrafRebirth.java index 3634044f72d..6a7d1186acf 100644 --- a/Mage.Sets/src/mage/cards/d/DiregrafRebirth.java +++ b/Mage.Sets/src/mage/cards/d/DiregrafRebirth.java @@ -37,7 +37,7 @@ public final class DiregrafRebirth extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); // Flashback {5}{B}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{5}{B}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{5}{B}{G}"))); } private DiregrafRebirth(final DiregrafRebirth card) { diff --git a/Mage.Sets/src/mage/cards/d/DiseasedVermin.java b/Mage.Sets/src/mage/cards/d/DiseasedVermin.java index efc31859241..ad0957bafaf 100644 --- a/Mage.Sets/src/mage/cards/d/DiseasedVermin.java +++ b/Mage.Sets/src/mage/cards/d/DiseasedVermin.java @@ -114,7 +114,7 @@ class DiseasedVerminEffect extends OneShotEffect { } } -class DiseasedVerminPredicate implements ObjectSourcePlayerPredicate> { +class DiseasedVerminPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/d/DivineReckoning.java b/Mage.Sets/src/mage/cards/d/DivineReckoning.java index bf59ff23b4c..946a25e9803 100644 --- a/Mage.Sets/src/mage/cards/d/DivineReckoning.java +++ b/Mage.Sets/src/mage/cards/d/DivineReckoning.java @@ -35,7 +35,7 @@ public final class DivineReckoning extends CardImpl { this.getSpellAbility().addEffect(new DivineReckoningEffect()); // Flashback {5}{W}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{W}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{W}{W}"))); } private DivineReckoning(final DivineReckoning card) { diff --git a/Mage.Sets/src/mage/cards/d/DjinnOfWishes.java b/Mage.Sets/src/mage/cards/d/DjinnOfWishes.java index b51e75ef4d6..3269631dec2 100644 --- a/Mage.Sets/src/mage/cards/d/DjinnOfWishes.java +++ b/Mage.Sets/src/mage/cards/d/DjinnOfWishes.java @@ -78,7 +78,7 @@ class DjinnOfWishesEffect extends OneShotEffect { Cards cards = new CardsImpl(card); controller.revealCards(sourceObject.getIdName(), cards, game); if (!controller.chooseUse(Outcome.PlayForFree, "Play " + card.getName() + " without paying its mana cost?", source, game) - || !controller.playCard(card, game, true, true, new ApprovingObject(source, game))) { + || !controller.playCard(card, game, true, new ApprovingObject(source, game))) { controller.moveCards(card, Zone.EXILED, source, game); } return true; diff --git a/Mage.Sets/src/mage/cards/d/DralnuLichLord.java b/Mage.Sets/src/mage/cards/d/DralnuLichLord.java index 1470d9d36d8..fc97de46680 100644 --- a/Mage.Sets/src/mage/cards/d/DralnuLichLord.java +++ b/Mage.Sets/src/mage/cards/d/DralnuLichLord.java @@ -1,7 +1,5 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -23,21 +21,24 @@ import mage.game.events.DamageEvent; import mage.game.events.GameEvent; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author emerald000 */ public final class DralnuLichLord 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())); + CardType.SORCERY.getPredicate() + )); } public DralnuLichLord(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{U}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{B}"); addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.ZOMBIE); this.subtype.add(SubType.WIZARD); @@ -46,10 +47,10 @@ public final class DralnuLichLord extends CardImpl { this.toughness = new MageInt(3); // If damage would be dealt to Dralnu, Lich Lord, sacrifice that many permanents instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DralnuLichLordReplacementEffect())); - + this.addAbility(new SimpleStaticAbility(new DralnuLichLordReplacementEffect())); + // {tap}: 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 SimpleActivatedAbility(Zone.BATTLEFIELD, new DralnuLichLordFlashbackEffect(), new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(new DralnuLichLordFlashbackEffect(), new TapSourceCost()); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } @@ -85,7 +86,7 @@ class DralnuLichLordReplacementEffect extends ReplacementEffectImpl { public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.DAMAGE_PERMANENT; } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { return event.getTargetId().equals(source.getSourceId()); @@ -117,13 +118,7 @@ class DralnuLichLordFlashbackEffect extends ContinuousEffectImpl { public boolean apply(Game game, Ability source) { Card card = game.getCard(targetPointer.getFirst(game, source)); if (card != null) { - FlashbackAbility ability; - if (card.isInstant(game)) { - ability = new FlashbackAbility(card.getManaCost(), TimingRule.INSTANT); - } - else { - ability = new FlashbackAbility(card.getManaCost(), TimingRule.SORCERY); - } + FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); ability.setSourceId(card.getId()); ability.setControllerId(card.getOwnerId()); game.getState().addOtherAbility(card, ability); diff --git a/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java b/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java index 3e8d08a85f7..b664c9e230c 100644 --- a/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java +++ b/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java @@ -17,6 +17,7 @@ import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import mage.players.ManaPoolItem; import mage.players.Player; +import mage.util.CardUtil; import java.util.UUID; @@ -79,10 +80,8 @@ class DraugrNecromancerReplacementEffect extends ReplacementEffectImpl { || !controller.hasOpponent(permanent.getControllerId(), game)) { return false; } - Card card = game.getCard(permanent.getId()); - controller.moveCards(permanent, Zone.EXILED, source, game); - card.getMainCard().addCounters(CounterType.ICE.createInstance(), source.getControllerId(), source, game); - return true; + + return CardUtil.moveCardWithCounter(game, source, controller, permanent, Zone.EXILED, CounterType.ICE.createInstance()); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DreadReturn.java b/Mage.Sets/src/mage/cards/d/DreadReturn.java index f68946f58a0..76d8a35e5cd 100644 --- a/Mage.Sets/src/mage/cards/d/DreadReturn.java +++ b/Mage.Sets/src/mage/cards/d/DreadReturn.java @@ -30,7 +30,7 @@ public final class DreadReturn extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); // Flashback-Sacrifice three creatures. - this.addAbility(new FlashbackAbility(new SacrificeTargetCost(new TargetControlledPermanent(3, filter)), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new SacrificeTargetCost(new TargetControlledPermanent(3, filter)))); } private DreadReturn(final DreadReturn card) { diff --git a/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java b/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java index 8ba2508b57d..58c6deb298f 100644 --- a/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java +++ b/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java @@ -69,7 +69,7 @@ public final class DreadhordeArcanist extends CardImpl { } } -enum DreadhordeArcanistPredicate implements ObjectSourcePlayerPredicate> { +enum DreadhordeArcanistPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/d/DreamTwist.java b/Mage.Sets/src/mage/cards/d/DreamTwist.java index 96eca41bc01..5aadffe592c 100644 --- a/Mage.Sets/src/mage/cards/d/DreamTwist.java +++ b/Mage.Sets/src/mage/cards/d/DreamTwist.java @@ -25,7 +25,7 @@ public final class DreamTwist extends CardImpl { this.getSpellAbility().addEffect(new PutLibraryIntoGraveTargetEffect(3)); // Flashback {1}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{U}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{U}"))); } private DreamTwist(final DreamTwist card) { diff --git a/Mage.Sets/src/mage/cards/d/DrownInDreams.java b/Mage.Sets/src/mage/cards/d/DrownInDreams.java new file mode 100644 index 00000000000..3d716d0eba5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DrownInDreams.java @@ -0,0 +1,51 @@ +package mage.cards.d; + +import mage.abilities.Mode; +import mage.abilities.condition.common.ControlACommanderCondition; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.MultipliedValue; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DrownInDreams extends CardImpl { + + private static final DynamicValue xValue = new MultipliedValue(ManacostVariableValue.REGULAR, 2); + + public DrownInDreams(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{2}{U}"); + + // Choose one. If you control a commander as you cast this spell, you may choose both. + this.getSpellAbility().getModes().setChooseText( + "Choose one. If you control a commander as you cast this spell, you may choose both." + ); + this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); + + // • Target player draws X cards. + this.getSpellAbility().addEffect(new DrawCardTargetEffect(ManacostVariableValue.REGULAR)); + this.getSpellAbility().addTarget(new TargetPlayer()); + + // • Target player mills twice X cards. + Mode mode = new Mode(new MillCardsTargetEffect(xValue).setText("target player mills twice X cards")); + mode.addTarget(new TargetPlayer()); + this.getSpellAbility().addMode(mode); + } + + private DrownInDreams(final DrownInDreams card) { + super(card); + } + + @Override + public DrownInDreams copy() { + return new DrownInDreams(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DryadsRevival.java b/Mage.Sets/src/mage/cards/d/DryadsRevival.java index e49405fc4b2..cdecd89d77c 100644 --- a/Mage.Sets/src/mage/cards/d/DryadsRevival.java +++ b/Mage.Sets/src/mage/cards/d/DryadsRevival.java @@ -24,7 +24,7 @@ public final class DryadsRevival extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInYourGraveyard()); // Flashback {4}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{4}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{4}{G}"))); } private DryadsRevival(final DryadsRevival card) { diff --git a/Mage.Sets/src/mage/cards/d/DuelcraftTrainer.java b/Mage.Sets/src/mage/cards/d/DuelcraftTrainer.java new file mode 100644 index 00000000000..4e145682161 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DuelcraftTrainer.java @@ -0,0 +1,55 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.condition.common.CovenCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.hint.common.CovenHint; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DuelcraftTrainer extends CardImpl { + + public DuelcraftTrainer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, target creature you control gains double strike until end of turn. + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new BeginningOfCombatTriggeredAbility(new GainAbilityTargetEffect( + DoubleStrikeAbility.getInstance(), Duration.EndOfTurn + ), TargetController.YOU, false), CovenCondition.instance, "At the beginning " + + "of combat on your turn, if you control three or more creatures with different powers, " + + "target creature you control gains double strike until end of turn." + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability.addHint(CovenHint.instance).setAbilityWord(AbilityWord.COVEN)); + } + + private DuelcraftTrainer(final DuelcraftTrainer card) { + super(card); + } + + @Override + public DuelcraftTrainer copy() { + return new DuelcraftTrainer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EarthRift.java b/Mage.Sets/src/mage/cards/e/EarthRift.java index 3a8b6ff66b6..812dab060f3 100644 --- a/Mage.Sets/src/mage/cards/e/EarthRift.java +++ b/Mage.Sets/src/mage/cards/e/EarthRift.java @@ -25,7 +25,7 @@ public final class EarthRift extends CardImpl { this.getSpellAbility().addEffect(new DestroyTargetEffect()); this.getSpellAbility().addTarget(new TargetLandPermanent()); // Flashback {5}{R}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{R}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{R}{R}"))); } private EarthRift(final EarthRift card) { diff --git a/Mage.Sets/src/mage/cards/e/EarthshakerKhenra.java b/Mage.Sets/src/mage/cards/e/EarthshakerKhenra.java index 126accce1b5..41343176fdb 100644 --- a/Mage.Sets/src/mage/cards/e/EarthshakerKhenra.java +++ b/Mage.Sets/src/mage/cards/e/EarthshakerKhenra.java @@ -68,7 +68,7 @@ public final class EarthshakerKhenra extends CardImpl { } } -enum EarthshakerKhenraPredicate implements ObjectSourcePlayerPredicate> { +enum EarthshakerKhenraPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/e/EccentricFarmer.java b/Mage.Sets/src/mage/cards/e/EccentricFarmer.java new file mode 100644 index 00000000000..018ce65be2b --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EccentricFarmer.java @@ -0,0 +1,81 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EccentricFarmer extends CardImpl { + + public EccentricFarmer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When Eccentric Farmer enters the battlefield, mill three cards, then you may return a land card from your graveyard to your hand. + this.addAbility(new EntersBattlefieldTriggeredAbility(new EccentricFarmerEffect())); + } + + private EccentricFarmer(final EccentricFarmer card) { + super(card); + } + + @Override + public EccentricFarmer copy() { + return new EccentricFarmer(this); + } +} + +class EccentricFarmerEffect extends OneShotEffect { + + EccentricFarmerEffect() { + super(Outcome.Benefit); + staticText = "mill three cards, then you may return a land card from your graveyard to your hand"; + } + + private EccentricFarmerEffect(final EccentricFarmerEffect effect) { + super(effect); + } + + @Override + public EccentricFarmerEffect copy() { + return new EccentricFarmerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.millCards(3, source, game); + if (player.getGraveyard().count(StaticFilters.FILTER_CARD_LAND, game) < 1) { + return true; + } + TargetCard target = new TargetCardInYourGraveyard( + 0, 1, StaticFilters.FILTER_CARD_LAND, true + ); + player.choose(outcome, target, source.getSourceId(), game); + player.moveCards(game.getCard(target.getFirstTarget()), Zone.HAND, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EchoOfEons.java b/Mage.Sets/src/mage/cards/e/EchoOfEons.java index c831c4b65f4..742b095f6e8 100644 --- a/Mage.Sets/src/mage/cards/e/EchoOfEons.java +++ b/Mage.Sets/src/mage/cards/e/EchoOfEons.java @@ -27,7 +27,7 @@ public final class EchoOfEons extends CardImpl { this.getSpellAbility().addEffect(effect); // Flashback {2}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{U}"))); } private EchoOfEons(final EchoOfEons card) { diff --git a/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java index 51900dabbac..422f92a1d28 100644 --- a/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java +++ b/Mage.Sets/src/mage/cards/e/EcologicalAppreciation.java @@ -1,6 +1,5 @@ package mage.cards.e; -import com.google.common.collect.Sets; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -22,6 +21,7 @@ import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetOpponent; import mage.util.CardUtil; +import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -109,7 +109,10 @@ class EcologicalAppreciationEffect extends OneShotEffect { Set disallowedCards = this.getTargets().stream() .map(game::getCard) .collect(Collectors.toSet()); - return isValidTarget(card, Sets.union(disallowedCards, cards.getCards(game))); + Set checkList = new HashSet<>(); + checkList.addAll(disallowedCards); + checkList.addAll(cards.getCards(game)); + return isValidTarget(card, checkList); } }; targetCardsInGY.setNotTarget(true); diff --git a/Mage.Sets/src/mage/cards/e/EcstaticAwakener.java b/Mage.Sets/src/mage/cards/e/EcstaticAwakener.java new file mode 100644 index 00000000000..50f4541680b --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EcstaticAwakener.java @@ -0,0 +1,54 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +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.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EcstaticAwakener extends CardImpl { + + public EcstaticAwakener(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + this.transformable = true; + this.secondSideCardClazz = mage.cards.a.AwokenDemon.class; + + // {2}{B}, Sacrifice another creature: Draw a card, then transform Ecstatic Awakener. Activate only once each turn. + this.addAbility(new TransformAbility()); + Ability ability = new LimitedTimesPerTurnActivatedAbility( + Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{2}{B}") + ); + ability.addEffect(new TransformSourceEffect(true).concatBy(", then")); + ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE))); + this.addAbility(ability); + } + + private EcstaticAwakener(final EcstaticAwakener card) { + super(card); + } + + @Override + public EcstaticAwakener copy() { + return new EcstaticAwakener(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElectricRevelation.java b/Mage.Sets/src/mage/cards/e/ElectricRevelation.java index 363f68c5968..7396bf67331 100644 --- a/Mage.Sets/src/mage/cards/e/ElectricRevelation.java +++ b/Mage.Sets/src/mage/cards/e/ElectricRevelation.java @@ -26,7 +26,7 @@ public final class ElectricRevelation extends CardImpl { this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); // Flashback {3}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{R}"))); } private ElectricRevelation(final ElectricRevelation card) { diff --git a/Mage.Sets/src/mage/cards/e/ElephantAmbush.java b/Mage.Sets/src/mage/cards/e/ElephantAmbush.java index 454ea235572..493271104f8 100644 --- a/Mage.Sets/src/mage/cards/e/ElephantAmbush.java +++ b/Mage.Sets/src/mage/cards/e/ElephantAmbush.java @@ -24,7 +24,7 @@ public final class ElephantAmbush extends CardImpl { // Create a 3/3 green Elephant creature token. this.getSpellAbility().addEffect(new CreateTokenEffect(new ElephantToken())); // Flashback {6}{G}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{6}{G}{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{6}{G}{G}"))); } private ElephantAmbush(final ElephantAmbush card) { diff --git a/Mage.Sets/src/mage/cards/e/EliteHeadhunter.java b/Mage.Sets/src/mage/cards/e/EliteHeadhunter.java index d06d5be2602..c28c80af80d 100644 --- a/Mage.Sets/src/mage/cards/e/EliteHeadhunter.java +++ b/Mage.Sets/src/mage/cards/e/EliteHeadhunter.java @@ -63,7 +63,7 @@ public final class EliteHeadhunter extends CardImpl { } } -enum EliteHeadhunterPredicate implements ObjectSourcePlayerPredicate> { +enum EliteHeadhunterPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/e/EloiseNephaliaSleuth.java b/Mage.Sets/src/mage/cards/e/EloiseNephaliaSleuth.java new file mode 100644 index 00000000000..f5f8f03f758 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EloiseNephaliaSleuth.java @@ -0,0 +1,57 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.effects.keyword.SurveilEffect; +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.permanent.TokenPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EloiseNephaliaSleuth extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another creature you control"); + private static final FilterPermanent filter2 = new FilterPermanent("a token"); + + static { + filter.add(AnotherPredicate.instance); + filter2.add(TokenPredicate.TRUE); + } + + public EloiseNephaliaSleuth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Whenever another creature you control dies, investigate. + this.addAbility(new DiesCreatureTriggeredAbility(new InvestigateEffect(1), false, filter)); + + // Whenever you sacrifice a token, surveil 1. + this.addAbility(new SacrificePermanentTriggeredAbility(new SurveilEffect(1), filter2)); + } + + private EloiseNephaliaSleuth(final EloiseNephaliaSleuth card) { + super(card); + } + + @Override + public EloiseNephaliaSleuth copy() { + return new EloiseNephaliaSleuth(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/Embolden.java b/Mage.Sets/src/mage/cards/e/Embolden.java index bc28d573110..ab93ab68df4 100644 --- a/Mage.Sets/src/mage/cards/e/Embolden.java +++ b/Mage.Sets/src/mage/cards/e/Embolden.java @@ -26,7 +26,7 @@ public final class Embolden extends CardImpl { this.getSpellAbility().addTarget(new TargetAnyTargetAmount(4)); // Flashback {1}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{W}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{W}"))); } diff --git a/Mage.Sets/src/mage/cards/e/EmptyTheLaboratory.java b/Mage.Sets/src/mage/cards/e/EmptyTheLaboratory.java new file mode 100644 index 00000000000..74e0868a828 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EmptyTheLaboratory.java @@ -0,0 +1,110 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +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.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterCreatureCard; +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 EmptyTheLaboratory extends CardImpl { + + public EmptyTheLaboratory(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{U}{U}"); + + // Sacrifice X Zombies, then reveal cards from the top of your library until you reveal a number of Zombie creature cards equal to the number of Zombies sacrificed this way. Put those cards onto the battlefield and the rest on the bottom of your library in a random order. + this.getSpellAbility().addEffect(new EmptyTheLaboratoryEffect()); + } + + private EmptyTheLaboratory(final EmptyTheLaboratory card) { + super(card); + } + + @Override + public EmptyTheLaboratory copy() { + return new EmptyTheLaboratory(this); + } +} + +class EmptyTheLaboratoryEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ZOMBIE, "Zombies"); + private static final FilterCard filter2 = new FilterCreatureCard(); + + static { + filter2.add(SubType.ZOMBIE.getPredicate()); + } + + EmptyTheLaboratoryEffect() { + super(Outcome.Benefit); + staticText = "sacrifice X Zombies, then reveal cards from the top of your library until you reveal " + + "a number of Zombie creature cards equal to the number of Zombies sacrificed this way. " + + "Put those cards onto the battlefield and the rest on the bottom of your library in a random order"; + } + + private EmptyTheLaboratoryEffect(final EmptyTheLaboratoryEffect effect) { + super(effect); + } + + @Override + public EmptyTheLaboratoryEffect copy() { + return new EmptyTheLaboratoryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int toSacrifice = Math.min( + source.getManaCostsToPay().getX(), + game.getBattlefield().count( + filter, source.getSourceId(), source.getControllerId(), game + ) + ); + if (toSacrifice < 1) { + return false; + } + TargetPermanent target = new TargetPermanent(toSacrifice, filter); + target.setNotTarget(true); + player.choose(Outcome.Sacrifice, target, source.getSourceId(), game); + int sacrificed = 0; + for (UUID permanentId : target.getTargets()) { + Permanent permanent = game.getPermanent(permanentId); + if (permanent != null && permanent.sacrifice(source, game)) { + sacrificed++; + } + } + Cards toReveal = new CardsImpl(); + int zombies = 0; + for (Card card : player.getLibrary().getCards(game)) { + toReveal.add(card); + if (card.isCreature(game) && card.hasSubtype(SubType.ZOMBIE, game)) { + zombies++; + } + if (zombies >= sacrificed) { + break; + } + } + player.revealCards(source, toReveal, game); + player.moveCards(toReveal.getCards(filter2, game), Zone.BATTLEFIELD, source, game); + toReveal.retainZone(Zone.LIBRARY, game); + player.putCardsOnBottomOfLibrary(toReveal, game, source, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EmrakulThePromisedEnd.java b/Mage.Sets/src/mage/cards/e/EmrakulThePromisedEnd.java index f7f99c4ab4b..3c23183b322 100644 --- a/Mage.Sets/src/mage/cards/e/EmrakulThePromisedEnd.java +++ b/Mage.Sets/src/mage/cards/e/EmrakulThePromisedEnd.java @@ -1,18 +1,16 @@ - package mage.cards.e; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CastSourceTriggeredAbility; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.common.CardTypesInGraveyardHint; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ProtectionAbility; import mage.abilities.keyword.TrampleAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -21,10 +19,7 @@ import mage.game.Game; import mage.game.turn.TurnMod; import mage.players.Player; import mage.target.common.TargetOpponent; -import mage.util.CardUtil; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -46,12 +41,15 @@ public final class EmrakulThePromisedEnd extends CardImpl { this.toughness = new MageInt(13); // Emrakul, the Promised End costs {1} less to cast for each card type among cards in your graveyard. - Ability ability = new SimpleStaticAbility(Zone.ALL, new EmrakulThePromisedEndCostReductionEffect()); - ability.setRuleAtTheTop(true); - this.addAbility(ability.addHint(CardTypesInGraveyardHint.YOU)); + this.addAbility(new SimpleStaticAbility( + Zone.ALL, + new SpellCostReductionForEachSourceEffect( + 1, CardTypesInGraveyardCount.YOU + ).setText("this spell costs {1} less to cast for each card type among cards in your graveyard") + ).setRuleAtTheTop(true).addHint(CardTypesInGraveyardHint.YOU)); // When you cast Emrakul, you gain control of target opponent during that player's next turn. After that turn, that player takes an extra turn. - ability = new CastSourceTriggeredAbility(new EmrakulThePromisedEndGainControlEffect()); + Ability ability = new CastSourceTriggeredAbility(new EmrakulThePromisedEndGainControlEffect()); ability.addTarget(new TargetOpponent()); this.addAbility(ability); @@ -75,44 +73,6 @@ public final class EmrakulThePromisedEnd extends CardImpl { } } -class EmrakulThePromisedEndCostReductionEffect extends CostModificationEffectImpl { - - EmrakulThePromisedEndCostReductionEffect() { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "this spell costs {1} less to cast for each card type among cards in your graveyard"; - } - - EmrakulThePromisedEndCostReductionEffect(EmrakulThePromisedEndCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Set foundCardTypes = new HashSet<>(8); - for (Card card : controller.getGraveyard().getCards(game)) { - foundCardTypes.addAll(card.getCardType(game)); - } - CardUtil.reduceCost(abilityToModify, foundCardTypes.size()); - return true; - } - return false; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return abilityToModify instanceof SpellAbility - && abilityToModify.getSourceId().equals(source.getSourceId()) - && game.getCard(abilityToModify.getSourceId()) != null; - } - - @Override - public EmrakulThePromisedEndCostReductionEffect copy() { - return new EmrakulThePromisedEndCostReductionEffect(this); - } -} - class EmrakulThePromisedEndGainControlEffect extends OneShotEffect { EmrakulThePromisedEndGainControlEffect() { @@ -120,7 +80,7 @@ class EmrakulThePromisedEndGainControlEffect extends OneShotEffect { this.staticText = "you gain control of target opponent during that player's next turn. After that turn, that player takes an extra turn"; } - EmrakulThePromisedEndGainControlEffect(final EmrakulThePromisedEndGainControlEffect effect) { + private EmrakulThePromisedEndGainControlEffect(final EmrakulThePromisedEndGainControlEffect effect) { super(effect); } diff --git a/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java b/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java index 2b65428decd..1af558de01c 100644 --- a/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java +++ b/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java @@ -62,7 +62,7 @@ public final class EnchantmentAlteration extends CardImpl { } -class SharesEnchantedCardTypePredicate implements ObjectSourcePlayerPredicate> { +class SharesEnchantedCardTypePredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/e/EngulfingFlames.java b/Mage.Sets/src/mage/cards/e/EngulfingFlames.java index f837f06ad3d..7f81b63295d 100644 --- a/Mage.Sets/src/mage/cards/e/EngulfingFlames.java +++ b/Mage.Sets/src/mage/cards/e/EngulfingFlames.java @@ -28,7 +28,7 @@ public final class EngulfingFlames extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addEffect(new CantRegenerateTargetEffect(Duration.EndOfTurn, "It")); // Flashback {3}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{R}"))); } private EngulfingFlames(final EngulfingFlames card) { diff --git a/Mage.Sets/src/mage/cards/e/Epochrasite.java b/Mage.Sets/src/mage/cards/e/Epochrasite.java index a6122b684ee..6c6b15ee5b1 100644 --- a/Mage.Sets/src/mage/cards/e/Epochrasite.java +++ b/Mage.Sets/src/mage/cards/e/Epochrasite.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; @@ -17,22 +15,23 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.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; + /** - * * @author LevelX2 */ public final class Epochrasite extends CardImpl { public Epochrasite(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},"{2}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); this.subtype.add(SubType.CONSTRUCT); this.power = new MageInt(1); @@ -40,9 +39,9 @@ public final class Epochrasite extends CardImpl { // Epochrasite enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand. this.addAbility(new EntersBattlefieldAbility( - new AddCountersSourceEffect(CounterType.P1P1.createInstance(3)), - new InvertCondition(CastFromHandSourcePermanentCondition.instance), - "{this} enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand",""), + new AddCountersSourceEffect(CounterType.P1P1.createInstance(3)), + new InvertCondition(CastFromHandSourcePermanentCondition.instance), + "{this} enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand", ""), new CastFromHandWatcher()); // When Epochrasite dies, exile it with three time counters on it and it gains suspend. @@ -79,15 +78,20 @@ class EpochrasiteEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Card card = game.getCard(source.getSourceId()); - if (controller != null && card != null) { - if (game.getState().getZone(card.getId()) == Zone.GRAVEYARD) { - 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; + if (controller == null || card == null) { + return false; } - 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/EtherealValkyrie.java b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java index 9a9f83918b7..1da51c68698 100644 --- a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java +++ b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java @@ -177,6 +177,7 @@ class EtherealValkyrieEffect extends OneShotEffect { foretellAbility.activate(game, true); ContinuousEffect effect = foretellAbility.new ForetellAddCostEffect(new MageObjectReference(exileCard, game)); game.addEffect(effect, source); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.FORETOLD, exileCard.getId(), null, null)); return true; } } diff --git a/Mage.Sets/src/mage/cards/e/EvilTwin.java b/Mage.Sets/src/mage/cards/e/EvilTwin.java index 047e18248d8..c2f531b9f34 100644 --- a/Mage.Sets/src/mage/cards/e/EvilTwin.java +++ b/Mage.Sets/src/mage/cards/e/EvilTwin.java @@ -75,7 +75,7 @@ class EvilTwinCopyApplier extends CopyApplier { } -class EvilTwinPredicate implements ObjectSourcePlayerPredicate> { +class EvilTwinPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/e/EyeOfTheStorm.java b/Mage.Sets/src/mage/cards/e/EyeOfTheStorm.java index b38d643aa17..36273b7f0c7 100644 --- a/Mage.Sets/src/mage/cards/e/EyeOfTheStorm.java +++ b/Mage.Sets/src/mage/cards/e/EyeOfTheStorm.java @@ -154,7 +154,9 @@ class EyeOfTheStormEffect1 extends OneShotEffect { if (cardToCopy != null) { Card copy = game.copyCard(cardToCopy, source, source.getControllerId()); if (spellController.chooseUse(outcome, "Cast " + copy.getIdName() + " without paying mana cost?", source, game)) { - spellController.cast(copy.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + copy.getId(), Boolean.TRUE); + spellController.cast(spellController.chooseAbilityForCast(copy, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + copy.getId(), null); } } } diff --git a/Mage.Sets/src/mage/cards/f/FaithfulMending.java b/Mage.Sets/src/mage/cards/f/FaithfulMending.java index 7025bd611c7..ee079d3841e 100644 --- a/Mage.Sets/src/mage/cards/f/FaithfulMending.java +++ b/Mage.Sets/src/mage/cards/f/FaithfulMending.java @@ -26,7 +26,7 @@ public final class FaithfulMending extends CardImpl { this.getSpellAbility().addEffect(new DiscardControllerEffect(2).concatBy(", then")); // Flashback {1}{W}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{1}{W}{U}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{W}{U}"))); } private FaithfulMending(final FaithfulMending card) { diff --git a/Mage.Sets/src/mage/cards/f/FaithlessLooting.java b/Mage.Sets/src/mage/cards/f/FaithlessLooting.java index 754a3b292a8..c2c63fd555c 100644 --- a/Mage.Sets/src/mage/cards/f/FaithlessLooting.java +++ b/Mage.Sets/src/mage/cards/f/FaithlessLooting.java @@ -23,7 +23,7 @@ public final class FaithlessLooting extends CardImpl { // Draw two cards, then discard two cards. this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(2,2)); // Flashback {2}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{R}"))); } private FaithlessLooting(final FaithlessLooting card) { diff --git a/Mage.Sets/src/mage/cards/f/FalkenrathPitFighter.java b/Mage.Sets/src/mage/cards/f/FalkenrathPitFighter.java index 8309aefdfc5..48587739a13 100644 --- a/Mage.Sets/src/mage/cards/f/FalkenrathPitFighter.java +++ b/Mage.Sets/src/mage/cards/f/FalkenrathPitFighter.java @@ -24,7 +24,7 @@ import java.util.UUID; */ public final class FalkenrathPitFighter extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.VAMPIRE); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.VAMPIRE, "Vampire"); public FalkenrathPitFighter(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); diff --git a/Mage.Sets/src/mage/cards/f/FalseOrders.java b/Mage.Sets/src/mage/cards/f/FalseOrders.java index ba4101ff11d..8b4b1cf5bed 100644 --- a/Mage.Sets/src/mage/cards/f/FalseOrders.java +++ b/Mage.Sets/src/mage/cards/f/FalseOrders.java @@ -13,8 +13,8 @@ import mage.constants.Outcome; import mage.constants.PhaseStep; import mage.filter.FilterPermanent; import mage.filter.common.FilterAttackingCreature; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.permanent.PermanentInListPredicate; import mage.game.Controllable; import mage.game.Game; @@ -64,11 +64,11 @@ public final class FalseOrders extends CardImpl { } -enum FalseOrdersDefendingPlayerControlsPredicate implements ObjectPlayerPredicate> { +enum FalseOrdersDefendingPlayerControlsPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { return game.getCombat().getPlayerDefenders(game).contains(input.getObject().getControllerId()); } } diff --git a/Mage.Sets/src/mage/cards/f/FangbladeBrigand.java b/Mage.Sets/src/mage/cards/f/FangbladeBrigand.java new file mode 100644 index 00000000000..c8bab4a3313 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FangbladeBrigand.java @@ -0,0 +1,57 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.DayboundAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FangbladeBrigand extends CardImpl { + + public FangbladeBrigand(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WEREWOLF); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + this.transformable = true; + this.secondSideCardClazz = mage.cards.f.FangbladeEviscerator.class; + + // {1}{R}: Fangblade Brigand gets +1/+0 and gains first strike until end of turn. + Ability ability = new SimpleActivatedAbility(new BoostSourceEffect( + 1, 0, Duration.EndOfTurn + ).setText("{this} gets +1/+0"), new ManaCostsImpl<>("{1}{R}")); + ability.addEffect(new GainAbilitySourceEffect( + FirstStrikeAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains first strike until end of turn")); + this.addAbility(ability); + + // Daybound + this.addAbility(new TransformAbility()); + this.addAbility(new DayboundAbility()); + } + + private FangbladeBrigand(final FangbladeBrigand card) { + super(card); + } + + @Override + public FangbladeBrigand copy() { + return new FangbladeBrigand(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FangbladeEviscerator.java b/Mage.Sets/src/mage/cards/f/FangbladeEviscerator.java new file mode 100644 index 00000000000..f3a9ba0926a --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FangbladeEviscerator.java @@ -0,0 +1,61 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.NightboundAbility; +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 FangbladeEviscerator extends CardImpl { + + public FangbladeEviscerator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.WEREWOLF); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + this.color.setRed(true); + this.transformable = true; + this.nightCard = true; + + // {1}{R}: Fangblade Eviscerator gets +1/+0 and gains first strike until end of turn. + Ability ability = new SimpleActivatedAbility(new BoostSourceEffect( + 1, 0, Duration.EndOfTurn + ).setText("{this} gets +1/+0"), new ManaCostsImpl<>("{1}{R}")); + ability.addEffect(new GainAbilitySourceEffect( + FirstStrikeAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains first strike until end of turn")); + this.addAbility(ability); + + // {4}{R}: Creatures you control get +2/+0 until end of turn. + this.addAbility(new SimpleActivatedAbility(new BoostControlledEffect( + 2, 0, Duration.EndOfTurn + ), new ManaCostsImpl<>("{4}{R}"))); + + // Nightbound + this.addAbility(new NightboundAbility()); + } + + private FangbladeEviscerator(final FangbladeEviscerator card) { + super(card); + } + + @Override + public FangbladeEviscerator copy() { + return new FangbladeEviscerator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FeelingOfDread.java b/Mage.Sets/src/mage/cards/f/FeelingOfDread.java index 71933458621..a47b8bf95b5 100644 --- a/Mage.Sets/src/mage/cards/f/FeelingOfDread.java +++ b/Mage.Sets/src/mage/cards/f/FeelingOfDread.java @@ -26,7 +26,7 @@ public final class FeelingOfDread extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2)); // Flashback {1}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{U}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{U}"))); } private FeelingOfDread(final FeelingOfDread card) { diff --git a/Mage.Sets/src/mage/cards/f/FerventDenial.java b/Mage.Sets/src/mage/cards/f/FerventDenial.java index 7c20d75425d..d7c4fc8f218 100644 --- a/Mage.Sets/src/mage/cards/f/FerventDenial.java +++ b/Mage.Sets/src/mage/cards/f/FerventDenial.java @@ -25,7 +25,7 @@ public final class FerventDenial extends CardImpl { this.getSpellAbility().addEffect(new CounterTargetEffect()); this.getSpellAbility().addTarget(new TargetSpell()); // Flashback {5}{U}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{U}{U}"),TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{U}{U}"))); } private FerventDenial(final FerventDenial card) { diff --git a/Mage.Sets/src/mage/cards/f/FireAndBrimstone.java b/Mage.Sets/src/mage/cards/f/FireAndBrimstone.java index 6dc7fe7f3d7..f7258a53ac3 100644 --- a/Mage.Sets/src/mage/cards/f/FireAndBrimstone.java +++ b/Mage.Sets/src/mage/cards/f/FireAndBrimstone.java @@ -45,7 +45,7 @@ public final class FireAndBrimstone extends CardImpl { } } -enum FireAndBrimstonePredicate implements ObjectSourcePlayerPredicate> { +enum FireAndBrimstonePredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/f/Firebolt.java b/Mage.Sets/src/mage/cards/f/Firebolt.java index 13c0f663759..a012d1cd264 100644 --- a/Mage.Sets/src/mage/cards/f/Firebolt.java +++ b/Mage.Sets/src/mage/cards/f/Firebolt.java @@ -25,7 +25,7 @@ public final class Firebolt extends CardImpl { this.getSpellAbility().addEffect(new DamageTargetEffect(2)); this.getSpellAbility().addTarget(new TargetAnyTarget()); // Flashback {4}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{R}"))); } private Firebolt(final Firebolt card) { diff --git a/Mage.Sets/src/mage/cards/f/FirecatBlitz.java b/Mage.Sets/src/mage/cards/f/FirecatBlitz.java index ea733f26ed0..c6527f4225f 100644 --- a/Mage.Sets/src/mage/cards/f/FirecatBlitz.java +++ b/Mage.Sets/src/mage/cards/f/FirecatBlitz.java @@ -41,7 +41,7 @@ public final class FirecatBlitz extends CardImpl { this.getSpellAbility().addEffect(new FirecatBlitzEffect()); // Flashback-{R}{R}, Sacrifice X Mountains. - Ability ability = new FlashbackAbility(new SacrificeXTargetCost(filter), TimingRule.SORCERY); + Ability ability = new FlashbackAbility(this, new SacrificeXTargetCost(filter)); ability.addManaCost(new ManaCostsImpl("{R}{R}")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/f/FiresOfInvention.java b/Mage.Sets/src/mage/cards/f/FiresOfInvention.java index 2383d18bfcb..7cc79d35271 100644 --- a/Mage.Sets/src/mage/cards/f/FiresOfInvention.java +++ b/Mage.Sets/src/mage/cards/f/FiresOfInvention.java @@ -52,7 +52,7 @@ public final class FiresOfInvention extends CardImpl { } } -enum FiresOfInventionPredicate implements ObjectSourcePlayerPredicate> { +enum FiresOfInventionPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/f/FiresOfUndeath.java b/Mage.Sets/src/mage/cards/f/FiresOfUndeath.java index 987c9253440..7d8ceb162ab 100644 --- a/Mage.Sets/src/mage/cards/f/FiresOfUndeath.java +++ b/Mage.Sets/src/mage/cards/f/FiresOfUndeath.java @@ -25,7 +25,7 @@ public final class FiresOfUndeath extends CardImpl { this.getSpellAbility().addTarget(new TargetAnyTarget()); this.getSpellAbility().addEffect(new DamageTargetEffect(2)); // Flashback {5}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{B}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{B}"))); } private FiresOfUndeath(final FiresOfUndeath card) { diff --git a/Mage.Sets/src/mage/cards/f/FirjasRetribution.java b/Mage.Sets/src/mage/cards/f/FirjasRetribution.java index 56f0adbfbfb..d4ea16d5d2f 100644 --- a/Mage.Sets/src/mage/cards/f/FirjasRetribution.java +++ b/Mage.Sets/src/mage/cards/f/FirjasRetribution.java @@ -75,7 +75,7 @@ public final class FirjasRetribution extends CardImpl { } } -enum FirjasRetributionPredicate implements ObjectSourcePlayerPredicate> { +enum FirjasRetributionPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/f/FlameSweep.java b/Mage.Sets/src/mage/cards/f/FlameSweep.java index 6b27948005b..801cfed2139 100644 --- a/Mage.Sets/src/mage/cards/f/FlameSweep.java +++ b/Mage.Sets/src/mage/cards/f/FlameSweep.java @@ -7,8 +7,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -43,11 +43,11 @@ public final class FlameSweep extends CardImpl { } } -enum FlameSweepPredicate implements ObjectPlayerPredicate> { +enum FlameSweepPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent object = input.getObject(); UUID playerId = input.getPlayerId(); return !(object.isControlledBy(playerId) diff --git a/Mage.Sets/src/mage/cards/f/Flameshot.java b/Mage.Sets/src/mage/cards/f/Flameshot.java index 15fde82d4ad..d82386829b1 100644 --- a/Mage.Sets/src/mage/cards/f/Flameshot.java +++ b/Mage.Sets/src/mage/cards/f/Flameshot.java @@ -32,7 +32,7 @@ public final class Flameshot extends CardImpl { this.addAbility(new AlternativeCostSourceAbility(new DiscardTargetCost(new TargetCardInHand(filter)))); // Flameshot deals 3 damage divided as you choose among one, two, or three target creatures. - this.getSpellAbility().addEffect(new DamageMultiEffect(3)); + this.getSpellAbility().addEffect(new DamageMultiEffect(3).setText("{this} deals 3 damage divided as you choose among one, two, or three target creatures")); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(3)); } diff --git a/Mage.Sets/src/mage/cards/f/FlaringPain.java b/Mage.Sets/src/mage/cards/f/FlaringPain.java index dc37fbd2593..4c80330baa0 100644 --- a/Mage.Sets/src/mage/cards/f/FlaringPain.java +++ b/Mage.Sets/src/mage/cards/f/FlaringPain.java @@ -24,7 +24,7 @@ public final class FlaringPain extends CardImpl { // Damage can't be prevented this turn. this.getSpellAbility().addEffect(new DamageCantBePreventedEffect(Duration.EndOfTurn, "Damage can't be prevented this turn", false, false)); // Flashback {R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{R}"))); } private FlaringPain(final FlaringPain card) { diff --git a/Mage.Sets/src/mage/cards/f/FlashOfDefiance.java b/Mage.Sets/src/mage/cards/f/FlashOfDefiance.java index b63c718e0e9..8635f90a1dc 100644 --- a/Mage.Sets/src/mage/cards/f/FlashOfDefiance.java +++ b/Mage.Sets/src/mage/cards/f/FlashOfDefiance.java @@ -38,7 +38,7 @@ public final class FlashOfDefiance extends CardImpl { this.getSpellAbility().addEffect(new CantBlockAllEffect(filter, Duration.EndOfTurn)); // Flashback-{1}{R}, Pay 3 life. - Ability ability = new FlashbackAbility(new ManaCostsImpl("{1}{R}"), TimingRule.SORCERY); + Ability ability = new FlashbackAbility(this, new ManaCostsImpl("{1}{R}")); ability.addCost(new PayLifeCost(3)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/f/FlashOfInsight.java b/Mage.Sets/src/mage/cards/f/FlashOfInsight.java index 6e1a3c210ab..c0eee07e074 100644 --- a/Mage.Sets/src/mage/cards/f/FlashOfInsight.java +++ b/Mage.Sets/src/mage/cards/f/FlashOfInsight.java @@ -37,7 +37,7 @@ public final class FlashOfInsight extends CardImpl { this.getSpellAbility().addEffect(new FlashOfInsightEffect()); // Flashback-{1}{U}, Exile X blue cards from your graveyard. - Ability ability = new FlashbackAbility(new ManaCostsImpl("{1}{U}"), TimingRule.INSTANT); + Ability ability = new FlashbackAbility(this, new ManaCostsImpl("{1}{U}")); FilterCard filter = new FilterCard("blue cards from your graveyard"); filter.add(new ColorPredicate(ObjectColor.BLUE)); filter.add(Predicates.not(new CardIdPredicate(getId()))); diff --git a/Mage.Sets/src/mage/cards/f/FlorianVoldarenScion.java b/Mage.Sets/src/mage/cards/f/FlorianVoldarenScion.java new file mode 100644 index 00000000000..e9c9c7355cd --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlorianVoldarenScion.java @@ -0,0 +1,102 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfPostCombatMainTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.cards.*; +import mage.constants.*; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.watchers.common.PlayerLostLifeWatcher; + +/** + * + * @author weirddan455 + */ +public final class FlorianVoldarenScion extends CardImpl { + + public FlorianVoldarenScion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // At the beginning of your postcombat main phase, look at the top X cards of your library, where X is the total amount of life your opponents lost this turn. + // Exile one of those cards and put the rest on the bottom of your library in a random order. You may play the exiled card this turn. + this.addAbility(new BeginningOfPostCombatMainTriggeredAbility(new FlorianVoldarenScionEffect(), TargetController.YOU, false)); + } + + private FlorianVoldarenScion(final FlorianVoldarenScion card) { + super(card); + } + + @Override + public FlorianVoldarenScion copy() { + return new FlorianVoldarenScion(this); + } +} + +class FlorianVoldarenScionEffect extends OneShotEffect { + + public FlorianVoldarenScionEffect() { + super(Outcome.Benefit); + staticText = "look at the top X cards of your library, where X is the total amount of life your opponents lost this turn. " + + "Exile one of those cards and put the rest on the bottom of your library in a random order. You may play the exiled card this turn"; + } + + private FlorianVoldarenScionEffect(final FlorianVoldarenScionEffect effect) { + super(effect); + } + + @Override + public FlorianVoldarenScionEffect copy() { + return new FlorianVoldarenScionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + PlayerLostLifeWatcher watcher = game.getState().getWatcher(PlayerLostLifeWatcher.class); + if (controller != null && watcher != null) { + int lifeLost = watcher.getAllOppLifeLost(controller.getId(), game); + if (lifeLost > 0) { + Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, lifeLost)); + int numCards = cards.size(); + if (numCards > 0) { + controller.lookAtCards(source, null, cards, game); + Card selectedCard; + if (numCards == 1) { + selectedCard = game.getCard(cards.iterator().next()); + } else { + TargetCard target = new TargetCard(Zone.LIBRARY, StaticFilters.FILTER_CARD); + controller.chooseTarget(outcome, cards, target, source, game); + selectedCard = game.getCard(target.getFirstTarget()); + } + if (selectedCard != null) { + cards.remove(selectedCard); + PlayFromNotOwnHandZoneTargetEffect.exileAndPlayFromExile( + game, source, selectedCard, TargetController.YOU, Duration.EndOfTurn, false, false, false + ); + } + if (!cards.isEmpty()) { + controller.putCardsOnBottomOfLibrary(cards, game, source, false); + } + return true; + } + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FolkMedicine.java b/Mage.Sets/src/mage/cards/f/FolkMedicine.java index 84634147aa0..aead1d97917 100644 --- a/Mage.Sets/src/mage/cards/f/FolkMedicine.java +++ b/Mage.Sets/src/mage/cards/f/FolkMedicine.java @@ -25,7 +25,7 @@ public final class FolkMedicine extends CardImpl { DynamicValue amount = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_CREATURE); this.getSpellAbility().addEffect(new GainLifeEffect(amount)); // Flashback {1}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{W}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{W}"))); } private FolkMedicine(final FolkMedicine card) { diff --git a/Mage.Sets/src/mage/cards/f/ForbiddenAlchemy.java b/Mage.Sets/src/mage/cards/f/ForbiddenAlchemy.java index 0c7335a50e4..cdc2e92ae55 100644 --- a/Mage.Sets/src/mage/cards/f/ForbiddenAlchemy.java +++ b/Mage.Sets/src/mage/cards/f/ForbiddenAlchemy.java @@ -26,7 +26,7 @@ public final class ForbiddenAlchemy extends CardImpl { StaticFilters.FILTER_CARD, Zone.GRAVEYARD, false, false, false, Zone.HAND, false)); // Flashback {6}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{6}{B}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{6}{B}"))); } private ForbiddenAlchemy(final ForbiddenAlchemy card) { diff --git a/Mage.Sets/src/mage/cards/f/FoulPlay.java b/Mage.Sets/src/mage/cards/f/FoulPlay.java index e13f2ec8e10..cceb6ba97c5 100644 --- a/Mage.Sets/src/mage/cards/f/FoulPlay.java +++ b/Mage.Sets/src/mage/cards/f/FoulPlay.java @@ -21,7 +21,7 @@ public final class FoulPlay extends CardImpl { private static final FilterPermanent filter = new FilterCreaturePermanent("creature with power 2 or less"); static { - filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 2)); + filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 3)); } public FoulPlay(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/f/FrenziedTrapbreaker.java b/Mage.Sets/src/mage/cards/f/FrenziedTrapbreaker.java new file mode 100644 index 00000000000..3eaf2dae55e --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrenziedTrapbreaker.java @@ -0,0 +1,66 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +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.NightboundAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterArtifactOrEnchantmentPermanent; +import mage.filter.predicate.permanent.DefendingPlayerControlsPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FrenziedTrapbreaker extends CardImpl { + + private static final FilterPermanent filter + = new FilterArtifactOrEnchantmentPermanent("artifact or enchantment defending player controls"); + + static { + filter.add(DefendingPlayerControlsPredicate.instance); + } + + public FrenziedTrapbreaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.WEREWOLF); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + this.color.setGreen(true); + this.transformable = true; + this.nightCard = true; + + // {1}, Sacrifice Frenzied Trapbreaker: Destroy target artifact or enchantment. + Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new GenericManaCost(1)); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + + // Whenever Frenzied Trapbreaker attacks, destroy target artifact or enchantment defending player controls. + ability = new AttacksTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // Nightbound + this.addAbility(new NightboundAbility()); + } + + private FrenziedTrapbreaker(final FrenziedTrapbreaker card) { + super(card); + } + + @Override + public FrenziedTrapbreaker copy() { + return new FrenziedTrapbreaker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FrostpyreArcanist.java b/Mage.Sets/src/mage/cards/f/FrostpyreArcanist.java index 7d636885db1..e6b0102c6aa 100644 --- a/Mage.Sets/src/mage/cards/f/FrostpyreArcanist.java +++ b/Mage.Sets/src/mage/cards/f/FrostpyreArcanist.java @@ -20,8 +20,8 @@ import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterInstantOrSorceryCard; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.players.Player; @@ -79,11 +79,11 @@ public final class FrostpyreArcanist extends CardImpl { } } -enum FrostpyreArcanistPredicate implements ObjectPlayerPredicate> { +enum FrostpyreArcanistPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Player player = game.getPlayer(input.getPlayerId()); if (player == null || player.getGraveyard().isEmpty()) { return false; diff --git a/Mage.Sets/src/mage/cards/g/Galedrifter.java b/Mage.Sets/src/mage/cards/g/Galedrifter.java new file mode 100644 index 00000000000..1ef6a7f13da --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/Galedrifter.java @@ -0,0 +1,45 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TransformAbility; +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 Galedrifter extends CardImpl { + + public Galedrifter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.HIPPOGRIFF); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + this.transformable = true; + this.secondSideCardClazz = mage.cards.w.Waildrifter.class; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Disturb {4}{U} + this.addAbility(new TransformAbility()); + this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{4}{U}"))); + } + + private Galedrifter(final Galedrifter card) { + super(card); + } + + @Override + public Galedrifter copy() { + return new Galedrifter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GalvanicIteration.java b/Mage.Sets/src/mage/cards/g/GalvanicIteration.java index f2756748012..892dbebc041 100644 --- a/Mage.Sets/src/mage/cards/g/GalvanicIteration.java +++ b/Mage.Sets/src/mage/cards/g/GalvanicIteration.java @@ -29,7 +29,7 @@ public final class GalvanicIteration extends CardImpl { this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new GalvanicIterationAbility())); // Flashback {1}{U}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{1}{U}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{U}{R}"))); } private GalvanicIteration(final GalvanicIteration card) { diff --git a/Mage.Sets/src/mage/cards/g/GazeOfJustice.java b/Mage.Sets/src/mage/cards/g/GazeOfJustice.java index d7b02929bdf..12647541ddf 100644 --- a/Mage.Sets/src/mage/cards/g/GazeOfJustice.java +++ b/Mage.Sets/src/mage/cards/g/GazeOfJustice.java @@ -41,7 +41,7 @@ public final class GazeOfJustice extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {5}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{W}"))); } private GazeOfJustice(final GazeOfJustice card) { diff --git a/Mage.Sets/src/mage/cards/g/Geistflame.java b/Mage.Sets/src/mage/cards/g/Geistflame.java index e783c6ba0a3..7d8c774cd65 100644 --- a/Mage.Sets/src/mage/cards/g/Geistflame.java +++ b/Mage.Sets/src/mage/cards/g/Geistflame.java @@ -25,7 +25,7 @@ public final class Geistflame extends CardImpl { this.getSpellAbility().addTarget(new TargetAnyTarget()); // Flashback {3}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{R}"))); } private Geistflame(final Geistflame card) { diff --git a/Mage.Sets/src/mage/cards/g/Geistwave.java b/Mage.Sets/src/mage/cards/g/Geistwave.java new file mode 100644 index 00000000000..b21a901bc79 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/Geistwave.java @@ -0,0 +1,71 @@ +package mage.cards.g; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Geistwave extends CardImpl { + + public Geistwave(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Return target nonland permanent to its owner's hand. If you controlled that permanent, draw a card. + this.getSpellAbility().addEffect(new GeistwaveEffect()); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + } + + private Geistwave(final Geistwave card) { + super(card); + } + + @Override + public Geistwave copy() { + return new Geistwave(this); + } +} + +class GeistwaveEffect extends OneShotEffect { + + GeistwaveEffect() { + super(Outcome.Benefit); + staticText = "return target nonland permanent to its owner's hand. " + + "If you controlled that permanent, draw a card"; + } + + private GeistwaveEffect(final GeistwaveEffect effect) { + super(effect); + } + + @Override + public GeistwaveEffect copy() { + return new GeistwaveEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getControllerId()); + if (player == null || permanent == null) { + return false; + } + boolean flag = permanent.isControlledBy(source.getControllerId()); + player.moveCards(permanent, Zone.HAND, source, game); + if (flag) { + player.drawCards(1, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GhostlyCastigator.java b/Mage.Sets/src/mage/cards/g/GhostlyCastigator.java new file mode 100644 index 00000000000..35833d52144 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GhostlyCastigator.java @@ -0,0 +1,53 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; +import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.effects.common.ShuffleIntoLibraryTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GhostlyCastigator extends CardImpl { + + public GhostlyCastigator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + this.color.setBlue(true); + this.transformable = true; + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Ghostly Castigator enters the battlefield, shuffle up to three target cards from your graveyard into your library. + Ability ability = new EntersBattlefieldTriggeredAbility(new ShuffleIntoLibraryTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(0, 3)); + this.addAbility(ability); + + // If Ghostly Castigator would be put into a graveyard from anywhere, exile it instead. + this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + } + + private GhostlyCastigator(final GhostlyCastigator card) { + super(card); + } + + @Override + public GhostlyCastigator copy() { + return new GhostlyCastigator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GhoulcallersHarvest.java b/Mage.Sets/src/mage/cards/g/GhoulcallersHarvest.java index f33a3f72459..e4173903f24 100644 --- a/Mage.Sets/src/mage/cards/g/GhoulcallersHarvest.java +++ b/Mage.Sets/src/mage/cards/g/GhoulcallersHarvest.java @@ -39,7 +39,7 @@ public final class GhoulcallersHarvest extends CardImpl { this.getSpellAbility().addHint(hint); // Flashback {3}{B}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{B}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{B}{G}"))); } private GhoulcallersHarvest(final GhoulcallersHarvest card) { diff --git a/Mage.Sets/src/mage/cards/g/GhoulishProcession.java b/Mage.Sets/src/mage/cards/g/GhoulishProcession.java new file mode 100644 index 00000000000..f52c54c7461 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GhoulishProcession.java @@ -0,0 +1,42 @@ +package mage.cards.g; + +import java.util.UUID; + +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.ZombieDecayedToken; + +/** + * + * @author weirddan455 + */ +public final class GhoulishProcession extends CardImpl { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("one or more nontoken creatures"); + + static { + filter.add(TokenPredicate.FALSE); + } + + public GhoulishProcession(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); + + // Whenever one or more nontoken creatures die, create a 2/2 black Zombie creature token with decayed. This ability triggers only once each turn. + this.addAbility(new DiesCreatureTriggeredAbility(new CreateTokenEffect(new ZombieDecayedToken()), false, filter).setTriggersOnce(true)); + } + + private GhoulishProcession(final GhoulishProcession card) { + super(card); + } + + @Override + public GhoulishProcession copy() { + return new GhoulishProcession(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GhoulsNightOut.java b/Mage.Sets/src/mage/cards/g/GhoulsNightOut.java new file mode 100644 index 00000000000..86f1ba5d8ef --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GhoulsNightOut.java @@ -0,0 +1,153 @@ +package mage.cards.g; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.DecayedAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.targetpointer.FixedTargets; + +/** + * + * @author weirddan455 + */ +public final class GhoulsNightOut extends CardImpl { + + public GhoulsNightOut(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{B}"); + + // For each player, choose a creature card in that player's graveyard. Put those cards onto the battlefield under your control. They're black Zombies in addition to their other colors and types and they gain decayed. + this.getSpellAbility().addEffect(new GhoulsNightOutEffect()); + } + + private GhoulsNightOut(final GhoulsNightOut card) { + super(card); + } + + @Override + public GhoulsNightOut copy() { + return new GhoulsNightOut(this); + } +} + +class GhoulsNightOutEffect extends OneShotEffect { + + public GhoulsNightOutEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "For each player, choose a creature card in that player's graveyard. Put those cards onto the battlefield under your control. They're black Zombies in addition to their other colors and types and they gain decayed"; + } + + private GhoulsNightOutEffect(final GhoulsNightOutEffect effect) { + super(effect); + } + + @Override + public GhoulsNightOutEffect copy() { + return new GhoulsNightOutEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + UUID controllerId = source.getControllerId(); + Player controller = game.getPlayer(controllerId); + if (controller != null) { + Set cardsToBattlefield = new HashSet<>(); + for (UUID playerId : game.getState().getPlayersInRange(controllerId, game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + boolean creatureInGraveyard = false; + for (UUID cardId : player.getGraveyard()) { + Card card = game.getCard(cardId); + if (card != null && card.isCreature(game)) { + creatureInGraveyard = true; + break; + } + } + if (creatureInGraveyard) { + FilterCreatureCard filter = new FilterCreatureCard("creature card in " + player.getName() + "'s graveyard"); + TargetCard target = new TargetCard(Zone.GRAVEYARD, filter); + target.setNotTarget(true); + controller.chooseTarget(controllerId.equals(playerId) ? Outcome.Benefit : Outcome.Detriment, player.getGraveyard(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + cardsToBattlefield.add(card); + } + } + } + } + if (!cardsToBattlefield.isEmpty()) { + controller.moveCards(cardsToBattlefield, Zone.BATTLEFIELD, source, game); + cardsToBattlefield.removeIf(card -> game.getState().getZone(card.getId()) != Zone.BATTLEFIELD); + if (!cardsToBattlefield.isEmpty()) { + game.addEffect(new GhoulsNightOutTypeChangingEffect( + ).setTargetPointer(new FixedTargets(cardsToBattlefield, game)), source); + game.addEffect(new GainAbilityTargetEffect( + new DecayedAbility(), Duration.Custom + ).setTargetPointer(new FixedTargets(cardsToBattlefield, game)), source); + return true; + } + } + } + return false; + } +} + +class GhoulsNightOutTypeChangingEffect extends ContinuousEffectImpl { + + public GhoulsNightOutTypeChangingEffect() { + super(Duration.Custom, Outcome.Neutral); + } + + private GhoulsNightOutTypeChangingEffect(final GhoulsNightOutTypeChangingEffect effect) { + super(effect); + } + + @Override + public GhoulsNightOutTypeChangingEffect copy() { + return new GhoulsNightOutTypeChangingEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer subLayer, Ability source, Game game) { + boolean isActive = false; + for (UUID permId : targetPointer.getTargets(game, source)) { + Permanent permanent = game.getPermanent(permId); + if (permanent != null) { + switch (layer) { + case ColorChangingEffects_5: + permanent.getColor(game).setBlack(true); + isActive = true; + break; + case TypeChangingEffects_4: + permanent.addSubType(game, SubType.ZOMBIE); + isActive = true; + break; + } + } + } + return isActive; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.ColorChangingEffects_5 || layer == Layer.TypeChangingEffects_4; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GiantGrowth.java b/Mage.Sets/src/mage/cards/g/GiantGrowth.java index 7dcc98f4450..c4318a46a76 100644 --- a/Mage.Sets/src/mage/cards/g/GiantGrowth.java +++ b/Mage.Sets/src/mage/cards/g/GiantGrowth.java @@ -3,11 +3,13 @@ package mage.cards.g; import java.util.UUID; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.Outcome; import mage.target.common.TargetCreaturePermanent; /** @@ -20,7 +22,10 @@ public final class GiantGrowth extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{G}"); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); - this.getSpellAbility().addEffect(new BoostTargetEffect(3, 3, Duration.EndOfTurn)); + Effect effect = new BoostTargetEffect(3, 3, Duration.EndOfTurn); + effect.setOutcome(Outcome.Benefit); + this.getSpellAbility().addEffect(effect); + } private GiantGrowth(final GiantGrowth card) { diff --git a/Mage.Sets/src/mage/cards/g/GisaGloriousResurrector.java b/Mage.Sets/src/mage/cards/g/GisaGloriousResurrector.java new file mode 100644 index 00000000000..d21bd6a7c0e --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GisaGloriousResurrector.java @@ -0,0 +1,157 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.DecayedAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.PermanentToken; +import mage.players.Player; +import mage.target.targetpointer.FixedTargets; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GisaGloriousResurrector extends CardImpl { + + public GisaGloriousResurrector(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // If a creature an opponent controls would die, exile it instead. + this.addAbility(new SimpleStaticAbility(new GisaGloriousResurrectorExileEffect())); + + // At the beginning of your upkeep, put all creature cards exiled with Gisa, Glorious Resurrector onto the battlefield under your control. They gain decayed. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new GisaGloriousResurrectorReturnEffect(), TargetController.YOU, false + )); + } + + private GisaGloriousResurrector(final GisaGloriousResurrector card) { + super(card); + } + + @Override + public GisaGloriousResurrector copy() { + return new GisaGloriousResurrector(this); + } +} + +class GisaGloriousResurrectorExileEffect extends ReplacementEffectImpl { + + GisaGloriousResurrectorExileEffect() { + super(Duration.WhileOnBattlefield, Outcome.Exile); + staticText = "if a creature an opponent controls would die, exile it instead"; + } + + private GisaGloriousResurrectorExileEffect(final GisaGloriousResurrectorExileEffect effect) { + super(effect); + } + + @Override + public GisaGloriousResurrectorExileEffect copy() { + return new GisaGloriousResurrectorExileEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getTarget() instanceof PermanentToken) { + return player.moveCards(zEvent.getTarget(), Zone.EXILED, source, game); + } + game.getState().setValue("GisaGloriousResurrectorExile" + + source.getSourceId().toString() + + game.getState().getZoneChangeCounter(source.getSourceId()), source); + return player.moveCardsToExile( + zEvent.getTarget(), source, game, false, + CardUtil.getExileZoneId(game, source), "Gisa, Glorious Resurrector" + ); + } + + @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.isDiesEvent() + && zEvent.getTarget() != null + && zEvent.getTarget().isCreature(game) + && game.getOpponents(zEvent.getTarget().getControllerId()).contains(source.getControllerId()); + } +} + +class GisaGloriousResurrectorReturnEffect extends OneShotEffect { + + GisaGloriousResurrectorReturnEffect() { + super(Outcome.Benefit); + staticText = "put all creature cards exiled with {this} " + + "onto the battlefield under your control. They gain decayed"; + } + + private GisaGloriousResurrectorReturnEffect(final GisaGloriousResurrectorReturnEffect effect) { + super(effect); + } + + @Override + public GisaGloriousResurrectorReturnEffect copy() { + return new GisaGloriousResurrectorReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Ability exiledWithSource = (Ability) game.getState().getValue("GisaGloriousResurrectorExile" + + source.getSourceId().toString() + + game.getState().getZoneChangeCounter(source.getSourceId())); + if (exiledWithSource == null) { + return false; + } + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, exiledWithSource)); + if (player == null + || exileZone == null + || exileZone.isEmpty()) { + return false; + } + Cards cards = new CardsImpl(exileZone.getCards(StaticFilters.FILTER_CARD_CREATURE, game)); + if (cards.isEmpty()) { + return false; + } + player.moveCards(cards, Zone.BATTLEFIELD, source, game); + cards.retainZone(Zone.BATTLEFIELD, game); + if (cards.isEmpty()) { + return false; + } + game.addEffect(new GainAbilityTargetEffect( + new DecayedAbility(), Duration.Custom + ).setTargetPointer(new FixedTargets(cards, game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java b/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java index 79f2b118f00..e293a529f20 100644 --- a/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java +++ b/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java @@ -1,161 +1,161 @@ -package mage.cards.g; - -import mage.abilities.Ability; -import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; -import mage.abilities.dynamicvalue.common.StaticValue; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledPermanent; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.players.Player; -import java.util.UUID; -import mage.Mana; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.decorator.ConditionalAsThoughEffect; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.watchers.common.ManaSpentToCastWatcher; - -/** - * - * @author jeffwadsworth - */ - -public class GlimpseTheCosmos extends CardImpl { - - public GlimpseTheCosmos(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{U}"); - - // Look at the top three cards of your library. Put one of them into your hand and the rest on the bottom of your library in any order. - this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect( - StaticValue.get(3), false, StaticValue.get(1), - StaticFilters.FILTER_CARD, Zone.LIBRARY, false, - false, false, Zone.HAND, false - ).setText("look at the top three cards of your library. " - + "Put one of them into your hand and the rest on the bottom of your library in any order")); - - //As long as you control a Giant, you may cast Glimpse the Cosmos from your graveyard by paying {U} rather than paying its mana cost. If you cast Glimpse the Cosmos this way and it would be put into your graveyard, exile it instead. - this.addAbility(new SimpleStaticAbility(Zone.GRAVEYARD, - new ConditionalAsThoughEffect( - new GlimpseTheCosmosPlayEffect(), - new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.GIANT))))); - - this.addAbility(new SimpleStaticAbility(Zone.ALL, new GlimpseTheCosmosReplacementEffect())); - - } - - private GlimpseTheCosmos(final GlimpseTheCosmos card) { - super(card); - } - - @Override - public GlimpseTheCosmos copy() { - return new GlimpseTheCosmos(this); - } - -} - -class GlimpseTheCosmosPlayEffect extends AsThoughEffectImpl { - - public GlimpseTheCosmosPlayEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); - staticText = "As long as you control a Giant, you may cast {this} from your graveyard by paying {U} rather than paying its mana cost"; - } - - public GlimpseTheCosmosPlayEffect(final GlimpseTheCosmosPlayEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public GlimpseTheCosmosPlayEffect copy() { - return new GlimpseTheCosmosPlayEffect(this); - } - - @Override - public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - if (sourceId.equals(source.getSourceId()) - && source.isControlledBy(affectedControllerId)) { - if (game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { - Player controller = game.getPlayer(affectedControllerId); - if (controller != null) { - controller.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{U}"), null); - return true; - } - } - } - return false; - } - -} - -class GlimpseTheCosmosReplacementEffect extends ReplacementEffectImpl { - - public GlimpseTheCosmosReplacementEffect() { - super(Duration.OneUse, Outcome.Exile); - staticText = "As long as you control a Giant, you may cast {this} from your graveyard by paying {U} rather than paying its mana cost. If you cast {this} this way and it would be put into your graveyard, exile it instead"; - } - - public GlimpseTheCosmosReplacementEffect(final GlimpseTheCosmosReplacementEffect effect) { - super(effect); - } - - @Override - public GlimpseTheCosmosReplacementEffect copy() { - return new GlimpseTheCosmosReplacementEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Card card = game.getCard(event.getTargetId()); - if (card != null) { - discard(); - return controller.moveCards( - card, Zone.EXILED, source, game, false, false, false, event.getAppliedEffects()); - } - } - 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) { - ManaSpentToCastWatcher watcher = game.getState().getWatcher(ManaSpentToCastWatcher.class); - if (watcher == null) { - return false; - } - Mana payment = watcher.getLastManaPayment(source.getSourceId()); - if (payment != null - && payment.getBlue() == 1 // must be blue mana - && payment.count() == 1) { // must be just one - if (event.getTargetId().equals(source.getSourceId()) - && ((ZoneChangeEvent) event).getFromZone() == Zone.STACK - && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { - return true; - } - } - return false; - } -} +package mage.cards.g; + +import mage.abilities.Ability; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import java.util.UUID; +import mage.Mana; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.watchers.common.ManaSpentToCastWatcher; + +/** + * + * @author jeffwadsworth + */ + +public class GlimpseTheCosmos extends CardImpl { + + public GlimpseTheCosmos(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{U}"); + + // Look at the top three cards of your library. Put one of them into your hand and the rest on the bottom of your library in any order. + this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect( + StaticValue.get(3), false, StaticValue.get(1), + StaticFilters.FILTER_CARD, Zone.LIBRARY, false, + false, false, Zone.HAND, false + ).setText("look at the top three cards of your library. " + + "Put one of them into your hand and the rest on the bottom of your library in any order")); + + //As long as you control a Giant, you may cast Glimpse the Cosmos from your graveyard by paying {U} rather than paying its mana cost. If you cast Glimpse the Cosmos this way and it would be put into your graveyard, exile it instead. + this.addAbility(new SimpleStaticAbility(Zone.GRAVEYARD, + new ConditionalAsThoughEffect( + new GlimpseTheCosmosPlayEffect(), + new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.GIANT))))); + + this.addAbility(new SimpleStaticAbility(Zone.ALL, new GlimpseTheCosmosReplacementEffect())); + + } + + private GlimpseTheCosmos(final GlimpseTheCosmos card) { + super(card); + } + + @Override + public GlimpseTheCosmos copy() { + return new GlimpseTheCosmos(this); + } + +} + +class GlimpseTheCosmosPlayEffect extends AsThoughEffectImpl { + + public GlimpseTheCosmosPlayEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); + staticText = "As long as you control a Giant, you may cast {this} from your graveyard by paying {U} rather than paying its mana cost"; + } + + public GlimpseTheCosmosPlayEffect(final GlimpseTheCosmosPlayEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public GlimpseTheCosmosPlayEffect copy() { + return new GlimpseTheCosmosPlayEffect(this); + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + if (sourceId.equals(source.getSourceId()) + && source.isControlledBy(affectedControllerId)) { + if (game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { + Player controller = game.getPlayer(affectedControllerId); + if (controller != null) { + controller.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{U}"), null); + return true; + } + } + } + return false; + } + +} + +class GlimpseTheCosmosReplacementEffect extends ReplacementEffectImpl { + + public GlimpseTheCosmosReplacementEffect() { + super(Duration.OneUse, Outcome.Exile); + staticText = "As long as you control a Giant, you may cast {this} from your graveyard by paying {U} rather than paying its mana cost. If you cast {this} this way and it would be put into your graveyard, exile it instead"; + } + + public GlimpseTheCosmosReplacementEffect(final GlimpseTheCosmosReplacementEffect effect) { + super(effect); + } + + @Override + public GlimpseTheCosmosReplacementEffect copy() { + return new GlimpseTheCosmosReplacementEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Card card = game.getCard(event.getTargetId()); + if (card != null) { + discard(); + return controller.moveCards( + card, Zone.EXILED, source, game, false, false, false, event.getAppliedEffects()); + } + } + 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) { + ManaSpentToCastWatcher watcher = game.getState().getWatcher(ManaSpentToCastWatcher.class); + if (watcher == null) { + return false; + } + Mana payment = watcher.getLastManaPayment(source.getSourceId()); + if (payment != null + && payment.getBlue() == 1 // must be blue mana + && payment.count() == 1) { // must be just one + if (event.getTargetId().equals(source.getSourceId()) + && ((ZoneChangeEvent) event).getFromZone() == Zone.STACK + && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GnawToTheBone.java b/Mage.Sets/src/mage/cards/g/GnawToTheBone.java index 288092df271..26949fb457d 100644 --- a/Mage.Sets/src/mage/cards/g/GnawToTheBone.java +++ b/Mage.Sets/src/mage/cards/g/GnawToTheBone.java @@ -26,7 +26,7 @@ public final class GnawToTheBone extends CardImpl { this.getSpellAbility().addEffect(new GainLifeEffect(value)); // Flashback {2}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{G}"))); } private GnawToTheBone(final GnawToTheBone card) { diff --git a/Mage.Sets/src/mage/cards/g/GomaFadaVanguard.java b/Mage.Sets/src/mage/cards/g/GomaFadaVanguard.java index 725ed0fd9a8..ae5bb8e7cf6 100644 --- a/Mage.Sets/src/mage/cards/g/GomaFadaVanguard.java +++ b/Mage.Sets/src/mage/cards/g/GomaFadaVanguard.java @@ -66,7 +66,7 @@ public final class GomaFadaVanguard extends CardImpl { } } -enum GomaFadaVanguardPredicate implements ObjectSourcePlayerPredicate> { +enum GomaFadaVanguardPredicate implements ObjectSourcePlayerPredicate { instance; private static final FilterPermanent filter = new FilterPermanent(SubType.WARRIOR, ""); diff --git a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java index ffd0b7369a2..669690eb3ee 100644 --- a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java +++ b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java @@ -29,8 +29,6 @@ import java.util.UUID; */ public final class GontiLordOfLuxury extends CardImpl { - protected static final String VALUE_PREFIX = "ExileZones"; - public GontiLordOfLuxury(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); addSuperType(SuperType.LEGENDARY); @@ -62,6 +60,8 @@ public final class GontiLordOfLuxury extends CardImpl { class GontiLordOfLuxuryEffect extends OneShotEffect { + private static final String VALUE_PREFIX = "ExileZones"; + public GontiLordOfLuxuryEffect() { super(Outcome.Benefit); this.staticText = "look at the top four cards of target opponent's library, exile one of them face down, then put the rest on the bottom of that library in a random order. You may look at and cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any type to cast that spell"; @@ -81,48 +81,47 @@ class GontiLordOfLuxuryEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); MageObject sourceObject = source.getSourceObject(game); - if (controller != null && opponent != null && sourceObject != null) { - Cards topCards = new CardsImpl(); - topCards.addAll(opponent.getLibrary().getTopCards(game, 4)); - TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card to exile")); - if (controller.choose(outcome, topCards, target, game)) { - Card card = game.getCard(target.getFirstTarget()); - if (card != null) { - topCards.remove(card); - // move card to exile - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - card.setFaceDown(true, game); - if (controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName())) { - card.setFaceDown(true, game); - Set exileZones = (Set) game.getState().getValue(GontiLordOfLuxury.VALUE_PREFIX + source.getSourceId().toString()); - if (exileZones == null) { - exileZones = new HashSet<>(); - game.getState().setValue(GontiLordOfLuxury.VALUE_PREFIX + source.getSourceId().toString(), exileZones); - } - exileZones.add(exileZoneId); - // allow to cast the card - ContinuousEffect effect = new GontiLordOfLuxuryCastFromExileEffect(); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(effect, source); - // and you may spend mana as though it were mana of any color to cast it - effect = new GontiLordOfLuxurySpendAnyManaEffect(); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(effect, source); - // For as long as that card remains exiled, you may look at it - effect = new GontiLordOfLuxuryLookEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(effect, source); - } - } - } - // then put the rest on the bottom of that library in a random order + if (controller == null || opponent == null || sourceObject == null) { + return false; + } + Cards topCards = new CardsImpl(); + topCards.addAll(opponent.getLibrary().getTopCards(game, 4)); + TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card to exile")); + controller.choose(outcome, topCards, target, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { controller.putCardsOnBottomOfLibrary(topCards, game, source, false); return true; } - - return false; + topCards.remove(card); + // move card to exile + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + card.setFaceDown(true, game); + if (controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName())) { + card.setFaceDown(true, game); + Set exileZones = (Set) game.getState().getValue(VALUE_PREFIX + source.getSourceId().toString()); + if (exileZones == null) { + exileZones = new HashSet<>(); + game.getState().setValue(VALUE_PREFIX + source.getSourceId().toString(), exileZones); + } + exileZones.add(exileZoneId); + // allow to cast the card + ContinuousEffect effect = new GontiLordOfLuxuryCastFromExileEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + // and you may spend mana as though it were mana of any color to cast it + effect = new GontiLordOfLuxurySpendAnyManaEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + // For as long as that card remains exiled, you may look at it + effect = new GontiLordOfLuxuryLookEffect(controller.getId()); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + } + // then put the rest on the bottom of that library in a random order + controller.putCardsOnBottomOfLibrary(topCards, game, source, false); + return true; } - } class GontiLordOfLuxuryCastFromExileEffect extends AsThoughEffectImpl { diff --git a/Mage.Sets/src/mage/cards/g/GraspOfPhantoms.java b/Mage.Sets/src/mage/cards/g/GraspOfPhantoms.java index da3ca2980b0..31fb236694e 100644 --- a/Mage.Sets/src/mage/cards/g/GraspOfPhantoms.java +++ b/Mage.Sets/src/mage/cards/g/GraspOfPhantoms.java @@ -26,7 +26,7 @@ public final class GraspOfPhantoms extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {7}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{7}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{7}{U}"))); } private GraspOfPhantoms(final GraspOfPhantoms card) { diff --git a/Mage.Sets/src/mage/cards/g/GraveyardGlutton.java b/Mage.Sets/src/mage/cards/g/GraveyardGlutton.java index 9e700d816c4..8cd92dd191b 100644 --- a/Mage.Sets/src/mage/cards/g/GraveyardGlutton.java +++ b/Mage.Sets/src/mage/cards/g/GraveyardGlutton.java @@ -46,7 +46,7 @@ public final class GraveyardGlutton extends CardImpl { this.addAbility(ability); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private GraveyardGlutton(final GraveyardGlutton card) { diff --git a/Mage.Sets/src/mage/cards/g/GraveyardTrespasser.java b/Mage.Sets/src/mage/cards/g/GraveyardTrespasser.java index f74e5125d35..4b505e9d417 100644 --- a/Mage.Sets/src/mage/cards/g/GraveyardTrespasser.java +++ b/Mage.Sets/src/mage/cards/g/GraveyardTrespasser.java @@ -48,7 +48,7 @@ public final class GraveyardTrespasser extends CardImpl { // Daybound this.addAbility(new TransformAbility()); - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new DayboundAbility()); } private GraveyardTrespasser(final GraveyardTrespasser card) { diff --git a/Mage.Sets/src/mage/cards/g/GrizzlyFate.java b/Mage.Sets/src/mage/cards/g/GrizzlyFate.java index 7a79d495b12..fe9b432feb8 100644 --- a/Mage.Sets/src/mage/cards/g/GrizzlyFate.java +++ b/Mage.Sets/src/mage/cards/g/GrizzlyFate.java @@ -33,7 +33,7 @@ public final class GrizzlyFate extends CardImpl { this.getSpellAbility().addEffect(effect); // Flashback {5}{G}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{G}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{G}{G}"))); } private GrizzlyFate(final GrizzlyFate card) { diff --git a/Mage.Sets/src/mage/cards/g/Guile.java b/Mage.Sets/src/mage/cards/g/Guile.java index d4ddadcd01c..057c1cec99b 100644 --- a/Mage.Sets/src/mage/cards/g/Guile.java +++ b/Mage.Sets/src/mage/cards/g/Guile.java @@ -86,7 +86,7 @@ class GuileReplacementEffect extends ReplacementEffectImpl { Card spellCard = spell.getCard(); if (spellCard != null && controller.chooseUse(Outcome.PlayForFree, "Play " + spellCard.getIdName() + " for free?", source, game)) { - controller.playCard(spellCard, game, true, true, new ApprovingObject(source, game)); + controller.playCard(spellCard, game, true, new ApprovingObject(source, game)); } return true; } diff --git a/Mage.Sets/src/mage/cards/g/GyrusWakerOfCorpses.java b/Mage.Sets/src/mage/cards/g/GyrusWakerOfCorpses.java index 48dc31882cd..28f936bdeb6 100644 --- a/Mage.Sets/src/mage/cards/g/GyrusWakerOfCorpses.java +++ b/Mage.Sets/src/mage/cards/g/GyrusWakerOfCorpses.java @@ -105,7 +105,7 @@ class GyrusWakerOfCorpsesEffect extends OneShotEffect { } } -class GyrusWakerOfCorpsesPowerLessThanSourcePredicate implements ObjectSourcePlayerPredicate> { +class GyrusWakerOfCorpsesPowerLessThanSourcePredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/h/HaktosTheUnscarred.java b/Mage.Sets/src/mage/cards/h/HaktosTheUnscarred.java index 3a585e8ee91..dd5a6e39c14 100644 --- a/Mage.Sets/src/mage/cards/h/HaktosTheUnscarred.java +++ b/Mage.Sets/src/mage/cards/h/HaktosTheUnscarred.java @@ -101,7 +101,7 @@ class HaktosTheUnscarredChooseEffect extends OneShotEffect { } } -enum HaktosTheUnscarredPredicate implements ObjectSourcePlayerPredicate> { +enum HaktosTheUnscarredPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/h/HallowedRespite.java b/Mage.Sets/src/mage/cards/h/HallowedRespite.java new file mode 100644 index 00000000000..c7176564068 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HallowedRespite.java @@ -0,0 +1,88 @@ +package mage.cards.h; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetForSourceEffect; +import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.constants.TimingRule; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author weirddan455 + */ +public final class HallowedRespite extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonlegendary creature"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public HallowedRespite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}{U}"); + + // Exile target nonlegendary creature, then return it to the battlefield under its owner's control. If it entered under your control, put a +1/+1 counter on it. Otherwise, tap it. + this.getSpellAbility().addEffect(new ExileTargetForSourceEffect()); + this.getSpellAbility().addEffect(new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false)); + this.getSpellAbility().addEffect(new HallowedRespiteEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + + // Flashback {1}{W}{U} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{W}{U}"))); + } + + private HallowedRespite(final HallowedRespite card) { + super(card); + } + + @Override + public HallowedRespite copy() { + return new HallowedRespite(this); + } +} + +class HallowedRespiteEffect extends OneShotEffect { + + public HallowedRespiteEffect() { + super(Outcome.Benefit); + staticText = "If it entered under your control, put a +1/+1 counter on it. Otherwise, tap it"; + } + + private HallowedRespiteEffect(final HallowedRespiteEffect effect) { + super(effect); + } + + @Override + public HallowedRespiteEffect copy() { + return new HallowedRespiteEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + if (permanent.isControlledBy(source.getControllerId())) { + permanent.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game); + } else { + permanent.tap(source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HalvarGodOfBattle.java b/Mage.Sets/src/mage/cards/h/HalvarGodOfBattle.java index 4d2513cc5c0..d2e1568137b 100644 --- a/Mage.Sets/src/mage/cards/h/HalvarGodOfBattle.java +++ b/Mage.Sets/src/mage/cards/h/HalvarGodOfBattle.java @@ -181,7 +181,7 @@ class SwordOfTheRealmsEffect extends OneShotEffect { } } -class HalvarGodOfBattlePredicate implements ObjectSourcePlayerPredicate> { +class HalvarGodOfBattlePredicate implements ObjectSourcePlayerPredicate { private final FilterPermanent filter; diff --git a/Mage.Sets/src/mage/cards/h/HarvesttideAssailant.java b/Mage.Sets/src/mage/cards/h/HarvesttideAssailant.java index 4c888f9ad8f..55c37c19d41 100644 --- a/Mage.Sets/src/mage/cards/h/HarvesttideAssailant.java +++ b/Mage.Sets/src/mage/cards/h/HarvesttideAssailant.java @@ -29,7 +29,7 @@ public final class HarvesttideAssailant extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private HarvesttideAssailant(final HarvesttideAssailant card) { diff --git a/Mage.Sets/src/mage/cards/h/HarvesttideInfiltrator.java b/Mage.Sets/src/mage/cards/h/HarvesttideInfiltrator.java index 25528bc3cb4..66e8715dc57 100644 --- a/Mage.Sets/src/mage/cards/h/HarvesttideInfiltrator.java +++ b/Mage.Sets/src/mage/cards/h/HarvesttideInfiltrator.java @@ -3,6 +3,7 @@ package mage.cards.h; import mage.MageInt; import mage.abilities.keyword.DayboundAbility; import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -29,7 +30,8 @@ public final class HarvesttideInfiltrator extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Daybound - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new TransformAbility()); + this.addAbility(new DayboundAbility()); } private HarvesttideInfiltrator(final HarvesttideInfiltrator card) { diff --git a/Mage.Sets/src/mage/cards/h/HarvesttideSentry.java b/Mage.Sets/src/mage/cards/h/HarvesttideSentry.java new file mode 100644 index 00000000000..38c8683bfdb --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HarvesttideSentry.java @@ -0,0 +1,55 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.condition.common.CovenCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; +import mage.abilities.hint.common.CovenHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HarvesttideSentry extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 3)); + } + + public HarvesttideSentry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, Harvesttide Sentry can't be blocked by creatures with power 2 or less this turn. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new BeginningOfCombatTriggeredAbility( + new CantBeBlockedByCreaturesSourceEffect(filter, Duration.EndOfTurn), + TargetController.YOU, false + ), CovenCondition.instance, "At the beginning of combat on your turn, " + + "if you control three or more creatures with different powers, " + + "{this} can't be blocked by creatures with power 2 or less this turn." + ).addHint(CovenHint.instance).setAbilityWord(AbilityWord.COVEN)); + } + + private HarvesttideSentry(final HarvesttideSentry card) { + super(card); + } + + @Override + public HarvesttideSentry copy() { + return new HarvesttideSentry(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HeronbladeElite.java b/Mage.Sets/src/mage/cards/h/HeronbladeElite.java new file mode 100644 index 00000000000..e4b4ec2a3da --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeronbladeElite.java @@ -0,0 +1,57 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.mana.AnyColorManaAbility; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HeronbladeElite extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent(SubType.HUMAN, "another Human"); + private static final DynamicValue xValue = new SourcePermanentPowerCount(); + + public HeronbladeElite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever another Human enters the battlefield under your control, put a +1/+1 counter on Heronblade Elite. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter + )); + + // {T}: Add X mana of any one color, where X is Heronblade Elite's power. + this.addAbility(new AnyColorManaAbility(new TapSourceCost(), xValue, false)); + } + + private HeronbladeElite(final HeronbladeElite card) { + super(card); + } + + @Override + public HeronbladeElite copy() { + return new HeronbladeElite(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HinderingLight.java b/Mage.Sets/src/mage/cards/h/HinderingLight.java index 3fda3c40dbe..f0589a07876 100644 --- a/Mage.Sets/src/mage/cards/h/HinderingLight.java +++ b/Mage.Sets/src/mage/cards/h/HinderingLight.java @@ -9,8 +9,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterSpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; @@ -49,10 +49,10 @@ public final class HinderingLight extends CardImpl { } } -class HinderingLightPredicate implements ObjectPlayerPredicate> { +class HinderingLightPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { UUID controllerId = input.getPlayerId(); if (controllerId == null) { return false; diff --git a/Mage.Sets/src/mage/cards/h/HomesteadCourage.java b/Mage.Sets/src/mage/cards/h/HomesteadCourage.java index 6e1986628ec..2b011596836 100644 --- a/Mage.Sets/src/mage/cards/h/HomesteadCourage.java +++ b/Mage.Sets/src/mage/cards/h/HomesteadCourage.java @@ -31,7 +31,7 @@ public final class HomesteadCourage extends CardImpl { this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); // Flashback {W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{W}"))); } private HomesteadCourage(final HomesteadCourage card) { diff --git a/Mage.Sets/src/mage/cards/h/HopeOfGhirapur.java b/Mage.Sets/src/mage/cards/h/HopeOfGhirapur.java index f9a870d5b43..9a8fa224708 100644 --- a/Mage.Sets/src/mage/cards/h/HopeOfGhirapur.java +++ b/Mage.Sets/src/mage/cards/h/HopeOfGhirapur.java @@ -114,7 +114,7 @@ class HopeOfGhirapurCantCastEffect extends ContinuousRuleModifyingEffectImpl { } } -class HopeOfGhirapurPlayerLostLifePredicate implements ObjectSourcePlayerPredicate> { +class HopeOfGhirapurPlayerLostLifePredicate implements ObjectSourcePlayerPredicate { public HopeOfGhirapurPlayerLostLifePredicate() { } diff --git a/Mage.Sets/src/mage/cards/h/HordeOfNotions.java b/Mage.Sets/src/mage/cards/h/HordeOfNotions.java index 0fbbdfe19b4..eaf2125caf2 100644 --- a/Mage.Sets/src/mage/cards/h/HordeOfNotions.java +++ b/Mage.Sets/src/mage/cards/h/HordeOfNotions.java @@ -84,7 +84,7 @@ class HordeOfNotionsEffect extends OneShotEffect { if (controller != null) { Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (card != null && controller.chooseUse(outcome, "Play " + card.getName() + " from your graveyard for free?", source, game)) { - controller.playCard(card, game, true, true, new ApprovingObject(source, game)); + controller.playCard(card, game, true, new ApprovingObject(source, game)); } return true; } diff --git a/Mage.Sets/src/mage/cards/h/HordewingSkaab.java b/Mage.Sets/src/mage/cards/h/HordewingSkaab.java new file mode 100644 index 00000000000..ffca3d92a1c --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HordewingSkaab.java @@ -0,0 +1,115 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.events.DamagedEvent; +import mage.game.events.DamagedPlayerBatchEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HordewingSkaab extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent(SubType.ZOMBIE, "Zombies"); + + public HordewingSkaab(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Other Zombies you control have flying. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.WhileOnBattlefield, filter, true + ))); + + // Whenever one or more Zombies you control deal combat damage to one or more of your opponents, you may draw cards equal to the number of opponents dealt damage this way. If you do, discard that many cards. + this.addAbility(new HordewingSkaabTriggeredAbility()); + } + + private HordewingSkaab(final HordewingSkaab card) { + super(card); + } + + @Override + public HordewingSkaab copy() { + return new HordewingSkaab(this); + } +} + +class HordewingSkaabTriggeredAbility extends TriggeredAbilityImpl { + + HordewingSkaabTriggeredAbility() { + super(Zone.BATTLEFIELD, null, true); + } + + private HordewingSkaabTriggeredAbility(final HordewingSkaabTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER_BATCH; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + DamagedPlayerBatchEvent dEvent = (DamagedPlayerBatchEvent) event; + Set opponents = new HashSet<>(); + for (DamagedEvent damagedEvent : dEvent.getEvents()) { + if (!damagedEvent.isCombatDamage()) { + continue; + } + Permanent permanent = game.getPermanent(damagedEvent.getSourceId()); + if (permanent == null + || !permanent.isControlledBy(getControllerId()) + || !permanent.hasSubtype(SubType.ZOMBIE, game) + || !game.getOpponents(getControllerId()).contains(damagedEvent.getTargetId())) { + continue; + } + opponents.add(damagedEvent.getTargetId()); + } + if (opponents.size() < 1) { + return false; + } + this.getEffects().clear(); + this.addEffect(new DrawDiscardControllerEffect(opponents.size(), opponents.size())); + return true; + } + + @Override + public HordewingSkaabTriggeredAbility copy() { + return new HordewingSkaabTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever one or more Zombies you control deal combat damage to one " + + "or more of your opponents, you may draw cards equal to the number " + + "of opponents dealt damage this way. If you do, discard that many cards."; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HostileHostel.java b/Mage.Sets/src/mage/cards/h/HostileHostel.java new file mode 100644 index 00000000000..9156b6faebf --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HostileHostel.java @@ -0,0 +1,92 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +import static mage.filter.StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT; + +/** + * @author LePwnerer + */ +public final class HostileHostel extends CardImpl { + + public HostileHostel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + this.transformable = true; + this.secondSideCardClazz = mage.cards.c.CreepingInn.class; + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {1}, {T}, Sacrifice a creature: Put a soul counter on Hostile Hostel. Then if there are three or more soul counters on it, remove those counters, transform it, then untap it. Activate only as a sorcery. + this.addAbility(new TransformAbility()); + Ability ability = new ActivateAsSorceryActivatedAbility(Zone.BATTLEFIELD, new HostileHostelEffect(), new ManaCostsImpl("{1}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(FILTER_CONTROLLED_CREATURE_SHORT_TEXT))); + this.addAbility(ability); + + } + + private HostileHostel(final HostileHostel card) { + super(card); + } + + @Override + public HostileHostel copy() { + return new HostileHostel(this); + } +} + +class HostileHostelEffect extends OneShotEffect { + + HostileHostelEffect() { + super(Outcome.Benefit); + this.staticText = "Put a soul counter on {this}. " + + "Then if there are three or more soul counters on it, remove those counters, transform it, then untap it."; + } + + HostileHostelEffect(final mage.cards.h.HostileHostelEffect effect) { + super(effect); + } + + @Override + public mage.cards.h.HostileHostelEffect copy() { + return new mage.cards.h.HostileHostelEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent != null && player != null) { + permanent.addCounters(CounterType.SOUL.createInstance(), source.getControllerId(), source, game); + int counters = permanent.getCounters(game).getCount(CounterType.SOUL); + if (counters > 2) { + permanent.removeCounters(CounterType.SOUL.getName(), counters, source, game); + new TransformSourceEffect(true).apply(game, source); + permanent.untap(game); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HoundTamer.java b/Mage.Sets/src/mage/cards/h/HoundTamer.java index 02d1b4f3cbb..d45833ecbd5 100644 --- a/Mage.Sets/src/mage/cards/h/HoundTamer.java +++ b/Mage.Sets/src/mage/cards/h/HoundTamer.java @@ -44,7 +44,7 @@ public final class HoundTamer extends CardImpl { // Daybound this.addAbility(new TransformAbility()); - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new DayboundAbility()); } private HoundTamer(final HoundTamer card) { diff --git a/Mage.Sets/src/mage/cards/h/HowlingGale.java b/Mage.Sets/src/mage/cards/h/HowlingGale.java index 1ec9b6e4a58..7c8ae9fae78 100644 --- a/Mage.Sets/src/mage/cards/h/HowlingGale.java +++ b/Mage.Sets/src/mage/cards/h/HowlingGale.java @@ -36,7 +36,7 @@ public final class HowlingGale extends CardImpl { effect.setText("and each player"); this.getSpellAbility().addEffect(effect); // Flashback {1}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{G}"))); } private HowlingGale(final HowlingGale card) { diff --git a/Mage.Sets/src/mage/cards/h/HungryForMore.java b/Mage.Sets/src/mage/cards/h/HungryForMore.java index 3e527743025..2c91750e367 100644 --- a/Mage.Sets/src/mage/cards/h/HungryForMore.java +++ b/Mage.Sets/src/mage/cards/h/HungryForMore.java @@ -31,7 +31,7 @@ public final class HungryForMore extends CardImpl { this.getSpellAbility().addEffect(new HungryForMoreEffect()); // Flashback {1}{B}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{1}{B}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{B}{R}"))); } private HungryForMore(final HungryForMore card) { diff --git a/Mage.Sets/src/mage/cards/i/IgniteDisorder.java b/Mage.Sets/src/mage/cards/i/IgniteDisorder.java index de752843f36..7a15ec028a3 100644 --- a/Mage.Sets/src/mage/cards/i/IgniteDisorder.java +++ b/Mage.Sets/src/mage/cards/i/IgniteDisorder.java @@ -31,7 +31,7 @@ public final class IgniteDisorder extends CardImpl { // Ignite Disorder deals 3 damage divided as you choose among one, two, or three target white and/or blue creatures. - this.getSpellAbility().addEffect(new DamageMultiEffect(3)); + this.getSpellAbility().addEffect(new DamageMultiEffect(3).setText("{this} deals 3 damage divided as you choose among one, two, or three target white and/or blue creatures")); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(3, filter)); } diff --git a/Mage.Sets/src/mage/cards/i/IgniteTheFuture.java b/Mage.Sets/src/mage/cards/i/IgniteTheFuture.java index 9fdf7a6aaa2..4883b16f657 100644 --- a/Mage.Sets/src/mage/cards/i/IgniteTheFuture.java +++ b/Mage.Sets/src/mage/cards/i/IgniteTheFuture.java @@ -28,7 +28,7 @@ public final class IgniteTheFuture extends CardImpl { this.getSpellAbility().addEffect(new IgniteTheFutureEffect()); // Flashback {7}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{7}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{7}{R}"))); } private IgniteTheFuture(final IgniteTheFuture card) { diff --git a/Mage.Sets/src/mage/cards/i/IncreasingAmbition.java b/Mage.Sets/src/mage/cards/i/IncreasingAmbition.java index eae6ce33edc..f32b6cfb445 100644 --- a/Mage.Sets/src/mage/cards/i/IncreasingAmbition.java +++ b/Mage.Sets/src/mage/cards/i/IncreasingAmbition.java @@ -37,7 +37,7 @@ public final class IncreasingAmbition extends CardImpl { )); // Flashback {7}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{7}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{7}{B}"))); } private IncreasingAmbition(final IncreasingAmbition card) { diff --git a/Mage.Sets/src/mage/cards/i/IncreasingConfusion.java b/Mage.Sets/src/mage/cards/i/IncreasingConfusion.java index 410c76d2e72..7e6f63dd965 100644 --- a/Mage.Sets/src/mage/cards/i/IncreasingConfusion.java +++ b/Mage.Sets/src/mage/cards/i/IncreasingConfusion.java @@ -29,7 +29,7 @@ public final class IncreasingConfusion extends CardImpl { this.getSpellAbility().addTarget(new TargetPlayer()); // Flashback {X}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{X}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{X}{U}"))); } private IncreasingConfusion(final IncreasingConfusion card) { diff --git a/Mage.Sets/src/mage/cards/i/IncreasingDevotion.java b/Mage.Sets/src/mage/cards/i/IncreasingDevotion.java index 7c78a06360c..fa318c4bddd 100644 --- a/Mage.Sets/src/mage/cards/i/IncreasingDevotion.java +++ b/Mage.Sets/src/mage/cards/i/IncreasingDevotion.java @@ -29,7 +29,7 @@ public final class IncreasingDevotion extends CardImpl { this.getSpellAbility().addEffect(new IncreasingDevotionEffect()); // Flashback {7}{W}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{7}{W}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{7}{W}{W}"))); } private IncreasingDevotion(final IncreasingDevotion card) { diff --git a/Mage.Sets/src/mage/cards/i/IncreasingSavagery.java b/Mage.Sets/src/mage/cards/i/IncreasingSavagery.java index 096e5b95f1b..5eddaae8427 100644 --- a/Mage.Sets/src/mage/cards/i/IncreasingSavagery.java +++ b/Mage.Sets/src/mage/cards/i/IncreasingSavagery.java @@ -30,7 +30,7 @@ public final class IncreasingSavagery extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {5}{G}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{G}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{G}{G}"))); } private IncreasingSavagery(final IncreasingSavagery card) { diff --git a/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java b/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java index 166fef9d023..78b4b1d479a 100644 --- a/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java +++ b/Mage.Sets/src/mage/cards/i/IncreasingVengeance.java @@ -41,7 +41,7 @@ public final class IncreasingVengeance extends CardImpl { this.getSpellAbility().addTarget(target); // Flashback {3}{R}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{R}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{R}{R}"))); } private IncreasingVengeance(final IncreasingVengeance card) { diff --git a/Mage.Sets/src/mage/cards/i/InfectiousCurse.java b/Mage.Sets/src/mage/cards/i/InfectiousCurse.java index cc822992d5b..4d5aec75422 100644 --- a/Mage.Sets/src/mage/cards/i/InfectiousCurse.java +++ b/Mage.Sets/src/mage/cards/i/InfectiousCurse.java @@ -2,7 +2,7 @@ package mage.cards.i; import mage.abilities.Ability; import mage.abilities.SpellAbility; -import mage.abilities.common.BeginningOfUpkeepAttachedTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.GainLifeEffect; @@ -45,8 +45,9 @@ public final class InfectiousCurse extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfectiousCurseCostReductionEffect())); // At the beginning of enchanted player's upkeep, that player loses 1 life and you gain 1 life. - Ability ability = new BeginningOfUpkeepAttachedTriggeredAbility( - new LoseLifeTargetEffect(1).setText("that player loses 1 life") + Ability ability = new BeginningOfUpkeepTriggeredAbility( + new LoseLifeTargetEffect(1).setText("that player loses 1 life"), + TargetController.ENCHANTED, false ); ability.addEffect(new GainLifeEffect(1).concatBy("and")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/i/IsochronScepter.java b/Mage.Sets/src/mage/cards/i/IsochronScepter.java index d2ab47d3751..b52f68e325d 100644 --- a/Mage.Sets/src/mage/cards/i/IsochronScepter.java +++ b/Mage.Sets/src/mage/cards/i/IsochronScepter.java @@ -33,15 +33,13 @@ public final class IsochronScepter extends CardImpl { public IsochronScepter(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); - // Imprint - When Isochron Scepter enters the battlefield, you may exile an - // instant card with converted mana cost 2 or less from your hand. + // Imprint - When Isochron Scepter enters the battlefield, you may exile an instant card with converted mana cost 2 or less from your hand. this.addAbility(new EntersBattlefieldTriggeredAbility( new IsochronScepterImprintEffect(),true) .withFlavorWord("Imprint") ); - // {2}, {tap}: You may copy the exiled card. If you do, you may cast the - // copy without paying its mana cost. + // {2}, {T}: You may copy the exiled card. If you do, you may cast the copy without paying its mana cost. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new IsochronScepterCopyEffect(), new GenericManaCost(2)); ability.addCost(new TapSourceCost()); diff --git a/Mage.Sets/src/mage/cards/j/JackOLantern.java b/Mage.Sets/src/mage/cards/j/JackOLantern.java index 0ef4aae1f98..99d3651b684 100644 --- a/Mage.Sets/src/mage/cards/j/JackOLantern.java +++ b/Mage.Sets/src/mage/cards/j/JackOLantern.java @@ -1,15 +1,15 @@ package mage.cards.j; -import mage.Mana; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.ExileSourceFromGraveCost; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.ExileTargetEffect; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.AnyColorManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -35,7 +35,7 @@ public final class JackOLantern extends CardImpl { this.addAbility(ability); // {1}, Exile Jack-o'-Lantern from your graveyard: Add one mana of any color. - ability = new SimpleManaAbility(Zone.GRAVEYARD, Mana.AnyMana(1), new GenericManaCost(1)); + ability = new AnyColorManaAbility(Zone.GRAVEYARD, new GenericManaCost(1), StaticValue.get(1), false); ability.addCost(new ExileSourceFromGraveCost()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java b/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java new file mode 100644 index 00000000000..aafbae17164 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java @@ -0,0 +1,139 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.PermanentToken; +import mage.game.permanent.token.HumanToken; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JerrenCorruptedBishop extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.HUMAN); + + public JerrenCorruptedBishop(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + this.transformable = true; + this.secondSideCardClazz = mage.cards.o.OrmendahlTheCorrupter.class; + + // Whenever Jerren, Corrupted Bishop enters the battlefield or another nontoken Human you control dies, you lose 1 life and create a 1/1 white Human creature token. + this.addAbility(new JerrenCorruptedBishopTriggeredAbility()); + + // {2}: Target Human you control gains lifelink until end of turn. + Ability ability = new SimpleActivatedAbility(new GainAbilityTargetEffect( + LifelinkAbility.getInstance(), Duration.EndOfTurn + ), new GenericManaCost(2)); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // At the beginning of your end step, if you have exactly 13 life, you may pay {4}{B}{B}. If you do, transform Jerren. + this.addAbility(new TransformAbility()); + this.addAbility(new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, new DoIfCostPaid( + new TransformSourceEffect(true), new ManaCostsImpl<>("{4}{B}{B}") + ), TargetController.YOU, JerrenCorruptedBishopCondition.instance, false)); + } + + private JerrenCorruptedBishop(final JerrenCorruptedBishop card) { + super(card); + } + + @Override + public JerrenCorruptedBishop copy() { + return new JerrenCorruptedBishop(this); + } +} + +enum JerrenCorruptedBishopCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + return player != null && player.getLife() == 13; + } + + @Override + public String toString() { + return "if you have exactly 13 life"; + } +} + +class JerrenCorruptedBishopTriggeredAbility extends TriggeredAbilityImpl { + + JerrenCorruptedBishopTriggeredAbility() { + super(Zone.BATTLEFIELD, new LoseLifeSourceControllerEffect(1)); + this.addEffect(new CreateTokenEffect(new HumanToken())); + } + + private JerrenCorruptedBishopTriggeredAbility(final JerrenCorruptedBishopTriggeredAbility ability) { + super(ability); + } + + @Override + public JerrenCorruptedBishopTriggeredAbility copy() { + return new JerrenCorruptedBishopTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD + || event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + switch (event.getType()) { + case ENTERS_THE_BATTLEFIELD: + return event.getSourceId().equals(getSourceId()); + case ZONE_CHANGE: + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + return zEvent.isDiesEvent() + && zEvent.getTarget() != null + && !zEvent.getTarget().getId().equals(getSourceId()) + && zEvent.getTarget().isControlledBy(getControllerId()) + && !(zEvent.getTarget() instanceof PermanentToken) + && zEvent.getTarget().hasSubtype(SubType.HUMAN, game); + + default: + return false; + } + } + + @Override + public String getRule() { + return "Whenever {this} enters the battlefield or another nontoken Human you control dies, " + + "you lose 1 life and create a 1/1 white Human creature token."; + } +} diff --git a/Mage.Sets/src/mage/cards/j/JeweledTorque.java b/Mage.Sets/src/mage/cards/j/JeweledTorque.java index 2d3e25c715d..ad295d7912f 100644 --- a/Mage.Sets/src/mage/cards/j/JeweledTorque.java +++ b/Mage.Sets/src/mage/cards/j/JeweledTorque.java @@ -56,7 +56,7 @@ public final class JeweledTorque extends CardImpl { } } -enum JeweledTorquePredicate implements ObjectSourcePlayerPredicate> { +enum JeweledTorquePredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/j/JhoiraOfTheGhitu.java b/Mage.Sets/src/mage/cards/j/JhoiraOfTheGhitu.java index bfc7e937e9d..c2b5cb9ef5b 100644 --- a/Mage.Sets/src/mage/cards/j/JhoiraOfTheGhitu.java +++ b/Mage.Sets/src/mage/cards/j/JhoiraOfTheGhitu.java @@ -88,6 +88,8 @@ class JhoiraOfTheGhituSuspendEffect extends OneShotEffect { if (card == null) { return false; } + card = card.getMainCard(); + boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class); UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); diff --git a/Mage.Sets/src/mage/cards/j/JoinTheDance.java b/Mage.Sets/src/mage/cards/j/JoinTheDance.java index 78f62ccdd88..a62b20cd8dd 100644 --- a/Mage.Sets/src/mage/cards/j/JoinTheDance.java +++ b/Mage.Sets/src/mage/cards/j/JoinTheDance.java @@ -23,7 +23,7 @@ public final class JoinTheDance extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new HumanToken(), 2)); // Flashback {3}{G}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{G}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{G}{W}"))); } private JoinTheDance(final JoinTheDance card) { diff --git a/Mage.Sets/src/mage/cards/k/Kaleidoscorch.java b/Mage.Sets/src/mage/cards/k/Kaleidoscorch.java index 8eaf49672ea..8bee5084d56 100644 --- a/Mage.Sets/src/mage/cards/k/Kaleidoscorch.java +++ b/Mage.Sets/src/mage/cards/k/Kaleidoscorch.java @@ -28,7 +28,7 @@ public final class Kaleidoscorch extends CardImpl { this.getSpellAbility().setAbilityWord(AbilityWord.CONVERGE); // Flashback {4}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{4}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{4}{R}"))); } private Kaleidoscorch(final Kaleidoscorch card) { diff --git a/Mage.Sets/src/mage/cards/k/KatildaDawnhartPrime.java b/Mage.Sets/src/mage/cards/k/KatildaDawnhartPrime.java new file mode 100644 index 00000000000..1a80e57098b --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KatildaDawnhartPrime.java @@ -0,0 +1,197 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.Mana; +import mage.ObjectColor; +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.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.effects.mana.ManaEffect; +import mage.abilities.keyword.ProtectionAbility; +import mage.abilities.mana.ActivatedManaAbilityImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author weirddan455 + */ +public final class KatildaDawnhartPrime extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.WEREWOLF, "Werewolves"); + private static final FilterPermanent filter2 = new FilterControlledCreaturePermanent(SubType.HUMAN, "Human creatures"); + + public KatildaDawnhartPrime(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Protection from Werewolves + this.addAbility(new ProtectionAbility(filter)); + + // Human creatures you control have "{T}: Add one mana of any of this creature's colors." + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + new KatildaDawnhartPrimeManaAbility(), Duration.WhileOnBattlefield, filter2 + ))); + + // {4}{G}{W}, {T}: Put a +1/+1 counter on each creature you control. + Ability ability = new SimpleActivatedAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ), new ManaCostsImpl<>("{4}{G}{W}")); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private KatildaDawnhartPrime(final KatildaDawnhartPrime card) { + super(card); + } + + @Override + public KatildaDawnhartPrime copy() { + return new KatildaDawnhartPrime(this); + } +} + +// Mana code based on CommanderColorIdentityManaAbility +class KatildaDawnhartPrimeManaAbility extends ActivatedManaAbilityImpl { + + public KatildaDawnhartPrimeManaAbility() { + super(Zone.BATTLEFIELD, new KatildaDawnhartPrimeManaEffect(), new TapSourceCost()); + } + + private KatildaDawnhartPrimeManaAbility(final KatildaDawnhartPrimeManaAbility ability) { + super(ability); + } + + @Override + public KatildaDawnhartPrimeManaAbility copy() { + return new KatildaDawnhartPrimeManaAbility(this); + } + + @Override + public boolean definesMana(Game game) { + return true; + } +} + +class KatildaDawnhartPrimeManaEffect extends ManaEffect { + + public KatildaDawnhartPrimeManaEffect() { + super(); + staticText = "Add one mana of any of this creature's colors"; + } + + private KatildaDawnhartPrimeManaEffect(final KatildaDawnhartPrimeManaEffect effect) { + super(effect); + } + + @Override + public KatildaDawnhartPrimeManaEffect copy() { + return new KatildaDawnhartPrimeManaEffect(this); + } + + @Override + public List getNetMana(Game game, Ability source) { + List netMana = new ArrayList<>(); + if (game != null) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent != null) { + ObjectColor color = permanent.getColor(game); + if (color.isWhite()) { + netMana.add(new Mana(ColoredManaSymbol.W)); + } + if (color.isBlue()) { + netMana.add(new Mana(ColoredManaSymbol.U)); + } + if (color.isBlack()) { + netMana.add(new Mana(ColoredManaSymbol.B)); + } + if (color.isRed()) { + netMana.add(new Mana(ColoredManaSymbol.R)); + } + if (color.isGreen()) { + netMana.add(new Mana(ColoredManaSymbol.G)); + } + } + } + return netMana; + } + + @Override + public Mana produceMana(Game game, Ability source) { + Mana mana = new Mana(); + if (game != null) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (controller != null && permanent != null) { + Choice choice = new ChoiceImpl(); + choice.setMessage("Pick a mana color"); + ObjectColor color = permanent.getColor(game); + if (color.isWhite()) { + choice.getChoices().add("White"); + } + if (color.isBlue()) { + choice.getChoices().add("Blue"); + } + if (color.isBlack()) { + choice.getChoices().add("Black"); + } + if (color.isRed()) { + choice.getChoices().add("Red"); + } + if (color.isGreen()) { + choice.getChoices().add("Green"); + } + if (!choice.getChoices().isEmpty()) { + if (choice.getChoices().size() == 1) { + choice.setChoice(choice.getChoices().iterator().next()); + } else { + controller.choose(outcome, choice, game); + } + + if (choice.getChoice() != null) { + switch (choice.getChoice()) { + case "White": + mana.setWhite(1); + break; + case "Blue": + mana.setBlue(1); + break; + case "Black": + mana.setBlack(1); + break; + case "Red": + mana.setRed(1); + break; + case "Green": + mana.setGreen(1); + break; + } + } + } + } + } + return mana; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java index 50249cf421f..387a3a2442d 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java @@ -74,7 +74,7 @@ public final class KeeperOfTheDead extends CardImpl { } } -class KeeperOfDeadPredicate implements ObjectSourcePlayerPredicate> { +class KeeperOfDeadPredicate implements ObjectSourcePlayerPredicate { private static final FilterCard filter = new FilterCard("creature cards"); diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheFlame.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheFlame.java index 9e5cfb6ec3a..6c1d1015ae9 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheFlame.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheFlame.java @@ -58,7 +58,7 @@ public final class KeeperOfTheFlame extends CardImpl { } } -class KeeperOfTheFlamePredicate implements ObjectSourcePlayerPredicate> { +class KeeperOfTheFlamePredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheMind.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheMind.java index fb3779ac291..3e943da7277 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheMind.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheMind.java @@ -102,7 +102,7 @@ enum KeeperOfTheMindAdjuster implements TargetAdjuster { } } -class KeeperOfTheMindPredicate implements ObjectSourcePlayerPredicate> { +class KeeperOfTheMindPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/k/KelpieGuide.java b/Mage.Sets/src/mage/cards/k/KelpieGuide.java index 71dc527d5ee..9cd5913574b 100644 --- a/Mage.Sets/src/mage/cards/k/KelpieGuide.java +++ b/Mage.Sets/src/mage/cards/k/KelpieGuide.java @@ -19,6 +19,7 @@ import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledLandPermanent; import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.TargetPermanent; import java.util.UUID; @@ -34,6 +35,10 @@ public final class KelpieGuide extends CardImpl { = new FilterControlledPermanent("another target permanent you control"); private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter, ComparisonType.MORE_THAN, 7); + + static { + filter2.add(AnotherPredicate.instance); + } public KelpieGuide(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); diff --git a/Mage.Sets/src/mage/cards/k/KessigNaturalist.java b/Mage.Sets/src/mage/cards/k/KessigNaturalist.java index c9e52efc30b..bc9096b8f54 100644 --- a/Mage.Sets/src/mage/cards/k/KessigNaturalist.java +++ b/Mage.Sets/src/mage/cards/k/KessigNaturalist.java @@ -38,7 +38,7 @@ public final class KessigNaturalist extends CardImpl { // Daybound this.addAbility(new TransformAbility()); - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new DayboundAbility()); } private KessigNaturalist(final KessigNaturalist card) { diff --git a/Mage.Sets/src/mage/cards/k/KitesailSkirmisher.java b/Mage.Sets/src/mage/cards/k/KitesailSkirmisher.java index 38e8c6a1767..8c0a5231491 100644 --- a/Mage.Sets/src/mage/cards/k/KitesailSkirmisher.java +++ b/Mage.Sets/src/mage/cards/k/KitesailSkirmisher.java @@ -1,6 +1,5 @@ package mage.cards.k; -import com.google.common.base.Objects; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -22,6 +21,7 @@ import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.target.TargetPermanent; +import java.util.Objects; import java.util.UUID; /** @@ -69,12 +69,12 @@ public final class KitesailSkirmisher extends CardImpl { } } -enum KitesailSkirmisherPredicate implements ObjectSourcePlayerPredicate> { +enum KitesailSkirmisherPredicate implements ObjectSourcePlayerPredicate { instance; @Override public boolean apply(ObjectSourcePlayer input, Game game) { - return Objects.equal( + return Objects.equals( game.getCombat().getDefenderId(input.getSourceId()), game.getCombat().getDefenderId(input.getObject().getId()) ); diff --git a/Mage.Sets/src/mage/cards/k/KnowledgePool.java b/Mage.Sets/src/mage/cards/k/KnowledgePool.java index c4af8384624..c813eea3cb1 100644 --- a/Mage.Sets/src/mage/cards/k/KnowledgePool.java +++ b/Mage.Sets/src/mage/cards/k/KnowledgePool.java @@ -157,7 +157,9 @@ class KnowledgePoolEffect2 extends OneShotEffect { if (player.choose(Outcome.PlayForFree, game.getExile().getExileZone(exileZoneId), target, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null && !card.getId().equals(spell.getSourceId())) { - player.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + player.cast(player.chooseAbilityForCast(card, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); } } } diff --git a/Mage.Sets/src/mage/cards/k/KrosanReclamation.java b/Mage.Sets/src/mage/cards/k/KrosanReclamation.java index fb95f86803c..56730558301 100644 --- a/Mage.Sets/src/mage/cards/k/KrosanReclamation.java +++ b/Mage.Sets/src/mage/cards/k/KrosanReclamation.java @@ -26,7 +26,7 @@ public final class KrosanReclamation extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInTargetPlayersGraveyard(2)); // Flashback {1}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{G}"))); } private KrosanReclamation(final KrosanReclamation card) { diff --git a/Mage.Sets/src/mage/cards/k/KumenasSpeaker.java b/Mage.Sets/src/mage/cards/k/KumenasSpeaker.java index 9626941ed44..0e3521712e5 100644 --- a/Mage.Sets/src/mage/cards/k/KumenasSpeaker.java +++ b/Mage.Sets/src/mage/cards/k/KumenasSpeaker.java @@ -48,7 +48,7 @@ public final class KumenasSpeaker extends CardImpl { } } -enum KumenasSpeakerPredicate implements ObjectSourcePlayerPredicate> { +enum KumenasSpeakerPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/k/KurbisHarvestCelebrant.java b/Mage.Sets/src/mage/cards/k/KurbisHarvestCelebrant.java new file mode 100644 index 00000000000..e52cdc8e959 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KurbisHarvestCelebrant.java @@ -0,0 +1,70 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.dynamicvalue.common.ManaSpentToCastCount; +import mage.abilities.effects.common.PreventDamageToTargetEffect; +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.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KurbisHarvestCelebrant extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("another creature with a +1/+1 counter on it"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(CounterType.P1P1.getPredicate()); + } + + public KurbisHarvestCelebrant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}{G}{G}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.TREEFOLK); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Kurbis, Harvest Celebrant enters the battlefield with a number of +1/+1 counters on it equal to the amount of mana spent to cast it. + this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect( + CounterType.P1P1.createInstance(), ManaSpentToCastCount.instance, true + ), "with a number of +1/+1 counters on it equal to the amount of mana spent to cast it")); + + // Remove a +1/+1 counter from Kurbis: Prevent all damage that would be dealt this turn to another target creature with a +1/+1 counter on it. + Ability ability = new SimpleActivatedAbility( + new PreventDamageToTargetEffect(Duration.EndOfTurn) + .setText("prevent all damage that would be dealt this turn " + + "to another target creature with a +1/+1 counter on it"), + new RemoveCountersSourceCost(CounterType.P1P1.createInstance()) + ); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private KurbisHarvestCelebrant(final KurbisHarvestCelebrant card) { + super(card); + } + + @Override + public KurbisHarvestCelebrant copy() { + return new KurbisHarvestCelebrant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KylerSigardianEmissary.java b/Mage.Sets/src/mage/cards/k/KylerSigardianEmissary.java new file mode 100644 index 00000000000..1bab0e90675 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KylerSigardianEmissary.java @@ -0,0 +1,121 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.Counter; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KylerSigardianEmissary extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent(SubType.HUMAN, "another Human"); + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent(SubType.HUMAN, ""); + + static { + filter.add(AnotherPredicate.instance); + filter2.add(AnotherPredicate.instance); + filter2.add(TargetController.YOU.getControllerPredicate()); + } + + public KylerSigardianEmissary(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever another Human enters the battlefield under your control, put a +1/+1 counter on Kyler, Sigardian Emissary. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter + )); + + // Other Humans you control get +1/+1 for each counter on Kyler, Sigardian Emissary. + this.addAbility(new SimpleStaticAbility(new BoostAllEffect( + KylerSigardianEmissaryValue.instance, KylerSigardianEmissaryValue.instance, + Duration.WhileOnBattlefield, filter2, true, + "Other Humans you control get +1/+1 for each counter on {this}" + )).addHint(KylerSigardianEmissaryHint.instance)); + } + + private KylerSigardianEmissary(final KylerSigardianEmissary card) { + super(card); + } + + @Override + public KylerSigardianEmissary copy() { + return new KylerSigardianEmissary(this); + } +} + +enum KylerSigardianEmissaryValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Permanent permanent = sourceAbility.getSourcePermanentIfItStillExists(game); + return permanent != null + ? permanent + .getCounters(game) + .values() + .stream() + .mapToInt(Counter::getCount) + .sum() : 0; + } + + @Override + public KylerSigardianEmissaryValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } +} + +enum KylerSigardianEmissaryHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + Permanent permanent = ability.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + return null; + } + return "Counters on " + permanent.getName() + ": " + + permanent + .getCounters(game) + .values() + .stream() + .mapToInt(Counter::getCount) + .sum(); + } + + @Override + public KylerSigardianEmissaryHint copy() { + return this; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LarderZombie.java b/Mage.Sets/src/mage/cards/l/LarderZombie.java new file mode 100644 index 00000000000..4f591aed0cf --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LarderZombie.java @@ -0,0 +1,86 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.DefenderAbility; +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.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LarderZombie extends CardImpl { + + public LarderZombie(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}"); + + this.subtype.add(SubType.ZOMBIE); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + + // Tap three untapped creatures you control: Look at the top card of your library. You may put it into your graveyard. + this.addAbility(new SimpleActivatedAbility( + new LarderZombieEffect(), + new TapTargetCost(new TargetControlledPermanent( + 3, StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURES + )) + )); + } + + private LarderZombie(final LarderZombie card) { + super(card); + } + + @Override + public LarderZombie copy() { + return new LarderZombie(this); + } +} + +class LarderZombieEffect extends OneShotEffect { + + LarderZombieEffect() { + super(Outcome.Benefit); + staticText = "look at the top card of your library. You may put it into your graveyard"; + } + + private LarderZombieEffect(final LarderZombieEffect effect) { + super(effect); + } + + @Override + public LarderZombieEffect copy() { + return new LarderZombieEffect(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); + player.lookAtCards("Top card of your library", card, game); + if (player.chooseUse(Outcome.AIDontUseIt, "Put the top card of your library into your graveyard?", source, game)) { + player.moveCards(card, Zone.GRAVEYARD, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LavaDart.java b/Mage.Sets/src/mage/cards/l/LavaDart.java index b6228d7b6db..8842cc5176a 100644 --- a/Mage.Sets/src/mage/cards/l/LavaDart.java +++ b/Mage.Sets/src/mage/cards/l/LavaDart.java @@ -34,7 +34,7 @@ public final class LavaDart extends CardImpl { this.getSpellAbility().addTarget(new TargetAnyTarget()); // Flashback-Sacrifice a Mountain. - this.addAbility(new FlashbackAbility(new SacrificeTargetCost(new TargetControlledPermanent(filter)), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new SacrificeTargetCost(new TargetControlledPermanent(filter)))); } private LavaDart(final LavaDart card) { diff --git a/Mage.Sets/src/mage/cards/l/LeafCrownedElder.java b/Mage.Sets/src/mage/cards/l/LeafCrownedElder.java index 4369e88b393..370a1db7352 100644 --- a/Mage.Sets/src/mage/cards/l/LeafCrownedElder.java +++ b/Mage.Sets/src/mage/cards/l/LeafCrownedElder.java @@ -63,7 +63,7 @@ class LeafCrownedElderPlayEffect extends OneShotEffect { Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (controller != null && card != null) { if (controller.chooseUse(Outcome.PlayForFree, "Play " + card.getIdName() + " without paying its mana cost?", source, game)) { - controller.playCard(card, game, true, true, new ApprovingObject(source, game)); + controller.playCard(card, game, true, new ApprovingObject(source, game)); } return true; } diff --git a/Mage.Sets/src/mage/cards/l/Legerdemain.java b/Mage.Sets/src/mage/cards/l/Legerdemain.java index 89cff853c42..bf3f729578f 100644 --- a/Mage.Sets/src/mage/cards/l/Legerdemain.java +++ b/Mage.Sets/src/mage/cards/l/Legerdemain.java @@ -55,7 +55,7 @@ public final class Legerdemain extends CardImpl { } } -class SharesTypePredicate implements ObjectSourcePlayerPredicate> { +class SharesTypePredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/l/LeinoreAutumnSovereign.java b/Mage.Sets/src/mage/cards/l/LeinoreAutumnSovereign.java new file mode 100644 index 00000000000..3621947141f --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LeinoreAutumnSovereign.java @@ -0,0 +1,54 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.condition.common.CovenCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.CovenHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LeinoreAutumnSovereign extends CardImpl { + + public LeinoreAutumnSovereign(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Coven — At the beginning of combat on your turn, put a +1/+1 counter on up to one target creature you control. Then if you control three or more creatures with different powers, draw a card. + Ability ability = new BeginningOfCombatTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + TargetController.YOU, false + ); + ability.addEffect(new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(1), CovenCondition.instance, + "Then if you control three or more creatures with different powers, draw a card" + )); + ability.addTarget(new TargetControlledCreaturePermanent(0, 1)); + this.addAbility(ability.addHint(CovenHint.instance).setAbilityWord(AbilityWord.COVEN)); + } + + private LeinoreAutumnSovereign(final LeinoreAutumnSovereign card) { + super(card); + } + + @Override + public LeinoreAutumnSovereign copy() { + return new LeinoreAutumnSovereign(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LierDiscipleOfTheDrowned.java b/Mage.Sets/src/mage/cards/l/LierDiscipleOfTheDrowned.java index 87af1a11a5f..2eb3748b1a3 100644 --- a/Mage.Sets/src/mage/cards/l/LierDiscipleOfTheDrowned.java +++ b/Mage.Sets/src/mage/cards/l/LierDiscipleOfTheDrowned.java @@ -104,9 +104,7 @@ class LierDiscipleOfTheDrownedFlashbackEffect extends ContinuousEffectImpl { return false; } for (Card card : player.getGraveyard().getCards(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, game)) { - Ability ability = new FlashbackAbility( - card.getManaCost(), card.isInstant(game) ? TimingRule.INSTANT : TimingRule.SORCERY - ); + Ability ability = new FlashbackAbility(card, card.getManaCost()); ability.setSourceId(card.getId()); ability.setControllerId(card.getOwnerId()); game.getState().addOtherAbility(card, ability); diff --git a/Mage.Sets/src/mage/cards/l/LiesaForgottenArchangel.java b/Mage.Sets/src/mage/cards/l/LiesaForgottenArchangel.java new file mode 100644 index 00000000000..e2c159ad2d4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LiesaForgottenArchangel.java @@ -0,0 +1,139 @@ +package mage.cards.l; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.constants.*; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author weirddan455 + */ +public final class LiesaForgottenArchangel extends CardImpl { + + private final static FilterControlledCreaturePermanent filter + = new FilterControlledCreaturePermanent("another nontoken creature you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TokenPredicate.FALSE); + } + + public LiesaForgottenArchangel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{W}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever another nontoken creature you control dies, return that card to its owner's hand at the beginning of the next end step. + this.addAbility(new DiesCreatureTriggeredAbility(new LiesaForgottenArchangelReturnToHandEffect(), false, filter, true)); + + // If a creature an opponent controls would die, exile it instead. + this.addAbility(new SimpleStaticAbility(new LiesaForgottenArchangelReplacementEffect())); + } + + private LiesaForgottenArchangel(final LiesaForgottenArchangel card) { + super(card); + } + + @Override + public LiesaForgottenArchangel copy() { + return new LiesaForgottenArchangel(this); + } +} + +class LiesaForgottenArchangelReturnToHandEffect extends OneShotEffect { + + public LiesaForgottenArchangelReturnToHandEffect() { + super(Outcome.ReturnToHand); + staticText = "return that card to its owner's hand at the beginning of the next end step"; + } + + private LiesaForgottenArchangelReturnToHandEffect(final LiesaForgottenArchangelReturnToHandEffect effect) { + super(effect); + } + + @Override + public LiesaForgottenArchangelReturnToHandEffect copy() { + return new LiesaForgottenArchangelReturnToHandEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Effect effect = new ReturnToHandTargetEffect(); + effect.setText("return that card to its owner's hand"); + effect.setTargetPointer(targetPointer); + DelayedTriggeredAbility ability = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); + game.addDelayedTriggeredAbility(ability, source); + return true; + } +} + +class LiesaForgottenArchangelReplacementEffect extends ReplacementEffectImpl { + + public LiesaForgottenArchangelReplacementEffect() { + super(Duration.WhileOnBattlefield, Outcome.Exile); + staticText = "If a creature an opponent controls would die, exile it instead"; + } + + private LiesaForgottenArchangelReplacementEffect(final LiesaForgottenArchangelReplacementEffect effect) { + super(effect); + } + + @Override + public LiesaForgottenArchangelReplacementEffect copy() { + return new LiesaForgottenArchangelReplacementEffect(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; + if (zEvent.isDiesEvent()) { + Permanent permanent = zEvent.getTarget(); + if (permanent != null && permanent.isCreature()) { + Player player = game.getPlayer(source.getControllerId()); + return player != null && player.hasOpponent(permanent.getControllerId(), game); + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LightUpTheNight.java b/Mage.Sets/src/mage/cards/l/LightUpTheNight.java new file mode 100644 index 00000000000..29fb8a01c09 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LightUpTheNight.java @@ -0,0 +1,89 @@ +package mage.cards.l; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.costs.common.RemoveVariableCountersTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetAnyTarget; + +/** + * + * @author weirddan455 + */ +public final class LightUpTheNight extends CardImpl { + + public LightUpTheNight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}"); + + // Light Up the Night deals X damage to any target. It deals X plus 1 damage instead if that target is a creature or planeswalker. + this.getSpellAbility().addEffect(new LightUpTheNightEffect()); + this.getSpellAbility().addTarget(new TargetAnyTarget()); + + // Flashback—{3}{R}, Remove X loyalty counters from among planeswalkers you control. If you cast this spell this way, X can't be 0. + Ability ability = new FlashbackAbility(this, new ManaCostsImpl<>("{3}{R}")); + ability.addCost(new RemoveVariableCountersTargetCost( + StaticFilters.FILTER_CONTROLLED_PERMANENT_PLANESWALKER, CounterType.LOYALTY, "X", 1, + "Remove X loyalty counters from among planeswalkers you control. If you cast this spell this way, X can't be 0" + )); + this.addAbility(ability); + } + + private LightUpTheNight(final LightUpTheNight card) { + super(card); + } + + @Override + public LightUpTheNight copy() { + return new LightUpTheNight(this); + } +} + +class LightUpTheNightEffect extends OneShotEffect { + + public LightUpTheNightEffect() { + super(Outcome.Damage); + staticText = "{this} deals X damage to any target. It deals X plus 1 damage instead if that target is a creature or planeswalker"; + } + + private LightUpTheNightEffect(final LightUpTheNightEffect effect) { + super(effect); + } + + @Override + public LightUpTheNightEffect copy() { + return new LightUpTheNightEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + // Normal cast + int damage = source.getManaCostsToPay().getX(); + // Flashback cast + damage += GetXValue.instance.calculate(game, source, this); + UUID targetId = source.getFirstTarget(); + Player player = game.getPlayer(targetId); + if (player != null) { + player.damage(damage, source.getSourceId(), source, game); + return true; + } + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + permanent.damage(damage + 1, source.getSourceId(), source, game); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LightningSurge.java b/Mage.Sets/src/mage/cards/l/LightningSurge.java index 3d2aec4148a..0c2bdf6f2e5 100644 --- a/Mage.Sets/src/mage/cards/l/LightningSurge.java +++ b/Mage.Sets/src/mage/cards/l/LightningSurge.java @@ -33,7 +33,7 @@ public final class LightningSurge extends CardImpl { this.getSpellAbility().addEffect(effect); // Flashback {5}{R}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{R}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{R}{R}"))); } private LightningSurge(final LightningSurge card) { diff --git a/Mage.Sets/src/mage/cards/l/LingeringSouls.java b/Mage.Sets/src/mage/cards/l/LingeringSouls.java index 51d53922b21..109d50883ae 100644 --- a/Mage.Sets/src/mage/cards/l/LingeringSouls.java +++ b/Mage.Sets/src/mage/cards/l/LingeringSouls.java @@ -23,7 +23,7 @@ public final class LingeringSouls extends CardImpl { // Create two 1/1 white Spirit creature tokens with flying. this.getSpellAbility().addEffect(new CreateTokenEffect(new SpiritWhiteToken(), 2)); // Flashback {1}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{B}"))); } private LingeringSouls(final LingeringSouls card) { diff --git a/Mage.Sets/src/mage/cards/l/LockedInTheCemetery.java b/Mage.Sets/src/mage/cards/l/LockedInTheCemetery.java new file mode 100644 index 00000000000..a292808439a --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LockedInTheCemetery.java @@ -0,0 +1,61 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +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.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LockedInTheCemetery extends CardImpl { + + private static final Condition condition = new CardsInControllerGraveyardCondition(5); + + public LockedInTheCemetery(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // When Locked in the Cemetery enters the battlefield, if there are five or more cards in your graveyard, tap enchanted creature. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new TapEnchantedEffect()), + condition, "When {this} enters the battlefield, if there are " + + "five or more cards in your graveyard, tap enchanted creature." + )); + + // Enchanted creature doesn't untap during its controller's untap step. + this.addAbility(new SimpleStaticAbility(new DontUntapInControllersUntapStepEnchantedEffect())); + } + + private LockedInTheCemetery(final LockedInTheCemetery card) { + super(card); + } + + @Override + public LockedInTheCemetery copy() { + return new LockedInTheCemetery(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LordOfTheForsaken.java b/Mage.Sets/src/mage/cards/l/LordOfTheForsaken.java new file mode 100644 index 00000000000..04041271e1e --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LordOfTheForsaken.java @@ -0,0 +1,111 @@ +package mage.cards.l; + +import mage.ConditionalMana; +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.mana.ConditionalColorlessManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +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.game.stack.Spell; +import mage.target.TargetPlayer; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LordOfTheForsaken extends CardImpl { + + public LordOfTheForsaken(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add(SubType.DEMON); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // {B}, Sacrifice another creature: Target player mills three cards. + Ability ability = new SimpleActivatedAbility( + new MillCardsTargetEffect(3), new ManaCostsImpl<>("{B}") + ); + ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent( + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE + ))); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + + // Pay 1 life: Add {C}. Spend this mana only to cast a spell from your graveyard. + this.addAbility(new ConditionalColorlessManaAbility( + new PayLifeCost(1), 1, new LordOfTheForsakenManaBuilder() + )); + } + + private LordOfTheForsaken(final LordOfTheForsaken card) { + super(card); + } + + @Override + public LordOfTheForsaken copy() { + return new LordOfTheForsaken(this); + } +} + +class LordOfTheForsakenManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new LordOfTheForsakenConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to cast a spell from a graveyard"; + } +} + +class LordOfTheForsakenConditionalMana extends ConditionalMana { + + public LordOfTheForsakenConditionalMana(Mana mana) { + super(mana); + staticText = "Spend this mana only to cast a spell from a graveyard"; + addCondition(LordOfTheForsakenManaCondition.instance); + } +} + +enum LordOfTheForsakenManaCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + if (game == null || !game.inCheckPlayableState()) { + return false; + } + if (game.getCard(source.getSourceId()) != null + && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { + return true; + } + Spell spell = game.getSpell(source.getSourceId()); + return spell != null && spell.getFromZone() == Zone.GRAVEYARD; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LordOfTheUlvenwald.java b/Mage.Sets/src/mage/cards/l/LordOfTheUlvenwald.java index 4bd34e1408c..05f56567b31 100644 --- a/Mage.Sets/src/mage/cards/l/LordOfTheUlvenwald.java +++ b/Mage.Sets/src/mage/cards/l/LordOfTheUlvenwald.java @@ -56,7 +56,7 @@ public final class LordOfTheUlvenwald extends CardImpl { this.addAbility(new AttacksTriggeredAbility(new LordOfTheUlvenwaldEffect())); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private LordOfTheUlvenwald(final LordOfTheUlvenwald card) { diff --git a/Mage.Sets/src/mage/cards/l/LuminousPhantom.java b/Mage.Sets/src/mage/cards/l/LuminousPhantom.java new file mode 100644 index 00000000000..8819843a1ba --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LuminousPhantom.java @@ -0,0 +1,53 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility; +import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; +import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LuminousPhantom extends CardImpl { + + public LuminousPhantom(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + this.color.setWhite(true); + this.transformable = true; + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever another creature you control leaves the battlefield, you gain 1 life. + this.addAbility(new LeavesBattlefieldAllTriggeredAbility( + new GainLifeEffect(1), StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE + )); + + // If Luminous Phantom would be put into a graveyard from anywhere, exile it instead. + this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + } + + private LuminousPhantom(final LuminousPhantom card) { + super(card); + } + + @Override + public LuminousPhantom copy() { + return new LuminousPhantom(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LunarchVeteran.java b/Mage.Sets/src/mage/cards/l/LunarchVeteran.java new file mode 100644 index 00000000000..61596d263f3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LunarchVeteran.java @@ -0,0 +1,50 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.TransformAbility; +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 LunarchVeteran extends CardImpl { + + public LunarchVeteran(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + this.transformable = true; + this.secondSideCardClazz = mage.cards.l.LuminousPhantom.class; + + // Whenever another creature enters the battlefield under your control, you gain 1 life. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + new GainLifeEffect(1), StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE + )); + + // Disturb {1}{W} + this.addAbility(new TransformAbility()); + this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{1}{W}"))); + } + + private LunarchVeteran(final LunarchVeteran card) { + super(card); + } + + @Override + public LunarchVeteran copy() { + return new LunarchVeteran(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MaceWindu.java b/Mage.Sets/src/mage/cards/m/MaceWindu.java index 94b36eb6da7..6749bb9c43b 100644 --- a/Mage.Sets/src/mage/cards/m/MaceWindu.java +++ b/Mage.Sets/src/mage/cards/m/MaceWindu.java @@ -26,7 +26,8 @@ public final class MaceWindu extends CardImpl { private static final FilterSpellOrPermanent filter = new FilterSpellOrPermanent("spell or creature you don't control"); static { - filter.add(TargetController.NOT_YOU.getControllerPredicate()); + filter.getPermanentFilter().add(TargetController.NOT_YOU.getControllerPredicate()); + filter.getSpellFilter().add(TargetController.NOT_YOU.getControllerPredicate()); } public MaceWindu(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/m/MaddeningHex.java b/Mage.Sets/src/mage/cards/m/MaddeningHex.java index fcb9610174a..d1bf2ec0e8c 100644 --- a/Mage.Sets/src/mage/cards/m/MaddeningHex.java +++ b/Mage.Sets/src/mage/cards/m/MaddeningHex.java @@ -131,9 +131,11 @@ class MaddeningHexEffect extends OneShotEffect { if (player != null) { opponents.remove(player.getId()); } - if (!opponents.isEmpty()) { - permanent.attachTo(RandomUtil.randomFromCollection(opponents), source, game); + Player opponent = game.getPlayer(RandomUtil.randomFromCollection(opponents)); + if (opponent == null) { + return true; } + opponent.addAttachment(permanent.getId(), source, game); return true; } } diff --git a/Mage.Sets/src/mage/cards/m/MairsilThePretender.java b/Mage.Sets/src/mage/cards/m/MairsilThePretender.java index c1e0146f003..7329c29521e 100644 --- a/Mage.Sets/src/mage/cards/m/MairsilThePretender.java +++ b/Mage.Sets/src/mage/cards/m/MairsilThePretender.java @@ -1,7 +1,5 @@ package mage.cards.m; -import java.util.Objects; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; @@ -12,14 +10,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; @@ -29,9 +20,12 @@ import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.UUID; /** - * * @author TheElk801 */ public final class MairsilThePretender extends CardImpl { @@ -90,27 +84,27 @@ class MairsilThePretenderExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - if (controller.chooseUse(Outcome.PutCardInPlay, "Exile a card from your hand? (No = from graveyard)", source, game)) { - Target target = new TargetCardInHand(0, 1, filter); - controller.choose(outcome, target, source.getSourceId(), game); - Card card = controller.getHand().get(target.getFirstTarget(), game); - if (card != null) { - controller.moveCards(card, Zone.EXILED, source, game); - card.addCounters(CounterType.CAGE.createInstance(), source.getControllerId(), source, game); - } - } else { - Target target = new TargetCardInYourGraveyard(0, 1, filter); - target.choose(Outcome.PutCardInPlay, source.getControllerId(), source.getSourceId(), game); - Card card = controller.getGraveyard().get(target.getFirstTarget(), game); - if (card != null) { - controller.moveCards(card, Zone.EXILED, source, game); - card.addCounters(CounterType.CAGE.createInstance(), source.getControllerId(), source, game); - } - } - return true; + if (controller == null) { + return false; } - return false; + + // Outcome.Detriment - AI must exile from grave only, not hand + Target target; + if (controller.chooseUse(Outcome.Detriment, "Exile a card from your hand? (No = from graveyard)", source, game)) { + // from hand + target = new TargetCardInHand(0, 1, filter); + controller.choose(outcome, target, source.getSourceId(), game); + } else { + // from graveyard + target = new TargetCardInYourGraveyard(0, 1, filter); + target.choose(outcome, source.getControllerId(), source.getSourceId(), game); + } + + Card card = controller.getHand().get(target.getFirstTarget(), game); + if (card != null) { + CardUtil.moveCardWithCounter(game, source, controller, card, Zone.EXILED, CounterType.CAGE.createInstance()); + } + return true; } } diff --git a/Mage.Sets/src/mage/cards/m/MalevolentHermit.java b/Mage.Sets/src/mage/cards/m/MalevolentHermit.java new file mode 100644 index 00000000000..743e168f114 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MalevolentHermit.java @@ -0,0 +1,57 @@ +package mage.cards.m; + +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.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CounterUnlessPaysEffect; +import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MalevolentHermit extends CardImpl { + + public MalevolentHermit(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(2); + this.toughness = new MageInt(1); + this.transformable = true; + this.secondSideCardClazz = mage.cards.b.BenevolentGeist.class; + + // {U}, Sacrifice Malevolent Hermit: Counter target noncreature spell unless its controller pays {3}. + Ability ability = new SimpleActivatedAbility( + new CounterUnlessPaysEffect(new GenericManaCost(3)), new ManaCostsImpl<>("{U}") + ); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetSpell(StaticFilters.FILTER_SPELL_NON_CREATURE)); + this.addAbility(ability); + + // Disturb {2}{U} + this.addAbility(new TransformAbility()); + this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{2}{U}"))); + } + + private MalevolentHermit(final MalevolentHermit card) { + super(card); + } + + @Override + public MalevolentHermit copy() { + return new MalevolentHermit(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MalevolentNoble.java b/Mage.Sets/src/mage/cards/m/MalevolentNoble.java index 611ecf4f3b9..71c8dec34a5 100644 --- a/Mage.Sets/src/mage/cards/m/MalevolentNoble.java +++ b/Mage.Sets/src/mage/cards/m/MalevolentNoble.java @@ -58,7 +58,7 @@ public final class MalevolentNoble extends CardImpl { } } -enum MalevolentNoblePredicate implements ObjectSourcePlayerPredicate> { +enum MalevolentNoblePredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/m/MarshalingCry.java b/Mage.Sets/src/mage/cards/m/MarshalingCry.java index e02d033bcd3..7a2535de656 100644 --- a/Mage.Sets/src/mage/cards/m/MarshalingCry.java +++ b/Mage.Sets/src/mage/cards/m/MarshalingCry.java @@ -36,7 +36,7 @@ public final class MarshalingCry extends CardImpl { this.addAbility(new CyclingAbility(new ManaCostsImpl("{2}"))); // Flashback {3}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{W}"))); } private MarshalingCry(final MarshalingCry card) { diff --git a/Mage.Sets/src/mage/cards/m/MartialImpetus.java b/Mage.Sets/src/mage/cards/m/MartialImpetus.java index e251becd38c..a57193f549a 100644 --- a/Mage.Sets/src/mage/cards/m/MartialImpetus.java +++ b/Mage.Sets/src/mage/cards/m/MartialImpetus.java @@ -64,7 +64,7 @@ public final class MartialImpetus extends CardImpl { } } -enum MartialImpetusPredicate implements ObjectSourcePlayerPredicate> { +enum MartialImpetusPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/m/MassDiminish.java b/Mage.Sets/src/mage/cards/m/MassDiminish.java index d11f660cbee..4cc90e77d00 100644 --- a/Mage.Sets/src/mage/cards/m/MassDiminish.java +++ b/Mage.Sets/src/mage/cards/m/MassDiminish.java @@ -31,7 +31,7 @@ public final class MassDiminish extends CardImpl { this.getSpellAbility().addTarget(new TargetPlayer()); // Flashback {3}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{U}"))); } private MassDiminish(final MassDiminish card) { diff --git a/Mage.Sets/src/mage/cards/m/MemoryDeluge.java b/Mage.Sets/src/mage/cards/m/MemoryDeluge.java new file mode 100644 index 00000000000..76bd75248d6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MemoryDeluge.java @@ -0,0 +1,44 @@ +package mage.cards.m; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.ManaSpentToCastCount; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MemoryDeluge extends CardImpl { + + public MemoryDeluge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}{U}"); + + // Look at the top X cards of your library, where X is the amount of mana spent to cast this spell. Put two of them into your hand and the rest on the bottom of your library in a random order. + this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect( + ManaSpentToCastCount.instance, false, StaticValue.get(2), + StaticFilters.FILTER_CARD, Zone.LIBRARY, false, false + ).setBackInRandomOrder(true).setText("look at the top X cards of your library, where X " + + "is the amount of mana spent to cast this spell. Put two of them into your " + + "hand and the rest on the bottom of your library in a random order")); + + // Flashback {5}{U}{U} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{5}{U}{U}"))); + } + + private MemoryDeluge(final MemoryDeluge card) { + super(card); + } + + @Override + public MemoryDeluge copy() { + return new MemoryDeluge(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MemorysJourney.java b/Mage.Sets/src/mage/cards/m/MemorysJourney.java index cd3dba5db43..9ed78043b07 100644 --- a/Mage.Sets/src/mage/cards/m/MemorysJourney.java +++ b/Mage.Sets/src/mage/cards/m/MemorysJourney.java @@ -26,7 +26,7 @@ public final class MemorysJourney extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInTargetPlayersGraveyard(3)); // Flashback {G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{G}"))); } private MemorysJourney(final MemorysJourney card) { diff --git a/Mage.Sets/src/mage/cards/m/MightOfTheOldWays.java b/Mage.Sets/src/mage/cards/m/MightOfTheOldWays.java index e56f49a0142..a340f3c8182 100644 --- a/Mage.Sets/src/mage/cards/m/MightOfTheOldWays.java +++ b/Mage.Sets/src/mage/cards/m/MightOfTheOldWays.java @@ -9,6 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.AbilityWord; import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -22,6 +23,7 @@ public final class MightOfTheOldWays extends CardImpl { // Target creature gets +2/+2 until end of turn. this.getSpellAbility().addEffect(new BoostTargetEffect(2, 2)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Coven — Then if you control three or more creatures with different powers, draw a card. this.getSpellAbility().addEffect(new ConditionalOneShotEffect( diff --git a/Mage.Sets/src/mage/cards/m/MirrorSheen.java b/Mage.Sets/src/mage/cards/m/MirrorSheen.java index 4f59ebf5c6d..c77f0049e60 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorSheen.java +++ b/Mage.Sets/src/mage/cards/m/MirrorSheen.java @@ -11,8 +11,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; import mage.filter.FilterSpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.stack.StackObject; @@ -53,10 +53,10 @@ public final class MirrorSheen extends CardImpl { } } -class TargetYouPredicate implements ObjectPlayerPredicate> { +class TargetYouPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { UUID controllerId = input.getPlayerId(); if (controllerId == null) { return false; diff --git a/Mage.Sets/src/mage/cards/m/Mistfolk.java b/Mage.Sets/src/mage/cards/m/Mistfolk.java index 0a6e5a971a1..f4b6c58b68b 100644 --- a/Mage.Sets/src/mage/cards/m/Mistfolk.java +++ b/Mage.Sets/src/mage/cards/m/Mistfolk.java @@ -60,7 +60,7 @@ public final class Mistfolk extends CardImpl { } } -enum MistfolkPredicate implements ObjectSourcePlayerPredicate> { +enum MistfolkPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/m/MistformWarchief.java b/Mage.Sets/src/mage/cards/m/MistformWarchief.java index 4d71ee79c51..bc6260c717f 100644 --- a/Mage.Sets/src/mage/cards/m/MistformWarchief.java +++ b/Mage.Sets/src/mage/cards/m/MistformWarchief.java @@ -58,7 +58,7 @@ public final class MistformWarchief extends CardImpl { } } -class MistformWarchiefPredicate implements ObjectSourcePlayerPredicate> { +class MistformWarchiefPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/m/MoanOfTheUnhallowed.java b/Mage.Sets/src/mage/cards/m/MoanOfTheUnhallowed.java index 3e59ada135b..4b3ccb090c7 100644 --- a/Mage.Sets/src/mage/cards/m/MoanOfTheUnhallowed.java +++ b/Mage.Sets/src/mage/cards/m/MoanOfTheUnhallowed.java @@ -24,7 +24,7 @@ public final class MoanOfTheUnhallowed extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieToken(), 2)); // Flashback {5}{B}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{B}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{B}{B}"))); } private MoanOfTheUnhallowed(final MoanOfTheUnhallowed card) { diff --git a/Mage.Sets/src/mage/cards/m/MomentaryBlink.java b/Mage.Sets/src/mage/cards/m/MomentaryBlink.java index b1e127743e1..16da3515398 100644 --- a/Mage.Sets/src/mage/cards/m/MomentaryBlink.java +++ b/Mage.Sets/src/mage/cards/m/MomentaryBlink.java @@ -26,7 +26,7 @@ public final class MomentaryBlink extends CardImpl { this.getSpellAbility().addEffect(new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false)); // Flashback {3}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{U}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{U}"))); } private MomentaryBlink(final MomentaryBlink card) { diff --git a/Mage.Sets/src/mage/cards/m/MomentsPeace.java b/Mage.Sets/src/mage/cards/m/MomentsPeace.java index ffc81e051a8..6fa6f598d54 100644 --- a/Mage.Sets/src/mage/cards/m/MomentsPeace.java +++ b/Mage.Sets/src/mage/cards/m/MomentsPeace.java @@ -24,7 +24,7 @@ public final class MomentsPeace extends CardImpl { this.getSpellAbility().addEffect(new PreventAllDamageByAllPermanentsEffect(Duration.EndOfTurn, true)); // Flashback {2}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{G}"))); } private MomentsPeace(final MomentsPeace card) { diff --git a/Mage.Sets/src/mage/cards/m/MonkClass.java b/Mage.Sets/src/mage/cards/m/MonkClass.java index b603c85c54e..f756912b88f 100644 --- a/Mage.Sets/src/mage/cards/m/MonkClass.java +++ b/Mage.Sets/src/mage/cards/m/MonkClass.java @@ -23,6 +23,7 @@ import mage.game.Game; import mage.players.Player; import mage.target.common.TargetNonlandPermanent; import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; import mage.watchers.common.SpellsCastWatcher; import java.util.UUID; @@ -114,7 +115,7 @@ class MonkClassEffect extends OneShotEffect { } player.moveCards(card, Zone.EXILED, source, game); game.addEffect(new GainAbilityTargetEffect( - new SimpleStaticAbility(new MonkClassCastEffect()), + new SimpleStaticAbility(Zone.EXILED, new MonkClassCastEffect()), Duration.Custom, null, true ).setTargetPointer(new FixedTarget(card, game)), source); return true; @@ -134,12 +135,13 @@ class MonkClassCastEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - if (!sourceId.equals(source.getSourceId()) || !source.isControlledBy(affectedControllerId)) { + UUID mainCardId = CardUtil.getMainCardId(game, sourceId); + if (!mainCardId.equals(source.getSourceId()) || !source.isControlledBy(affectedControllerId)) { return false; } - Card card = game.getCard(source.getSourceId()); + Card card = game.getCard(sourceId); SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); - return card != null && watcher != null + return card != null && watcher != null && !card.isLand(game) && watcher.getSpellsCastThisTurn(affectedControllerId).size() > 0; } diff --git a/Mage.Sets/src/mage/cards/m/MoonrageBrute.java b/Mage.Sets/src/mage/cards/m/MoonrageBrute.java new file mode 100644 index 00000000000..bbd90c57b43 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoonrageBrute.java @@ -0,0 +1,48 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.NightboundAbility; +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 MoonrageBrute extends CardImpl { + + public MoonrageBrute(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.WEREWOLF); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + this.color.setRed(true); + this.transformable = true; + this.nightCard = true; + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Ward—Pay 3 life. + this.addAbility(new WardAbility(new PayLifeCost(3))); + + // Nightbound + this.addAbility(new NightboundAbility()); + } + + private MoonrageBrute(final MoonrageBrute card) { + super(card); + } + + @Override + public MoonrageBrute copy() { + return new MoonrageBrute(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoonragersSlash.java b/Mage.Sets/src/mage/cards/m/MoonragersSlash.java new file mode 100644 index 00000000000..68b745297e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoonragersSlash.java @@ -0,0 +1,42 @@ +package mage.cards.m; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.NightCondition; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.hint.common.NightHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MoonragersSlash extends CardImpl { + + public MoonragersSlash(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + // This spell costs {2} less to cast if it's night. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(2, NightCondition.instance) + ).addHint(NightHint.instance)); + + // Moonrager's Slash deals 3 damage to any target. + this.getSpellAbility().addEffect(new DamageTargetEffect(3)); + this.getSpellAbility().addTarget(new TargetAnyTarget()); + } + + private MoonragersSlash(final MoonragersSlash card) { + super(card); + } + + @Override + public MoonragersSlash copy() { + return new MoonragersSlash(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoonveilRegent.java b/Mage.Sets/src/mage/cards/m/MoonveilRegent.java new file mode 100644 index 00000000000..b0e147769ec --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoonveilRegent.java @@ -0,0 +1,141 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.costs.common.DiscardHandCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.Hint; +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 mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class MoonveilRegent extends CardImpl { + + public MoonveilRegent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast a spell, you may discard your hand. If you do, draw a card for each of that spell's colors. + this.addAbility(new SpellCastControllerTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(MoonveilRegentSpellValue.instance) + .setText("draw a card for each of that spell's colors"), + new DiscardHandCost() + ), false)); + + // When Moonveil Regent dies, it deals X damage to any target, where X is the number of colors among permanents you control. + Ability ability = new DiesSourceTriggeredAbility(new DamageTargetEffect( + MoonveilRegentColorValue.instance + ).setText("it deals X damage to any target, where X is the number of colors among permanents you control")); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + } + + private MoonveilRegent(final MoonveilRegent card) { + super(card); + } + + @Override + public MoonveilRegent copy() { + return new MoonveilRegent(this); + } +} + +enum MoonveilRegentSpellValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Spell spell = (Spell) effect.getValue("spellCast"); + return spell != null ? spell.getColor(game).getColorCount() : 0; + } + + @Override + public MoonveilRegentSpellValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } +} + +enum MoonveilRegentColorValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return getColorUnion(game, sourceAbility).getColorCount(); + } + + static ObjectColor getColorUnion(Game game, Ability ability) { + ObjectColor color = new ObjectColor(); + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT, ability.getControllerId(), game + )) { + color.addColor(permanent.getColor(game)); + if (color.getColorCount() >= 5) { + break; + } + } + return color; + } + + @Override + public MoonveilRegentColorValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } +} + +enum MoonveilRegentHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + ObjectColor color = MoonveilRegentColorValue.getColorUnion(game, ability); + return "Colors among permanents you control: " + color.getColorCount() + ( + color.getColorCount() > 0 + ? color + .getColors() + .stream() + .map(ObjectColor::getDescription) + .collect(Collectors.joining(", ", " (", ")")) : "" + ); + } + + @Override + public MoonveilRegentHint copy() { + return this; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java b/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java new file mode 100644 index 00000000000..4b4d579ab2e --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java @@ -0,0 +1,135 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +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.common.FilterCreatureCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MoorlandRescuer extends CardImpl { + + public MoorlandRescuer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Moorland Rescuer dies, return any number of other creature cards with total power X or less from your graveyard to the battlefield, where X is Moorland Rescuer's power. Exile Moorland Rescuer. + this.addAbility(new DiesSourceTriggeredAbility(new MoorlandRescuerEffect())); + } + + private MoorlandRescuer(final MoorlandRescuer card) { + super(card); + } + + @Override + public MoorlandRescuer copy() { + return new MoorlandRescuer(this); + } +} + +class MoorlandRescuerEffect extends OneShotEffect { + + MoorlandRescuerEffect() { + super(Outcome.Benefit); + staticText = "return any number of other creature cards with total power X or less " + + "from your graveyard to the battlefield, where X is {this}'s power. Exile {this}"; + } + + private MoorlandRescuerEffect(final MoorlandRescuerEffect effect) { + super(effect); + } + + @Override + public MoorlandRescuerEffect copy() { + return new MoorlandRescuerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = (Permanent) getValue("permanentLeftBattlefield"); + if (player == null || permanent == null) { + return false; + } + TargetCard target = new MoorlandRescuerTarget(permanent.getPower().getValue(), source, game); + player.choose(outcome, player.getGraveyard(), target, game); + player.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); + Card sourceCard = (Card) source.getSourceObjectIfItStillExists(game); + if (sourceCard != null) { + player.moveCards(sourceCard, Zone.EXILED, source, game); + } + return true; + } +} + +class MoorlandRescuerTarget extends TargetCardInYourGraveyard { + + private final int xValue; + + MoorlandRescuerTarget(int xValue, Ability source, Game game) { + super(0, Integer.MAX_VALUE, makeFilter(xValue, source, game)); + this.xValue = xValue; + this.notTarget = true; + } + + private MoorlandRescuerTarget(final MoorlandRescuerTarget target) { + super(target); + this.xValue = target.xValue; + } + + @Override + public MoorlandRescuerTarget copy() { + return new MoorlandRescuerTarget(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); + if (card == null) { + return false; + } + int powerSum = this + .getTargets() + .stream() + .map(game::getCard) + .map(Card::getPower) + .mapToInt(MageInt::getValue) + .sum(); + return card.getPower().getValue() + powerSum <= xValue; + } + + private static FilterCard makeFilter(int xValue, Ability source, Game game) { + FilterCard filter = new FilterCreatureCard( + "creature cards with total power " + xValue + " or less from your graveyard" + ); + filter.add(Predicates.not(new MageObjectReferencePredicate(source.getSourceObject(game), game))); + return filter; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MorbidHunger.java b/Mage.Sets/src/mage/cards/m/MorbidHunger.java index ef554322be9..60453a85eee 100644 --- a/Mage.Sets/src/mage/cards/m/MorbidHunger.java +++ b/Mage.Sets/src/mage/cards/m/MorbidHunger.java @@ -27,7 +27,7 @@ public final class MorbidHunger extends CardImpl { this.getSpellAbility().addTarget(new TargetAnyTarget()); this.getSpellAbility().addEffect(new GainLifeEffect(3)); // Flashback {7}{B}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{7}{B}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{7}{B}{B}"))); } diff --git a/Mage.Sets/src/mage/cards/m/MorgueTheft.java b/Mage.Sets/src/mage/cards/m/MorgueTheft.java index 2063f2697ad..c2046bc60ca 100644 --- a/Mage.Sets/src/mage/cards/m/MorgueTheft.java +++ b/Mage.Sets/src/mage/cards/m/MorgueTheft.java @@ -24,7 +24,7 @@ public final class MorgueTheft extends CardImpl { this.getSpellAbility().addEffect(new ReturnFromGraveyardToHandTargetEffect()); this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE)); // Flashback {4}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{B}"))); } diff --git a/Mage.Sets/src/mage/cards/m/MorkrutBehemoth.java b/Mage.Sets/src/mage/cards/m/MorkrutBehemoth.java new file mode 100644 index 00000000000..4dd44b8fbbf --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MorkrutBehemoth.java @@ -0,0 +1,49 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.costs.OrCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MorkrutBehemoth extends CardImpl { + + public MorkrutBehemoth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.GIANT); + this.power = new MageInt(7); + this.toughness = new MageInt(6); + + // As an additional cost to cast this spell, sacrifice a creature or pay {1}{B}. + this.getSpellAbility().addCost(new OrCost( + new SacrificeTargetCost(new TargetControlledPermanent( + StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT + )), new ManaCostsImpl<>("{1}{B}"), "sacrifice a creature or pay {1}{B}" + )); + + // Menace + this.addAbility(new MenaceAbility()); + } + + private MorkrutBehemoth(final MorkrutBehemoth card) { + super(card); + } + + @Override + public MorkrutBehemoth copy() { + return new MorkrutBehemoth(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MorningApparition.java b/Mage.Sets/src/mage/cards/m/MorningApparition.java new file mode 100644 index 00000000000..2ab7a6c2582 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MorningApparition.java @@ -0,0 +1,49 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; +import mage.abilities.effects.common.ExileSourceEffect; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MorningApparition extends CardImpl { + + public MorningApparition(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + this.color.setWhite(true); + this.transformable = true; + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // If Morning Apparition would be put into a graveyard from anywhere, exile it instead. + this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + } + + private MorningApparition(final MorningApparition card) { + super(card); + } + + @Override + public MorningApparition copy() { + return new MorningApparition(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MountedDreadknight.java b/Mage.Sets/src/mage/cards/m/MountedDreadknight.java new file mode 100644 index 00000000000..2386f4f08a6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MountedDreadknight.java @@ -0,0 +1,49 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.condition.common.OpponentsLostLifeCondition; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.OpponentsLostLifeHint; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MountedDreadknight extends CardImpl { + + public MountedDreadknight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Mounted Dreadknight enters the battlefield with a +1/+1 counter on it if an opponent lost life this turn. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + OpponentsLostLifeCondition.instance, null, + "with a +1/+1 counter on it if an opponent lost life this turn" + ).addHint(OpponentsLostLifeHint.instance)); + } + + private MountedDreadknight(final MountedDreadknight card) { + super(card); + } + + @Override + public MountedDreadknight copy() { + return new MountedDreadknight(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MourningPatrol.java b/Mage.Sets/src/mage/cards/m/MourningPatrol.java new file mode 100644 index 00000000000..8065c06f1f8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MourningPatrol.java @@ -0,0 +1,46 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MourningPatrol extends CardImpl { + + public MourningPatrol(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + this.transformable = true; + this.secondSideCardClazz = mage.cards.m.MorningApparition.class; + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Disturb {3}{W} + this.addAbility(new TransformAbility()); + this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{3}{W}"))); + } + + private MourningPatrol(final MourningPatrol card) { + super(card); + } + + @Override + public MourningPatrol copy() { + return new MourningPatrol(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MuckDrubb.java b/Mage.Sets/src/mage/cards/m/MuckDrubb.java index adecc34727c..47f4327956f 100644 --- a/Mage.Sets/src/mage/cards/m/MuckDrubb.java +++ b/Mage.Sets/src/mage/cards/m/MuckDrubb.java @@ -16,8 +16,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterSpell; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.mageobject.TargetsPermanentPredicate; import mage.game.Game; import mage.game.stack.Spell; @@ -69,10 +69,10 @@ public final class MuckDrubb extends CardImpl { } } -class SpellWithOnlySingleTargetPredicate implements ObjectPlayerPredicate> { +class SpellWithOnlySingleTargetPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Spell spell = input.getObject(); if (spell == null) { return false; diff --git a/Mage.Sets/src/mage/cards/m/MysteriousTome.java b/Mage.Sets/src/mage/cards/m/MysteriousTome.java new file mode 100644 index 00000000000..3458823bb07 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MysteriousTome.java @@ -0,0 +1,45 @@ +package mage.cards.m; + +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.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MysteriousTome extends CardImpl { + + public MysteriousTome(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + this.transformable = true; + this.secondSideCardClazz = mage.cards.c.ChillingChronicle.class; + + // {2}, {T}: Draw a card. Transform Mysterious Tome. + this.addAbility(new TransformAbility()); + Ability ability = new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(1), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addEffect(new TransformSourceEffect(true)); + this.addAbility(ability); + } + + private MysteriousTome(final MysteriousTome card) { + super(card); + } + + @Override + public MysteriousTome copy() { + return new MysteriousTome(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MysticRetrieval.java b/Mage.Sets/src/mage/cards/m/MysticRetrieval.java index 0bdf0f18601..36fd978faf8 100644 --- a/Mage.Sets/src/mage/cards/m/MysticRetrieval.java +++ b/Mage.Sets/src/mage/cards/m/MysticRetrieval.java @@ -34,7 +34,7 @@ public final class MysticRetrieval extends CardImpl { this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter)); // Flashback {2}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{R}"))); } private MysticRetrieval(final MysticRetrieval card) { diff --git a/Mage.Sets/src/mage/cards/m/MysticalTeachings.java b/Mage.Sets/src/mage/cards/m/MysticalTeachings.java index 5f2075c478f..3678e99a796 100644 --- a/Mage.Sets/src/mage/cards/m/MysticalTeachings.java +++ b/Mage.Sets/src/mage/cards/m/MysticalTeachings.java @@ -37,7 +37,7 @@ public final class MysticalTeachings extends CardImpl { // Search your library for an instant card or a card with flash, reveal it, and put it into your hand. Then shuffle your library. this.getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true, true)); // Flashback {5}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{B}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{B}"))); } private MysticalTeachings(final MysticalTeachings card) { diff --git a/Mage.Sets/src/mage/cards/n/NeblegastIntruder.java b/Mage.Sets/src/mage/cards/n/NebelgastIntruder.java similarity index 82% rename from Mage.Sets/src/mage/cards/n/NeblegastIntruder.java rename to Mage.Sets/src/mage/cards/n/NebelgastIntruder.java index 9a871d1a0b1..178b28d563a 100644 --- a/Mage.Sets/src/mage/cards/n/NeblegastIntruder.java +++ b/Mage.Sets/src/mage/cards/n/NebelgastIntruder.java @@ -17,9 +17,9 @@ import java.util.UUID; /** * @author TheElk801 */ -public final class NeblegastIntruder extends CardImpl { +public final class NebelgastIntruder extends CardImpl { - public NeblegastIntruder(UUID ownerId, CardSetInfo setInfo) { + public NebelgastIntruder(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); this.subtype.add(SubType.SPIRIT); @@ -38,12 +38,12 @@ public final class NeblegastIntruder extends CardImpl { this.addAbility(ability); } - private NeblegastIntruder(final NeblegastIntruder card) { + private NebelgastIntruder(final NebelgastIntruder card) { super(card); } @Override - public NeblegastIntruder copy() { - return new NeblegastIntruder(this); + public NebelgastIntruder copy() { + return new NebelgastIntruder(this); } } diff --git a/Mage.Sets/src/mage/cards/n/Necrosynthesis.java b/Mage.Sets/src/mage/cards/n/Necrosynthesis.java new file mode 100644 index 00000000000..e3338e09093 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/Necrosynthesis.java @@ -0,0 +1,105 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.common.DiesAttachedTriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.EnchantAbility; +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.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author ciaccona007 + */ +public final class Necrosynthesis extends CardImpl { + + public Necrosynthesis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature has "Whenever another creature dies, put a +1/+1 counter on this creature." + Effect effect = new GainAbilityAttachedEffect(new DiesCreatureTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false), AttachmentType.AURA); + effect.setText("Enchanted creature has \"Whenever another creature dies, put a +1/+1 counter on this creature.\""); + this.addAbility(new SimpleStaticAbility(effect)); + + // When enchanted creature dies, look at the top X cards of your library, where X is its power. Put one of those cards into your hand and the rest on the bottom of your library in a random order. + DynamicValue attachedPower = new NecrosynthesisAttachedPermanentPowerCount(); + effect = new LookLibraryAndPickControllerEffect( + attachedPower, false, StaticValue.get(1), StaticFilters.FILTER_CARD, false, + false + ).setBackInRandomOrder(true); + effect.setText("look at the top X cards of your library, where X is its power. " + + "Put one of those cards into your hand and the rest on the bottom of your library in a random order"); + ability = new DiesAttachedTriggeredAbility(effect, "enchanted creature"); + this.addAbility(ability); + } + + private Necrosynthesis(final Necrosynthesis card) { + super(card); + } + + @Override + public Necrosynthesis copy() { + return new Necrosynthesis(this); + } +} + +class NecrosynthesisAttachedPermanentPowerCount implements DynamicValue { + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Permanent attachmentPermanent = game.getPermanent(sourceAbility.getSourceId()); + if (attachmentPermanent == null) { + attachmentPermanent = (Permanent) game.getLastKnownInformation(sourceAbility.getSourceId(), Zone.BATTLEFIELD, sourceAbility.getSourceObjectZoneChangeCounter()); + } + if (attachmentPermanent != null && attachmentPermanent.getAttachedTo() != null) { + if (effect.getValue("attachedTo") != null) { + Permanent attached = (Permanent) effect.getValue("attachedTo"); + if (attached != null) { + return attached.getPower().getValue(); + } + } + } + return 0; + } + + @Override + public NecrosynthesisAttachedPermanentPowerCount copy() { + return new NecrosynthesisAttachedPermanentPowerCount(); + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return "its power"; + } +} diff --git a/Mage.Sets/src/mage/cards/n/NeonatesRush.java b/Mage.Sets/src/mage/cards/n/NeonatesRush.java new file mode 100644 index 00000000000..578aa28814e --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NeonatesRush.java @@ -0,0 +1,87 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.effects.OneShotEffect; +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.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.FilterPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NeonatesRush extends CardImpl { + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition( + new FilterPermanent(SubType.VAMPIRE, "you control a Vampire") + ); + private static final Hint hint = new ConditionHint(condition, "You control a Vampire"); + + public NeonatesRush(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + // This spell costs {1} less to cast if you control a Vampire. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(1, condition) + ).addHint(hint).setRuleAtTheTop(true)); + + // Neonate's Rush deals 1 damage to target creature and 1 damage to its controller. Draw a card. + this.getSpellAbility().addEffect(new NeonatesRushEffect()); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); + } + + private NeonatesRush(final NeonatesRush card) { + super(card); + } + + @Override + public NeonatesRush copy() { + return new NeonatesRush(this); + } +} + +class NeonatesRushEffect extends OneShotEffect { + + NeonatesRushEffect() { + super(Outcome.Benefit); + staticText = "{this} deals 1 damage to target creature and 1 damage to its controller."; + } + + private NeonatesRushEffect(final NeonatesRushEffect effect) { + super(effect); + } + + @Override + public NeonatesRushEffect copy() { + return new NeonatesRushEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + permanent.damage(1, source.getSourceId(), source, game); + Player player = game.getPlayer(permanent.getControllerId()); + if (player != null) { + player.damage(1, source.getSourceId(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/n/NightbirdsClutches.java b/Mage.Sets/src/mage/cards/n/NightbirdsClutches.java index 64cf3b93a3c..d5a6ade10ee 100644 --- a/Mage.Sets/src/mage/cards/n/NightbirdsClutches.java +++ b/Mage.Sets/src/mage/cards/n/NightbirdsClutches.java @@ -26,7 +26,7 @@ public final class NightbirdsClutches extends CardImpl { this.getSpellAbility().addEffect(new CantBlockTargetEffect(Duration.EndOfTurn)); this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2)); // Flashback {3}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{R}"))); } private NightbirdsClutches(final NightbirdsClutches card) { diff --git a/Mage.Sets/src/mage/cards/n/NilsDisciplineEnforcer.java b/Mage.Sets/src/mage/cards/n/NilsDisciplineEnforcer.java index 6378da0aede..4375cbb4c98 100644 --- a/Mage.Sets/src/mage/cards/n/NilsDisciplineEnforcer.java +++ b/Mage.Sets/src/mage/cards/n/NilsDisciplineEnforcer.java @@ -19,11 +19,9 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.Target; import mage.target.TargetPermanent; import mage.target.targetadjustment.TargetAdjuster; -import java.util.Objects; import java.util.UUID; /** @@ -67,15 +65,14 @@ enum NilsDisciplineEnforcerAdjuster implements TargetAdjuster { @Override public void adjustTargets(Ability ability, Game game) { ability.getTargets().clear(); - for (UUID playerId : game.getState().getPlayersInRange(ability.getControllerId(), game)) { + game.getState().getPlayersInRange(ability.getControllerId(), game).forEach(playerId -> { Player player = game.getPlayer(playerId); - if (player == null) { - continue; + if (!(player == null)) { + FilterPermanent filter = new FilterCreaturePermanent("creature controlled by " + player.getName()); + filter.add(new ControllerIdPredicate(playerId)); + ability.addTarget(new TargetPermanent(0, 1, filter)); } - FilterPermanent filter = new FilterCreaturePermanent("creature controlled by " + player.getName()); - filter.add(new ControllerIdPredicate(playerId)); - ability.addTarget(new TargetPermanent(0, 1, filter)); - } + }); } } @@ -99,13 +96,14 @@ class NilsDisciplineEnforcerCountersEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { source.getTargets() .stream() - .map(Target::getFirstTarget) - .map(game::getPermanent) - .filter(Objects::nonNull) - .map(permanent -> permanent.addCounters( - CounterType.P1P1.createInstance(), - source.getControllerId(), source, game - )); + .filter( + t -> (t != null)) + .map(t -> game.getPermanent(t.getFirstTarget())) + .filter(targetedPermanent + -> (targetedPermanent != null)) + .forEachOrdered(targetedPermanent -> { + targetedPermanent.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game); + }); return true; } } @@ -114,8 +112,8 @@ class NilsDisciplineEnforcerEffect extends CantAttackYouUnlessPayManaAllEffect { NilsDisciplineEnforcerEffect() { super(null, true); - staticText = "Each creature with one or more counters on it can't attack you or planeswalkers you control " + - "unless its controller pays {X}, where X is the number of counters on that creature."; + staticText = "Each creature with one or more counters on it can't attack you or planeswalkers you control " + + "unless its controller pays {X}, where X is the number of counters on that creature."; } private NilsDisciplineEnforcerEffect(NilsDisciplineEnforcerEffect effect) { diff --git a/Mage.Sets/src/mage/cards/n/NoWayOut.java b/Mage.Sets/src/mage/cards/n/NoWayOut.java new file mode 100644 index 00000000000..9dad5c1f124 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NoWayOut.java @@ -0,0 +1,36 @@ +package mage.cards.n; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.ZombieDecayedToken; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NoWayOut extends CardImpl { + + public NoWayOut(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // Target opponent discards two cards. You create a 2/2 black Zombie creature token with decayed. + this.getSpellAbility().addEffect(new DiscardTargetEffect(2)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieDecayedToken()).concatBy("You")); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + private NoWayOut(final NoWayOut card) { + super(card); + } + + @Override + public NoWayOut copy() { + return new NoWayOut(this); + } +} +// how does kevin costner keep getting work? diff --git a/Mage.Sets/src/mage/cards/n/NoviceOccultist.java b/Mage.Sets/src/mage/cards/n/NoviceOccultist.java new file mode 100644 index 00000000000..67ee4ae8510 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NoviceOccultist.java @@ -0,0 +1,44 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NoviceOccultist extends CardImpl { + + public NoviceOccultist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // When Novice Occultist dies, you draw a card and you lose 1 life. + Ability ability = new DiesSourceTriggeredAbility( + new DrawCardSourceControllerEffect(1).setText("you draw a card") + ); + ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private NoviceOccultist(final NoviceOccultist card) { + super(card); + } + + @Override + public NoviceOccultist copy() { + return new NoviceOccultist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OathOfDruids.java b/Mage.Sets/src/mage/cards/o/OathOfDruids.java index ae1f201e044..ef54f07cafe 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfDruids.java +++ b/Mage.Sets/src/mage/cards/o/OathOfDruids.java @@ -66,7 +66,7 @@ enum OathOfDruidsAdjuster implements TargetAdjuster { } } -class OathOfDruidsPredicate implements ObjectSourcePlayerPredicate> { +class OathOfDruidsPredicate implements ObjectSourcePlayerPredicate { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); diff --git a/Mage.Sets/src/mage/cards/o/OathOfGhouls.java b/Mage.Sets/src/mage/cards/o/OathOfGhouls.java index e692587c55a..dc1552060aa 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfGhouls.java +++ b/Mage.Sets/src/mage/cards/o/OathOfGhouls.java @@ -72,7 +72,7 @@ enum OathOfGhoulsAdjuster implements TargetAdjuster { } } -class OathOfGhoulsPredicate implements ObjectSourcePlayerPredicate> { +class OathOfGhoulsPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/o/OathOfLieges.java b/Mage.Sets/src/mage/cards/o/OathOfLieges.java index 643d754d134..d09d5546987 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfLieges.java +++ b/Mage.Sets/src/mage/cards/o/OathOfLieges.java @@ -100,7 +100,7 @@ class OathOfLiegesEffect extends OneShotEffect { } } -class OathOfLiegesPredicate implements ObjectSourcePlayerPredicate> { +class OathOfLiegesPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/o/OathOfMages.java b/Mage.Sets/src/mage/cards/o/OathOfMages.java index 0dbe5e03500..6d6efdef132 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfMages.java +++ b/Mage.Sets/src/mage/cards/o/OathOfMages.java @@ -66,7 +66,7 @@ enum OathOfMagesAdjuster implements TargetAdjuster { } } -enum OathOfMagesPredicate implements ObjectSourcePlayerPredicate> { +enum OathOfMagesPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/o/OathOfScholars.java b/Mage.Sets/src/mage/cards/o/OathOfScholars.java index 02c02dc71e9..194a5709b92 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfScholars.java +++ b/Mage.Sets/src/mage/cards/o/OathOfScholars.java @@ -64,7 +64,7 @@ enum OathOfScholarsAdjuster implements TargetAdjuster { } } -class OathOfScholarsPredicate implements ObjectSourcePlayerPredicate> { +class OathOfScholarsPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/o/OboshThePreypiercer.java b/Mage.Sets/src/mage/cards/o/OboshThePreypiercer.java index acce61c7468..9f2a8a9f13f 100644 --- a/Mage.Sets/src/mage/cards/o/OboshThePreypiercer.java +++ b/Mage.Sets/src/mage/cards/o/OboshThePreypiercer.java @@ -88,7 +88,7 @@ class OboshThePreypiercerEffect extends ReplacementEffectImpl { @Override public boolean checksEventType(GameEvent event, Game game) { return event.getType().equals(GameEvent.EventType.DAMAGE_PLAYER) - || event.getType().equals(GameEvent.EventType.DAMAGED_PERMANENT); + || event.getType().equals(GameEvent.EventType.DAMAGE_PERMANENT); } @Override diff --git a/Mage.Sets/src/mage/cards/o/OdricsOutrider.java b/Mage.Sets/src/mage/cards/o/OdricsOutrider.java index 1a8a042d803..28c5a7ef0db 100644 --- a/Mage.Sets/src/mage/cards/o/OdricsOutrider.java +++ b/Mage.Sets/src/mage/cards/o/OdricsOutrider.java @@ -9,6 +9,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.target.common.TargetControlledCreaturePermanent; import java.util.UUID; @@ -28,7 +29,8 @@ public final class OdricsOutrider extends CardImpl { // Whenever Odric's Outrider or another creature you control dies, put a +1/+1 counter on target creature you control. Ability ability = new DiesThisOrAnotherCreatureTriggeredAbility( - new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + false, StaticFilters.FILTER_CONTROLLED_CREATURE ); ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/o/OldManOfTheSea.java b/Mage.Sets/src/mage/cards/o/OldManOfTheSea.java index 7a64a2a896d..4e376e46b3d 100644 --- a/Mage.Sets/src/mage/cards/o/OldManOfTheSea.java +++ b/Mage.Sets/src/mage/cards/o/OldManOfTheSea.java @@ -21,8 +21,8 @@ import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -148,7 +148,7 @@ class SourcePowerGreaterEqualTargetCondition implements Condition { } } -class PowerLowerEqualSourcePredicate implements ObjectPlayerPredicate> { +class PowerLowerEqualSourcePredicate implements ObjectSourcePlayerPredicate { UUID sourceId; @@ -157,7 +157,7 @@ class PowerLowerEqualSourcePredicate implements ObjectPlayerPredicate input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent sourcePermanent = game.getPermanent(sourceId); Permanent permanent = input.getObject(); if (permanent != null && sourcePermanent != null) { diff --git a/Mage.Sets/src/mage/cards/o/OldStickfingers.java b/Mage.Sets/src/mage/cards/o/OldStickfingers.java new file mode 100644 index 00000000000..64bf0dbb52a --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OldStickfingers.java @@ -0,0 +1,97 @@ +package mage.cards.o; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +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.CastSourceTriggeredAbility; +import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; +import mage.cards.*; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author ciaccona007 + */ +public final class OldStickfingers extends CardImpl { + + public OldStickfingers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}{B}{G}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HORROR); + + // When you cast this spell, reveal cards from the top of your library until you reveal X creature cards. Put all the creature cards revealed this way into your graveyard and the rest on the bottom of your library in a random order. + this.addAbility(new CastSourceTriggeredAbility(new OldStickfingersEffect())); + + // Old Stickfingers' power and toughness are equal to the number of creature cards in your graveyard. + DynamicValue value = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURES); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetPowerToughnessSourceEffect(value, Duration.EndOfGame))); + } + + private OldStickfingers(final OldStickfingers card) { + super(card); + } + + @Override + public OldStickfingers copy() { + return new OldStickfingers(this); + } +} + +class OldStickfingersEffect extends OneShotEffect { + + public OldStickfingersEffect() { + super(Outcome.Discard); + this.staticText = "reveal cards from the top of your library until you reveal X creature cards. Put all the creature cards revealed this way into your graveyard and the rest on the bottom of your library in a random order"; + } + + public OldStickfingersEffect(final OldStickfingersEffect effect) { + super(effect); + } + + @Override + public OldStickfingersEffect copy() { + return new OldStickfingersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Object obj = getValue(CastSourceTriggeredAbility.SOURCE_CAST_SPELL_ABILITY); + if (!(obj instanceof SpellAbility)) { + return false; + } + int xValue = ((SpellAbility) obj).getManaCostsToPay().getX(); + if (xValue < 1) { + return false; + } + Player controller = game.getPlayer(source.getControllerId()); + + Cards revealed = new CardsImpl(); + Cards otherCards = new CardsImpl(); + Set creatureCards = new LinkedHashSet<>(); + for (Card card : controller.getLibrary().getCards(game)) { + revealed.add(card); + if (card.isCreature(game)) { + creatureCards.add(card); + if(creatureCards.size() == xValue) { + break; + } + } else { + otherCards.add(card); + } + } + controller.revealCards(source, revealed, game); + controller.moveCards(creatureCards, Zone.GRAVEYARD, source, game); + controller.putCardsOnBottomOfLibrary(otherCards, game, source, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OliviasMidnightAmbush.java b/Mage.Sets/src/mage/cards/o/OliviasMidnightAmbush.java new file mode 100644 index 00000000000..872ea104fa6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OliviasMidnightAmbush.java @@ -0,0 +1,64 @@ +package mage.cards.o; + +import mage.abilities.Ability; +import mage.abilities.condition.common.NightCondition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.common.NightHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OliviasMidnightAmbush extends CardImpl { + + public OliviasMidnightAmbush(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); + + // Target creature gets -2/-2 until end of turn. If it's night, that creature gets -13/-13 until end of turn instead. + this.getSpellAbility().addEffect(new OliviasMidnightAmbushEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(NightHint.instance); + } + + private OliviasMidnightAmbush(final OliviasMidnightAmbush card) { + super(card); + } + + @Override + public OliviasMidnightAmbush copy() { + return new OliviasMidnightAmbush(this); + } +} + +class OliviasMidnightAmbushEffect extends OneShotEffect { + + OliviasMidnightAmbushEffect() { + super(Outcome.Benefit); + staticText = "target creature gets -2/-2 until end of turn. " + + "If it's night, that creature gets -13/-13 until end of turn instead"; + } + + private OliviasMidnightAmbushEffect(final OliviasMidnightAmbushEffect effect) { + super(effect); + } + + @Override + public OliviasMidnightAmbushEffect copy() { + return new OliviasMidnightAmbushEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int boost = NightCondition.instance.apply(game, source) ? -13 : -2; + game.addEffect(new BoostTargetEffect(boost, boost), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OmenMachine.java b/Mage.Sets/src/mage/cards/o/OmenMachine.java index b0c7a5bf1de..dc23dc54aec 100644 --- a/Mage.Sets/src/mage/cards/o/OmenMachine.java +++ b/Mage.Sets/src/mage/cards/o/OmenMachine.java @@ -100,7 +100,9 @@ class OmenMachineEffect2 extends OneShotEffect { if (card.isLand(game)) { player.moveCards(card, Zone.BATTLEFIELD, source, game); } else { - player.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + player.cast(player.chooseAbilityForCast(card, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); } } return true; diff --git a/Mage.Sets/src/mage/cards/o/OminousRoost.java b/Mage.Sets/src/mage/cards/o/OminousRoost.java new file mode 100644 index 00000000000..1c2d18c6760 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OminousRoost.java @@ -0,0 +1,80 @@ +package mage.cards.o; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.OminousRoostToken; +import mage.game.stack.Spell; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author ciaccona007 + */ +public final class OminousRoost extends CardImpl { + + public OminousRoost(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); + + // When Ominous Roost enters the battlefield or whenever you cast a spell from your graveyard, create a 1/1 blue Bird creature token with flying and "This creature can block only creatures with flying." + this.addAbility(new OminousRoostTriggeredAbility()); + } + + private OminousRoost(final OminousRoost card) { + super(card); + } + + @Override + public OminousRoost copy() { + return new OminousRoost(this); + } +} + +class OminousRoostTriggeredAbility extends TriggeredAbilityImpl { + + OminousRoostTriggeredAbility() { + super(Zone.ALL, new CreateTokenEffect(new OminousRoostToken())); + } + + private OminousRoostTriggeredAbility(final OminousRoostTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST + || event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + switch (event.getType()) { + case SPELL_CAST: + if (event.getPlayerId().equals(controllerId) && event.getZone() == Zone.GRAVEYARD) { + return true; + } + case ENTERS_THE_BATTLEFIELD: + return event.getTargetId().equals(getSourceId()); + default: + return false; + } + } + + @Override + public String getRule() { + return "When {this} enters the battlefield or whenever you cast a spell from your graveyard, create a " + + "1/1 blue Bird creature token with flying and \"This creature can block only creatures with flying.\""; + } + + @Override + public OminousRoostTriggeredAbility copy() { + return new OminousRoostTriggeredAbility(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OrmendahlTheCorrupter.java b/Mage.Sets/src/mage/cards/o/OrmendahlTheCorrupter.java new file mode 100644 index 00000000000..59c786aa5df --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OrmendahlTheCorrupter.java @@ -0,0 +1,62 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OrmendahlTheCorrupter extends CardImpl { + + public OrmendahlTheCorrupter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.DEMON); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + this.color.setBlack(true); + this.transformable = true; + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Sacrifice another creature: Draw a card. + this.addAbility(new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(1), + new SacrificeTargetCost(new TargetControlledPermanent( + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE + )) + )); + } + + private OrmendahlTheCorrupter(final OrmendahlTheCorrupter card) { + super(card); + } + + @Override + public OrmendahlTheCorrupter copy() { + return new OrmendahlTheCorrupter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OtherworldlyGaze.java b/Mage.Sets/src/mage/cards/o/OtherworldlyGaze.java new file mode 100644 index 00000000000..0463022624e --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OtherworldlyGaze.java @@ -0,0 +1,41 @@ +package mage.cards.o; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OtherworldlyGaze extends CardImpl { + + public OtherworldlyGaze(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // Look at the top three cards of your library. Put any number of them into your graveyard and the rest back on top of your library in any order. + this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect( + StaticValue.get(3), false, StaticValue.get(5), StaticFilters.FILTER_CARD_CARDS, + Zone.LIBRARY, true, false, true, Zone.GRAVEYARD, false + )); + + // Flashback {1}{U} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{U}"))); + } + + private OtherworldlyGaze(final OtherworldlyGaze card) { + super(card); + } + + @Override + public OtherworldlyGaze copy() { + return new OtherworldlyGaze(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OutOfTime.java b/Mage.Sets/src/mage/cards/o/OutOfTime.java index 02a4c1188ca..bc0bd0fca97 100644 --- a/Mage.Sets/src/mage/cards/o/OutOfTime.java +++ b/Mage.Sets/src/mage/cards/o/OutOfTime.java @@ -5,8 +5,8 @@ import java.util.*; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.PhaseOutAllEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.VanishingSacrificeAbility; @@ -23,7 +23,6 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; -import mage.util.CardUtil; /** * @@ -56,7 +55,7 @@ public final class OutOfTime extends CardImpl { class OutOfTimePhaseOutEffect extends OneShotEffect { public OutOfTimePhaseOutEffect() { - super(Outcome.Detriment); + super(Outcome.AIDontUseIt); this.staticText = "untap all creatures, then phase them out until {this} leaves the battlefield. " + "Put a time counter on {this} for each creature phased out this way"; } @@ -83,13 +82,15 @@ class OutOfTimePhaseOutEffect extends OneShotEffect { } // https://magic.wizards.com/en/articles/archive/feature/modern-horizons-2-release-notes-2021-06-04 // If Out of Time leaves the battlefield before its enter the battlefield trigger resolves, creatures will untap, but they won't phase out. - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { + Permanent outOfTime = game.getPermanent(source.getSourceId()); + if (outOfTime != null) { new PhaseOutAllEffect(new ArrayList<>(creatureIds)).apply(game, source); new AddCountersSourceEffect(CounterType.TIME.createInstance(numCreatures)).apply(game, source); - game.getState().setValue(CardUtil.getCardZoneString("phasedOutCreatures", source.getSourceId(), game), creatureIds); + game.getState().setValue("phasedOutCreatures" + + source.getId().toString(), creatureIds); + game.getState().setValue("phasedOutBySourceId" + source.getSourceId(), source.getId()); game.addDelayedTriggeredAbility(new OutOfTimeDelayedTriggeredAbility(), source); - game.addEffect(new OutOfTimeReplcementEffect(), source); + game.addEffect(new OutOfTimeReplacementEffect(), source); } } return true; @@ -130,7 +131,7 @@ class OutOfTimeDelayedTriggeredAbility extends DelayedTriggeredAbility { class OutOfTimeLeavesBattlefieldEffect extends OneShotEffect { public OutOfTimeLeavesBattlefieldEffect() { - super(Outcome.Benefit); + super(Outcome.Neutral); } private OutOfTimeLeavesBattlefieldEffect(final OutOfTimeLeavesBattlefieldEffect effect) { @@ -144,12 +145,14 @@ class OutOfTimeLeavesBattlefieldEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Set creatureIds = (Set) game.getState().getValue(CardUtil.getCardZoneString( - "phasedOutCreatures", source.getSourceId(), game, true)); + UUID sourceId = (UUID) game.getState().getValue("phasedOutBySourceId" + source.getSourceId()); + Set creatureIds = (Set) game.getState().getValue("phasedOutCreatures" + + sourceId.toString()); if (creatureIds != null) { for (UUID creatureId : creatureIds) { Permanent creature = game.getPermanent(creatureId); - if (creature != null && !creature.isPhasedIn()) { + if (creature != null + && !creature.isPhasedIn()) { creature.phaseIn(game); } } @@ -160,19 +163,19 @@ class OutOfTimeLeavesBattlefieldEffect extends OneShotEffect { } // Stops creatures from phasing back in on their controller's next turn -class OutOfTimeReplcementEffect extends ReplacementEffectImpl { +class OutOfTimeReplacementEffect extends ContinuousRuleModifyingEffectImpl { - public OutOfTimeReplcementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); + public OutOfTimeReplacementEffect() { + super(Duration.WhileOnBattlefield, Outcome.Neutral, false, false); } - private OutOfTimeReplcementEffect(final OutOfTimeReplcementEffect effect) { + private OutOfTimeReplacementEffect(final OutOfTimeReplacementEffect effect) { super(effect); } @Override - public OutOfTimeReplcementEffect copy() { - return new OutOfTimeReplcementEffect(this); + public OutOfTimeReplacementEffect copy() { + return new OutOfTimeReplacementEffect(this); } @Override @@ -181,14 +184,16 @@ class OutOfTimeReplcementEffect extends ReplacementEffectImpl { } @Override - public boolean applies(GameEvent event, Ability source, Game game) { - Set creatureIds = (Set) game.getState().getValue(CardUtil.getCardZoneString( - "phasedOutCreatures", source.getSourceId(), game)); - return creatureIds != null && creatureIds.contains(event.getTargetId()); + public boolean apply(Game game, Ability source) { + return true; } @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - return true; + public boolean applies(GameEvent event, Ability source, Game game) { + Set creatureIds = (Set) game.getState().getValue("phasedOutCreatures" + + source.getId().toString()); + return source.getSourceObjectZoneChangeCounter() == game.getState().getZoneChangeCounter(source.getSourceId()) // blinked + && creatureIds != null + && creatureIds.contains(event.getTargetId()); } } diff --git a/Mage.Sets/src/mage/cards/o/OutlandLiberator.java b/Mage.Sets/src/mage/cards/o/OutlandLiberator.java new file mode 100644 index 00000000000..3e27af77112 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OutlandLiberator.java @@ -0,0 +1,51 @@ +package mage.cards.o; + +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.DayboundAbility; +import mage.abilities.keyword.TransformAbility; +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 OutlandLiberator extends CardImpl { + + public OutlandLiberator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WEREWOLF); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.transformable = true; + this.secondSideCardClazz = mage.cards.f.FrenziedTrapbreaker.class; + + // {1}, Sacrifice Outland Liberator: Destroy target artifact or enchantment. + Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new GenericManaCost(1)); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + + // Daybound + this.addAbility(new TransformAbility()); + this.addAbility(new DayboundAbility()); + } + + private OutlandLiberator(final OutlandLiberator card) { + super(card); + } + + @Override + public OutlandLiberator copy() { + return new OutlandLiberator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OverwhelmedArchivist.java b/Mage.Sets/src/mage/cards/o/OverwhelmedArchivist.java index 11d1cc90be4..724a434b1f5 100644 --- a/Mage.Sets/src/mage/cards/o/OverwhelmedArchivist.java +++ b/Mage.Sets/src/mage/cards/o/OverwhelmedArchivist.java @@ -5,6 +5,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.abilities.keyword.DisturbAbility; +import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -31,6 +32,7 @@ public final class OverwhelmedArchivist extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawDiscardControllerEffect(1, 1))); // Disturb {3}{U} + this.addAbility(new TransformAbility()); this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{3}{U}"))); } diff --git a/Mage.Sets/src/mage/cards/p/PacksBetrayal.java b/Mage.Sets/src/mage/cards/p/PacksBetrayal.java new file mode 100644 index 00000000000..f71fa6184db --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PacksBetrayal.java @@ -0,0 +1,66 @@ +package mage.cards.p; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +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.Predicates; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PacksBetrayal extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(Predicates.or( + SubType.WOLF.getPredicate(), + SubType.WEREWOLF.getPredicate() + )); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); + private static final Hint hint = new ConditionHint(condition, "You control a Wolf or Werewolf"); + + public PacksBetrayal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); + + // Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn. If you control a Wolf or Werewolf, scry 2. + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + 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().addEffect(new ConditionalOneShotEffect( + new ScryEffect(2), condition, + "If you control a Wolf or Werewolf, scry 2" + )); + this.getSpellAbility().addHint(hint); + } + + private PacksBetrayal(final PacksBetrayal card) { + super(card); + } + + @Override + public PacksBetrayal copy() { + return new PacksBetrayal(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PaladinClass.java b/Mage.Sets/src/mage/cards/p/PaladinClass.java index 15c0b70713b..3f09a67d05e 100644 --- a/Mage.Sets/src/mage/cards/p/PaladinClass.java +++ b/Mage.Sets/src/mage/cards/p/PaladinClass.java @@ -48,7 +48,7 @@ public final class PaladinClass extends CardImpl { // Spells your opponents cast during your turn cost {1} more to cast. this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( new SpellsCostIncreasingAllEffect( - 2, StaticFilters.FILTER_CARD, TargetController.OPPONENT + 1, StaticFilters.FILTER_CARD, TargetController.OPPONENT ), MyTurnCondition.instance, "spells your opponents cast during your turn cost {1} more to cast" ))); diff --git a/Mage.Sets/src/mage/cards/p/ParallelEvolution.java b/Mage.Sets/src/mage/cards/p/ParallelEvolution.java index 764fb263858..46ce42596c3 100644 --- a/Mage.Sets/src/mage/cards/p/ParallelEvolution.java +++ b/Mage.Sets/src/mage/cards/p/ParallelEvolution.java @@ -31,7 +31,7 @@ public final class ParallelEvolution extends CardImpl { this.getSpellAbility().addEffect(new ParallelEvolutionEffect()); // Flashback {4}{G}{G}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{G}{G}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{G}{G}{G}"))); } private ParallelEvolution(final ParallelEvolution card) { diff --git a/Mage.Sets/src/mage/cards/p/PastInFlames.java b/Mage.Sets/src/mage/cards/p/PastInFlames.java index 509c98ec848..4477908a722 100644 --- a/Mage.Sets/src/mage/cards/p/PastInFlames.java +++ b/Mage.Sets/src/mage/cards/p/PastInFlames.java @@ -22,12 +22,11 @@ public final class PastInFlames extends CardImpl { public PastInFlames(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}"); - // Each instant and sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost. this.getSpellAbility().addEffect(new PastInFlamesEffect()); // Flashback {4}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{4}{R}"))); } @@ -73,25 +72,18 @@ class PastInFlamesEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.getGraveyard().stream().filter((cardId) -> (affectedObjectList.contains(new MageObjectReference(cardId, game)))).forEachOrdered((cardId) -> { - Card card = game.getCard(cardId); - if (card != null) { - FlashbackAbility ability = null; - if (card.isInstant(game)) { - ability = new FlashbackAbility(card.getManaCost(), TimingRule.INSTANT); - } else if (card.isSorcery(game)) { - ability = new FlashbackAbility(card.getManaCost(), TimingRule.SORCERY); - } - if (ability != null) { - ability.setSourceId(cardId); - ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card, ability); - } - } - }); - return true; + if (player == null) { + return false; } - return false; + player.getGraveyard().stream().filter((cardId) -> (affectedObjectList.contains(new MageObjectReference(cardId, game)))).forEachOrdered((cardId) -> { + Card card = game.getCard(cardId); + if (card != null) { + FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); + ability.setSourceId(cardId); + ability.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, ability); + } + }); + return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/p/PathToTheFestival.java b/Mage.Sets/src/mage/cards/p/PathToTheFestival.java new file mode 100644 index 00000000000..3964b46d1ec --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PathToTheFestival.java @@ -0,0 +1,63 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.DomainValue; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.hint.common.DomainHint; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TimingRule; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PathToTheFestival extends CardImpl { + + public PathToTheFestival(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); + + // Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Then if there are three or more basic land types among lands you control, scry 1. + this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect( + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true + )); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new ScryEffect(1), PathToTheFestivalCondition.instance, + "Then if there are three or more basic land types among lands you control, scry 1" + )); + this.getSpellAbility().addHint(DomainHint.instance); + + // Flashback {4}{G} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{4}{G}"))); + } + + private PathToTheFestival(final PathToTheFestival card) { + super(card); + } + + @Override + public PathToTheFestival copy() { + return new PathToTheFestival(this); + } +} + +enum PathToTheFestivalCondition implements Condition { + instance; + private static final DynamicValue xValue = new DomainValue(); + + @Override + public boolean apply(Game game, Ability source) { + return xValue.calculate(game, source, null) >= 3; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PeaceTalks.java b/Mage.Sets/src/mage/cards/p/PeaceTalks.java index 2e4785aaf30..b0b9785f8b2 100644 --- a/Mage.Sets/src/mage/cards/p/PeaceTalks.java +++ b/Mage.Sets/src/mage/cards/p/PeaceTalks.java @@ -24,11 +24,8 @@ public final class PeaceTalks extends CardImpl { public PeaceTalks(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}"); - // This turn and next turn, creatures can't attack, - // and players and permanents can't be the targets - // of spells or activated abilities. + // This turn and next turn, creatures can't attack, and players and permanents can't be the targets of spells or activated abilities. this.getSpellAbility().addEffect(new PeaceTalksEffect()); - } private PeaceTalks(final PeaceTalks card) { diff --git a/Mage.Sets/src/mage/cards/p/PersonalEnergyShield.java b/Mage.Sets/src/mage/cards/p/PersonalEnergyShield.java index 89c9313d7a7..7f63b56e162 100644 --- a/Mage.Sets/src/mage/cards/p/PersonalEnergyShield.java +++ b/Mage.Sets/src/mage/cards/p/PersonalEnergyShield.java @@ -8,8 +8,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterSpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; @@ -46,10 +46,10 @@ public final class PersonalEnergyShield extends CardImpl { } } -class PersonalEnergyFieldPredicate implements ObjectPlayerPredicate> { +class PersonalEnergyFieldPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { UUID controllerId = input.getPlayerId(); if (controllerId == null) { return false; diff --git a/Mage.Sets/src/mage/cards/p/PossibilityStorm.java b/Mage.Sets/src/mage/cards/p/PossibilityStorm.java index 273fff0ad1a..10dafb8feac 100644 --- a/Mage.Sets/src/mage/cards/p/PossibilityStorm.java +++ b/Mage.Sets/src/mage/cards/p/PossibilityStorm.java @@ -128,7 +128,9 @@ class PossibilityStormEffect extends OneShotEffect { && !card.isLand(game) && card.getSpellAbility().canChooseTarget(game, spellController.getId())) { if (spellController.chooseUse(Outcome.PlayForFree, "Cast " + card.getLogName() + " without paying cost?", source, game)) { - spellController.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + spellController.cast(spellController.chooseAbilityForCast(card, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); } } diff --git a/Mage.Sets/src/mage/cards/p/PrismaticStrands.java b/Mage.Sets/src/mage/cards/p/PrismaticStrands.java index ac8e713ca25..803605e2a27 100644 --- a/Mage.Sets/src/mage/cards/p/PrismaticStrands.java +++ b/Mage.Sets/src/mage/cards/p/PrismaticStrands.java @@ -44,7 +44,7 @@ public final class PrismaticStrands extends CardImpl { this.getSpellAbility().addEffect(new PrismaticStrandsEffect()); // Flashback-Tap an untapped white creature you control. - this.addAbility(new FlashbackAbility(new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, false)), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new TapTargetCost(new TargetControlledCreaturePermanent(1, 1, filter, false)))); } private PrismaticStrands(final PrismaticStrands card) { diff --git a/Mage.Sets/src/mage/cards/p/ProwlingGeistcatcher.java b/Mage.Sets/src/mage/cards/p/ProwlingGeistcatcher.java new file mode 100644 index 00000000000..18dd75e861b --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ProwlingGeistcatcher.java @@ -0,0 +1,120 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +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.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ProwlingGeistcatcher extends CardImpl { + + public ProwlingGeistcatcher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Whenever you sacrifice another creature, exile it. If that creature was a token, put a +1/+1 counter on Prowling Geistcatcher. + this.addAbility(new SacrificePermanentTriggeredAbility( + new ProwlingGeistcatcherExileEffect(), + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE, true + )); + + // When Prowling Geistcatcher leaves the battlefield, return each card exiled with it to the battlefield under your control. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ProwlingGeistcatcherReturnEffect(), false)); + } + + private ProwlingGeistcatcher(final ProwlingGeistcatcher card) { + super(card); + } + + @Override + public ProwlingGeistcatcher copy() { + return new ProwlingGeistcatcher(this); + } +} + +class ProwlingGeistcatcherExileEffect extends OneShotEffect { + + ProwlingGeistcatcherExileEffect() { + super(Outcome.Benefit); + staticText = "exile it. If that creature was a token, put a +1/+1 counter on {this}"; + } + + private ProwlingGeistcatcherExileEffect(final ProwlingGeistcatcherExileEffect effect) { + super(effect); + } + + @Override + public ProwlingGeistcatcherExileEffect copy() { + return new ProwlingGeistcatcherExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player != null && card != null) { + player.moveCardsToExile( + card, source, game, true, + CardUtil.getExileZoneId(game, source), + CardUtil.getSourceLogName(game, source) + ); + } + Permanent exiled = (Permanent) getValue("sacrificedPermanent"); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (exiled instanceof PermanentToken && permanent != null) { + permanent.addCounters(CounterType.P1P1.createInstance(), source, game); + } + return true; + } +} + +class ProwlingGeistcatcherReturnEffect extends OneShotEffect { + + ProwlingGeistcatcherReturnEffect() { + super(Outcome.Benefit); + staticText = "return each card exiled with it to the battlefield under your control"; + } + + private ProwlingGeistcatcherReturnEffect(final ProwlingGeistcatcherReturnEffect effect) { + super(effect); + } + + @Override + public ProwlingGeistcatcherReturnEffect copy() { + return new ProwlingGeistcatcherReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + return player.moveCards(game.getExile().getExileZone( + CardUtil.getExileZoneId(game, source) + ), Zone.BATTLEFIELD, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PsychicRebuttal.java b/Mage.Sets/src/mage/cards/p/PsychicRebuttal.java index 960db894b79..ac8dbac8b09 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicRebuttal.java +++ b/Mage.Sets/src/mage/cards/p/PsychicRebuttal.java @@ -10,8 +10,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.filter.FilterSpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.stack.Spell; @@ -91,10 +91,10 @@ class PsychicRebuttalEffect extends OneShotEffect { } } -class PsychicRebuttalPredicate implements ObjectPlayerPredicate> { +class PsychicRebuttalPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { UUID controllerId = input.getPlayerId(); if (controllerId == null) { return false; diff --git a/Mage.Sets/src/mage/cards/p/PurifyTheGrave.java b/Mage.Sets/src/mage/cards/p/PurifyTheGrave.java index 4c75313121c..cca1e6de826 100644 --- a/Mage.Sets/src/mage/cards/p/PurifyTheGrave.java +++ b/Mage.Sets/src/mage/cards/p/PurifyTheGrave.java @@ -26,7 +26,7 @@ public final class PurifyTheGrave extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInGraveyard()); // Flashback {W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{W}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{W}"))); } private PurifyTheGrave(final PurifyTheGrave card) { diff --git a/Mage.Sets/src/mage/cards/p/PurifyingDragon.java b/Mage.Sets/src/mage/cards/p/PurifyingDragon.java new file mode 100644 index 00000000000..036cb0aa637 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PurifyingDragon.java @@ -0,0 +1,88 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.DefendingPlayerControlsPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PurifyingDragon extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("creature defending player controls"); + + static { + filter.add(DefendingPlayerControlsPredicate.instance); + } + + public PurifyingDragon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Purifying Dragon attacks, it deals 1 damage to target creature defending player controls. If that creature is a Zombie, Purifying Dragon deals 2 damage to that creature instead. + Ability ability = new AttacksTriggeredAbility(new PurifyingDragonEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private PurifyingDragon(final PurifyingDragon card) { + super(card); + } + + @Override + public PurifyingDragon copy() { + return new PurifyingDragon(this); + } +} + +class PurifyingDragonEffect extends OneShotEffect { + + PurifyingDragonEffect() { + super(Outcome.Benefit); + staticText = "it deals 1 damage to target creature defending player controls. " + + "If that creature is a Zombie, {this} deals 2 damage to that creature instead"; + } + + private PurifyingDragonEffect(final PurifyingDragonEffect effect) { + super(effect); + } + + @Override + public PurifyingDragonEffect copy() { + return new PurifyingDragonEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + return permanent.damage( + permanent.hasSubtype(SubType.ZOMBIE, game) ? 2 : 1, + source.getSourceId(), source, game + ) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/p/Pyramids.java b/Mage.Sets/src/mage/cards/p/Pyramids.java index d0281ba7f56..297ab08af36 100644 --- a/Mage.Sets/src/mage/cards/p/Pyramids.java +++ b/Mage.Sets/src/mage/cards/p/Pyramids.java @@ -16,8 +16,8 @@ import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterEnchantmentPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetLandPermanent; @@ -60,9 +60,9 @@ public final class Pyramids extends CardImpl { return new Pyramids(this); } } -class PyramidsPredicate implements ObjectPlayerPredicate> { +class PyramidsPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent attachment = input.getObject(); if (attachment != null) { Permanent permanent = game.getPermanent(attachment.getAttachedTo()); diff --git a/Mage.Sets/src/mage/cards/r/Radiate.java b/Mage.Sets/src/mage/cards/r/Radiate.java index c50204dd57d..5e8eeabc69e 100644 --- a/Mage.Sets/src/mage/cards/r/Radiate.java +++ b/Mage.Sets/src/mage/cards/r/Radiate.java @@ -10,8 +10,8 @@ import mage.constants.CardType; import mage.filter.FilterSpell; import mage.filter.StaticFilters; import mage.filter.common.FilterInstantOrSorcerySpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; import mage.game.stack.Spell; @@ -55,11 +55,11 @@ public final class Radiate extends CardImpl { } } -enum SpellWithOnlySingleTargetPredicate implements ObjectPlayerPredicate> { +enum SpellWithOnlySingleTargetPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Spell spell = input.getObject(); if (spell == null) { return false; @@ -79,11 +79,11 @@ enum SpellWithOnlySingleTargetPredicate implements ObjectPlayerPredicate> { +enum SpellWithOnlyPermanentOrPlayerTargetsPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Spell spell = input.getObject(); if (spell == null) { return false; diff --git a/Mage.Sets/src/mage/cards/r/RallyThePeasants.java b/Mage.Sets/src/mage/cards/r/RallyThePeasants.java index eadf0365257..5f75b77de9c 100644 --- a/Mage.Sets/src/mage/cards/r/RallyThePeasants.java +++ b/Mage.Sets/src/mage/cards/r/RallyThePeasants.java @@ -24,7 +24,7 @@ public final class RallyThePeasants extends CardImpl { this.getSpellAbility().addEffect(new BoostControlledEffect(2, 0, Duration.EndOfTurn)); // Flashback {2}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{R}"))); } private RallyThePeasants(final RallyThePeasants card) { diff --git a/Mage.Sets/src/mage/cards/r/RavenousRotbelly.java b/Mage.Sets/src/mage/cards/r/RavenousRotbelly.java new file mode 100644 index 00000000000..0c90d344ad4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RavenousRotbelly.java @@ -0,0 +1,95 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeOpponentsEffect; +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.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RavenousRotbelly extends CardImpl { + + public RavenousRotbelly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // When Ravenous Rotbelly enters the battlefield, you may sacrifice up to three Zombies. When you sacrifice one or more Zombies this way, each opponent sacrifices that many creatures. + this.addAbility(new EntersBattlefieldTriggeredAbility(new RavenousRotbellyEffect())); + } + + private RavenousRotbelly(final RavenousRotbelly card) { + super(card); + } + + @Override + public RavenousRotbelly copy() { + return new RavenousRotbelly(this); + } +} + +class RavenousRotbellyEffect extends OneShotEffect { + + private static final FilterPermanent filter + = new FilterControlledPermanent(SubType.ZOMBIE, "Zombies you control"); + + RavenousRotbellyEffect() { + super(Outcome.Benefit); + staticText = "you may sacrifice up to three Zombies. When you sacrifice " + + "one or more Zombies this way, each opponent sacrifices that many creatures"; + } + + private RavenousRotbellyEffect(final RavenousRotbellyEffect effect) { + super(effect); + } + + @Override + public RavenousRotbellyEffect copy() { + return new RavenousRotbellyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetPermanent target = new TargetPermanent(0, 3, filter, true); + player.choose(outcome, target, source.getSourceId(), game); + int amount = 0; + for (UUID permanentId : target.getTargets()) { + Permanent permanent = game.getPermanent(permanentId); + if (permanent != null && permanent.sacrifice(source, game)) { + amount++; + } + } + if (amount < 1) { + return false; + } + game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility( + new SacrificeOpponentsEffect(amount, StaticFilters.FILTER_PERMANENT_CREATURES), + false, "each opponent sacrifices that many creatures" + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RayOfDistortion.java b/Mage.Sets/src/mage/cards/r/RayOfDistortion.java index fabf4c432cb..05d26a65545 100644 --- a/Mage.Sets/src/mage/cards/r/RayOfDistortion.java +++ b/Mage.Sets/src/mage/cards/r/RayOfDistortion.java @@ -26,7 +26,7 @@ public final class RayOfDistortion extends CardImpl { this.getSpellAbility().addEffect(new DestroyTargetEffect()); this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); // Flashback {4}{W}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{W}{W}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{W}{W}"))); } private RayOfDistortion(final RayOfDistortion card) { diff --git a/Mage.Sets/src/mage/cards/r/RayOfRevelation.java b/Mage.Sets/src/mage/cards/r/RayOfRevelation.java index 701114b54df..c6747afdf4b 100644 --- a/Mage.Sets/src/mage/cards/r/RayOfRevelation.java +++ b/Mage.Sets/src/mage/cards/r/RayOfRevelation.java @@ -25,7 +25,7 @@ public final class RayOfRevelation extends CardImpl { this.getSpellAbility().addTarget(new TargetEnchantmentPermanent()); this.getSpellAbility().addEffect(new DestroyTargetEffect()); // Flashback {G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{G}"))); } private RayOfRevelation(final RayOfRevelation card) { diff --git a/Mage.Sets/src/mage/cards/r/ReapTheSeagraf.java b/Mage.Sets/src/mage/cards/r/ReapTheSeagraf.java index 8562bc22417..8cfa7c141a6 100644 --- a/Mage.Sets/src/mage/cards/r/ReapTheSeagraf.java +++ b/Mage.Sets/src/mage/cards/r/ReapTheSeagraf.java @@ -19,7 +19,7 @@ public final class ReapTheSeagraf extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{B}"); this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieToken())); - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{U}"))); } private ReapTheSeagraf(final ReapTheSeagraf card) { diff --git a/Mage.Sets/src/mage/cards/r/RebbecArchitectOfAscension.java b/Mage.Sets/src/mage/cards/r/RebbecArchitectOfAscension.java index 1720c48f8c3..e5f023a7ba0 100644 --- a/Mage.Sets/src/mage/cards/r/RebbecArchitectOfAscension.java +++ b/Mage.Sets/src/mage/cards/r/RebbecArchitectOfAscension.java @@ -62,7 +62,7 @@ public final class RebbecArchitectOfAscension extends CardImpl { } } -enum RebbecArchitectOfAscensionPredicate implements ObjectSourcePlayerPredicate> { +enum RebbecArchitectOfAscensionPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/r/RecklessCharge.java b/Mage.Sets/src/mage/cards/r/RecklessCharge.java index 0208a3d353a..4b1fcb8c420 100644 --- a/Mage.Sets/src/mage/cards/r/RecklessCharge.java +++ b/Mage.Sets/src/mage/cards/r/RecklessCharge.java @@ -31,7 +31,7 @@ public final class RecklessCharge extends CardImpl { ).setText("and gains haste until end of turn")); // Flashback {2}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{2}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{2}{R}"))); } private RecklessCharge(final RecklessCharge card) { diff --git a/Mage.Sets/src/mage/cards/r/RecklessStormseeker.java b/Mage.Sets/src/mage/cards/r/RecklessStormseeker.java index da11e7ace7f..8b78f15c067 100644 --- a/Mage.Sets/src/mage/cards/r/RecklessStormseeker.java +++ b/Mage.Sets/src/mage/cards/r/RecklessStormseeker.java @@ -47,7 +47,7 @@ public final class RecklessStormseeker extends CardImpl { // Daybound this.addAbility(new TransformAbility()); - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new DayboundAbility()); } private RecklessStormseeker(final RecklessStormseeker card) { diff --git a/Mage.Sets/src/mage/cards/r/Recoup.java b/Mage.Sets/src/mage/cards/r/Recoup.java index bb144aa1117..d6aaf7223fe 100644 --- a/Mage.Sets/src/mage/cards/r/Recoup.java +++ b/Mage.Sets/src/mage/cards/r/Recoup.java @@ -1,7 +1,5 @@ - package mage.cards.r; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; @@ -9,38 +7,34 @@ import mage.abilities.keyword.FlashbackAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.TimingRule; +import mage.constants.*; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author cbt33, BetaSteward (PastInFlames) */ public final class Recoup extends CardImpl { - + private static final FilterCard filter = new FilterCard("sorcery card"); - - static{ + + static { filter.add(CardType.SORCERY.getPredicate()); } public Recoup(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{R}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); // Target sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost. this.getSpellAbility().addEffect(new RecoupEffect()); this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter)); + // Flashback {3}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{R}"))); } private Recoup(final Recoup card) { @@ -69,20 +63,20 @@ class RecoupEffect extends ContinuousEffectImpl { return new RecoupEffect(this); } - @Override + @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - Card card = game.getCard(targetPointer.getFirst(game, source)); - if (card != null) { - FlashbackAbility ability = new FlashbackAbility(card.getManaCost(), TimingRule.SORCERY); + if (player == null) { + return false; + } + Card card = game.getCard(targetPointer.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; + } } - -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/RemKarolusStalwartSlayer.java b/Mage.Sets/src/mage/cards/r/RemKarolusStalwartSlayer.java new file mode 100644 index 00000000000..daf36be95e4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RemKarolusStalwartSlayer.java @@ -0,0 +1,149 @@ +package mage.cards.r; + +import java.util.Set; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.constants.*; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.util.CardUtil; + +/** + * + * @author weirddan455 + */ +public final class RemKarolusStalwartSlayer extends CardImpl { + + public RemKarolusStalwartSlayer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // If a spell would deal damage to you or another permanent you control, prevent that damage. + this.addAbility(new SimpleStaticAbility(new RemKarolusStalwartSlayerPreventionEffect())); + + // If a spell would deal damage to an opponent or a permanent an opponent controls, it deals that much damage plus 1 instead. + this.addAbility(new SimpleStaticAbility(new RemKarolusStalwartSlayerReplacementEffect())); + } + + private RemKarolusStalwartSlayer(final RemKarolusStalwartSlayer card) { + super(card); + } + + @Override + public RemKarolusStalwartSlayer copy() { + return new RemKarolusStalwartSlayer(this); + } +} + +class RemKarolusStalwartSlayerPreventionEffect extends PreventionEffectImpl { + + public RemKarolusStalwartSlayerPreventionEffect() { + super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false); + staticText = "If a spell would deal damage to you or another permanent you control, prevent that damage"; + } + + private RemKarolusStalwartSlayerPreventionEffect(final RemKarolusStalwartSlayerPreventionEffect effect) { + super(effect); + } + + @Override + public RemKarolusStalwartSlayerPreventionEffect copy() { + return new RemKarolusStalwartSlayerPreventionEffect(this); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + UUID targetId = event.getTargetId(); + if (targetId.equals(source.getSourceId())) { + return false; + } + UUID controllerId = source.getControllerId(); + if (!targetId.equals(controllerId)) { + Permanent permanent = game.getPermanent(targetId); + if (permanent == null || !permanent.isControlledBy(controllerId)) { + return false; + } + } + StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); + if (stackObject == null) { + stackObject = (StackObject) game.getLastKnownInformation(event.getSourceId(), Zone.STACK); + } + if (stackObject instanceof Spell) { + return super.applies(event, source, game); + } + return false; + } +} + +class RemKarolusStalwartSlayerReplacementEffect extends ReplacementEffectImpl { + + public RemKarolusStalwartSlayerReplacementEffect() { + super(Duration.WhileOnBattlefield, Outcome.Damage); + staticText = "If a spell would deal damage to an opponent or a permanent an opponent controls, it deals that much damage plus 1 instead"; + } + + private RemKarolusStalwartSlayerReplacementEffect(final RemKarolusStalwartSlayerReplacementEffect effect) { + super(effect); + } + + @Override + public RemKarolusStalwartSlayerReplacementEffect copy() { + return new RemKarolusStalwartSlayerReplacementEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(CardUtil.overflowInc(event.getAmount(), 1)); + return false; + } + + @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) { + UUID targetId = event.getTargetId(); + Set opponents = game.getOpponents(source.getControllerId()); + if (!opponents.contains(targetId)) { + Permanent permanent = game.getPermanent(targetId); + if (permanent == null || !opponents.contains(permanent.getControllerId())) { + return false; + } + } + StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); + if (stackObject == null) { + stackObject = (StackObject) game.getLastKnownInformation(event.getSourceId(), Zone.STACK); + } + return stackObject instanceof Spell; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RemoveEnchantments.java b/Mage.Sets/src/mage/cards/r/RemoveEnchantments.java index 89a4b0a853d..6a345bfdba5 100644 --- a/Mage.Sets/src/mage/cards/r/RemoveEnchantments.java +++ b/Mage.Sets/src/mage/cards/r/RemoveEnchantments.java @@ -11,8 +11,8 @@ import mage.constants.SubType; import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledEnchantmentPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.permanent.AttachedToControlledPermanentPredicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -74,10 +74,10 @@ public final class RemoveEnchantments extends CardImpl { } } -class AttachedToOpponentControlledAttackingCreaturePredicate implements ObjectPlayerPredicate> { +class AttachedToOpponentControlledAttackingCreaturePredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent attachement = input.getObject(); if (attachement != null) { Permanent permanent = game.getPermanent(attachement.getAttachedTo()); diff --git a/Mage.Sets/src/mage/cards/r/RevengeOfTheDrowned.java b/Mage.Sets/src/mage/cards/r/RevengeOfTheDrowned.java new file mode 100644 index 00000000000..0187a86e455 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RevengeOfTheDrowned.java @@ -0,0 +1,70 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.PutOnLibraryTargetEffect; +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.ZombieDecayedToken; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RevengeOfTheDrowned extends CardImpl { + + public RevengeOfTheDrowned(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); + + // Target creature's owner puts it on the top or bottom of their library. You create a 2/2 black Zombie creature token with decayed. + this.getSpellAbility().addEffect(new RevengeOfTheDrownedEffect()); + this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieDecayedToken()).concatBy("You")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private RevengeOfTheDrowned(final RevengeOfTheDrowned card) { + super(card); + } + + @Override + public RevengeOfTheDrowned copy() { + return new RevengeOfTheDrowned(this); + } +} + +class RevengeOfTheDrownedEffect extends OneShotEffect { + + RevengeOfTheDrownedEffect() { + super(Outcome.Benefit); + staticText = "target creature's owner puts it on the top or bottom of their library."; + } + + private RevengeOfTheDrownedEffect(final RevengeOfTheDrownedEffect effect) { + super(effect); + } + + @Override + public RevengeOfTheDrownedEffect copy() { + return new RevengeOfTheDrownedEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); + if (player == null) { + return false; + } + if (player.chooseUse(Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", + "", "Top", "Bottom", source, game)) { + return new PutOnLibraryTargetEffect(true).apply(game, source); + } + return new PutOnLibraryTargetEffect(false).apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/r/Ricochet.java b/Mage.Sets/src/mage/cards/r/Ricochet.java index 365d5b78a39..029f4e5d193 100644 --- a/Mage.Sets/src/mage/cards/r/Ricochet.java +++ b/Mage.Sets/src/mage/cards/r/Ricochet.java @@ -17,8 +17,8 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SetTargetPointer; import mage.filter.FilterSpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.other.NumberOfTargetsPredicate; import mage.game.Game; import mage.game.stack.Spell; @@ -57,10 +57,10 @@ public final class Ricochet extends CardImpl { } } -class SpellWithOnlyPlayerTargetsPredicate implements ObjectPlayerPredicate> { +class SpellWithOnlyPlayerTargetsPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Spell spell = input.getObject(); if (spell == null) { return false; diff --git a/Mage.Sets/src/mage/cards/r/RiseOfTheAnts.java b/Mage.Sets/src/mage/cards/r/RiseOfTheAnts.java new file mode 100644 index 00000000000..afb446d9c5e --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RiseOfTheAnts.java @@ -0,0 +1,38 @@ +package mage.cards.r; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.RiseOfTheAntsToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RiseOfTheAnts extends CardImpl { + + public RiseOfTheAnts(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}{G}"); + + // Create two 3/3 green Insect creature tokens. You gain 2 life. + this.getSpellAbility().addEffect(new CreateTokenEffect(new RiseOfTheAntsToken(), 2)); + this.getSpellAbility().addEffect(new GainLifeEffect(2).concatBy(".")); + + // Flashback {6}{G}{G} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{6}{G}{G}"))); + } + + private RiseOfTheAnts(final RiseOfTheAnts card) { + super(card); + } + + @Override + public RiseOfTheAnts copy() { + return new RiseOfTheAnts(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RiteOfHarmony.java b/Mage.Sets/src/mage/cards/r/RiteOfHarmony.java index 1a16039ac87..d5e5db6d7c5 100644 --- a/Mage.Sets/src/mage/cards/r/RiteOfHarmony.java +++ b/Mage.Sets/src/mage/cards/r/RiteOfHarmony.java @@ -32,7 +32,7 @@ public final class RiteOfHarmony extends CardImpl { getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new RiteOfHarmonyTriggeredAbility())); // Flashback {2}{G}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{G}{W}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{G}{W}"))); } diff --git a/Mage.Sets/src/mage/cards/r/RiteOfOblivion.java b/Mage.Sets/src/mage/cards/r/RiteOfOblivion.java new file mode 100644 index 00000000000..b0c85137e9b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RiteOfOblivion.java @@ -0,0 +1,52 @@ +package mage.cards.r; + +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author ciaccona007 + */ +public final class RiteOfOblivion extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("nonland permanent"); + + static { + filter.add(Predicates.not(CardType.LAND.getPredicate())); + } + + public RiteOfOblivion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}{B}"); + + + // As an additional cost to cast this spell, sacrifice a nonland permanent. + this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetControlledPermanent(filter))); + + // Exile target nonland permanent + this.getSpellAbility().addEffect(new ExileTargetEffect()); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + + // Flashback {2}{W}{B} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{2}{W}{B}"))); + + } + + private RiteOfOblivion(final RiteOfOblivion card) { + super(card); + } + + @Override + public RiteOfOblivion copy() { + return new RiteOfOblivion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RoarOfTheWurm.java b/Mage.Sets/src/mage/cards/r/RoarOfTheWurm.java index 09bb68a1e28..6138c755928 100644 --- a/Mage.Sets/src/mage/cards/r/RoarOfTheWurm.java +++ b/Mage.Sets/src/mage/cards/r/RoarOfTheWurm.java @@ -25,7 +25,7 @@ public final class RoarOfTheWurm extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new WurmToken())); // Flashback {3}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{G}"))); } private RoarOfTheWurm(final RoarOfTheWurm card) { diff --git a/Mage.Sets/src/mage/cards/r/RollingTemblor.java b/Mage.Sets/src/mage/cards/r/RollingTemblor.java index d6da7bb290f..ac06b6b7f29 100644 --- a/Mage.Sets/src/mage/cards/r/RollingTemblor.java +++ b/Mage.Sets/src/mage/cards/r/RollingTemblor.java @@ -33,7 +33,7 @@ public final class RollingTemblor extends CardImpl { // Rolling Temblor deals 2 damage to each creature without flying. this.getSpellAbility().addEffect(new DamageAllEffect(2, filter)); // Flashback {4}{R}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{R}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{R}{R}"))); } private RollingTemblor(final RollingTemblor card) { diff --git a/Mage.Sets/src/mage/cards/r/RootcoilCreeper.java b/Mage.Sets/src/mage/cards/r/RootcoilCreeper.java new file mode 100644 index 00000000000..248b8e5a938 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RootcoilCreeper.java @@ -0,0 +1,118 @@ +package mage.cards.r; + +import mage.ConditionalMana; +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.ExileSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.abilities.mana.AnyColorManaAbility; +import mage.abilities.mana.ConditionalAnyColorManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +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.FilterOwnedCard; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.target.common.TargetCardInExile; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RootcoilCreeper extends CardImpl { + + private static final FilterCard filter = new FilterOwnedCard("card with flashback you own from exile"); + + static { + filter.add(new AbilityPredicate(FlashbackAbility.class)); + } + + public RootcoilCreeper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + + // {T}: Add two mana of any one color. Spend this mana only to cast spells from your graveyard. + this.addAbility(new ConditionalAnyColorManaAbility( + new TapSourceCost(), 2, new RootcoilCreeperManaBuilder(), true + )); + + // {G}{U}, {T}, Exile Rootcoil Creeper: Return target card with flashback you own in exile to your hand. + Ability ability = new SimpleActivatedAbility( + new ReturnToHandTargetEffect() + .setText("return target card with flashback you own in exile to your hand"), + new ManaCostsImpl<>("{G}{U}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new ExileSourceCost()); + ability.addTarget(new TargetCardInExile(filter)); + this.addAbility(ability); + } + + private RootcoilCreeper(final RootcoilCreeper card) { + super(card); + } + + @Override + public RootcoilCreeper copy() { + return new RootcoilCreeper(this); + } +} + +class RootcoilCreeperManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new RootcoilCreeperConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to cast spells from your graveyard"; + } +} + +class RootcoilCreeperConditionalMana extends ConditionalMana { + + public RootcoilCreeperConditionalMana(Mana mana) { + super(mana); + staticText = "Spend this mana only to cast spells from your graveyard"; + addCondition(RootcoilCreeperManaCondition.instance); + } +} + +enum RootcoilCreeperManaCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + if (game == null || !game.inCheckPlayableState() + || !source.isControlledBy(game.getOwnerId(source.getSourceId()))) { + return false; + } + if (game.getCard(source.getSourceId()) != null + && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { + return true; + } + Spell spell = game.getSpell(source.getSourceId()); + return spell != null && spell.getFromZone() == Zone.GRAVEYARD; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RottenReunion.java b/Mage.Sets/src/mage/cards/r/RottenReunion.java index 535fa76a6a8..d720c4f4804 100644 --- a/Mage.Sets/src/mage/cards/r/RottenReunion.java +++ b/Mage.Sets/src/mage/cards/r/RottenReunion.java @@ -27,7 +27,7 @@ public final class RottenReunion extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInGraveyard(0, 1)); // Flashback {1}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{1}{B}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{B}"))); } private RottenReunion(final RottenReunion card) { diff --git a/Mage.Sets/src/mage/cards/r/RuinousIntrusion.java b/Mage.Sets/src/mage/cards/r/RuinousIntrusion.java new file mode 100644 index 00000000000..b46997f519a --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RuinousIntrusion.java @@ -0,0 +1,79 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RuinousIntrusion extends CardImpl { + + public RuinousIntrusion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{G}"); + + // Exile target artifact or enchantment. Put X +1/+1 counters on target creature you control, where X is the mana value of the permanent exiled this way. + this.getSpellAbility().addEffect(new RuinousIntrusionEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + } + + private RuinousIntrusion(final RuinousIntrusion card) { + super(card); + } + + @Override + public RuinousIntrusion copy() { + return new RuinousIntrusion(this); + } +} + +class RuinousIntrusionEffect extends OneShotEffect { + + RuinousIntrusionEffect() { + super(Outcome.Benefit); + staticText = "exile target artifact or enchantment. Put X +1/+1 counters on target creature you control, " + + "where X is the mana value of the permanent exiled this way"; + } + + private RuinousIntrusionEffect(final RuinousIntrusionEffect effect) { + super(effect); + } + + @Override + public RuinousIntrusionEffect copy() { + return new RuinousIntrusionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (player == null || permanent == null) { + return false; + } + int mv = permanent.getManaValue(); + player.moveCards(permanent, Zone.EXILED, source, game); + if (mv < 1) { + return true; + } + Permanent creature = game.getPermanent(source.getTargets().get(0).getFirstTarget()); + if (creature != null) { + creature.addCounters(CounterType.P1P1.createInstance(mv), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SacredFire.java b/Mage.Sets/src/mage/cards/s/SacredFire.java index 4d0a65072de..27efc4e4c86 100644 --- a/Mage.Sets/src/mage/cards/s/SacredFire.java +++ b/Mage.Sets/src/mage/cards/s/SacredFire.java @@ -26,7 +26,7 @@ public final class SacredFire extends CardImpl { this.getSpellAbility().addTarget(new TargetAnyTarget()); // Flashback {4}{R}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{4}{R}{W}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{4}{R}{W}"))); } private SacredFire(final SacredFire card) { diff --git a/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java b/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java index 8c8fad4a209..4317d671c37 100644 --- a/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java +++ b/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java @@ -12,8 +12,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.predicate.ObjectPlayerPredicate; import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.stack.Spell; @@ -59,7 +59,7 @@ public final class SageOfTheBeyond extends CardImpl { } } -enum SageOfTheBeyondPredicate implements ObjectPlayerPredicate> { +enum SageOfTheBeyondPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/s/SalvageTrader.java b/Mage.Sets/src/mage/cards/s/SalvageTrader.java index 76e21c7352f..eb31a44a3e7 100644 --- a/Mage.Sets/src/mage/cards/s/SalvageTrader.java +++ b/Mage.Sets/src/mage/cards/s/SalvageTrader.java @@ -57,7 +57,7 @@ public final class SalvageTrader extends CardImpl { } } -class SameCastingCostPredicate implements ObjectSourcePlayerPredicate> { +class SameCastingCostPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/s/SarythTheVipersFang.java b/Mage.Sets/src/mage/cards/s/SarythTheVipersFang.java index be8a2d116e7..37ca2031495 100644 --- a/Mage.Sets/src/mage/cards/s/SarythTheVipersFang.java +++ b/Mage.Sets/src/mage/cards/s/SarythTheVipersFang.java @@ -18,6 +18,7 @@ import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterUntappedCreature; import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.target.TargetPermanent; @@ -28,8 +29,8 @@ import java.util.UUID; */ public final class SarythTheVipersFang extends CardImpl { - private static final FilterPermanent filterTapped = new FilterControlledCreaturePermanent("tapped creatures you control"); - private static final FilterPermanent filterAbility = new FilterControlledPermanent("creature or land you control"); + private static final FilterPermanent filterTapped = new FilterControlledCreaturePermanent("tapped creatures"); + private static final FilterPermanent filterAbility = new FilterControlledPermanent("another target creature or land you control"); static { filterTapped.add(TappedPredicate.TAPPED); @@ -37,6 +38,7 @@ public final class SarythTheVipersFang extends CardImpl { CardType.CREATURE.getPredicate(), CardType.LAND.getPredicate() )); + filterAbility.add(AnotherPredicate.instance); } public SarythTheVipersFang(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/s/SavaenElves.java b/Mage.Sets/src/mage/cards/s/SavaenElves.java index 2c48c386efe..5b0d87ac768 100644 --- a/Mage.Sets/src/mage/cards/s/SavaenElves.java +++ b/Mage.Sets/src/mage/cards/s/SavaenElves.java @@ -15,8 +15,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterEnchantmentPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; @@ -59,9 +59,9 @@ public final class SavaenElves extends CardImpl { } } -class SavaenElvesPredicate implements ObjectPlayerPredicate> { +class SavaenElvesPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent attachment = input.getObject(); if (attachment != null) { Permanent permanent = game.getPermanent(attachment.getAttachedTo()); diff --git a/Mage.Sets/src/mage/cards/s/SavingGrasp.java b/Mage.Sets/src/mage/cards/s/SavingGrasp.java index 588d35a8255..775056361de 100644 --- a/Mage.Sets/src/mage/cards/s/SavingGrasp.java +++ b/Mage.Sets/src/mage/cards/s/SavingGrasp.java @@ -33,7 +33,7 @@ public final class SavingGrasp extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); // Flashback {W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{W}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{W}"))); } private SavingGrasp(final SavingGrasp card) { diff --git a/Mage.Sets/src/mage/cards/s/ScorchingMissile.java b/Mage.Sets/src/mage/cards/s/ScorchingMissile.java index dec85edfaae..8a217d2ff8f 100644 --- a/Mage.Sets/src/mage/cards/s/ScorchingMissile.java +++ b/Mage.Sets/src/mage/cards/s/ScorchingMissile.java @@ -25,7 +25,7 @@ public final class ScorchingMissile extends CardImpl { this.getSpellAbility().addTarget(new TargetPlayerOrPlaneswalker()); // Flashback {9}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{9}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{9}{R}"))); } diff --git a/Mage.Sets/src/mage/cards/s/ScourAllPossibilities.java b/Mage.Sets/src/mage/cards/s/ScourAllPossibilities.java index 6ce1b3d0f25..9018a5534a3 100644 --- a/Mage.Sets/src/mage/cards/s/ScourAllPossibilities.java +++ b/Mage.Sets/src/mage/cards/s/ScourAllPossibilities.java @@ -24,7 +24,7 @@ public final class ScourAllPossibilities extends CardImpl { this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy(", then")); // Flashback {4}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{U}"))); } diff --git a/Mage.Sets/src/mage/cards/s/ScreechingBat.java b/Mage.Sets/src/mage/cards/s/ScreechingBat.java index 63b29ed5cea..8c1c31fd8b7 100644 --- a/Mage.Sets/src/mage/cards/s/ScreechingBat.java +++ b/Mage.Sets/src/mage/cards/s/ScreechingBat.java @@ -1,15 +1,13 @@ - package mage.cards.s; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.condition.common.TransformedCondition; import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; @@ -20,7 +18,6 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; /** @@ -29,7 +26,7 @@ import mage.game.permanent.Permanent; public final class ScreechingBat extends CardImpl { public ScreechingBat(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); this.subtype.add(SubType.BAT); this.transformable = true; @@ -42,7 +39,7 @@ public final class ScreechingBat extends CardImpl { // At the beginning of your upkeep, you may pay {2}{B}{B}. If you do, transform Screeching Bat. this.addAbility(new TransformAbility()); - this.addAbility(new ConditionalTriggeredAbility(new ScreechingBatBeginningOfUpkeepTriggeredAbility(), new TransformedCondition(true), "")); + this.addAbility(new ScreechingBatBeginningOfUpkeepTriggeredAbility()); } private ScreechingBat(final ScreechingBat card) { @@ -108,9 +105,7 @@ class ScreechingBatTransformSourceEffect extends OneShotEffect { if (permanent != null) { Cost cost = new ManaCostsImpl("{2}{B}{B}"); if (cost.pay(source, game, source, permanent.getControllerId(), false, null)) { - if (permanent.isTransformable()) { - permanent.setTransformed(!permanent.isTransformed()); - } + new TransformSourceEffect(true).apply(game, source); } return true; } diff --git a/Mage.Sets/src/mage/cards/s/SeafaringWerewolf.java b/Mage.Sets/src/mage/cards/s/SeafaringWerewolf.java index f2789e2301d..c075a58ed96 100644 --- a/Mage.Sets/src/mage/cards/s/SeafaringWerewolf.java +++ b/Mage.Sets/src/mage/cards/s/SeafaringWerewolf.java @@ -36,7 +36,7 @@ public final class SeafaringWerewolf extends CardImpl { )); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private SeafaringWerewolf(final SeafaringWerewolf card) { diff --git a/Mage.Sets/src/mage/cards/s/SearchPartyCaptain.java b/Mage.Sets/src/mage/cards/s/SearchPartyCaptain.java new file mode 100644 index 00000000000..ae1ee2a13d6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SearchPartyCaptain.java @@ -0,0 +1,80 @@ +package mage.cards.s; + +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.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.watchers.common.PlayerAttackedWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SearchPartyCaptain extends CardImpl { + + private static final Hint hint = new ValueHint( + "Creatures you attacked with this turn", SearchPartyCaptainValue.instance + ); + + public SearchPartyCaptain(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // This spell costs {1} less to cast for each creature you attacked with this turn. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(SearchPartyCaptainValue.instance) + .setText("this spell costs {1} less to cast for each creature you attacked with this turn") + ).addHint(hint), new PlayerAttackedWatcher()); + + // When Search Party Captain enters the battlefield, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1))); + } + + private SearchPartyCaptain(final SearchPartyCaptain card) { + super(card); + } + + @Override + public SearchPartyCaptain copy() { + return new SearchPartyCaptain(this); + } +} + +enum SearchPartyCaptainValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getState() + .getWatcher(PlayerAttackedWatcher.class) + .getNumberOfAttackersCurrentTurn(sourceAbility.getControllerId()); + } + + @Override + public SearchPartyCaptainValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SeasonedCathar.java b/Mage.Sets/src/mage/cards/s/SeasonedCathar.java new file mode 100644 index 00000000000..85d9b7e5876 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeasonedCathar.java @@ -0,0 +1,40 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SeasonedCathar extends CardImpl { + + public SeasonedCathar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + this.color.setWhite(true); + this.transformable = true; + this.nightCard = true; + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + } + + private SeasonedCathar(final SeasonedCathar card) { + super(card); + } + + @Override + public SeasonedCathar copy() { + return new SeasonedCathar(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SecretsOfTheKey.java b/Mage.Sets/src/mage/cards/s/SecretsOfTheKey.java index 52e01b162a5..4e0874441e0 100644 --- a/Mage.Sets/src/mage/cards/s/SecretsOfTheKey.java +++ b/Mage.Sets/src/mage/cards/s/SecretsOfTheKey.java @@ -31,7 +31,7 @@ public final class SecretsOfTheKey extends CardImpl { )); // Flashback {3}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{U}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{U}"))); } private SecretsOfTheKey(final SecretsOfTheKey card) { diff --git a/Mage.Sets/src/mage/cards/s/SeizeTheDay.java b/Mage.Sets/src/mage/cards/s/SeizeTheDay.java index 0ef6c503e99..8c9e698c07d 100644 --- a/Mage.Sets/src/mage/cards/s/SeizeTheDay.java +++ b/Mage.Sets/src/mage/cards/s/SeizeTheDay.java @@ -27,7 +27,7 @@ public final class SeizeTheDay extends CardImpl { this.getSpellAbility().addEffect(new AddCombatAndMainPhaseEffect()); // Flashback {2}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{R}"))); } private SeizeTheDay(final SeizeTheDay card) { diff --git a/Mage.Sets/src/mage/cards/s/SeizeTheStorm.java b/Mage.Sets/src/mage/cards/s/SeizeTheStorm.java new file mode 100644 index 00000000000..420f316c59c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeizeTheStorm.java @@ -0,0 +1,80 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.SeizeTheStormToken; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SeizeTheStorm extends CardImpl { + + private static final Hint hint = new ValueHint( + "Spells in your graveyard and flashback cards in exile", SeizeTheStormValue.instance + ); + + public SeizeTheStorm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{R}"); + + // Create a red Elemental creature token with trample and "This creature's power and toughness are each equal to the number of instant and sorcery cards in your graveyard, plus the number of cards with flashback you own in exile." + this.getSpellAbility().addEffect(new CreateTokenEffect( + new SeizeTheStormToken(SeizeTheStormValue.instance, hint) + )); + this.getSpellAbility().addHint(hint); + + // Flashback {6}{R} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{6}{R}"))); + } + + private SeizeTheStorm(final SeizeTheStorm card) { + super(card); + } + + @Override + public SeizeTheStorm copy() { + return new SeizeTheStorm(this); + } +} + +enum SeizeTheStormValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Player player = game.getPlayer(sourceAbility.getControllerId()); + if (player == null) { + return 0; + } + return player.getGraveyard().count( + StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, game + ) + game.getExile() + .getAllCards(game, sourceAbility.getControllerId()) + .stream() + .filter(card -> card.getAbilities(game).containsClass(FlashbackAbility.class)) + .mapToInt(x -> 1).sum(); + } + + @Override + public SeizeTheStormValue copy() { + return this; + } + + @Override + public String getMessage() { + return "instant and sorcery cards in your graveyard, plus the number of cards with flashback you own in exile"; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java b/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java index c470cdb91c5..4c94dc9d2e3 100644 --- a/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java +++ b/Mage.Sets/src/mage/cards/s/SeverTheBloodline.java @@ -34,7 +34,7 @@ public final class SeverTheBloodline extends CardImpl { this.getSpellAbility().addEffect(new SeverTheBloodlineEffect()); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {5}{B}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{B}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{B}{B}"))); } private SeverTheBloodline(final SeverTheBloodline card) { diff --git a/Mage.Sets/src/mage/cards/s/SevinnesReclamation.java b/Mage.Sets/src/mage/cards/s/SevinnesReclamation.java index e60c3d23784..0cf6945e4b1 100644 --- a/Mage.Sets/src/mage/cards/s/SevinnesReclamation.java +++ b/Mage.Sets/src/mage/cards/s/SevinnesReclamation.java @@ -40,7 +40,7 @@ public final class SevinnesReclamation extends CardImpl { this.getSpellAbility().addEffect(new SevinnesReclamationEffect()); // Flashback {4}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{W}"))); } private SevinnesReclamation(final SevinnesReclamation card) { diff --git a/Mage.Sets/src/mage/cards/s/ShacklesOfTreachery.java b/Mage.Sets/src/mage/cards/s/ShacklesOfTreachery.java index 12af6a0c8b9..06667a49972 100644 --- a/Mage.Sets/src/mage/cards/s/ShacklesOfTreachery.java +++ b/Mage.Sets/src/mage/cards/s/ShacklesOfTreachery.java @@ -56,7 +56,7 @@ public final class ShacklesOfTreachery extends CardImpl { class ShacklesOfTreacheryTriggeredAbility extends TriggeredAbilityImpl { - private enum ShacklesOfTreacheryPredicate implements ObjectSourcePlayerPredicate> { + private enum ShacklesOfTreacheryPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/s/ShadowbeastSighting.java b/Mage.Sets/src/mage/cards/s/ShadowbeastSighting.java index f51827fc8e3..6fdbb21cf82 100644 --- a/Mage.Sets/src/mage/cards/s/ShadowbeastSighting.java +++ b/Mage.Sets/src/mage/cards/s/ShadowbeastSighting.java @@ -23,7 +23,7 @@ public final class ShadowbeastSighting extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new BeastToken2())); // Flashback {6}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{6}{G}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{6}{G}"))); } private ShadowbeastSighting(final ShadowbeastSighting card) { diff --git a/Mage.Sets/src/mage/cards/s/ShadyTraveler.java b/Mage.Sets/src/mage/cards/s/ShadyTraveler.java index a8cee198aa5..f2424f30024 100644 --- a/Mage.Sets/src/mage/cards/s/ShadyTraveler.java +++ b/Mage.Sets/src/mage/cards/s/ShadyTraveler.java @@ -3,6 +3,7 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.keyword.DayboundAbility; import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -29,7 +30,8 @@ public final class ShadyTraveler extends CardImpl { this.addAbility(new MenaceAbility()); // Daybound - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new TransformAbility()); + this.addAbility(new DayboundAbility()); } private ShadyTraveler(final ShadyTraveler card) { diff --git a/Mage.Sets/src/mage/cards/s/ShatteredPerception.java b/Mage.Sets/src/mage/cards/s/ShatteredPerception.java index f5383b5fb6f..f6780909757 100644 --- a/Mage.Sets/src/mage/cards/s/ShatteredPerception.java +++ b/Mage.Sets/src/mage/cards/s/ShatteredPerception.java @@ -22,7 +22,7 @@ public final class ShatteredPerception extends CardImpl { // Discard all the cards in your hand, then draw that many cards. this.getSpellAbility().addEffect(new DiscardHandDrawSameNumberSourceEffect()); // Flashback {5}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{R}"))); } private ShatteredPerception(final ShatteredPerception card) { diff --git a/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java b/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java index f7f3022ab0f..1eade2f0432 100644 --- a/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java +++ b/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java @@ -17,8 +17,8 @@ import mage.constants.SuperType; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.FilterSpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; @@ -156,10 +156,10 @@ class ShellOfTheLastKappaCastEffect extends OneShotEffect { } } -class TargetYouPredicate implements ObjectPlayerPredicate> { +class TargetYouPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { UUID controllerId = input.getPlayerId(); if (controllerId == null) { return false; diff --git a/Mage.Sets/src/mage/cards/s/ShelteredValley.java b/Mage.Sets/src/mage/cards/s/ShelteredValley.java index a5721941a6e..51176f197a3 100644 --- a/Mage.Sets/src/mage/cards/s/ShelteredValley.java +++ b/Mage.Sets/src/mage/cards/s/ShelteredValley.java @@ -5,10 +5,9 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.costs.common.SacrificeAllCost; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.EnterBattlefieldPayCostOrPutGraveyardEffect; import mage.abilities.effects.common.GainLifeEffect; @@ -30,7 +29,7 @@ import mage.filter.predicate.mageobject.NamePredicate; public final class ShelteredValley extends CardImpl { private static final FilterPermanent filterShelteredValley = new FilterPermanent("permanent named Sheltered Valley"); - + static { filterShelteredValley.add(new NamePredicate("Sheltered Valley")); } @@ -43,13 +42,13 @@ public final class ShelteredValley extends CardImpl { effect.setText("If {this} would enter the battlefield, instead sacrifice each other permanent named {this} you control, then put {this} onto the battlefield."); Ability ability = new SimpleStaticAbility(Zone.ALL, effect); this.addAbility(ability); - + // At the beginning of your upkeep, if you control three or fewer lands, you gain 1 life. - Condition controls = new PermanentsOnTheBattlefieldCondition(StaticFilters.FILTER_LANDS, ComparisonType.FEWER_THAN, 4); - effect = new ConditionalOneShotEffect(new GainLifeEffect(1), controls); - effect.setText("if you control three or fewer lands, you gain 1 life"); - ability = new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, effect, TargetController.YOU, false); - this.addAbility(ability); + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new GainLifeEffect(1), TargetController.YOU, false), + new PermanentsOnTheBattlefieldCondition(StaticFilters.FILTER_LANDS, ComparisonType.FEWER_THAN, 4), + "At the beginning of your upkeep, if you control three or fewer lands, you gain 1 life." + )); // {tap}: Add {C}. this.addAbility(new ColorlessManaAbility()); } diff --git a/Mage.Sets/src/mage/cards/s/ShipwreckSifters.java b/Mage.Sets/src/mage/cards/s/ShipwreckSifters.java new file mode 100644 index 00000000000..1e95e8709be --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShipwreckSifters.java @@ -0,0 +1,60 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DiscardCardControllerTriggeredAbility; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.DisturbAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShipwreckSifters extends CardImpl { + + private static final FilterCard filter = new FilterCard("a Spirit card or a card with disturb"); + + static { + filter.add(Predicates.or( + SubType.SPIRIT.getPredicate(), + new AbilityPredicate(DisturbAbility.class) + )); + } + + public ShipwreckSifters(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // When Shipwreck Sifters enters the battlefield, draw a card, then discard a card. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new DrawDiscardControllerEffect(1, 1) + )); + + // Whenever you discard a Spirit card or a card with disturb, put a +1/+1 counter on Shipwreck Sifters. + this.addAbility(new DiscardCardControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false, filter + )); + } + + private ShipwreckSifters(final ShipwreckSifters card) { + super(card); + } + + @Override + public ShipwreckSifters copy() { + return new ShipwreckSifters(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SiegeZombie.java b/Mage.Sets/src/mage/cards/s/SiegeZombie.java new file mode 100644 index 00000000000..44df52957a4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SiegeZombie.java @@ -0,0 +1,45 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SiegeZombie extends CardImpl { + + public SiegeZombie(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Tap three untapped creatures you control: Each opponent loses 1 life. + this.addAbility(new SimpleActivatedAbility( + new LoseLifeOpponentsEffect(1), + new TapTargetCost(new TargetControlledPermanent( + 3, StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURES + )) + )); + } + + private SiegeZombie(final SiegeZombie card) { + super(card); + } + + @Override + public SiegeZombie copy() { + return new SiegeZombie(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SigardaChampionOfLight.java b/Mage.Sets/src/mage/cards/s/SigardaChampionOfLight.java index 51b3ab9f0cd..7875c82d9c8 100644 --- a/Mage.Sets/src/mage/cards/s/SigardaChampionOfLight.java +++ b/Mage.Sets/src/mage/cards/s/SigardaChampionOfLight.java @@ -29,7 +29,7 @@ public final class SigardaChampionOfLight extends CardImpl { private static final FilterCard filter2 = new FilterCreatureCard("Human creature card"); static { - filter.add(SubType.HUMAN.getPredicate()); + filter2.add(SubType.HUMAN.getPredicate()); } public SigardaChampionOfLight(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/s/SigardasSplendor.java b/Mage.Sets/src/mage/cards/s/SigardasSplendor.java new file mode 100644 index 00000000000..8a4e4b1bea1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SigardasSplendor.java @@ -0,0 +1,141 @@ +package mage.cards.s; + +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SigardasSplendor extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a white spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.WHITE)); + } + + public SigardasSplendor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}{W}"); + + // As Sigarda's Splendor enters the battlefield, note your life total. + this.addAbility(new AsEntersBattlefieldAbility(new SigardasSplendorNoteEffect())); + + // At the beginning of your upkeep, draw a card if your life total is greater than or equal to the last noted life total for Sigarda's Splendor. Then note your life total. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new SigardasSplendorDrawEffect(), TargetController.YOU, false + ).addHint(SigardasSplendorHint.instance)); + + // Whenever you cast a white spell, you gain 1 life. + this.addAbility(new SpellCastControllerTriggeredAbility(new GainLifeEffect(1), filter, false)); + } + + private SigardasSplendor(final SigardasSplendor card) { + super(card); + } + + @Override + public SigardasSplendor copy() { + return new SigardasSplendor(this); + } + + static String getKey(Ability source, int offset) { + return "SigardasSplendor_" + source.getControllerId() + "_" + source.getSourceId() + + "_" + (source.getSourceObjectZoneChangeCounter() + offset); + } +} + +enum SigardasSplendorHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + if (ability.getSourcePermanentIfItStillExists(game) == null) { + return null; + } + Object object = game.getState().getValue(SigardasSplendor.getKey(ability, 0)); + return "Last noted life total: " + (object != null ? (Integer) object : "None"); + } + + @Override + public SigardasSplendorHint copy() { + return this; + } +} + +class SigardasSplendorNoteEffect extends OneShotEffect { + + SigardasSplendorNoteEffect() { + super(Outcome.Benefit); + staticText = "note your life total"; + } + + private SigardasSplendorNoteEffect(final SigardasSplendorNoteEffect effect) { + super(effect); + } + + @Override + public SigardasSplendorNoteEffect copy() { + return new SigardasSplendorNoteEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + game.getState().setValue(SigardasSplendor.getKey(source, -1), player.getLife()); + return true; + } +} + +class SigardasSplendorDrawEffect extends OneShotEffect { + + SigardasSplendorDrawEffect() { + super(Outcome.Benefit); + staticText = "draw a card if your life total is greater than or equal " + + "to the last noted life total for {this}. Then note your life total"; + } + + private SigardasSplendorDrawEffect(final SigardasSplendorDrawEffect effect) { + super(effect); + } + + @Override + public SigardasSplendorDrawEffect copy() { + return new SigardasSplendorDrawEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + String key = SigardasSplendor.getKey(source, 0); + Object object = game.getState().getValue(key); + int notedLife = object instanceof Integer ? (Integer) object : Integer.MIN_VALUE; + if (player.getLife() >= notedLife) { + player.drawCards(1, source, game); + } + game.getState().setValue(key, player.getLife()); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SigardasVanguard.java b/Mage.Sets/src/mage/cards/s/SigardasVanguard.java new file mode 100644 index 00000000000..848778c0dce --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SigardasVanguard.java @@ -0,0 +1,92 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.hint.common.CovenHint; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturesWithDifferentPowers; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SigardasVanguard extends CardImpl { + + public SigardasVanguard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Sigarda's Vanguard enters the battlefield or attacks, choose any number of creatures with different powers. Those creatures gain double strike until end of turn. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new SigardasVanguardEffect()).addHint(CovenHint.instance)); + } + + private SigardasVanguard(final SigardasVanguard card) { + super(card); + } + + @Override + public SigardasVanguard copy() { + return new SigardasVanguard(this); + } +} + +class SigardasVanguardEffect extends OneShotEffect { + + SigardasVanguardEffect() { + super(Outcome.Benefit); + staticText = "choose any number of creatures with different powers. " + + "Those creatures gain double strike until end of turn"; + } + + private SigardasVanguardEffect(final SigardasVanguardEffect effect) { + super(effect); + } + + @Override + public SigardasVanguardEffect copy() { + return new SigardasVanguardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetPermanent target = new TargetCreaturesWithDifferentPowers(); + player.choose(outcome, target, source.getSourceId(), game); + if (target.getTargets().isEmpty()) { + return false; + } + game.addEffect(new GainAbilityTargetEffect( + DoubleStrikeAbility.getInstance(), Duration.EndOfTurn + ).setTargetPointer(new FixedTargets(new CardsImpl(target.getTargets()), game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SigardianSavior.java b/Mage.Sets/src/mage/cards/s/SigardianSavior.java new file mode 100644 index 00000000000..abb9cdd8171 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SigardianSavior.java @@ -0,0 +1,63 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.CastFromEverywhereSourceCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.FlyingAbility; +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.FilterCreatureCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SigardianSavior extends CardImpl { + + private static final FilterCard filter + = new FilterCreatureCard("creature cards with mana value 2 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 3)); + } + + public SigardianSavior(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Sigardian Savior enters the battlefield, if you cast it, return up to two target creature cards with mana value 2 or less from your graveyard to the battlefield. + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()), + CastFromEverywhereSourceCondition.instance, "When {this} enters the battlefield, " + + "if you cast it, return up to two target creature cards with mana value " + + "2 or less from your graveyard to the battlefield." + ); + ability.addTarget(new TargetCardInYourGraveyard(0, 2, filter)); + this.addAbility(ability); + } + + private SigardianSavior(final SigardianSavior card) { + super(card); + } + + @Override + public SigardianSavior copy() { + return new SigardianSavior(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SigardianZealot.java b/Mage.Sets/src/mage/cards/s/SigardianZealot.java new file mode 100644 index 00000000000..8a1ba21af81 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SigardianZealot.java @@ -0,0 +1,93 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturesWithDifferentPowers; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SigardianZealot extends CardImpl { + + public SigardianZealot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // At the beginning of combat on your turn, choose any number of creatures with different powers. Each of them gets +X/+X and gains vigilance until end of turn, where X is Sigardian Zealot's power. + this.addAbility(new BeginningOfCombatTriggeredAbility( + new SigardianZealotEffect(), TargetController.YOU, false + )); + } + + private SigardianZealot(final SigardianZealot card) { + super(card); + } + + @Override + public SigardianZealot copy() { + return new SigardianZealot(this); + } +} + +class SigardianZealotEffect extends OneShotEffect { + + SigardianZealotEffect() { + super(Outcome.Benefit); + staticText = "choose any number of creatures with different powers. " + + "Each of them gets +X/+X and gains vigilance until end of turn, where X is {this}'s power"; + } + + private SigardianZealotEffect(final SigardianZealotEffect effect) { + super(effect); + } + + @Override + public SigardianZealotEffect copy() { + return new SigardianZealotEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (player == null || permanent == null) { + return false; + } + int power = permanent.getPower().getValue(); + if (power == 0) { + return false; + } + TargetPermanent target = new TargetCreaturesWithDifferentPowers(); + player.choose(outcome, target, source.getSourceId(), game); + Cards cards = new CardsImpl(target.getTargets()); + if (cards.isEmpty()) { + return false; + } + game.addEffect(new BoostTargetEffect(power, power).setTargetPointer(new FixedTargets(cards, game)), source); + game.addEffect(new GainAbilityTargetEffect( + VigilanceAbility.getInstance(), Duration.EndOfTurn + ).setTargetPointer(new FixedTargets(cards, game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SilentDeparture.java b/Mage.Sets/src/mage/cards/s/SilentDeparture.java index c3770e0e07c..d0e8e7cb8a4 100644 --- a/Mage.Sets/src/mage/cards/s/SilentDeparture.java +++ b/Mage.Sets/src/mage/cards/s/SilentDeparture.java @@ -25,7 +25,7 @@ public final class SilentDeparture extends CardImpl { this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); // Flashback {4}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{U}"))); } private SilentDeparture(final SilentDeparture card) { diff --git a/Mage.Sets/src/mage/cards/s/SilverBolt.java b/Mage.Sets/src/mage/cards/s/SilverBolt.java new file mode 100644 index 00000000000..136d9f4be03 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SilverBolt.java @@ -0,0 +1,74 @@ +package mage.cards.s; + +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.OneShotEffect; +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.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SilverBolt extends CardImpl { + + public SilverBolt(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + // {3}, {T}, Sacrifice Silver Bolt: It deals 3 damage to target creature. If a Werewolf is dealt damage this way, destroy it. + Ability ability = new SimpleActivatedAbility(new SilverBoltEffect(), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private SilverBolt(final SilverBolt card) { + super(card); + } + + @Override + public SilverBolt copy() { + return new SilverBolt(this); + } +} + +class SilverBoltEffect extends OneShotEffect { + + SilverBoltEffect() { + super(Outcome.Benefit); + staticText = "it deals 3 damage to target creature. If a Werewolf is dealt damage this way, destroy it"; + } + + private SilverBoltEffect(final SilverBoltEffect effect) { + super(effect); + } + + @Override + public SilverBoltEffect copy() { + return new SilverBoltEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + if (permanent.damage(3, source, game) > 0 + && permanent.hasSubtype(SubType.WEREWOLF, game)) { + permanent.destroy(source, game, false); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SilverWyvern.java b/Mage.Sets/src/mage/cards/s/SilverWyvern.java index 1a01717a61f..b1b0e8d21ec 100644 --- a/Mage.Sets/src/mage/cards/s/SilverWyvern.java +++ b/Mage.Sets/src/mage/cards/s/SilverWyvern.java @@ -66,7 +66,7 @@ public final class SilverWyvern extends CardImpl { } } -enum SilverWyvernPredicate implements ObjectSourcePlayerPredicate> { +enum SilverWyvernPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/s/SilverquillSilencer.java b/Mage.Sets/src/mage/cards/s/SilverquillSilencer.java index 8ea5ef8ed9f..fba59f6c3a2 100644 --- a/Mage.Sets/src/mage/cards/s/SilverquillSilencer.java +++ b/Mage.Sets/src/mage/cards/s/SilverquillSilencer.java @@ -65,7 +65,7 @@ public final class SilverquillSilencer extends CardImpl { } } -enum SilverquillSilencerPredicate implements ObjectSourcePlayerPredicate> { +enum SilverquillSilencerPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/s/SimicGuildmage.java b/Mage.Sets/src/mage/cards/s/SimicGuildmage.java index 06cd89b6126..aaa757e9285 100644 --- a/Mage.Sets/src/mage/cards/s/SimicGuildmage.java +++ b/Mage.Sets/src/mage/cards/s/SimicGuildmage.java @@ -120,7 +120,7 @@ class MoveCounterFromTargetToTargetEffect extends OneShotEffect { } } -class SameControllerPredicate implements ObjectSourcePlayerPredicate> { +class SameControllerPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/s/SiphonInsight.java b/Mage.Sets/src/mage/cards/s/SiphonInsight.java new file mode 100644 index 00000000000..b3603389046 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SiphonInsight.java @@ -0,0 +1,244 @@ +package mage.cards.s; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.AsThoughManaEffect; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.ManaPoolItem; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 plus everyone who worked on Gonti + */ +public final class SiphonInsight extends CardImpl { + + public SiphonInsight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{B}"); + + // Look at the top two cards of target opponent's library. Exile one of them face down and put the other on the bottom of that library. You may look at and play the exiled card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast that spell. + this.getSpellAbility().addEffect(new SiphonInsightEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + + // Flashback {1}{U}{B} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{U}{B}"))); + } + + private SiphonInsight(final SiphonInsight card) { + super(card); + } + + @Override + public SiphonInsight copy() { + return new SiphonInsight(this); + } +} + +class SiphonInsightEffect extends OneShotEffect { + + private static final String VALUE_PREFIX = "ExileZones"; + + public SiphonInsightEffect() { + super(Outcome.Benefit); + this.staticText = "look at the top two cards of target opponent's library. " + + "Exile one of them face down and put the other on the bottom of that library. " + + "You may look at and play the exiled card for as long as it remains exiled, " + + "and you may spend mana as though it were mana of any color to cast that spell"; + } + + private SiphonInsightEffect(final SiphonInsightEffect effect) { + super(effect); + } + + @Override + public SiphonInsightEffect copy() { + return new SiphonInsightEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || opponent == null || sourceObject == null) { + return false; + } + Cards topCards = new CardsImpl(); + topCards.addAll(opponent.getLibrary().getTopCards(game, 2)); + TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card to exile")); + controller.choose(outcome, topCards, target, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + controller.putCardsOnBottomOfLibrary(topCards, game, source, false); + return true; + } + topCards.remove(card); + // move card to exile + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + card.setFaceDown(true, game); + if (controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName())) { + card.setFaceDown(true, game); + Set exileZones = (Set) game.getState().getValue(VALUE_PREFIX + source.getSourceId().toString()); + if (exileZones == null) { + exileZones = new HashSet<>(); + game.getState().setValue(VALUE_PREFIX + source.getSourceId().toString(), exileZones); + } + exileZones.add(exileZoneId); + // allow to cast the card + ContinuousEffect effect = new SiphonInsightCastFromExileEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + // and you may spend mana as though it were mana of any color to cast it + effect = new SiphonInsightSpendAnyManaEffect(); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + // For as long as that card remains exiled, you may look at it + effect = new SiphonInsightLookEffect(controller.getId()); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + } + // then put the rest on the bottom of that library in a random order + controller.putCardsOnBottomOfLibrary(topCards, game, source, false); + return true; + } +} + +class SiphonInsightCastFromExileEffect extends AsThoughEffectImpl { + + public SiphonInsightCastFromExileEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + staticText = "You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast that spell"; + } + + private SiphonInsightCastFromExileEffect(final SiphonInsightCastFromExileEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public SiphonInsightCastFromExileEffect copy() { + return new SiphonInsightCastFromExileEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID targetId = getTargetPointer().getFirst(game, source); + if (targetId == null) { + this.discard(); // card is no longer in the origin zone, effect can be discarded + return false; + } + Card theCard = game.getCard(objectId); + if (theCard == null || theCard.isLand(game)) { + return false; + } + objectId = theCard.getMainCard().getId(); // for split cards + + if (objectId.equals(targetId) + && affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); + // TODO: Allow to cast Zoetic Cavern face down + return card != null; + } + return false; + } +} + +class SiphonInsightSpendAnyManaEffect extends AsThoughEffectImpl implements AsThoughManaEffect { + + public SiphonInsightSpendAnyManaEffect() { + super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.Custom, Outcome.Benefit); + staticText = "you may spend mana as though it were mana of any color to cast it"; + } + + private SiphonInsightSpendAnyManaEffect(final SiphonInsightSpendAnyManaEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public SiphonInsightSpendAnyManaEffect copy() { + return new SiphonInsightSpendAnyManaEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + Card theCard = game.getCard(objectId); + if (theCard == null) { + return false; + } + objectId = theCard.getMainCard().getId(); // for split cards + if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) + && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { + // if the card moved from exile to spell the zone change counter is increased by 1 (effect must applies before and on stack, use isCheckPlayableMode?) + return source.isControlledBy(affectedControllerId); + } else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { + // object has moved zone so effect can be discarded + this.discard(); + } + return false; + } + + @Override + public ManaType getAsThoughManaType(ManaType manaType, ManaPoolItem mana, UUID affectedControllerId, Ability source, Game game) { + return mana.getFirstAvailable(); + } +} + +class SiphonInsightLookEffect extends AsThoughEffectImpl { + + private final UUID authorizedPlayerId; + + public SiphonInsightLookEffect(UUID authorizedPlayerId) { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + this.authorizedPlayerId = authorizedPlayerId; + staticText = "You may look at the cards exiled with {this}"; + } + + private SiphonInsightLookEffect(final SiphonInsightLookEffect effect) { + super(effect); + this.authorizedPlayerId = effect.authorizedPlayerId; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public SiphonInsightLookEffect copy() { + return new SiphonInsightLookEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID cardId = getTargetPointer().getFirst(game, source); + if (cardId == null) { + this.discard(); // card is no longer in the origin zone, effect can be discarded + } + return affectedControllerId.equals(authorizedPlayerId) + && objectId.equals(cardId); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SkaabWrangler.java b/Mage.Sets/src/mage/cards/s/SkaabWrangler.java new file mode 100644 index 00000000000..51523ee24d2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SkaabWrangler.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapTargetCost; +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.StaticFilters; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SkaabWrangler extends CardImpl { + + public SkaabWrangler(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(2); + this.toughness = new MageInt(1); + + // Tap three untapped creatures you control: Tap target creature. + Ability ability = new SimpleActivatedAbility( + new TapTargetEffect(), + new TapTargetCost(new TargetControlledPermanent( + 3, StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURES + )) + ); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private SkaabWrangler(final SkaabWrangler card) { + super(card); + } + + @Override + public SkaabWrangler copy() { + return new SkaabWrangler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SkeletalScrying.java b/Mage.Sets/src/mage/cards/s/SkeletalScrying.java index 3c935af9537..fe3181c71b5 100644 --- a/Mage.Sets/src/mage/cards/s/SkeletalScrying.java +++ b/Mage.Sets/src/mage/cards/s/SkeletalScrying.java @@ -6,7 +6,7 @@ import mage.abilities.costs.CostAdjuster; import mage.abilities.costs.common.ExileFromGraveCost; import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; import mage.abilities.effects.common.InfoEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -41,7 +41,7 @@ public final class SkeletalScrying extends CardImpl { this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect( ManacostVariableValue.REGULAR ).setText("you draw X cards")); - this.getSpellAbility().addEffect(new GainLifeEffect( + this.getSpellAbility().addEffect(new LoseLifeSourceControllerEffect( ManacostVariableValue.REGULAR ).concatBy("and")); } diff --git a/Mage.Sets/src/mage/cards/s/SkullFracture.java b/Mage.Sets/src/mage/cards/s/SkullFracture.java index 9ae9ca46462..a4186e6dd26 100644 --- a/Mage.Sets/src/mage/cards/s/SkullFracture.java +++ b/Mage.Sets/src/mage/cards/s/SkullFracture.java @@ -26,7 +26,7 @@ public final class SkullFracture extends CardImpl { this.getSpellAbility().addEffect(new DiscardTargetEffect(1)); this.getSpellAbility().addTarget(new TargetPlayer()); // Flashback {3}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{B}"))); } private SkullFracture(final SkullFracture card) { diff --git a/Mage.Sets/src/mage/cards/s/SkullportMerchant.java b/Mage.Sets/src/mage/cards/s/SkullportMerchant.java index 077336654e9..33aea4c8809 100644 --- a/Mage.Sets/src/mage/cards/s/SkullportMerchant.java +++ b/Mage.Sets/src/mage/cards/s/SkullportMerchant.java @@ -60,7 +60,7 @@ public final class SkullportMerchant extends CardImpl { } } -enum SkullportMerchantPredicate implements ObjectSourcePlayerPredicate> { +enum SkullportMerchantPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/s/SlaughterSpecialist.java b/Mage.Sets/src/mage/cards/s/SlaughterSpecialist.java index 2546bfbe793..6987cc67ef9 100644 --- a/Mage.Sets/src/mage/cards/s/SlaughterSpecialist.java +++ b/Mage.Sets/src/mage/cards/s/SlaughterSpecialist.java @@ -38,7 +38,7 @@ public final class SlaughterSpecialist extends CardImpl { // Whenever a creature an opponent controls dies, put a +1/+1 counter on Slaughter Specialist. this.addAbility(new DiesCreatureTriggeredAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance()), - false, StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE + false, StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE )); } diff --git a/Mage.Sets/src/mage/cards/s/SlogurkTheOverslime.java b/Mage.Sets/src/mage/cards/s/SlogurkTheOverslime.java new file mode 100644 index 00000000000..11f66bc3273 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SlogurkTheOverslime.java @@ -0,0 +1,70 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.common.PutCardIntoGraveFromAnywhereAllTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.effects.common.ReturnToHandSourceEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterLandCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author ciaccona007 + */ +public final class SlogurkTheOverslime extends CardImpl { + + public SlogurkTheOverslime(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.OOZE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever a land card is put into your graveyard from anywhere, put a +1/+1 counter on Slogurk, the Overslime. + this.addAbility(new PutCardIntoGraveFromAnywhereAllTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + false, StaticFilters.FILTER_CARD_LAND_A, TargetController.YOU + )); + + // Remove three +1/+1 counters from Slogurk: Return it to its owner's hand. + this.addAbility(new SimpleActivatedAbility( + new ReturnToHandSourceEffect().setText("return it to its owner's hand"), + new RemoveCountersSourceCost(CounterType.P1P1.createInstance(3)) + )); + + // When Slogurk leaves the battlefield, return up to three target land cards from your graveyard to your hand. + Ability ability = new LeavesBattlefieldTriggeredAbility(new ReturnToHandTargetEffect() + .setText("return up to three target land cards from your graveyard to your hand"), false); + ability.addTarget(new TargetCardInYourGraveyard( + 0, 3, new FilterLandCard("land cards from your graveyard"))); + this.addAbility(ability); + } + + private SlogurkTheOverslime(final SlogurkTheOverslime card) { + super(card); + } + + @Override + public SlogurkTheOverslime copy() { + return new SlogurkTheOverslime(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SludgeMonster.java b/Mage.Sets/src/mage/cards/s/SludgeMonster.java new file mode 100644 index 00000000000..29775592cd1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SludgeMonster.java @@ -0,0 +1,121 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SludgeMonster extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("other creature"); + + static { + filter.add(AnotherPredicate.instance); + } + + public SludgeMonster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); + + this.subtype.add(SubType.HORROR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Whenever Sludge Monster enters the battlefield or attacks, put a slime counter on up to one other target creature. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility( + new AddCountersTargetEffect(CounterType.SLIME.createInstance()) + .setText("put a slime counter on up to one other target creature") + ); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + + // Non-Horror creatures with slime counters on them lose all abilities and have base power and toughness 2/2. + this.addAbility(new SimpleStaticAbility(new SludgeMonsterEffect())); + } + + private SludgeMonster(final SludgeMonster card) { + super(card); + } + + @Override + public SludgeMonster copy() { + return new SludgeMonster(this); + } +} + +class SludgeMonsterEffect extends ContinuousEffectImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(CounterType.SLIME.getPredicate()); + filter.add(Predicates.not(SubType.HORROR.getPredicate())); + } + + SludgeMonsterEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "non-Horror creatures with slime counters on them " + + "lose all abilities and have base power and toughness 2/2"; + } + + private SludgeMonsterEffect(final SludgeMonsterEffect effect) { + super(effect); + } + + @Override + public SludgeMonsterEffect copy() { + return new SludgeMonsterEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + filter, source.getControllerId(), source.getSourceId(), game + )) { + switch (layer) { + case AbilityAddingRemovingEffects_6: + permanent.removeAllAbilities(source.getSourceId(), game); + return true; + case PTChangingEffects_7: + if (sublayer == SubLayer.SetPT_7b) { + permanent.getPower().setValue(2); + permanent.getToughness().setValue(2); + return true; + } + } + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + switch (layer) { + case AbilityAddingRemovingEffects_6: + case PTChangingEffects_7: + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SmitingHelix.java b/Mage.Sets/src/mage/cards/s/SmitingHelix.java index a2c6ec0951c..7dba0e9b9bc 100644 --- a/Mage.Sets/src/mage/cards/s/SmitingHelix.java +++ b/Mage.Sets/src/mage/cards/s/SmitingHelix.java @@ -26,7 +26,7 @@ public final class SmitingHelix extends CardImpl { this.getSpellAbility().addTarget(new TargetAnyTarget()); // Flashback {R}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{R}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{R}{W}"))); } private SmitingHelix(final SmitingHelix card) { diff --git a/Mage.Sets/src/mage/cards/s/SmolderingEgg.java b/Mage.Sets/src/mage/cards/s/SmolderingEgg.java new file mode 100644 index 00000000000..b64c045305d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SmolderingEgg.java @@ -0,0 +1,98 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.DefenderAbility; +import mage.abilities.keyword.TransformAbility; +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.game.stack.Spell; +import mage.watchers.common.ManaPaidSourceWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SmolderingEgg extends CardImpl { + + public SmolderingEgg(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.DRAGON); + this.subtype.add(SubType.EGG); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + this.transformable = true; + this.secondSideCardClazz = mage.cards.a.AshmouthDragon.class; + + // Defender + this.addAbility(DefenderAbility.getInstance()); + + // Whenever you cast an instant or sorcery spell, put a number of ember counters on Smoldering Egg equal to the amount of mana spent to cast that spell. Then if Smoldering Egg has seven or more ember counters on it, remove them and transform Smoldering Egg. + this.addAbility(new TransformAbility()); + this.addAbility(new SpellCastControllerTriggeredAbility( + new SmolderingEggEffect(), StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false, true + )); + } + + private SmolderingEgg(final SmolderingEgg card) { + super(card); + } + + @Override + public SmolderingEgg copy() { + return new SmolderingEgg(this); + } +} + +class SmolderingEggEffect extends OneShotEffect { + + SmolderingEggEffect() { + super(Outcome.Benefit); + staticText = "put a number of ember counters on {this} equal to the amount of mana spent to cast that spell. " + + "Then if {this} has seven or more ember counters on it, remove them and transform {this}"; + } + + private SmolderingEggEffect(final SmolderingEggEffect effect) { + super(effect); + } + + @Override + public SmolderingEggEffect copy() { + return new SmolderingEggEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + return false; + } + Spell spell = (Spell) getValue("spellCast"); + if (spell != null) { + permanent.addCounters( + CounterType.EMBER.createInstance( + ManaPaidSourceWatcher.getTotalPaid(spell.getId(), game) + ), source.getControllerId(), source, game + ); + } + int counters = permanent.getCounters(game).getCount(CounterType.EMBER); + if (counters < 7) { + return true; + } + permanent.removeCounters(CounterType.EMBER.createInstance(counters), source, game); + new TransformSourceEffect(true).apply(game, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SnapcasterMage.java b/Mage.Sets/src/mage/cards/s/SnapcasterMage.java index 75abfda093c..58a03c9c026 100644 --- a/Mage.Sets/src/mage/cards/s/SnapcasterMage.java +++ b/Mage.Sets/src/mage/cards/s/SnapcasterMage.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -11,20 +10,15 @@ import mage.abilities.keyword.FlashbackAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.TimingRule; +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 BetaSteward */ public final class SnapcasterMage extends CardImpl { @@ -38,7 +32,7 @@ public final class SnapcasterMage extends CardImpl { } public SnapcasterMage(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WIZARD); @@ -84,13 +78,7 @@ class SnapcasterMageEffect extends ContinuousEffectImpl { public boolean apply(Game game, Ability source) { Card card = game.getCard(targetPointer.getFirst(game, source)); if (card != null) { - FlashbackAbility ability; - if (card.isInstant(game)) { - ability = new FlashbackAbility(card.getManaCost(), TimingRule.INSTANT); - } - else { - ability = new FlashbackAbility(card.getManaCost(), TimingRule.SORCERY); - } + FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); ability.setSourceId(card.getId()); ability.setControllerId(card.getOwnerId()); game.getState().addOtherAbility(card, ability); diff --git a/Mage.Sets/src/mage/cards/s/SomberwaldBeastmaster.java b/Mage.Sets/src/mage/cards/s/SomberwaldBeastmaster.java new file mode 100644 index 00000000000..f9e1d8fb970 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SomberwaldBeastmaster.java @@ -0,0 +1,55 @@ +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.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.BeastToken; +import mage.game.permanent.token.BeastToken2; +import mage.game.permanent.token.WolfToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SomberwaldBeastmaster extends CardImpl { + + public SomberwaldBeastmaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.RANGER); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When Somberwald Beastmaster enters the battlefield, create a 2/2 green Wolf creature token, a 3/3 green Beast creature token, and a 4/4 green Beast creature token. + Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new WolfToken())); + ability.addEffect(new CreateTokenEffect(new BeastToken()).setText(", a 3/3 green Beast creature token")); + ability.addEffect(new CreateTokenEffect(new BeastToken2()).setText(", and a 4/4 green Beast creature token")); + this.addAbility(ability); + + // Creature tokens you control have deathtouch. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + DeathtouchAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_CREATURE_TOKENS + ))); + } + + private SomberwaldBeastmaster(final SomberwaldBeastmaster card) { + super(card); + } + + @Override + public SomberwaldBeastmaster copy() { + return new SomberwaldBeastmaster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SoulGuideGryff.java b/Mage.Sets/src/mage/cards/s/SoulGuideGryff.java new file mode 100644 index 00000000000..0ca2513aadf --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SoulGuideGryff.java @@ -0,0 +1,46 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SoulGuideGryff extends CardImpl { + + public SoulGuideGryff(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.HIPPOGRIFF); + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Soul-Guide Gryff enters the battlefield, exile up to one target card from a graveyard. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect()); + ability.addTarget(new TargetCardInGraveyard(0, 1)); + this.addAbility(ability); + } + + private SoulGuideGryff(final SoulGuideGryff card) { + super(card); + } + + @Override + public SoulGuideGryff copy() { + return new SoulGuideGryff(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SoulShatter.java b/Mage.Sets/src/mage/cards/s/SoulShatter.java index 60ec94f3a53..b690afb3380 100644 --- a/Mage.Sets/src/mage/cards/s/SoulShatter.java +++ b/Mage.Sets/src/mage/cards/s/SoulShatter.java @@ -8,8 +8,8 @@ import mage.constants.CardType; import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -47,7 +47,7 @@ public final class SoulShatter extends CardImpl { } } -enum SoulShatterPredicate implements ObjectPlayerPredicate> { +enum SoulShatterPredicate implements ObjectSourcePlayerPredicate { instance; private static final FilterPermanent filter = new FilterCreatureOrPlaneswalkerPermanent(); @@ -57,7 +57,7 @@ enum SoulShatterPredicate implements ObjectPlayerPredicate input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { int cmc = game.getBattlefield() .getActivePermanents(filter, input.getPlayerId(), game) .stream() diff --git a/Mage.Sets/src/mage/cards/s/SpareDagger.java b/Mage.Sets/src/mage/cards/s/SpareDagger.java index 2fef867de9a..42611a2ba0b 100644 --- a/Mage.Sets/src/mage/cards/s/SpareDagger.java +++ b/Mage.Sets/src/mage/cards/s/SpareDagger.java @@ -16,6 +16,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.game.Game; +import mage.target.common.TargetAnyTarget; import java.util.UUID; @@ -75,6 +76,8 @@ class SpareDaggerEffect extends GainAbilityWithAttachmentEffect { new DamageTargetEffect(1), false, "This creature deals 1 damage to any target" ); + ability.addTarget(new TargetAnyTarget()); + return new AttacksTriggeredAbility(new DoWhenCostPaid( ability, useAttachedCost.copy().setMageObjectReference(source, game), "Sacrifice " + sourceName + "?" diff --git a/Mage.Sets/src/mage/cards/s/SpectralDeluge.java b/Mage.Sets/src/mage/cards/s/SpectralDeluge.java index be30c1e1822..5bc96cc27d7 100644 --- a/Mage.Sets/src/mage/cards/s/SpectralDeluge.java +++ b/Mage.Sets/src/mage/cards/s/SpectralDeluge.java @@ -50,7 +50,7 @@ public final class SpectralDeluge extends CardImpl { } } -enum SpectralDelugePredicate implements ObjectSourcePlayerPredicate> { +enum SpectralDelugePredicate implements ObjectSourcePlayerPredicate { instance; private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ISLAND); diff --git a/Mage.Sets/src/mage/cards/s/SpellQueller.java b/Mage.Sets/src/mage/cards/s/SpellQueller.java index 70a2a9e74a5..219a71856f1 100644 --- a/Mage.Sets/src/mage/cards/s/SpellQueller.java +++ b/Mage.Sets/src/mage/cards/s/SpellQueller.java @@ -141,7 +141,9 @@ class SpellQuellerLeavesEffect extends OneShotEffect { Player cardOwner = game.getPlayer(card.getOwnerId()); if (cardOwner != null) { if (cardOwner.chooseUse(Outcome.PlayForFree, "Cast " + card.getLogName() + " without paying cost?", source, game)) { - cardOwner.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + cardOwner.cast(cardOwner.chooseAbilityForCast(card, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); } } } diff --git a/Mage.Sets/src/mage/cards/s/SpellruneHowler.java b/Mage.Sets/src/mage/cards/s/SpellruneHowler.java index c5c63b6da66..a7419fbe12c 100644 --- a/Mage.Sets/src/mage/cards/s/SpellruneHowler.java +++ b/Mage.Sets/src/mage/cards/s/SpellruneHowler.java @@ -35,7 +35,7 @@ public final class SpellruneHowler extends CardImpl { )); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private SpellruneHowler(final SpellruneHowler card) { diff --git a/Mage.Sets/src/mage/cards/s/SpellrunePainter.java b/Mage.Sets/src/mage/cards/s/SpellrunePainter.java index 1a6c4c568f8..8a7b20c3458 100644 --- a/Mage.Sets/src/mage/cards/s/SpellrunePainter.java +++ b/Mage.Sets/src/mage/cards/s/SpellrunePainter.java @@ -4,6 +4,7 @@ import mage.MageInt; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.keyword.DayboundAbility; +import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -36,7 +37,8 @@ public final class SpellrunePainter extends CardImpl { )); // Daybound - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new TransformAbility()); + this.addAbility(new DayboundAbility()); } private SpellrunePainter(final SpellrunePainter card) { diff --git a/Mage.Sets/src/mage/cards/s/Spellshift.java b/Mage.Sets/src/mage/cards/s/Spellshift.java index 274482596d2..2d0f5622767 100644 --- a/Mage.Sets/src/mage/cards/s/Spellshift.java +++ b/Mage.Sets/src/mage/cards/s/Spellshift.java @@ -78,7 +78,9 @@ class SpellshiftEffect extends OneShotEffect { } spellController.revealCards(source, cardsToReveal, game); if (toCast != null && spellController.chooseUse(outcome, "Cast " + toCast.getLogName() + " without paying its mana cost?", source, game)) { - spellController.cast(toCast.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + toCast.getId(), Boolean.TRUE); + spellController.cast(spellController.chooseAbilityForCast(toCast, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + toCast.getId(), null); } spellController.shuffleLibrary(source, game); return true; diff --git a/Mage.Sets/src/mage/cards/s/SpellstutterSprite.java b/Mage.Sets/src/mage/cards/s/SpellstutterSprite.java index ad378de75b5..0f6f0719e1c 100644 --- a/Mage.Sets/src/mage/cards/s/SpellstutterSprite.java +++ b/Mage.Sets/src/mage/cards/s/SpellstutterSprite.java @@ -62,7 +62,7 @@ public final class SpellstutterSprite extends CardImpl { } } -enum SpellstutterSpritePredicate implements ObjectSourcePlayerPredicate> { +enum SpellstutterSpritePredicate implements ObjectSourcePlayerPredicate { instance; private static final FilterPermanent filter = new FilterPermanent(); diff --git a/Mage.Sets/src/mage/cards/s/SpiderSpawning.java b/Mage.Sets/src/mage/cards/s/SpiderSpawning.java index 5a0a669947b..41bf4309977 100644 --- a/Mage.Sets/src/mage/cards/s/SpiderSpawning.java +++ b/Mage.Sets/src/mage/cards/s/SpiderSpawning.java @@ -24,7 +24,7 @@ public final class SpiderSpawning extends CardImpl { // Create a 1/2 green Spider creature token with reach for each creature card in your graveyard. this.getSpellAbility().addEffect(new CreateTokenEffect(new SpiderToken(), new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE))); // Flashback {6}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{6}{B}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{6}{B}"))); } private SpiderSpawning(final SpiderSpawning card) { diff --git a/Mage.Sets/src/mage/cards/s/SpiritFlare.java b/Mage.Sets/src/mage/cards/s/SpiritFlare.java index a45cf540d5e..e1190693adc 100644 --- a/Mage.Sets/src/mage/cards/s/SpiritFlare.java +++ b/Mage.Sets/src/mage/cards/s/SpiritFlare.java @@ -45,7 +45,7 @@ public final class SpiritFlare extends CardImpl { this.getSpellAbility().addTarget(new TargetPermanent(filter2)); // Flashback-{1}{W}, Pay 3 life. - FlashbackAbility ability = new FlashbackAbility(new ManaCostsImpl("{1}{W}"), TimingRule.INSTANT); + FlashbackAbility ability = new FlashbackAbility(this, new ManaCostsImpl<>("{1}{W}")); ability.addCost(new PayLifeCost(3)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/StalkingPredator.java b/Mage.Sets/src/mage/cards/s/StalkingPredator.java index 9721d074f27..98ce16017b5 100644 --- a/Mage.Sets/src/mage/cards/s/StalkingPredator.java +++ b/Mage.Sets/src/mage/cards/s/StalkingPredator.java @@ -29,7 +29,7 @@ public final class StalkingPredator extends CardImpl { this.addAbility(new MenaceAbility()); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private StalkingPredator(final StalkingPredator card) { diff --git a/Mage.Sets/src/mage/cards/s/StalkingVampire.java b/Mage.Sets/src/mage/cards/s/StalkingVampire.java index 888b4567855..66065d0fbac 100644 --- a/Mage.Sets/src/mage/cards/s/StalkingVampire.java +++ b/Mage.Sets/src/mage/cards/s/StalkingVampire.java @@ -1,15 +1,23 @@ - package mage.cards.s; import mage.MageInt; -import mage.abilities.condition.common.TransformedCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; /** * @author nantuko @@ -17,7 +25,7 @@ import java.util.UUID; public final class StalkingVampire extends CardImpl { public StalkingVampire(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},""); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); this.subtype.add(SubType.VAMPIRE); this.color.setBlack(true); @@ -28,7 +36,7 @@ public final class StalkingVampire extends CardImpl { this.toughness = new MageInt(5); // At the beginning of your upkeep, you may pay {2}{B}{B}. If you do, transform Stalking Vampire. - this.addAbility(new ConditionalTriggeredAbility(new ScreechingBatBeginningOfUpkeepTriggeredAbility(), new TransformedCondition(), "")); + this.addAbility(new StalkingVampireBeginningOfUpkeepTriggeredAbility()); } private StalkingVampire(final StalkingVampire card) { @@ -40,3 +48,65 @@ public final class StalkingVampire extends CardImpl { return new StalkingVampire(this); } } + +class StalkingVampireBeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl { + + public StalkingVampireBeginningOfUpkeepTriggeredAbility() { + super(Zone.BATTLEFIELD, new StalkingVampireTransformSourceEffect(), true); + } + + public StalkingVampireBeginningOfUpkeepTriggeredAbility(final StalkingVampireBeginningOfUpkeepTriggeredAbility ability) { + super(ability); + } + + @Override + public StalkingVampireBeginningOfUpkeepTriggeredAbility copy() { + return new StalkingVampireBeginningOfUpkeepTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getPlayerId().equals(this.controllerId); + } + + @Override + public String getRule() { + return "At the beginning of your upkeep, you may pay {2}{B}{B}. If you do, transform {this}."; + } +} + +class StalkingVampireTransformSourceEffect extends OneShotEffect { + + public StalkingVampireTransformSourceEffect() { + super(Outcome.Transform); + staticText = "transform {this}"; + } + + public StalkingVampireTransformSourceEffect(final StalkingVampireTransformSourceEffect effect) { + super(effect); + } + + @Override + public StalkingVampireTransformSourceEffect copy() { + return new StalkingVampireTransformSourceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + Cost cost = new ManaCostsImpl("{2}{B}{B}"); + if (cost.pay(source, game, source, permanent.getControllerId(), false, null)) { + new TransformSourceEffect(false).apply(game, source); + } + return true; + } + return false; + } + +} diff --git a/Mage.Sets/src/mage/cards/s/StalwartPathlighter.java b/Mage.Sets/src/mage/cards/s/StalwartPathlighter.java new file mode 100644 index 00000000000..97185c634fc --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StalwartPathlighter.java @@ -0,0 +1,53 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.condition.common.CovenCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.hint.common.CovenHint; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StalwartPathlighter extends CardImpl { + + public StalwartPathlighter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, creatures you control gain indestructible until end of turn. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new BeginningOfCombatTriggeredAbility(new GainAbilityControlledEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURE + ), TargetController.YOU, false), CovenCondition.instance, "At the beginning " + + "of combat on your turn, if you control three or more creatures with different powers, " + + "creatures you control gain indestructible until end of turn." + ).addHint(CovenHint.instance).setAbilityWord(AbilityWord.COVEN)); + } + + private StalwartPathlighter(final StalwartPathlighter card) { + super(card); + } + + @Override + public StalwartPathlighter copy() { + return new StalwartPathlighter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/Startle.java b/Mage.Sets/src/mage/cards/s/Startle.java index 58d3d8fe40f..c9ba316a270 100644 --- a/Mage.Sets/src/mage/cards/s/Startle.java +++ b/Mage.Sets/src/mage/cards/s/Startle.java @@ -25,7 +25,7 @@ public final class Startle extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieDecayedToken())); // Draw a card. - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); } private Startle(final Startle card) { diff --git a/Mage.Sets/src/mage/cards/s/StingingStudy.java b/Mage.Sets/src/mage/cards/s/StingingStudy.java index ccee4735dec..f70ead86aee 100644 --- a/Mage.Sets/src/mage/cards/s/StingingStudy.java +++ b/Mage.Sets/src/mage/cards/s/StingingStudy.java @@ -68,7 +68,7 @@ class StingingStudyEffect extends OneShotEffect { for (Card commander : game.getCommanderCardsFromAnyZones(player, CommanderCardType.ANY, Zone.BATTLEFIELD, Zone.COMMAND)) { manaValues.add(commander.getManaValue()); } - int chosenValue = 0; + int chosenValue; if (manaValues.size() > 1) { Choice choice = new ChoiceImpl(true); choice.setChoices(manaValues.stream().map(x -> "" + x).collect(Collectors.toSet())); diff --git a/Mage.Sets/src/mage/cards/s/StolenVitality.java b/Mage.Sets/src/mage/cards/s/StolenVitality.java new file mode 100644 index 00000000000..b4b47c4b04b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StolenVitality.java @@ -0,0 +1,44 @@ +package mage.cards.s; + +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StolenVitality extends CardImpl { + + public StolenVitality(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Target creature gets +3/+1 until end of turn. If it's your turn, that creature gains trample until end of turn. Otherwise, it gains first strike until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(3, 1)); + this.getSpellAbility().addEffect(new ConditionalContinuousEffect( + new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn), + new GainAbilityTargetEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn), + MyTurnCondition.instance, "If it's your turn, that creature gains trample " + + "until end of turn. Otherwise, it gains first strike until end of turn" + )); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private StolenVitality(final StolenVitality card) { + super(card); + } + + @Override + public StolenVitality copy() { + return new StolenVitality(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StormChargedSlasher.java b/Mage.Sets/src/mage/cards/s/StormChargedSlasher.java index 4b0e5f52872..93ba611b364 100644 --- a/Mage.Sets/src/mage/cards/s/StormChargedSlasher.java +++ b/Mage.Sets/src/mage/cards/s/StormChargedSlasher.java @@ -49,7 +49,7 @@ public final class StormChargedSlasher extends CardImpl { this.addAbility(ability); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private StormChargedSlasher(final StormChargedSlasher card) { diff --git a/Mage.Sets/src/mage/cards/s/StormSkreelix.java b/Mage.Sets/src/mage/cards/s/StormSkreelix.java new file mode 100644 index 00000000000..318b11d287a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StormSkreelix.java @@ -0,0 +1,56 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +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.Duration; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterInstantOrSorceryCard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StormSkreelix extends CardImpl { + + private static final FilterCard filter = new FilterInstantOrSorceryCard("instant and sorcery spells"); + + public StormSkreelix(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{R}"); + + this.subtype.add(SubType.DRAKE); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Instant and sorcery spells you cast cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); + + // Whenever you cast an instant or sorcery spell, Storm Skreelix gets +2/+0 until end of turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new BoostSourceEffect(2, 0, Duration.EndOfTurn), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false + )); + } + + private StormSkreelix(final StormSkreelix card) { + super(card); + } + + @Override + public StormSkreelix copy() { + return new StormSkreelix(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StormTheFestival.java b/Mage.Sets/src/mage/cards/s/StormTheFestival.java new file mode 100644 index 00000000000..cf0293f5fb8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StormTheFestival.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StormTheFestival extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard( + "up to two permanent cards with mana value 5 or less" + ); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 6)); + } + + public StormTheFestival(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}{G}{G}"); + + // Look at the top five cards of your library. Put up to two permanent cards with mana value 5 or less from among them onto the battlefield. Put the rest on the bottom of your library in a random order. + this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect( + 5, 2, filter, false, true, Zone.BATTLEFIELD, false + ).setBackInRandomOrder(true)); + + // Flashback {7}{G}{G}{G} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{7}{G}{G}{G}"))); + } + + private StormTheFestival(final StormTheFestival card) { + super(card); + } + + @Override + public StormTheFestival copy() { + return new StormTheFestival(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StranglingGrasp.java b/Mage.Sets/src/mage/cards/s/StranglingGrasp.java new file mode 100644 index 00000000000..90644555175 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StranglingGrasp.java @@ -0,0 +1,115 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.filter.common.FilterNonlandPermanent; +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 StranglingGrasp extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreatureOrPlaneswalkerPermanent("creature or planeswalker an opponent controls"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public StranglingGrasp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); + + this.subtype.add(SubType.AURA); + this.color.setBlack(true); + this.transformable = true; + this.nightCard = true; + + // Enchant creature or planeswalker an opponent controls + TargetPermanent auraTarget = new TargetPermanent(filter); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // At the beginning of your upkeep, enchanted permanent's controller sacrifices a nonland permanent and loses 1 life. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new StranglingGraspEffect(), TargetController.YOU, false + )); + } + + private StranglingGrasp(final StranglingGrasp card) { + super(card); + } + + @Override + public StranglingGrasp copy() { + return new StranglingGrasp(this); + } +} + +class StranglingGraspEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterNonlandPermanent("nonland permanent you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + } + + StranglingGraspEffect() { + super(Outcome.Benefit); + staticText = "enchanted permanent's controller sacrifices a nonland permanent and loses 1 life"; + } + + private StranglingGraspEffect(final StranglingGraspEffect effect) { + super(effect); + } + + @Override + public StranglingGraspEffect copy() { + return new StranglingGraspEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent sourcePermanent = source.getSourcePermanentOrLKI(game); + if (sourcePermanent == null) { + return false; + } + Permanent attachedTo = game.getPermanentOrLKIBattlefield(sourcePermanent.getAttachedTo()); + if (attachedTo == null) { + return false; + } + Player player = game.getPlayer(attachedTo.getControllerId()); + if (player == null) { + return false; + } + TargetPermanent target = new TargetPermanent(filter); + target.setNotTarget(true); + if (target.canChoose(source.getSourceId(), player.getId(), game)) { + player.choose(outcome, target, source.getSourceId(), game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + permanent.sacrifice(source, game); + } + } + player.loseLife(1, game, source, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/StranglingSoot.java b/Mage.Sets/src/mage/cards/s/StranglingSoot.java index 854599d26ce..b5061c16ab2 100644 --- a/Mage.Sets/src/mage/cards/s/StranglingSoot.java +++ b/Mage.Sets/src/mage/cards/s/StranglingSoot.java @@ -32,7 +32,7 @@ public final class StranglingSoot extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); this.getSpellAbility().addEffect(new DestroyTargetEffect()); // Flashback {5}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{R}"))); } private StranglingSoot(final StranglingSoot card) { diff --git a/Mage.Sets/src/mage/cards/s/StrengthOfTheTajuru.java b/Mage.Sets/src/mage/cards/s/StrengthOfTheTajuru.java index 4fcdabf85ee..a9053ff2492 100644 --- a/Mage.Sets/src/mage/cards/s/StrengthOfTheTajuru.java +++ b/Mage.Sets/src/mage/cards/s/StrengthOfTheTajuru.java @@ -68,7 +68,7 @@ class StrengthOfTheTajuruAddCountersTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - int amount = source.getManaCostsToPay().getX() + 1; + int amount = source.getManaCostsToPay().getX(); Counter counter = CounterType.P1P1.createInstance(amount); for (UUID uuid : targetPointer.getTargets(game, source)) { Permanent permanent = game.getPermanent(uuid); diff --git a/Mage.Sets/src/mage/cards/s/StrikeItRich.java b/Mage.Sets/src/mage/cards/s/StrikeItRich.java index 27e974ad193..5e3f94dae08 100644 --- a/Mage.Sets/src/mage/cards/s/StrikeItRich.java +++ b/Mage.Sets/src/mage/cards/s/StrikeItRich.java @@ -23,7 +23,7 @@ public final class StrikeItRich extends CardImpl { this.getSpellAbility().addEffect(new CreateTokenEffect(new TreasureToken())); // Flashback {2}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{2}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{2}{R}"))); } private StrikeItRich(final StrikeItRich card) { diff --git a/Mage.Sets/src/mage/cards/s/StumpsquallHydra.java b/Mage.Sets/src/mage/cards/s/StumpsquallHydra.java index 110ae1ffab9..e59c1c4e8b1 100644 --- a/Mage.Sets/src/mage/cards/s/StumpsquallHydra.java +++ b/Mage.Sets/src/mage/cards/s/StumpsquallHydra.java @@ -59,7 +59,7 @@ class StumpsquallHydraEffect extends OneShotEffect { filter.add(StumpsquallHydraPredicate.instance); } - private enum StumpsquallHydraPredicate implements ObjectSourcePlayerPredicate> { + private enum StumpsquallHydraPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/s/SungoldSentinel.java b/Mage.Sets/src/mage/cards/s/SungoldSentinel.java new file mode 100644 index 00000000000..1951791b24f --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SungoldSentinel.java @@ -0,0 +1,94 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.condition.common.CovenCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.combat.CantBeBlockedByAllSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.hint.common.CovenHint; +import mage.abilities.keyword.HexproofBaseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.ChoiceColor; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SungoldSentinel extends CardImpl { + + public SungoldSentinel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Whenever Sungold Sentinel enters the battlefield or attacks, exile up to one target card from a graveyard. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new ExileTargetEffect()); + ability.addTarget(new TargetCardInGraveyard(0, 1)); + this.addAbility(ability); + + // Coven — {1}{W}: Choose a color. Sungold Sentinel gains hexproof from that color until end of turn and can't be blocked by creatures of that color this turn. Activate only if you control three or more creatures with different powers. + this.addAbility(new ConditionalActivatedAbility( + Zone.BATTLEFIELD, new SungoldSentinelEffect(), + new ManaCostsImpl<>("{1}{W}"), CovenCondition.instance + ).addHint(CovenHint.instance).setAbilityWord(AbilityWord.COVEN)); + } + + private SungoldSentinel(final SungoldSentinel card) { + super(card); + } + + @Override + public SungoldSentinel copy() { + return new SungoldSentinel(this); + } +} + +class SungoldSentinelEffect extends OneShotEffect { + + SungoldSentinelEffect() { + super(Outcome.Benefit); + staticText = "choose a color. {this} gains hexproof from that color until end of turn " + + "and can't be blocked by creatures of that color this turn"; + } + + private SungoldSentinelEffect(final SungoldSentinelEffect effect) { + super(effect); + } + + @Override + public SungoldSentinelEffect copy() { + return new SungoldSentinelEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + ChoiceColor choice = new ChoiceColor(true); + player.choose(outcome, choice, game); + Ability ability = HexproofBaseAbility.getFirstFromColor(choice.getColor()); + game.addEffect(new GainAbilitySourceEffect(ability, Duration.EndOfTurn), source); + FilterCreaturePermanent filter = new FilterCreaturePermanent(); + filter.add(new ColorPredicate(choice.getColor())); + game.addEffect(new CantBeBlockedByAllSourceEffect(filter, Duration.EndOfTurn), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SunsetRevelry.java b/Mage.Sets/src/mage/cards/s/SunsetRevelry.java new file mode 100644 index 00000000000..59ad39611b1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunsetRevelry.java @@ -0,0 +1,105 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.token.HumanToken; +import mage.players.Player; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class SunsetRevelry extends CardImpl { + + public SunsetRevelry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}"); + + // If an opponent has more life than you, you gain 4 life. + // If an opponent controls more creatures than you, create two 1/1 white Human creature tokens. + // If an opponent has more cards in hand than you, draw a card. + // TODO: add hints to this + this.getSpellAbility().addEffect(new SunsetRevelryEffect()); + } + + private SunsetRevelry(final SunsetRevelry card) { + super(card); + } + + @Override + public SunsetRevelry copy() { + return new SunsetRevelry(this); + } +} + +class SunsetRevelryEffect extends OneShotEffect { + + SunsetRevelryEffect() { + super(Outcome.Benefit); + staticText = "If an opponent has more life than you, you gain 4 life." + + "
If an opponent controls more creatures than you, create two 1/1 white Human creature tokens." + + "
If an opponent has more cards in hand than you, draw a card."; + } + + private SunsetRevelryEffect(final SunsetRevelryEffect effect) { + super(effect); + } + + @Override + public SunsetRevelryEffect copy() { + return new SunsetRevelryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Set opponents = game.getOpponents(source.getControllerId()); + if (opponents.stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .mapToInt(Player::getLife) + .anyMatch(x -> x > player.getLife())) { + player.gainLife(4, game, source); + } + Map map = game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, + source.getControllerId(), game + ).stream() + .filter(Objects::nonNull) + .map(Controllable::getControllerId) + .filter(uuid -> opponents.contains(uuid) || source.getControllerId().equals(uuid)) + .collect(Collectors.toMap(Function.identity(), x -> 1, Integer::sum)); + if (map.getOrDefault( + source.getControllerId(), 0 + ) < map.values().stream().mapToInt(x -> x).max().orElse(0)) { + new HumanToken().putOntoBattlefield(2, game, source, source.getControllerId()); + } + if (opponents + .stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .map(Player::getHand) + .mapToInt(Set::size) + .anyMatch(x -> x > player.getHand().size())) { + player.drawCards(1, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SuspiciousStowaway.java b/Mage.Sets/src/mage/cards/s/SuspiciousStowaway.java index 8b09363b0cb..83c217f6fc6 100644 --- a/Mage.Sets/src/mage/cards/s/SuspiciousStowaway.java +++ b/Mage.Sets/src/mage/cards/s/SuspiciousStowaway.java @@ -39,7 +39,7 @@ public final class SuspiciousStowaway extends CardImpl { // Daybound this.addAbility(new TransformAbility()); - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new DayboundAbility()); } private SuspiciousStowaway(final SuspiciousStowaway card) { diff --git a/Mage.Sets/src/mage/cards/s/SylvanMight.java b/Mage.Sets/src/mage/cards/s/SylvanMight.java index 8452ea5ce7e..8345379ada5 100644 --- a/Mage.Sets/src/mage/cards/s/SylvanMight.java +++ b/Mage.Sets/src/mage/cards/s/SylvanMight.java @@ -33,7 +33,7 @@ public final class SylvanMight extends CardImpl { this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {2}{G}{G} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{G}{G}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{G}{G}"))); } private SylvanMight(final SylvanMight card) { diff --git a/Mage.Sets/src/mage/cards/t/TaintedPact.java b/Mage.Sets/src/mage/cards/t/TaintedPact.java index 5213e246034..5027296f817 100644 --- a/Mage.Sets/src/mage/cards/t/TaintedPact.java +++ b/Mage.Sets/src/mage/cards/t/TaintedPact.java @@ -67,7 +67,9 @@ class TaintedPactEffect extends OneShotEffect { && controller.getLibrary().hasCards()) { Card card = controller.getLibrary().getFromTop(game); if (card != null) { + // the card move is sequential, not all at once. controller.moveCards(card, Zone.EXILED, source, game); + game.getState().processAction(game); // Laelia, the Blade Reforged // Checks if there was already exiled a card with the same name if (names.contains(card.getName())) { break; diff --git a/Mage.Sets/src/mage/cards/t/TappingAtTheWindow.java b/Mage.Sets/src/mage/cards/t/TappingAtTheWindow.java new file mode 100644 index 00000000000..5b6fa979fbc --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TappingAtTheWindow.java @@ -0,0 +1,81 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TappingAtTheWindow extends CardImpl { + + public TappingAtTheWindow(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); + + // Look at the top three cards of your library. You may reveal a creature card from among them and put it into your hand. Put the rest into your graveyard. + this.getSpellAbility().addEffect(new TappingAtTheWindowEffect()); + + // Flashback {2}{G} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{2}{G}"))); + } + + private TappingAtTheWindow(final TappingAtTheWindow card) { + super(card); + } + + @Override + public TappingAtTheWindow copy() { + return new TappingAtTheWindow(this); + } +} + +class TappingAtTheWindowEffect extends OneShotEffect { + + TappingAtTheWindowEffect() { + super(Outcome.Benefit); + staticText = "look at the top three cards of your library. You may reveal a creature card " + + "from among them and put it into your hand. Put the rest into your graveyard"; + } + + private TappingAtTheWindowEffect(final TappingAtTheWindowEffect effect) { + super(effect); + } + + @Override + public TappingAtTheWindowEffect copy() { + return new TappingAtTheWindowEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 3)); + TargetCard target = new TargetCardInLibrary( + 0, 1, StaticFilters.FILTER_CARD_CREATURE + ); + player.choose(outcome, cards, target, game); + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + player.revealCards(source, new CardsImpl(card), game); + player.moveCards(card, Zone.HAND, source, game); + } + cards.retainZone(Zone.LIBRARY, game); + player.moveCards(card, Zone.GRAVEYARD, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TavernRuffian.java b/Mage.Sets/src/mage/cards/t/TavernRuffian.java index fa0f278d608..b06f875bd57 100644 --- a/Mage.Sets/src/mage/cards/t/TavernRuffian.java +++ b/Mage.Sets/src/mage/cards/t/TavernRuffian.java @@ -29,7 +29,7 @@ public final class TavernRuffian extends CardImpl { // Daybound this.addAbility(new TransformAbility()); - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new DayboundAbility()); } private TavernRuffian(final TavernRuffian card) { diff --git a/Mage.Sets/src/mage/cards/t/TavernSmasher.java b/Mage.Sets/src/mage/cards/t/TavernSmasher.java index bb399b15cc0..8a3b1258e57 100644 --- a/Mage.Sets/src/mage/cards/t/TavernSmasher.java +++ b/Mage.Sets/src/mage/cards/t/TavernSmasher.java @@ -28,7 +28,7 @@ public final class TavernSmasher extends CardImpl { this.toughness = new MageInt(5); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private TavernSmasher(final TavernSmasher card) { diff --git a/Mage.Sets/src/mage/cards/t/TayamLuminousEnigma.java b/Mage.Sets/src/mage/cards/t/TayamLuminousEnigma.java index 720399c2928..fde6ef9fbed 100644 --- a/Mage.Sets/src/mage/cards/t/TayamLuminousEnigma.java +++ b/Mage.Sets/src/mage/cards/t/TayamLuminousEnigma.java @@ -35,8 +35,6 @@ import java.util.HashSet; import java.util.Set; import java.util.UUID; -import static com.google.common.collect.Iterables.getOnlyElement; - /** * @author htrajan */ @@ -97,8 +95,7 @@ class TayamLuminousEnigmaCost extends RemoveCounterCost { Player controller = game.getPlayer(controllerId); for (int i = 0; i < countersToRemove; i++) { if (target.choose(Outcome.UnboostCreature, controllerId, source.getSourceId(), game)) { - UUID targetId = getOnlyElement(target.getTargets()); - Permanent permanent = game.getPermanent(targetId); + Permanent permanent = game.getPermanent(target.getFirstTarget()); if (permanent != null) { if (!permanent.getCounters(game).isEmpty()) { String counterName = null; diff --git a/Mage.Sets/src/mage/cards/t/TeferiWhoSlowsTheSunset.java b/Mage.Sets/src/mage/cards/t/TeferiWhoSlowsTheSunset.java new file mode 100644 index 00000000000..995228baba4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TeferiWhoSlowsTheSunset.java @@ -0,0 +1,101 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.GetEmblemEffect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +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.command.emblems.TeferiWhoSlowsTheSunsetEmblem; +import mage.game.permanent.Permanent; +import mage.target.Target; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetLandPermanent; + +import java.util.UUID; + +/** + * @author LePwnerer + */ +public final class TeferiWhoSlowsTheSunset extends CardImpl { + + public TeferiWhoSlowsTheSunset(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{W}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.TEFERI); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // +1: Choose up to one target artifact, up to one target creature, and up to one target land. Untap the chosen permanents you control. Tap the chosen permanents you don't control. You gain 2 life. + Ability ability = new LoyaltyAbility(new TeferiWhoSlowsTheSunsetEffect(), 1); + ability.addEffect(new GainLifeEffect(2)); + ability.addTarget(new TargetArtifactPermanent()); + ability.addTarget(new TargetCreaturePermanent()); + ability.addTarget(new TargetLandPermanent()); + this.addAbility(ability); + + // −2: Look at the top three cards of your library. Put one of them into your hand and the rest on the bottom of your library in any order. + this.addAbility(new LoyaltyAbility(new LookLibraryAndPickControllerEffect( + StaticValue.get(3), false, StaticValue.get(1), + StaticFilters.FILTER_CARD, false, false + ), -2)); + + // −7: You get an emblem with "Untap all permanents you control during each opponent's untap step" and "You draw a card during each opponent's draw step." + this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new TeferiWhoSlowsTheSunsetEmblem()), -7)); + } + + private TeferiWhoSlowsTheSunset(final TeferiWhoSlowsTheSunset card) { + super(card); + } + + @Override + public TeferiWhoSlowsTheSunset copy() { + return new TeferiWhoSlowsTheSunset(this); + } +} + +class TeferiWhoSlowsTheSunsetEffect extends OneShotEffect { + + TeferiWhoSlowsTheSunsetEffect() { + super(Outcome.Benefit); + staticText = "Choose up to one target artifact, up to one target creature, and up to one target land. " + + "Untap the chosen permanents you control. " + + "Tap the chosen permanents you don't control."; + } + + private TeferiWhoSlowsTheSunsetEffect(final TeferiWhoSlowsTheSunsetEffect effect) { + super(effect); + } + + @Override + public TeferiWhoSlowsTheSunsetEffect copy() { + return new TeferiWhoSlowsTheSunsetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Target target : source.getTargets()) { + Permanent targetPermanent = game.getPermanent(target.getFirstTarget()); + if (targetPermanent == null) { + continue; + } + if (targetPermanent.isControlledBy(source.getControllerId())) { + targetPermanent.untap(game); + } else { + targetPermanent.tap(source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TelimTorsEdict.java b/Mage.Sets/src/mage/cards/t/TelimTorsEdict.java index 5ee311dc8c6..c4f27760e73 100644 --- a/Mage.Sets/src/mage/cards/t/TelimTorsEdict.java +++ b/Mage.Sets/src/mage/cards/t/TelimTorsEdict.java @@ -10,8 +10,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; @@ -49,13 +49,13 @@ public final class TelimTorsEdict extends CardImpl { } } -class TelimTorsEdictPredicate implements ObjectPlayerPredicate> { +class TelimTorsEdictPredicate implements ObjectSourcePlayerPredicate { public TelimTorsEdictPredicate() { } @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent permanent = input.getObject(); UUID playerId = input.getPlayerId(); if (permanent.isControlledBy(playerId) || permanent.isOwnedBy(playerId)) { diff --git a/Mage.Sets/src/mage/cards/t/TheDeckOfManyThings.java b/Mage.Sets/src/mage/cards/t/TheDeckOfManyThings.java index 074542096b4..9d65ea25439 100644 --- a/Mage.Sets/src/mage/cards/t/TheDeckOfManyThings.java +++ b/Mage.Sets/src/mage/cards/t/TheDeckOfManyThings.java @@ -114,7 +114,8 @@ class TheDeckOfManyThingsRandomEffect extends OneShotEffect { TargetCard target = new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD); target.setRandom(true); target.setNotTarget(true); - player.chooseTarget(outcome, target, source, game); + target.chooseTarget(outcome, player.getId(), source, game); + Card card = game.getCard(target.getFirstTarget()); return card != null && player.moveCards(card, Zone.HAND, source, game); } diff --git a/Mage.Sets/src/mage/cards/t/TheMeathookMassacre.java b/Mage.Sets/src/mage/cards/t/TheMeathookMassacre.java new file mode 100644 index 00000000000..0401c6a7366 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheMeathookMassacre.java @@ -0,0 +1,58 @@ +package mage.cards.t; + +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.MultipliedValue; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheMeathookMassacre extends CardImpl { + + private static final DynamicValue xValue = new MultipliedValue(ManacostVariableValue.ETB, -1); + + public TheMeathookMassacre(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{X}{B}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + + // When The Meathook Massacre enters the battlefield, each creature gets -X/-X until end of turn. + this.addAbility(new EntersBattlefieldTriggeredAbility(new BoostAllEffect( + xValue, xValue, Duration.EndOfTurn + ).setText("each creature gets -X/-X until end of turn"))); + + // Whenever a creature you control dies, each opponent loses 1 life. + this.addAbility(new DiesCreatureTriggeredAbility( + new LoseLifeOpponentsEffect(1), false, + StaticFilters.FILTER_CONTROLLED_A_CREATURE + )); + + // Whenever a creature an opponent controls dies, you gain 1 life. + this.addAbility(new DiesCreatureTriggeredAbility( + new GainLifeEffect(1), false, + StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE + )); + } + + private TheMeathookMassacre(final TheMeathookMassacre card) { + super(card); + } + + @Override + public TheMeathookMassacre copy() { + return new TheMeathookMassacre(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThermoAlchemist.java b/Mage.Sets/src/mage/cards/t/ThermoAlchemist.java index ecf22e35355..5eb60808170 100644 --- a/Mage.Sets/src/mage/cards/t/ThermoAlchemist.java +++ b/Mage.Sets/src/mage/cards/t/ThermoAlchemist.java @@ -1,9 +1,6 @@ - package mage.cards.t; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.costs.common.TapSourceCost; @@ -15,17 +12,17 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.TargetController; -import mage.constants.Zone; -import mage.filter.common.FilterInstantOrSorcerySpell; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class ThermoAlchemist extends CardImpl { public ThermoAlchemist(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.SHAMAN); this.power = new MageInt(0); @@ -33,11 +30,16 @@ public final class ThermoAlchemist extends CardImpl { // Defender this.addAbility(DefenderAbility.getInstance()); + // {T}: Thermo-Alchemist deals 1 damage to each opponent. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamagePlayersEffect(1, TargetController.OPPONENT), new TapSourceCost()); - this.addAbility(ability); + this.addAbility(new SimpleActivatedAbility( + new DamagePlayersEffect(1, TargetController.OPPONENT), new TapSourceCost() + )); + // Whenever you cast an instant or sorcery spell, untap Thermo-Alchemist. - this.addAbility(new SpellCastControllerTriggeredAbility(new UntapSourceEffect(), new FilterInstantOrSorcerySpell(), false)); + this.addAbility(new SpellCastControllerTriggeredAbility( + new UntapSourceEffect(), StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false + )); } private ThermoAlchemist(final ThermoAlchemist card) { diff --git a/Mage.Sets/src/mage/cards/t/ThinkTwice.java b/Mage.Sets/src/mage/cards/t/ThinkTwice.java index 9f5268db679..093263187e7 100644 --- a/Mage.Sets/src/mage/cards/t/ThinkTwice.java +++ b/Mage.Sets/src/mage/cards/t/ThinkTwice.java @@ -24,7 +24,7 @@ public final class ThinkTwice extends CardImpl { this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); // Flashback {2}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{U}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{U}"))); } private ThinkTwice(final ThinkTwice card) { diff --git a/Mage.Sets/src/mage/cards/t/ThrillOfTheHunt.java b/Mage.Sets/src/mage/cards/t/ThrillOfTheHunt.java index a411ec865bb..edae0965579 100644 --- a/Mage.Sets/src/mage/cards/t/ThrillOfTheHunt.java +++ b/Mage.Sets/src/mage/cards/t/ThrillOfTheHunt.java @@ -25,7 +25,7 @@ public final class ThrillOfTheHunt extends CardImpl { this.getSpellAbility().addEffect(new BoostTargetEffect(1, 2, Duration.EndOfTurn)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{W}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{W}"))); } private ThrillOfTheHunt(final ThrillOfTheHunt card) { diff --git a/Mage.Sets/src/mage/cards/t/ThunderkinAwakener.java b/Mage.Sets/src/mage/cards/t/ThunderkinAwakener.java index a340a43b282..e8d3a109fee 100644 --- a/Mage.Sets/src/mage/cards/t/ThunderkinAwakener.java +++ b/Mage.Sets/src/mage/cards/t/ThunderkinAwakener.java @@ -70,7 +70,7 @@ public final class ThunderkinAwakener extends CardImpl { } } -enum ThunderkinAwakenerPredicate implements ObjectSourcePlayerPredicate> { +enum ThunderkinAwakenerPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/t/TirelessHauler.java b/Mage.Sets/src/mage/cards/t/TirelessHauler.java index 148791a6ed9..c50d0ac4ca8 100644 --- a/Mage.Sets/src/mage/cards/t/TirelessHauler.java +++ b/Mage.Sets/src/mage/cards/t/TirelessHauler.java @@ -2,6 +2,7 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.keyword.DayboundAbility; +import mage.abilities.keyword.TransformAbility; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -29,7 +30,8 @@ public final class TirelessHauler extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // Daybound - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new TransformAbility()); + this.addAbility(new DayboundAbility()); } private TirelessHauler(final TirelessHauler card) { diff --git a/Mage.Sets/src/mage/cards/t/TombTyrant.java b/Mage.Sets/src/mage/cards/t/TombTyrant.java new file mode 100644 index 00000000000..3aadd1726d0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TombTyrant.java @@ -0,0 +1,118 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.CompoundCondition; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.hint.common.MyTurnHint; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.players.Player; +import mage.util.RandomUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TombTyrant extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.ZOMBIE, "Zombies"); + private static final FilterCard filter2 = new FilterCreatureCard(); + + static { + filter2.add(SubType.ZOMBIE.getPredicate()); + } + + private static final Condition condition = new CompoundCondition( + "during your turn and only if there are at least three Zombie creature cards in your graveyard", + MyTurnCondition.instance, new CardsInControllerGraveyardCondition(3, filter2) + ); + private static final Hint hint = new ValueHint( + "Zombie creatures in your graveyard", new CardsInControllerGraveyardCount(filter2) + ); + + public TombTyrant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Other Zombies you control get +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + 1, 1, Duration.WhileOnBattlefield, filter, true + ))); + + // {2}{B}, {T}, Sacrifice a creature: Return a Zombie creature card at random from your graveyard to the battlefield. Activate only during your turn and only if there are at least three Zombie creature cards in your graveyard. + Ability ability = new ActivateIfConditionActivatedAbility( + Zone.BATTLEFIELD, new TombTyrantEffect(), + new ManaCostsImpl<>("{2}{B}"), condition + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE)); + this.addAbility(ability.addHint(MyTurnHint.instance).addHint(hint)); + } + + private TombTyrant(final TombTyrant card) { + super(card); + } + + @Override + public TombTyrant copy() { + return new TombTyrant(this); + } +} + +class TombTyrantEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCreatureCard(); + + static { + filter.add(SubType.ZOMBIE.getPredicate()); + } + + TombTyrantEffect() { + super(Outcome.Benefit); + staticText = "return a Zombie creature card at random from your graveyard to the battlefield"; + } + + private TombTyrantEffect(final TombTyrantEffect effect) { + super(effect); + } + + @Override + public TombTyrantEffect copy() { + return new TombTyrantEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = RandomUtil.randomFromCollection(player.getGraveyard().getCards(filter, game)); + return card != null && player.moveCards(card, Zone.BATTLEFIELD, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/t/Torchling.java b/Mage.Sets/src/mage/cards/t/Torchling.java index 3c3c1b0cfb9..53a094e7b18 100644 --- a/Mage.Sets/src/mage/cards/t/Torchling.java +++ b/Mage.Sets/src/mage/cards/t/Torchling.java @@ -83,7 +83,7 @@ public final class Torchling extends CardImpl { } } -enum TorchlingPredicate implements ObjectSourcePlayerPredicate> { +enum TorchlingPredicate implements ObjectSourcePlayerPredicate { instance; diff --git a/Mage.Sets/src/mage/cards/t/TormentOfScarabs.java b/Mage.Sets/src/mage/cards/t/TormentOfScarabs.java index ac2d734da37..78f81a776b3 100644 --- a/Mage.Sets/src/mage/cards/t/TormentOfScarabs.java +++ b/Mage.Sets/src/mage/cards/t/TormentOfScarabs.java @@ -1,7 +1,7 @@ package mage.cards.t; import mage.abilities.Ability; -import mage.abilities.common.BeginningOfUpkeepAttachedTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.keyword.EnchantAbility; @@ -10,6 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; @@ -38,7 +39,9 @@ public final class TormentOfScarabs extends CardImpl { this.addAbility(ability); // At the beginning of enchanted player's upkeep, that player loses 3 life unless they sacrifice a nonland permanent or discards a card. - this.addAbility(new BeginningOfUpkeepAttachedTriggeredAbility(new TormentOfScarabsEffect())); + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new TormentOfScarabsEffect(), TargetController.ENCHANTED, false + )); } private TormentOfScarabs(final TormentOfScarabs card) { diff --git a/Mage.Sets/src/mage/cards/t/TovolarsHuntmaster.java b/Mage.Sets/src/mage/cards/t/TovolarsHuntmaster.java new file mode 100644 index 00000000000..728ea25305f --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TovolarsHuntmaster.java @@ -0,0 +1,47 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.DayboundAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.WolfToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TovolarsHuntmaster extends CardImpl { + + public TovolarsHuntmaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WEREWOLF); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + this.transformable = true; + this.secondSideCardClazz = mage.cards.t.TovolarsPackleader.class; + + // Whenever Tovolar's Huntmaster enters the battlefield, create two 2/2 green Wolf creature tokens. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new WolfToken(), 2))); + + // Daybound + this.addAbility(new TransformAbility()); + this.addAbility(new DayboundAbility()); + } + + private TovolarsHuntmaster(final TovolarsHuntmaster card) { + super(card); + } + + @Override + public TovolarsHuntmaster copy() { + return new TovolarsHuntmaster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TovolarsPackleader.java b/Mage.Sets/src/mage/cards/t/TovolarsPackleader.java new file mode 100644 index 00000000000..ce1a3bff2a2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TovolarsPackleader.java @@ -0,0 +1,75 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.FightTargetsEffect; +import mage.abilities.keyword.NightboundAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.permanent.token.WolfToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TovolarsPackleader extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent("another Wolf or Werewolf you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(Predicates.or( + SubType.WOLF.getPredicate(), + SubType.WEREWOLF.getPredicate() + )); + } + + public TovolarsPackleader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.WEREWOLF); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + this.color.setGreen(true); + this.transformable = true; + this.nightCard = true; + + // Whenever Tovolar's Packleader enters the battlefield or attacks, create two 2/2 green Wolf creature tokens. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility( + new CreateTokenEffect(new WolfToken(), 2) + )); + + // {2}{G}{G}: Another target Wolf or Werewolf you control fights target creature you don't control. + Ability ability = new SimpleActivatedAbility(new FightTargetsEffect( + "another target Wolf or Werewolf you control fights target creature you don't control" + ), new ManaCostsImpl<>("{2}{G}{G}")); + ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); + + // Nightbound + this.addAbility(new NightboundAbility()); + } + + private TovolarsPackleader(final TovolarsPackleader card) { + super(card); + } + + @Override + public TovolarsPackleader copy() { + return new TovolarsPackleader(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TrackersInstincts.java b/Mage.Sets/src/mage/cards/t/TrackersInstincts.java index cf4ed011de2..a224dec3fec 100644 --- a/Mage.Sets/src/mage/cards/t/TrackersInstincts.java +++ b/Mage.Sets/src/mage/cards/t/TrackersInstincts.java @@ -33,7 +33,7 @@ public final class TrackersInstincts extends CardImpl { // Reveal the top four cards of your library. Put a creature card from among them into your hand and the rest into your graveyard. this.getSpellAbility().addEffect(new TrackersInstinctsEffect()); // Flashback {2}{U} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{U}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{U}"))); } private TrackersInstincts(final TrackersInstincts card) { diff --git a/Mage.Sets/src/mage/cards/t/TraitorsClutch.java b/Mage.Sets/src/mage/cards/t/TraitorsClutch.java index 54601a12cc6..50b5d830ef7 100644 --- a/Mage.Sets/src/mage/cards/t/TraitorsClutch.java +++ b/Mage.Sets/src/mage/cards/t/TraitorsClutch.java @@ -38,7 +38,7 @@ public final class TraitorsClutch extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Flashback {1}{B} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{B}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{B}"))); } private TraitorsClutch(final TraitorsClutch card) { diff --git a/Mage.Sets/src/mage/cards/t/TravelPreparations.java b/Mage.Sets/src/mage/cards/t/TravelPreparations.java index ab9c5767b9e..ae2d1e296ce 100644 --- a/Mage.Sets/src/mage/cards/t/TravelPreparations.java +++ b/Mage.Sets/src/mage/cards/t/TravelPreparations.java @@ -29,7 +29,7 @@ public final class TravelPreparations extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2)); // Flashback {1}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{W}"))); } private TravelPreparations(final TravelPreparations card) { diff --git a/Mage.Sets/src/mage/cards/t/TravelersCloak.java b/Mage.Sets/src/mage/cards/t/TravelersCloak.java index afeaf1e4d0f..230c7c34567 100644 --- a/Mage.Sets/src/mage/cards/t/TravelersCloak.java +++ b/Mage.Sets/src/mage/cards/t/TravelersCloak.java @@ -86,7 +86,7 @@ class TravelersCloakGainAbilityAttachedEffect extends GainAbilityAttachedEffect } } -enum TravelersCloakChosenSubtypePredicate implements ObjectSourcePlayerPredicate> { +enum TravelersCloakChosenSubtypePredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage.Sets/src/mage/cards/t/TurnTheEarth.java b/Mage.Sets/src/mage/cards/t/TurnTheEarth.java new file mode 100644 index 00000000000..a7c2fc214bd --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TurnTheEarth.java @@ -0,0 +1,42 @@ +package mage.cards.t; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.ShuffleIntoLibraryTargetEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TimingRule; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TurnTheEarth extends CardImpl { + + public TurnTheEarth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); + + // Choose up to three target cards in graveyards. The owners of those cards shuffle them into their libraries. You gain 2 life. + this.getSpellAbility().addEffect(new ShuffleIntoLibraryTargetEffect() + .setText("choose up to three target cards in graveyards. " + + "The owners of those cards shuffle them into their libraries.")); + this.getSpellAbility().addEffect(new GainLifeEffect(2)); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(0, 3)); + + // Flashback {1}{G} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{G}"))); + } + + private TurnTheEarth(final TurnTheEarth card) { + super(card); + } + + @Override + public TurnTheEarth copy() { + return new TurnTheEarth(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnblinkingObserver.java b/Mage.Sets/src/mage/cards/u/UnblinkingObserver.java new file mode 100644 index 00000000000..8f47ae7c5ae --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnblinkingObserver.java @@ -0,0 +1,89 @@ +package mage.cards.u; + +import mage.ConditionalMana; +import mage.MageInt; +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.Cost; +import mage.abilities.keyword.DisturbAbility; +import mage.abilities.mana.ConditionalColoredManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.abilities.mana.conditional.ManaCondition; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ManaType; +import mage.constants.SubType; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UnblinkingObserver extends CardImpl { + + public UnblinkingObserver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.HOMUNCULUS); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // {T}: Add {U}. Spend this mana only to pay a disturb cost or cast an instant or sorcery spell. + this.addAbility(new ConditionalColoredManaAbility( + new Mana(ManaType.BLUE, 1), new UnblinkingObserverManaBuilder() + )); + } + + private UnblinkingObserver(final UnblinkingObserver card) { + super(card); + } + + @Override + public UnblinkingObserver copy() { + return new UnblinkingObserver(this); + } +} + +class UnblinkingObserverManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new UnblinkingObserverConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to pay a disturb cost or cast an instant or sorcery spell."; + } +} + +class UnblinkingObserverConditionalMana extends ConditionalMana { + + UnblinkingObserverConditionalMana(Mana mana) { + super(mana); + staticText = "Spend this mana only to pay a disturb cost or cast an instant or sorcery spell."; + addCondition(new UnblinkingObserverManaCondition()); + } +} + +class UnblinkingObserverManaCondition extends ManaCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + if (source instanceof SpellAbility) { + MageObject object = game.getObject(source.getSourceId()); + return object != null && object.isInstantOrSorcery(game); + } + return source instanceof DisturbAbility; + } + + @Override + public boolean apply(Game game, Ability source, UUID originalId, Cost costsToPay) { + return apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnburialRites.java b/Mage.Sets/src/mage/cards/u/UnburialRites.java index 222d344b74b..269246ac265 100644 --- a/Mage.Sets/src/mage/cards/u/UnburialRites.java +++ b/Mage.Sets/src/mage/cards/u/UnburialRites.java @@ -25,7 +25,7 @@ public final class UnburialRites extends CardImpl { this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); // Flashback {3}{W} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{W}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{W}"))); } private UnburialRites(final UnburialRites card) { diff --git a/Mage.Sets/src/mage/cards/u/UnlivingPsychopath.java b/Mage.Sets/src/mage/cards/u/UnlivingPsychopath.java index 1fde042523d..490dc55e22e 100644 --- a/Mage.Sets/src/mage/cards/u/UnlivingPsychopath.java +++ b/Mage.Sets/src/mage/cards/u/UnlivingPsychopath.java @@ -63,7 +63,7 @@ public final class UnlivingPsychopath extends CardImpl { } } -class UnlivingPsychopathPowerLessThanSourcePredicate implements ObjectSourcePlayerPredicate> { +class UnlivingPsychopathPowerLessThanSourcePredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/u/UntamedPup.java b/Mage.Sets/src/mage/cards/u/UntamedPup.java index 89289789654..519b2ef3353 100644 --- a/Mage.Sets/src/mage/cards/u/UntamedPup.java +++ b/Mage.Sets/src/mage/cards/u/UntamedPup.java @@ -61,7 +61,7 @@ public final class UntamedPup extends CardImpl { this.addAbility(ability); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private UntamedPup(final UntamedPup card) { diff --git a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java index 8d2d2538ac2..dee95d173be 100644 --- a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java +++ b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java @@ -283,14 +283,16 @@ class ExileTopCardEachPlayersLibrary extends OneShotEffect { && Tibalt != null) { for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { Player player = game.getPlayer(playerId); - if (player != null) { + if (player != null + && player.getLibrary().hasCards()) { Card topCard = player.getLibrary().getFromTop(game); cardsToExile.add(topCard); } } // exile all cards at one time - controller.moveCardsToExile(cardsToExile, source, game, true, exileId, Tibalt.getName()); - return true; + if (!cardsToExile.isEmpty()) { + return controller.moveCardsToExile(cardsToExile, source, game, true, exileId, Tibalt.getName()); + } } return false; } diff --git a/Mage.Sets/src/mage/cards/v/VedalkenShackles.java b/Mage.Sets/src/mage/cards/v/VedalkenShackles.java index 470a36f907b..2c6adc7e209 100644 --- a/Mage.Sets/src/mage/cards/v/VedalkenShackles.java +++ b/Mage.Sets/src/mage/cards/v/VedalkenShackles.java @@ -18,8 +18,8 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterLandPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; @@ -62,7 +62,7 @@ public final class VedalkenShackles extends CardImpl { } } -class PowerIslandPredicate implements ObjectPlayerPredicate> { +class PowerIslandPredicate implements ObjectSourcePlayerPredicate { static final FilterLandPermanent filter = new FilterLandPermanent("Island"); static { @@ -70,7 +70,7 @@ class PowerIslandPredicate implements ObjectPlayerPredicate input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent permanent = input.getObject(); if (permanent != null) { int islands = game.getBattlefield().countAll(filter, input.getPlayerId(), game); diff --git a/Mage.Sets/src/mage/cards/v/VengefulStrangler.java b/Mage.Sets/src/mage/cards/v/VengefulStrangler.java new file mode 100644 index 00000000000..f1ca4ac0437 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VengefulStrangler.java @@ -0,0 +1,104 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CantBlockAbility; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.Card; +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.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VengefulStrangler extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreatureOrPlaneswalkerPermanent("creature or planeswalker an opponent controls"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public VengefulStrangler(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(1); + this.transformable = true; + this.secondSideCardClazz = mage.cards.s.StranglingGrasp.class; + + // Vengeful Strangler can't block. + this.addAbility(new CantBlockAbility()); + + // When Vengeful Strangler dies, return it to the battlefield transformed under your control attached to target creature or planeswalker an opponent controls. + this.addAbility(new TransformAbility()); + Ability ability = new DiesSourceTriggeredAbility(new VengefulStranglerEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private VengefulStrangler(final VengefulStrangler card) { + super(card); + } + + @Override + public VengefulStrangler copy() { + return new VengefulStrangler(this); + } +} + +class VengefulStranglerEffect extends OneShotEffect { + + VengefulStranglerEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "return it to the battlefield transformed under your control " + + "attached to target creature or planeswalker an opponent controls"; + } + + private VengefulStranglerEffect(final VengefulStranglerEffect effect) { + super(effect); + } + + @Override + public VengefulStranglerEffect copy() { + return new VengefulStranglerEffect(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 + || game.getState().getZone(source.getSourceId()) != Zone.GRAVEYARD) { + return false; + } + + Card card = game.getCard(source.getSourceId()); + if (card == null) { + return false; + } + + game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE); + UUID secondFaceId = game.getCard(source.getSourceId()).getSecondCardFace().getId(); + game.getState().setValue("attachTo:" + secondFaceId, permanent.getId()); + if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { + permanent.addAttachment(card.getId(), source, game); + } + return true; + } +} + diff --git a/Mage.Sets/src/mage/cards/v/VeyranVoiceOfDuality.java b/Mage.Sets/src/mage/cards/v/VeyranVoiceOfDuality.java index d9513a2bf44..53075230be6 100644 --- a/Mage.Sets/src/mage/cards/v/VeyranVoiceOfDuality.java +++ b/Mage.Sets/src/mage/cards/v/VeyranVoiceOfDuality.java @@ -12,7 +12,9 @@ import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.NumberOfTriggersEvent; +import mage.game.permanent.Permanent; import mage.game.stack.Spell; +import mage.util.CardUtil; import java.util.UUID; @@ -71,30 +73,23 @@ class VeyranVoiceOfDualityEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; - if (!source.isControlledBy(event.getPlayerId())) { - return false; - } - GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); - if (sourceEvent == null) { - return false; - } + GameEvent sourceEvent = ((NumberOfTriggersEvent) event).getSourceEvent(); if (sourceEvent.getType() != GameEvent.EventType.COPIED_STACKOBJECT && sourceEvent.getType() != GameEvent.EventType.SPELL_CAST) { return false; } - // Only for entering artifacts or creatures Spell spell = game.getSpell(sourceEvent.getTargetId()); - if (spell == null || !spell.isInstantOrSorcery(game)) { - return false; - } - // Only for triggers of permanents - return game.getPermanent(numberOfTriggersEvent.getSourceId()) != null; + Permanent permanent = game.getPermanent(sourceEvent.getSourceId()); + return spell != null + && permanent != null + && spell.isInstantOrSorcery(game) + && spell.isControlledBy(source.getControllerId()) + && permanent.isControlledBy(source.getControllerId()); } @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() + 1); + event.setAmount(CardUtil.overflowInc(event.getAmount(), 1)); return false; } } diff --git a/Mage.Sets/src/mage/cards/v/VillageReavers.java b/Mage.Sets/src/mage/cards/v/VillageReavers.java index 23a0a1ebd73..efa4fe5e3ff 100644 --- a/Mage.Sets/src/mage/cards/v/VillageReavers.java +++ b/Mage.Sets/src/mage/cards/v/VillageReavers.java @@ -49,7 +49,7 @@ public final class VillageReavers extends CardImpl { ))); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private VillageReavers(final VillageReavers card) { diff --git a/Mage.Sets/src/mage/cards/v/VillageWatch.java b/Mage.Sets/src/mage/cards/v/VillageWatch.java index 4641353d28f..c620c73046f 100644 --- a/Mage.Sets/src/mage/cards/v/VillageWatch.java +++ b/Mage.Sets/src/mage/cards/v/VillageWatch.java @@ -32,7 +32,7 @@ public final class VillageWatch extends CardImpl { // Daybound this.addAbility(new TransformAbility()); - this.addAbility(DayboundAbility.getInstance()); + this.addAbility(new DayboundAbility()); } private VillageWatch(final VillageWatch card) { diff --git a/Mage.Sets/src/mage/cards/v/VineGecko.java b/Mage.Sets/src/mage/cards/v/VineGecko.java index 3b0fc089a30..420e3b37194 100644 --- a/Mage.Sets/src/mage/cards/v/VineGecko.java +++ b/Mage.Sets/src/mage/cards/v/VineGecko.java @@ -16,8 +16,8 @@ import mage.constants.WatcherScope; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; @@ -67,11 +67,11 @@ public final class VineGecko extends CardImpl { } } -enum VineGeckoPredicate implements ObjectPlayerPredicate> { +enum VineGeckoPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { VineGeckoWatcher watcher = game.getState().getWatcher(VineGeckoWatcher.class); if (watcher == null || watcher.checkPlayer(input.getPlayerId())) { return false; diff --git a/Mage.Sets/src/mage/cards/v/VisionsOfDominance.java b/Mage.Sets/src/mage/cards/v/VisionsOfDominance.java new file mode 100644 index 00000000000..4489ab56ed8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VisionsOfDominance.java @@ -0,0 +1,77 @@ +package mage.cards.v; + +import mage.abilities.Ability; +import mage.abilities.costs.costadjusters.CommanderManaValueAdjuster; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VisionsOfDominance extends CardImpl { + + public VisionsOfDominance(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); + + // Put a +1/+1 counter on target creature, then double the number of +1/+1 counters on it. + this.getSpellAbility().addEffect(new VisionsOfDominanceEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // Flashback {8}{G}{G}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone. + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{8}{G}{G}")) + .setAbilityName("This spell costs {X} less to cast this way, where X is the greatest mana value " + + "of a commander you own on the battlefield or in the command zone.") + .setCostAdjuster(CommanderManaValueAdjuster.instance)); + } + + private VisionsOfDominance(final VisionsOfDominance card) { + super(card); + } + + @Override + public VisionsOfDominance copy() { + return new VisionsOfDominance(this); + } +} + +class VisionsOfDominanceEffect extends OneShotEffect { + + VisionsOfDominanceEffect() { + super(Outcome.Benefit); + staticText = "put a +1/+1 counter on target creature, then double the number of +1/+1 counters on it"; + } + + private VisionsOfDominanceEffect(final VisionsOfDominanceEffect effect) { + super(effect); + } + + @Override + public VisionsOfDominanceEffect copy() { + return new VisionsOfDominanceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + permanent.addCounters(CounterType.P1P1.createInstance(), source, game); + int counterCount = permanent.getCounters(game).getCount(CounterType.P1P1); + if (counterCount > 0) { + permanent.addCounters(CounterType.P1P1.createInstance(counterCount), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VisionsOfDread.java b/Mage.Sets/src/mage/cards/v/VisionsOfDread.java new file mode 100644 index 00000000000..f86f6534086 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VisionsOfDread.java @@ -0,0 +1,82 @@ +package mage.cards.v; + +import mage.abilities.Ability; +import mage.abilities.costs.costadjusters.CommanderManaValueAdjuster; +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.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VisionsOfDread extends CardImpl { + + public VisionsOfDread(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // Target opponent puts a creature card of their choice from their graveyard onto the battlefield under your control. + this.getSpellAbility().addEffect(new VisionsOfDreadEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + + // Flashback {8}{B}{B}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone. + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{8}{B}{B}")) + .setAbilityName("This spell costs {X} less to cast this way, where X is the greatest mana value " + + "of a commander you own on the battlefield or in the command zone.") + .setCostAdjuster(CommanderManaValueAdjuster.instance)); + } + + private VisionsOfDread(final VisionsOfDread card) { + super(card); + } + + @Override + public VisionsOfDread copy() { + return new VisionsOfDread(this); + } +} + +class VisionsOfDreadEffect extends OneShotEffect { + + VisionsOfDreadEffect() { + super(Outcome.Benefit); + staticText = "target opponent puts a creature card of their choice " + + "from their graveyard onto the battlefield under your control"; + } + + private VisionsOfDreadEffect(final VisionsOfDreadEffect effect) { + super(effect); + } + + @Override + public VisionsOfDreadEffect copy() { + return new VisionsOfDreadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(source.getFirstTarget()); + if (controller == null || opponent == null + || opponent.getGraveyard().count(StaticFilters.FILTER_CARD_CREATURE, game) < 1) { + return false; + } + TargetCardInYourGraveyard target = new TargetCardInYourGraveyard( + StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD + ); + target.setNotTarget(true); + opponent.choose(Outcome.Detriment, target, source.getSourceId(), game); + return controller.moveCards(game.getCard(target.getFirstTarget()), Zone.BATTLEFIELD, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VisionsOfDuplicity.java b/Mage.Sets/src/mage/cards/v/VisionsOfDuplicity.java new file mode 100644 index 00000000000..5e9a9ac27fb --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VisionsOfDuplicity.java @@ -0,0 +1,47 @@ +package mage.cards.v; + +import mage.abilities.costs.costadjusters.CommanderManaValueAdjuster; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VisionsOfDuplicity extends CardImpl { + + public VisionsOfDuplicity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}"); + + // Exchange control of two target creatures you don't control. + this.getSpellAbility().addEffect(new ExchangeControlTargetEffect( + Duration.EndOfGame, "exchange control of two target creatures you don't control" + )); + this.getSpellAbility().addTarget(new TargetPermanent( + 2, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL + )); + + // Flashback {8}{U}{U}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone. + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{8}{U}{U}")) + .setAbilityName("This spell costs {X} less to cast this way, where X is the greatest mana value " + + "of a commander you own on the battlefield or in the command zone.") + .setCostAdjuster(CommanderManaValueAdjuster.instance)); + } + + private VisionsOfDuplicity(final VisionsOfDuplicity card) { + super(card); + } + + @Override + public VisionsOfDuplicity copy() { + return new VisionsOfDuplicity(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VisionsOfGlory.java b/Mage.Sets/src/mage/cards/v/VisionsOfGlory.java new file mode 100644 index 00000000000..5115f463d53 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VisionsOfGlory.java @@ -0,0 +1,43 @@ +package mage.cards.v; + +import mage.abilities.costs.costadjusters.CommanderManaValueAdjuster; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.HumanToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VisionsOfGlory extends CardImpl { + + public VisionsOfGlory(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{W}"); + + // Create a 1/1 white Human creature token for each creature you control. + this.getSpellAbility().addEffect(new CreateTokenEffect( + new HumanToken(), CreaturesYouControlCount.instance + ).setText("create a 1/1 white Human creature token for each creature you control")); + + // Flashback {8}{W}{W}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone. + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{8}{W}{W}")) + .setAbilityName("This spell costs {X} less to cast this way, where X is the greatest mana value " + + "of a commander you own on the battlefield or in the command zone.") + .setCostAdjuster(CommanderManaValueAdjuster.instance)); + } + + private VisionsOfGlory(final VisionsOfGlory card) { + super(card); + } + + @Override + public VisionsOfGlory copy() { + return new VisionsOfGlory(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VisionsOfRuin.java b/Mage.Sets/src/mage/cards/v/VisionsOfRuin.java new file mode 100644 index 00000000000..de41c1c2d23 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VisionsOfRuin.java @@ -0,0 +1,94 @@ +package mage.cards.v; + +import mage.abilities.Ability; +import mage.abilities.costs.costadjusters.CommanderManaValueAdjuster; +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.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TreasureToken; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VisionsOfRuin extends CardImpl { + + public VisionsOfRuin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}"); + + // Each opponent sacrifices an artifact. For each artifact sacrificed this way, you create a Treasure token. + this.getSpellAbility().addEffect(new VisionsOfRuinEffect()); + + // Flashback {8}{R}{R}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone. + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{8}{R}{R}")) + .setAbilityName("This spell costs {X} less to cast this way, where X is the greatest mana value " + + "of a commander you own on the battlefield or in the command zone.") + .setCostAdjuster(CommanderManaValueAdjuster.instance)); + } + + private VisionsOfRuin(final VisionsOfRuin card) { + super(card); + } + + @Override + public VisionsOfRuin copy() { + return new VisionsOfRuin(this); + } +} + +class VisionsOfRuinEffect extends OneShotEffect { + + VisionsOfRuinEffect() { + super(Outcome.Benefit); + staticText = "each opponent sacrifices an artifact. For each artifact sacrificed this way, you create a Treasure token"; + } + + private VisionsOfRuinEffect(final VisionsOfRuinEffect effect) { + super(effect); + } + + @Override + public VisionsOfRuinEffect copy() { + return new VisionsOfRuinEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set permanents = new HashSet<>(); + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(playerId); + if (opponent == null || game.getBattlefield().count( + StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT, + source.getSourceId(), playerId, game + ) < 1) { + continue; + } + TargetPermanent target = new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT); + target.setNotTarget(true); + opponent.choose(Outcome.Sacrifice, target, source.getSourceId(), game); + permanents.add(game.getPermanent(target.getFirstTarget())); + } + int sacrificed = 0; + for (Permanent permanent : permanents) { + if (permanent != null && permanent.sacrifice(source, game)) { + sacrificed++; + } + } + if (sacrificed > 0) { + new TreasureToken().putOntoBattlefield(sacrificed, game, source, source.getControllerId()); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VolcanicSpray.java b/Mage.Sets/src/mage/cards/v/VolcanicSpray.java index 0d7ccbe3480..86995eb086e 100644 --- a/Mage.Sets/src/mage/cards/v/VolcanicSpray.java +++ b/Mage.Sets/src/mage/cards/v/VolcanicSpray.java @@ -32,7 +32,7 @@ public final class VolcanicSpray extends CardImpl { // Volcanic Spray deals 1 damage to each creature without flying and each player. this.getSpellAbility().addEffect(new DamageEverythingEffect(1, filter)); // Flashback {1}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{1}{R}"), TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{1}{R}"))); } private VolcanicSpray(final VolcanicSpray card) { diff --git a/Mage.Sets/src/mage/cards/v/VoldarenStinger.java b/Mage.Sets/src/mage/cards/v/VoldarenStinger.java new file mode 100644 index 00000000000..e118ad8d8fc --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VoldarenStinger.java @@ -0,0 +1,53 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.SourceAttackingCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FirstStrikeAbility; +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 VoldarenStinger extends CardImpl { + + public VoldarenStinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); + + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Voldaren Stinger has first strike as long as it's attacking. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield), + SourceAttackingCondition.instance, "{this} has first strike as long as it's attacking" + ))); + + // {2}{R}: Voldaren Stinger gets +2/+0 until end of turn. + this.addAbility(new SimpleActivatedAbility(new BoostSourceEffect( + 2, 0, Duration.EndOfTurn + ), new ManaCostsImpl<>("{2}{R}"))); + } + + private VoldarenStinger(final VoldarenStinger card) { + super(card); + } + + @Override + public VoldarenStinger copy() { + return new VoldarenStinger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VolleyOfBoulders.java b/Mage.Sets/src/mage/cards/v/VolleyOfBoulders.java index 5387b0f86a8..2918562e23c 100644 --- a/Mage.Sets/src/mage/cards/v/VolleyOfBoulders.java +++ b/Mage.Sets/src/mage/cards/v/VolleyOfBoulders.java @@ -25,7 +25,7 @@ public final class VolleyOfBoulders extends CardImpl { this.getSpellAbility().addEffect(new DamageMultiEffect(6)); this.getSpellAbility().addTarget(new TargetAnyTargetAmount(6)); // Flashback {R}{R}{R}{R}{R}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{R}{R}{R}{R}{R}{R}"),TimingRule.SORCERY)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{R}{R}{R}{R}{R}{R}"))); } private VolleyOfBoulders(final VolleyOfBoulders card) { diff --git a/Mage.Sets/src/mage/cards/w/Waildrifter.java b/Mage.Sets/src/mage/cards/w/Waildrifter.java new file mode 100644 index 00000000000..14dde19604a --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/Waildrifter.java @@ -0,0 +1,45 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; +import mage.abilities.effects.common.ExileSourceEffect; +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 Waildrifter extends CardImpl { + + public Waildrifter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.HIPPOGRIFF); + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.color.setBlue(true); + this.transformable = true; + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // If Waildrifter would be put into a graveyard from anywhere, exile it instead. + this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + } + + private Waildrifter(final Waildrifter card) { + super(card); + } + + @Override + public Waildrifter copy() { + return new Waildrifter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WakeToSlaughter.java b/Mage.Sets/src/mage/cards/w/WakeToSlaughter.java new file mode 100644 index 00000000000..cdb70557850 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WakeToSlaughter.java @@ -0,0 +1,126 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetCard; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; + +import java.util.Set; +import java.util.UUID; + +/** + * @author LePwnerer + */ +public final class WakeToSlaughter extends CardImpl { + + public WakeToSlaughter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{R}"); + + // Choose up to two target creature cards in your graveyard. An opponent chooses one of them. Return that card to your hand. Return the other to the battlefield under your control. It gains haste. Exile it at the beginning of the next end step. + this.getSpellAbility().addTarget(new TargetCardInGraveyard(0, 2, StaticFilters.FILTER_CARD_CREATURE)); + this.getSpellAbility().addEffect(new WakeToSlaughterEffect()); + + // Flashback {4}{B}{R} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{4}{B}{R}"))); + + } + + private WakeToSlaughter(final WakeToSlaughter card) { + super(card); + } + + @Override + public WakeToSlaughter copy() { + return new WakeToSlaughter(this); + } +} + +class WakeToSlaughterEffect extends OneShotEffect { + + public WakeToSlaughterEffect() { + super(Outcome.Benefit); + this.staticText = "Choose up to two target creature cards in your graveyard. " + + "An opponent chooses one of them. " + + "Return that card to your hand. " + + "Return the other to the battlefield under your control. " + + "It gains haste. " + + "Exile it at the beginning of the next end step."; + } + + public WakeToSlaughterEffect(final mage.cards.w.WakeToSlaughterEffect effect) { + super(effect); + } + + @Override + public mage.cards.w.WakeToSlaughterEffect copy() { + return new mage.cards.w.WakeToSlaughterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Cards pickedCards = new CardsImpl(getTargetPointer().getTargets(game, source)); + if (player != null && !pickedCards.isEmpty()) { + Card cardToHand; + if (pickedCards.size() == 1) { + cardToHand = pickedCards.getRandom(game); + } else { + Player opponent; + Set opponents = game.getOpponents(player.getId()); + if (opponents.size() == 1) { + opponent = game.getPlayer(opponents.iterator().next()); + } else { + Target targetOpponent = new TargetOpponent(true); + player.chooseTarget(Outcome.Detriment, targetOpponent, source, game); + opponent = game.getPlayer(targetOpponent.getFirstTarget()); + } + + TargetCard target = new TargetCard(1, Zone.GRAVEYARD, new FilterCard()); + target.withChooseHint("Card to go to opponent's hand (other goes to battlefield)"); + opponent.chooseTarget(outcome, pickedCards, target, source, game); + cardToHand = game.getCard(target.getFirstTarget()); + } + for (Card card : pickedCards.getCards(game)) { + if (card == cardToHand) { + player.moveCards(cardToHand, Zone.HAND, source, game); + } else { + player.moveCards(card, Zone.BATTLEFIELD, source, game); + + FixedTarget fixedTarget = new FixedTarget(card, game); + ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfGame); + effect.setTargetPointer(fixedTarget); + game.addEffect(effect, source); + + ExileTargetEffect exileEffect = new ExileTargetEffect(null, null, Zone.BATTLEFIELD); + exileEffect.setTargetPointer(fixedTarget); + DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(exileEffect); + game.addDelayedTriggeredAbility(delayedAbility, source); + } + } + pickedCards.clear(); + return true; + } + + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/w/WallOfMourning.java b/Mage.Sets/src/mage/cards/w/WallOfMourning.java new file mode 100644 index 00000000000..c97593d0a7c --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WallOfMourning.java @@ -0,0 +1,125 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.CovenCondition; +import mage.abilities.dynamicvalue.common.OpponentsCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.common.CovenHint; +import mage.abilities.keyword.DefenderAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WallOfMourning extends CardImpl { + + public WallOfMourning(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.WALL); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + + // When Wall of Mourning enters the battlefield, exile a card from the top of your library face down for each opponent you have. + this.addAbility(new EntersBattlefieldTriggeredAbility(new WallOfMourningExileEffect())); + + // Coven — At the beginning of your end step, if you control three or more creatures with different powers, put a card exiled with Wall of Mourning into its owner's hand. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + Zone.BATTLEFIELD, new WallOfMourningReturnEffect(), + TargetController.YOU, CovenCondition.instance, false + ).addHint(CovenHint.instance).setAbilityWord(AbilityWord.COVEN)); + } + + private WallOfMourning(final WallOfMourning card) { + super(card); + } + + @Override + public WallOfMourning copy() { + return new WallOfMourning(this); + } +} + +class WallOfMourningExileEffect extends OneShotEffect { + + WallOfMourningExileEffect() { + super(Outcome.Benefit); + staticText = "exile a card from the top of your library face down for each opponent you have"; + } + + private WallOfMourningExileEffect(final WallOfMourningExileEffect effect) { + super(effect); + } + + @Override + public WallOfMourningExileEffect copy() { + return new WallOfMourningExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int opponents = OpponentsCount.instance.calculate(game, source, this); + Set cards = player.getLibrary().getTopCards(game, opponents); + cards.removeIf(Objects::isNull); + player.moveCardsToExile( + cards, source, game, false, + CardUtil.getExileZoneId(game, source), + CardUtil.getSourceLogName(game, source) + ); + for (Card card : cards) { + card.setFaceDown(true, game); + } + return true; + } +} + +class WallOfMourningReturnEffect extends OneShotEffect { + + WallOfMourningReturnEffect() { + super(Outcome.Benefit); + staticText = "put a card exiled with {this} into its owner's hand"; + } + + private WallOfMourningReturnEffect(final WallOfMourningReturnEffect effect) { + super(effect); + } + + @Override + public WallOfMourningReturnEffect copy() { + return new WallOfMourningReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + if (exileZone == null || exileZone.isEmpty()) { + return false; + } + return player.moveCards(exileZone.getRandom(game), Zone.HAND, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WickedAkuba.java b/Mage.Sets/src/mage/cards/w/WickedAkuba.java index da754eaa406..3f601af77f1 100644 --- a/Mage.Sets/src/mage/cards/w/WickedAkuba.java +++ b/Mage.Sets/src/mage/cards/w/WickedAkuba.java @@ -56,7 +56,7 @@ public final class WickedAkuba extends CardImpl { } } -class WickedAkubaPredicate implements ObjectSourcePlayerPredicate> { +class WickedAkubaPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/w/WildHunger.java b/Mage.Sets/src/mage/cards/w/WildHunger.java index 76225d35d98..6b213997a25 100644 --- a/Mage.Sets/src/mage/cards/w/WildHunger.java +++ b/Mage.Sets/src/mage/cards/w/WildHunger.java @@ -29,7 +29,7 @@ public final class WildHunger extends CardImpl { this.getSpellAbility().addEffect(new BoostTargetEffect(3, 1, Duration.EndOfTurn)); this.getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn)); // Flashback {3}{R} - this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{R}"), TimingRule.INSTANT)); + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{R}"))); } private WildHunger(final WildHunger card) { diff --git a/Mage.Sets/src/mage/cards/w/WilheltTheRotcleaver.java b/Mage.Sets/src/mage/cards/w/WilheltTheRotcleaver.java new file mode 100644 index 00000000000..19506338b12 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WilheltTheRotcleaver.java @@ -0,0 +1,71 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.DecayedAbility; +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.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.permanent.token.ZombieDecayedToken; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WilheltTheRotcleaver extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent(SubType.ZOMBIE); + private static final FilterControlledPermanent filter2 + = new FilterControlledPermanent(SubType.ZOMBIE, "a Zombie"); + + static { + filter.add(Predicates.not(new AbilityPredicate(DecayedAbility.class))); + } + + public WilheltTheRotcleaver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever another Zombie you control dies, if it didn't have decayed, create a 2/2 black Zombie creature token with decayed. + this.addAbility(new DiesCreatureTriggeredAbility( + new CreateTokenEffect(new ZombieDecayedToken()) + .concatBy(", if it didn't have decayed, "), + false, filter + )); + + // At the beginning of your end step, you may sacrifice a Zombie. If you do, draw a card. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), + new SacrificeTargetCost(new TargetControlledPermanent(filter2)) + ), TargetController.YOU, false)); + } + + private WilheltTheRotcleaver(final WilheltTheRotcleaver card) { + super(card); + } + + @Override + public WilheltTheRotcleaver copy() { + return new WilheltTheRotcleaver(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WingShredder.java b/Mage.Sets/src/mage/cards/w/WingShredder.java index 33e792c600a..e1946dd6e5b 100644 --- a/Mage.Sets/src/mage/cards/w/WingShredder.java +++ b/Mage.Sets/src/mage/cards/w/WingShredder.java @@ -30,7 +30,7 @@ public final class WingShredder extends CardImpl { this.addAbility(ReachAbility.getInstance()); // Nightbound - this.addAbility(NightboundAbility.getInstance()); + this.addAbility(new NightboundAbility()); } private WingShredder(final WingShredder card) { diff --git a/Mage.Sets/src/mage/cards/w/WinterthornBlessing.java b/Mage.Sets/src/mage/cards/w/WinterthornBlessing.java new file mode 100644 index 00000000000..e1d11051f50 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WinterthornBlessing.java @@ -0,0 +1,48 @@ +package mage.cards.w; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FirstTargetPointer; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author LePwnerer + */ +public final class WinterthornBlessing extends CardImpl { + + public WinterthornBlessing(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}{U}"); + + // Put a +1/+1 counter on up to one target creature you control. Tap up to one target creature you don't control, and that creature doesn't untap during its controller's next untap step. + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(0, 1)); + this.getSpellAbility().addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance()).setTargetPointer(new FirstTargetPointer())); + this.getSpellAbility().addEffect(new TapTargetEffect().setTargetPointer(new SecondTargetPointer()).setText("tap up to one target creature you don't control")); + this.getSpellAbility().addEffect(new DontUntapInControllersNextUntapStepTargetEffect().setTargetPointer(new SecondTargetPointer()).setText(", and that creature doesn't untap during its controller's next untap step")); + + // Flashback {1}{G}{U} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{G}{U}"))); + + } + + private WinterthornBlessing(final WinterthornBlessing card) { + super(card); + } + + @Override + public WinterthornBlessing copy() { + return new WinterthornBlessing(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WordOfCommand.java b/Mage.Sets/src/mage/cards/w/WordOfCommand.java index ca70b2d3079..0bb6b35bbd3 100644 --- a/Mage.Sets/src/mage/cards/w/WordOfCommand.java +++ b/Mage.Sets/src/mage/cards/w/WordOfCommand.java @@ -115,7 +115,7 @@ class WordOfCommandEffect extends OneShotEffect { boolean canPlay = checkPlayability(card, targetPlayer, game, source); while (canPlay && targetPlayer.canRespond() - && !targetPlayer.playCard(card, game, false, true, new ApprovingObject(source, game))) { + && !targetPlayer.playCard(card, game, false, new ApprovingObject(source, game))) { SpellAbility spellAbility = card.getSpellAbility(); if (spellAbility != null) { spellAbility.getManaCostsToPay().clear(); diff --git a/Mage.Sets/src/mage/cards/y/YasharnImplacableEarth.java b/Mage.Sets/src/mage/cards/y/YasharnImplacableEarth.java index 2cd5ae54e3c..14e22f0f5db 100644 --- a/Mage.Sets/src/mage/cards/y/YasharnImplacableEarth.java +++ b/Mage.Sets/src/mage/cards/y/YasharnImplacableEarth.java @@ -1,26 +1,34 @@ package mage.cards.y; +import java.util.Optional; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.dynamicvalue.common.SubTypeAssignment; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.cards.*; import mage.constants.*; import mage.filter.FilterCard; -import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCardInLibrary; import java.util.UUID; +import mage.MageObject; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.PayVariableLifeCost; +import mage.abilities.costs.common.SacrificeAllCost; +import mage.abilities.costs.common.SacrificeAttachedCost; +import mage.abilities.costs.common.SacrificeAttachmentCost; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.SacrificeXTargetCost; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.filter.Filter; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; /** * @author TheElk801 @@ -39,13 +47,12 @@ public final class YasharnImplacableEarth extends CardImpl { // When Yasharn, Implacable Earth enters the battlefield, search your library for a basic Forest card and a basic Plains card, reveal those cards, put them into your hand, then shuffle your library. this.addAbility(new EntersBattlefieldTriggeredAbility( new SearchLibraryPutInHandEffect(new YasharnImplacableEarthTarget(), true) - .setText("search your library for a basic Forest card and a basic Plains card, " + - "reveal those cards, put them into your hand, then shuffle") + .setText("search your library for a basic Forest card and a basic Plains card, " + + "reveal those cards, put them into your hand, then shuffle") )); // Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities. Ability ability = new SimpleStaticAbility(new YasharnImplacableEarthEffect()); - ability.addEffect(new YasharnImplacableEarthSacrificeFilterEffect()); this.addAbility(ability); } @@ -105,14 +112,14 @@ class YasharnImplacableEarthTarget extends TargetCardInLibrary { } } -class YasharnImplacableEarthEffect extends ContinuousEffectImpl { +class YasharnImplacableEarthEffect extends ContinuousRuleModifyingEffectImpl { - YasharnImplacableEarthEffect() { - super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment); + public YasharnImplacableEarthEffect() { + super(Duration.WhileOnBattlefield, Outcome.Neutral); staticText = "Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities"; } - private YasharnImplacableEarthEffect(final YasharnImplacableEarthEffect effect) { + public YasharnImplacableEarthEffect(final YasharnImplacableEarthEffect effect) { super(effect); } @@ -123,48 +130,102 @@ class YasharnImplacableEarthEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { - Player player = game.getPlayer(playerId); - player.setCanPayLifeCost(false); - player.setCanPaySacrificeCostFilter(StaticFilters.FILTER_PERMANENTS_NON_LAND); - } return true; } -} - -class YasharnImplacableEarthSacrificeFilterEffect extends CostModificationEffectImpl { - - YasharnImplacableEarthSacrificeFilterEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment, CostModificationType.SET_COST); - } - - private YasharnImplacableEarthSacrificeFilterEffect(YasharnImplacableEarthSacrificeFilterEffect effect) { - super(effect); - } @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - for (Cost cost : abilityToModify.getCosts()) { - if (cost instanceof SacrificeTargetCost) { - ((SacrificeTargetCost) cost) - .getTargets() - .get(0) - .getFilter() - .add(CardType.LAND.getPredicate()); + public String getInfoMessage(Ability source, GameEvent event, Game game) { + MageObject mageObject = game.getObject(source.getSourceId()); + if (mageObject != null) { + return "Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities. (" + mageObject.getIdName() + ")."; + } + return null; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + boolean canTargetLand = true; + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (event.getType() == GameEvent.EventType.ACTIVATE_ABILITY + || event.getType() == GameEvent.EventType.CAST_SPELL) { + if (event.getType() == GameEvent.EventType.ACTIVATE_ABILITY) { + if (permanent == null) { + return false; + } + } + Optional ability = game.getAbility(event.getTargetId(), event.getSourceId()); + for (Cost cost : ability.get().getCosts()) { + if (cost instanceof PayLifeCost + || cost instanceof PayVariableLifeCost) { + return true; // can't pay with life + } + if (cost instanceof SacrificeSourceCost + && !permanent.isLand()) { + return true; + } + if (cost instanceof SacrificeTargetCost) { + SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost; + Filter filter = sacrificeCost.getTargets().get(0).getFilter(); + for (Object predicate : filter.getPredicates()) { + if (predicate instanceof CardType.CardTypePredicate) { + if (!predicate.toString().equals("CardType(Land)")) { + canTargetLand = false; + } + } + } + return !canTargetLand; // must be nonland target + } + if (cost instanceof SacrificeAllCost) { + SacrificeAllCost sacrificeAllCost = (SacrificeAllCost) cost; + Filter filter = sacrificeAllCost.getTargets().get(0).getFilter(); + for (Object predicate : filter.getPredicates()) { + if (predicate instanceof CardType.CardTypePredicate) { + if (!predicate.toString().equals("CardType(Land)")) { + canTargetLand = false; + } + } + } + return !canTargetLand; // must be nonland target + } + if (cost instanceof SacrificeAttachedCost) { + SacrificeAttachedCost sacrificeAllCost = (SacrificeAttachedCost) cost; + Filter filter = sacrificeAllCost.getTargets().get(0).getFilter(); + for (Object predicate : filter.getPredicates()) { + if (predicate instanceof CardType.CardTypePredicate) { + if (!predicate.toString().equals("CardType(Land)")) { + canTargetLand = false; + } + } + } + return !canTargetLand; // must be nonland target + } + if (cost instanceof SacrificeAttachmentCost) { + SacrificeAttachmentCost sacrificeAllCost = (SacrificeAttachmentCost) cost; + Filter filter = sacrificeAllCost.getTargets().get(0).getFilter(); + for (Object predicate : filter.getPredicates()) { + if (predicate instanceof CardType.CardTypePredicate) { + if (!predicate.toString().equals("CardType(Land)")) { + canTargetLand = false; + } + } + } + return !canTargetLand; // must be nonland target + } + + if (cost instanceof SacrificeXTargetCost) { + SacrificeXTargetCost sacrificeCost = (SacrificeXTargetCost) cost; + Filter filter = sacrificeCost.getFilter(); + for (Object predicate : filter.getPredicates()) { + if (predicate instanceof CardType.CardTypePredicate) { + if (!predicate.toString().equals("CardType(Land)")) { + canTargetLand = false; + } + } + } + return !canTargetLand; // must be nonland target + } } } - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return (abilityToModify.getAbilityType() == AbilityType.ACTIVATED - || abilityToModify instanceof SpellAbility) - && game.getState().getPlayersInRange(source.getControllerId(), game).contains(abilityToModify.getControllerId()); - } - - @Override - public YasharnImplacableEarthSacrificeFilterEffect copy() { - return new YasharnImplacableEarthSacrificeFilterEffect(this); + return false; } } diff --git a/Mage.Sets/src/mage/cards/y/YasovaDragonclaw.java b/Mage.Sets/src/mage/cards/y/YasovaDragonclaw.java index 70a555f694d..13fd40aa40d 100644 --- a/Mage.Sets/src/mage/cards/y/YasovaDragonclaw.java +++ b/Mage.Sets/src/mage/cards/y/YasovaDragonclaw.java @@ -71,7 +71,7 @@ public final class YasovaDragonclaw extends CardImpl { } } -class YasovaDragonclawPowerLessThanSourcePredicate implements ObjectSourcePlayerPredicate> { +class YasovaDragonclawPowerLessThanSourcePredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage.Sets/src/mage/cards/y/YennettCrypticSovereign.java b/Mage.Sets/src/mage/cards/y/YennettCrypticSovereign.java index 0caca766001..c6e65bb05b1 100644 --- a/Mage.Sets/src/mage/cards/y/YennettCrypticSovereign.java +++ b/Mage.Sets/src/mage/cards/y/YennettCrypticSovereign.java @@ -92,7 +92,9 @@ class YennettCrypticSovereignEffect extends OneShotEffect { player.revealCards(source, new CardsImpl(card), game); if (card.getManaValue() % 2 == 1) { if (player.chooseUse(outcome, "Cast " + card.getLogName() + " without paying its mana cost?", source, game)) { - player.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + player.cast(player.chooseAbilityForCast(card, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); } else { /* 7/13/2018 | If the revealed card doesn’t have an odd converted mana cost or if that card does but you diff --git a/Mage.Sets/src/mage/cards/y/YoreTillerNephilim.java b/Mage.Sets/src/mage/cards/y/YoreTillerNephilim.java index 163d649e4ff..cc0defe9056 100644 --- a/Mage.Sets/src/mage/cards/y/YoreTillerNephilim.java +++ b/Mage.Sets/src/mage/cards/y/YoreTillerNephilim.java @@ -1,25 +1,19 @@ package mage.cards.y; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; 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.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author fireshoes */ public final class YoreTillerNephilim extends CardImpl { @@ -31,8 +25,8 @@ public final class YoreTillerNephilim extends CardImpl { this.toughness = new MageInt(2); // Whenever Yore-Tiller Nephilim attacks, return target creature card from your graveyard to the battlefield tapped and attacking. - Ability ability = new AttacksTriggeredAbility(new YoreTillerNephilimEffect(), false); - ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE)); + Ability ability = new AttacksTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(true, true), false); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); this.addAbility(ability); } @@ -45,39 +39,3 @@ public final class YoreTillerNephilim extends CardImpl { return new YoreTillerNephilim(this); } } - -class YoreTillerNephilimEffect extends OneShotEffect { - - public YoreTillerNephilimEffect() { - super(Outcome.PutCreatureInPlay); - this.staticText = "return target creature card from your graveyard to the battlefield tapped and attacking"; - } - - public YoreTillerNephilimEffect(final YoreTillerNephilimEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - - if (controller != null) { - Card card = game.getCard(getTargetPointer().getFirst(game, source)); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); - Permanent permanent = game.getPermanent(card.getId()); - if (permanent != null) { - game.getCombat().addAttackingCreature(permanent.getId(), game); - } - } - return true; - - } - return false; - } - - @Override - public YoreTillerNephilimEffect copy() { - return new YoreTillerNephilimEffect(this); - } -} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 94bed009b6e..7eafe71f6ba 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -23,10 +23,11 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { } private AdventuresInTheForgottenRealms() { - super("Adventures in the Forgotten Realms", "AFR", ExpansionSet.buildDate(2021, 7, 23), SetType.EXPANSION, new AdventuresInTheForgottenRealmsCollator()); + super("Adventures in the Forgotten Realms", "AFR", ExpansionSet.buildDate(2021, 7, 23), SetType.EXPANSION); this.blockName = "Adventures in the Forgotten Realms"; this.hasBoosters = true; this.hasBasicLands = true; + this.numBoosterLands = 1; this.numBoosterCommon = 10; this.numBoosterUncommon = 3; this.numBoosterRare = 1; @@ -435,126 +436,56 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Zariel, Archduke of Avernus", 285, Rarity.MYTHIC, mage.cards.z.ZarielArchdukeOfAvernus.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Zombie Ogre", 129, Rarity.COMMON, mage.cards.z.ZombieOgre.class)); } + + @Override + public BoosterCollator createCollator() { + return new AdventuresInTheForgottenRealmsCollator(); + } } // Booster collation info from https://www.lethe.xyz/mtg/collation/afr.html // Using USA collation for common/uncommon, rare collation inferred from other information class AdventuresInTheForgottenRealmsCollator implements BoosterCollator { - private static class AdventuresInTheForgottenRealmsRun extends CardRun { - private static final AdventuresInTheForgottenRealmsRun commonA = new AdventuresInTheForgottenRealmsRun(true, "144", "30", "122", "85", "153", "250", "164", "199", "133", "38", "141", "251", "101", "74", "245", "153", "122", "85", "30", "318", "250", "133", "199", "38", "153", "245", "74", "101", "251", "141", "30", "85", "164", "122", "144", "101", "251", "133", "199", "164", "250", "85", "141", "38", "74", "144", "30", "245", "122", "164", "38", "133", "250", "153", "248", "144", "101", "245", "74", "199", "141", "251", "30", "250", "122", "153", "85", "38", "164", "199", "133", "245", "101", "251", "141", "74", "153", "85", "133", "250", "318", "122", "164", "30", "199", "101", "251", "38", "85", "122", "245", "164", "74", "30", "141", "199", "101", "153", "38", "144", "250", "133", "245", "74", "164", "85", "122", "251", "141", "250", "144", "30", "153", "38", "133", "199", "74", "251", "141", "245", "101"); - private static final AdventuresInTheForgottenRealmsRun commonB = new AdventuresInTheForgottenRealmsRun(true, "115", "182", "37", "47", "134", "119", "204", "16", "70", "146", "103", "189", "31", "51", "139", "102", "177", "14", "72", "158", "128", "205", "10", "46", "168", "108", "203", "43", "75", "150", "110", "178", "19", "52", "142", "123", "174", "40", "83", "140", "94", "213", "34", "73", "130", "109", "206", "9", "71", "162", "97", "183", "1", "65", "148", "118", "187", "11", "84", "159", "115", "204", "37", "51", "146", "119", "182", "31", "47", "134", "102", "177", "16", "70", "139", "103", "189", "14", "46", "168", "108", "205", "10", "72", "158", "128", "178", "19", "75", "150", "123", "203", "43", "52", "140", "110", "174", "40", "73", "142", "97", "206", "9", "83", "130", "118", "183", "34", "71", "162", "94", "213", "1", "65", "159", "109", "187", "11", "84", "148", "248"); - private static final AdventuresInTheForgottenRealmsRun commonC = new AdventuresInTheForgottenRealmsRun(true, "55", "311", "24", "208", "106", "69", "165", "179", "129", "249", "50", "42", "198", "353", "35", "113", "185", "2", "309", "195", "312", "5", "45", "334", "152", "89", "24", "173", "306", "106", "179", "165", "69", "129", "198", "256", "50", "249", "35", "113", "326", "42", "45", "195", "93", "299", "66", "208", "24", "89", "152", "324", "55", "5", "165", "69", "106", "179", "50", "314", "185", "2", "129", "349", "198", "42", "66", "256", "35", "329", "45", "5", "152", "89", "55", "208", "93", "310", "165", "173", "24", "249", "331", "42", "50", "106", "256", "35", "325", "129", "2", "185", "113", "66", "195", "93", "45", "301", "152", "173"); - private static final AdventuresInTheForgottenRealmsRun uncommonA = new AdventuresInTheForgottenRealmsRun(true, "76", "234", "92", "67", "175", "132", "96", "79", "154", "98", "188", "21", "125", "215", "61", "13", "120", "44", "136", "99", "244", "214", "135", "6", "81", "114", "170", "59", "116", "200", "68", "22", "240", "111", "58", "26", "210", "145", "25", "77", "242", "131", "33", "180", "163", "12", "117", "57", "32", "137", "212", "107", "36", "169", "191", "234", "76", "92", "132", "67", "96", "175", "79", "98", "21", "188", "125", "154", "44", "13", "120", "215", "61", "244", "136", "214", "99", "81", "135", "6", "59", "114", "170", "200", "22", "68", "116", "240", "26", "58", "111", "25", "131", "210", "33", "145", "77", "12", "163", "242", "180", "137", "107", "57", "32", "212", "117", "169", "191", "36"); - private static final AdventuresInTheForgottenRealmsRun uncommonB = new AdventuresInTheForgottenRealmsRun(true, "247", "332", "49", "224", "300", "41", "219", "357", "160", "223", "225", "54", "90", "7", "346", "218", "95", "231", "186", "149", "327", "221", "161", "194", "348", "49", "224", "201", "343", "41", "219", "260", "305", "223", "160", "3", "342", "95", "218", "7", "236", "291", "247", "149", "186", "345", "192", "339", "161", "194", "288", "219", "201", "289", "224", "226", "160", "340", "95", "3", "336", "54", "260", "225", "231", "7", "90", "149", "236", "295", "321", "192", "49", "341", "247", "194", "221", "226", "260", "54", "223", "41", "201", "337", "218", "302", "3", "95", "294", "225", "192", "236", "90", "319", "231", "186", "226", "328", "161", "221"); - private static final AdventuresInTheForgottenRealmsRun rareA = new AdventuresInTheForgottenRealmsRun(false, "87", "53", "100", "181", "143", "17", "20", "151", "62", "112", "227", "64", "197", "4", "91", "241", "207", "235", "239", "172", "216", "88", "217", "253", "176", "8", "138", "254", "56", "220", "255", "243", "222", "15", "104", "184", "105", "60", "18", "257", "246", "258", "147", "190", "259", "193", "23", "155", "63", "156", "228", "27", "196", "157", "229", "28", "29", "202", "230", "232", "233", "121", "78", "39", "48", "252", "261", "237", "80", "209", "238", "124", "211", "126", "127", "166", "82", "167", "86", "171", "216", "88", "217", "253", "176", "8", "138", "254", "56", "220", "255", "243", "222", "15", "104", "184", "105", "60", "18", "257", "246", "258", "147", "190", "259", "193", "23", "155", "63", "156", "228", "27", "196", "157", "229", "28", "29", "202", "230", "232", "233", "121", "78", "39", "48", "252", "261", "237", "80", "209", "238", "124", "211", "126", "127", "166", "82", "167", "86", "171"); - private static final AdventuresInTheForgottenRealmsRun rareB = new AdventuresInTheForgottenRealmsRun(false, "87", "53", "292", "286", "143", "282", "287", "293", "290", "284", "344", "283", "296", "4", "91", "241", "333", "298", "239", "285", "297", "88", "217", "350", "176", "8", "317", "351", "307", "338", "352", "243", "222", "15", "104", "184", "313", "60", "18", "354", "246", "355", "147", "190", "356", "193", "23", "155", "308", "320", "228", "303", "330", "157", "229", "304", "29", "202", "230", "232", "233", "121", "78", "39", "48", "397", "358", "237", "80", "335", "347", "396", "211", "315", "316", "166", "82", "322", "86", "323", "297", "88", "217", "350", "176", "8", "317", "351", "307", "338", "352", "243", "222", "15", "104", "184", "313", "60", "18", "354", "246", "355", "147", "190", "356", "193", "23", "155", "308", "320", "228", "303", "330", "157", "229", "304", "29", "202", "230", "232", "233", "121", "78", "39", "48", "397", "358", "237", "80", "335", "347", "396", "211", "315", "316", "166", "82", "322", "86", "323"); - private static final AdventuresInTheForgottenRealmsRun land = new AdventuresInTheForgottenRealmsRun(false, "262", "263", "264", "265", "266", "267", "268", "269", "270", "271", "272", "273", "274", "275", "276", "277", "278", "279", "280", "281"); + private final CardRun commonA = new CardRun(true, "144", "30", "122", "85", "153", "250", "164", "199", "133", "38", "141", "251", "101", "74", "245", "153", "122", "85", "30", "318", "250", "133", "199", "38", "153", "245", "74", "101", "251", "141", "30", "85", "164", "122", "144", "101", "251", "133", "199", "164", "250", "85", "141", "38", "74", "144", "30", "245", "122", "164", "38", "133", "250", "153", "248", "144", "101", "245", "74", "199", "141", "251", "30", "250", "122", "153", "85", "38", "164", "199", "133", "245", "101", "251", "141", "74", "153", "85", "133", "250", "318", "122", "164", "30", "199", "101", "251", "38", "85", "122", "245", "164", "74", "30", "141", "199", "101", "153", "38", "144", "250", "133", "245", "74", "164", "85", "122", "251", "141", "250", "144", "30", "153", "38", "133", "199", "74", "251", "141", "245", "101"); + private final CardRun commonB = new CardRun(true, "115", "182", "37", "47", "134", "119", "204", "16", "70", "146", "103", "189", "31", "51", "139", "102", "177", "14", "72", "158", "128", "205", "10", "46", "168", "108", "203", "43", "75", "150", "110", "178", "19", "52", "142", "123", "174", "40", "83", "140", "94", "213", "34", "73", "130", "109", "206", "9", "71", "162", "97", "183", "1", "65", "148", "118", "187", "11", "84", "159", "115", "204", "37", "51", "146", "119", "182", "31", "47", "134", "102", "177", "16", "70", "139", "103", "189", "14", "46", "168", "108", "205", "10", "72", "158", "128", "178", "19", "75", "150", "123", "203", "43", "52", "140", "110", "174", "40", "73", "142", "97", "206", "9", "83", "130", "118", "183", "34", "71", "162", "94", "213", "1", "65", "159", "109", "187", "11", "84", "148", "248"); + private final CardRun commonC = new CardRun(true, "55", "311", "24", "208", "106", "69", "165", "179", "129", "249", "50", "42", "198", "353", "35", "113", "185", "2", "309", "195", "312", "5", "45", "334", "152", "89", "24", "173", "306", "106", "179", "165", "69", "129", "198", "256", "50", "249", "35", "113", "326", "42", "45", "195", "93", "299", "66", "208", "24", "89", "152", "324", "55", "5", "165", "69", "106", "179", "50", "314", "185", "2", "129", "349", "198", "42", "66", "256", "35", "329", "45", "5", "152", "89", "55", "208", "93", "310", "165", "173", "24", "249", "331", "42", "50", "106", "256", "35", "325", "129", "2", "185", "113", "66", "195", "93", "45", "301", "152", "173"); + private final CardRun uncommonA = new CardRun(true, "76", "234", "92", "67", "175", "132", "96", "79", "154", "98", "188", "21", "125", "215", "61", "13", "120", "44", "136", "99", "244", "214", "135", "6", "81", "114", "170", "59", "116", "200", "68", "22", "240", "111", "58", "26", "210", "145", "25", "77", "242", "131", "33", "180", "163", "12", "117", "57", "32", "137", "212", "107", "36", "169", "191", "234", "76", "92", "132", "67", "96", "175", "79", "98", "21", "188", "125", "154", "44", "13", "120", "215", "61", "244", "136", "214", "99", "81", "135", "6", "59", "114", "170", "200", "22", "68", "116", "240", "26", "58", "111", "25", "131", "210", "33", "145", "77", "12", "163", "242", "180", "137", "107", "57", "32", "212", "117", "169", "191", "36"); + private final CardRun uncommonB = new CardRun(true, "247", "332", "49", "224", "300", "41", "219", "357", "160", "223", "225", "54", "90", "7", "346", "218", "95", "231", "186", "149", "327", "221", "161", "194", "348", "49", "224", "201", "343", "41", "219", "260", "305", "223", "160", "3", "342", "95", "218", "7", "236", "291", "247", "149", "186", "345", "192", "339", "161", "194", "288", "219", "201", "289", "224", "226", "160", "340", "95", "3", "336", "54", "260", "225", "231", "7", "90", "149", "236", "295", "321", "192", "49", "341", "247", "194", "221", "226", "260", "54", "223", "41", "201", "337", "218", "302", "3", "95", "294", "225", "192", "236", "90", "319", "231", "186", "226", "328", "161", "221"); + private final CardRun rareA = new CardRun(false, "87", "53", "100", "181", "143", "17", "20", "151", "62", "112", "227", "64", "197", "4", "91", "241", "207", "235", "239", "172", "216", "88", "217", "253", "176", "8", "138", "254", "56", "220", "255", "243", "222", "15", "104", "184", "105", "60", "18", "257", "246", "258", "147", "190", "259", "193", "23", "155", "63", "156", "228", "27", "196", "157", "229", "28", "29", "202", "230", "232", "233", "121", "78", "39", "48", "252", "261", "237", "80", "209", "238", "124", "211", "126", "127", "166", "82", "167", "86", "171", "216", "88", "217", "253", "176", "8", "138", "254", "56", "220", "255", "243", "222", "15", "104", "184", "105", "60", "18", "257", "246", "258", "147", "190", "259", "193", "23", "155", "63", "156", "228", "27", "196", "157", "229", "28", "29", "202", "230", "232", "233", "121", "78", "39", "48", "252", "261", "237", "80", "209", "238", "124", "211", "126", "127", "166", "82", "167", "86", "171"); + private final CardRun rareB = new CardRun(false, "87", "53", "292", "286", "143", "282", "287", "293", "290", "284", "344", "283", "296", "4", "91", "241", "333", "298", "239", "285", "297", "88", "217", "350", "176", "8", "317", "351", "307", "338", "352", "243", "222", "15", "104", "184", "313", "60", "18", "354", "246", "355", "147", "190", "356", "193", "23", "155", "308", "320", "228", "303", "330", "157", "229", "304", "29", "202", "230", "232", "233", "121", "78", "39", "48", "397", "358", "237", "80", "335", "347", "396", "211", "315", "316", "166", "82", "322", "86", "323", "297", "88", "217", "350", "176", "8", "317", "351", "307", "338", "352", "243", "222", "15", "104", "184", "313", "60", "18", "354", "246", "355", "147", "190", "356", "193", "23", "155", "308", "320", "228", "303", "330", "157", "229", "304", "29", "202", "230", "232", "233", "121", "78", "39", "48", "397", "358", "237", "80", "335", "347", "396", "211", "315", "316", "166", "82", "322", "86", "323"); + private final CardRun land = new CardRun(false, "262", "263", "264", "265", "266", "267", "268", "269", "270", "271", "272", "273", "274", "275", "276", "277", "278", "279", "280", "281"); - private AdventuresInTheForgottenRealmsRun(boolean keepOrder, String... numbers) { - super(keepOrder, numbers); - } - } - - private static class AdventuresInTheForgottenRealmsStructure extends BoosterStructure { - private static final AdventuresInTheForgottenRealmsStructure C1 = new AdventuresInTheForgottenRealmsStructure( - AdventuresInTheForgottenRealmsRun.commonA, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonC, - AdventuresInTheForgottenRealmsRun.commonC, - AdventuresInTheForgottenRealmsRun.commonC - ); - private static final AdventuresInTheForgottenRealmsStructure C2 = new AdventuresInTheForgottenRealmsStructure( - AdventuresInTheForgottenRealmsRun.commonA, - AdventuresInTheForgottenRealmsRun.commonA, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonC, - AdventuresInTheForgottenRealmsRun.commonC, - AdventuresInTheForgottenRealmsRun.commonC - ); - private static final AdventuresInTheForgottenRealmsStructure C3 = new AdventuresInTheForgottenRealmsStructure( - AdventuresInTheForgottenRealmsRun.commonA, - AdventuresInTheForgottenRealmsRun.commonA, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonB, - AdventuresInTheForgottenRealmsRun.commonC, - AdventuresInTheForgottenRealmsRun.commonC - ); - private static final AdventuresInTheForgottenRealmsStructure U1 = new AdventuresInTheForgottenRealmsStructure( - AdventuresInTheForgottenRealmsRun.uncommonA, - AdventuresInTheForgottenRealmsRun.uncommonA, - AdventuresInTheForgottenRealmsRun.uncommonA - ); - private static final AdventuresInTheForgottenRealmsStructure U2 = new AdventuresInTheForgottenRealmsStructure( - AdventuresInTheForgottenRealmsRun.uncommonB, - AdventuresInTheForgottenRealmsRun.uncommonB, - AdventuresInTheForgottenRealmsRun.uncommonB - ); - private static final AdventuresInTheForgottenRealmsStructure R1 = new AdventuresInTheForgottenRealmsStructure( - AdventuresInTheForgottenRealmsRun.rareA - ); - private static final AdventuresInTheForgottenRealmsStructure R2 = new AdventuresInTheForgottenRealmsStructure( - AdventuresInTheForgottenRealmsRun.rareB - ); - private static final AdventuresInTheForgottenRealmsStructure L1 = new AdventuresInTheForgottenRealmsStructure( - AdventuresInTheForgottenRealmsRun.land - ); - - private AdventuresInTheForgottenRealmsStructure(CardRun... runs) { - super(runs); - } - } - - private final RarityConfiguration commonRuns = new RarityConfiguration( - false, - AdventuresInTheForgottenRealmsStructure.C1, - AdventuresInTheForgottenRealmsStructure.C2, - AdventuresInTheForgottenRealmsStructure.C3 + private final BoosterStructure ABBBBBBCCC = new BoosterStructure( + commonA, + commonB, commonB, commonB, commonB, commonB, commonB, + commonC, commonC, commonC ); + private final BoosterStructure AABBBBBBCC = new BoosterStructure( + commonA, commonA, + commonB, commonB, commonB, commonB, commonB, commonB, + commonC, commonC + ); + private final BoosterStructure AAA = new BoosterStructure(uncommonA, uncommonA, uncommonA); + private final BoosterStructure BBB = new BoosterStructure(uncommonB, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA); + private final BoosterStructure R2 = new BoosterStructure(rareB); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 1.503 A commons (242 / 161) + // 6.012 B commons (968 / 161) + // 2.484 C commons (400 / 161) + // However, boosters with more than six B commons are not known to exist. + // This discrepancy is presumably related to foils--the above values are based on + // 10 commons per booster, but real boosters contain only 9.67 non-foil commons + private final RarityConfiguration commonRuns = new RarityConfiguration(ABBBBBBCCC, AABBBBBBCC); private final RarityConfiguration uncommonRuns = new RarityConfiguration( - false, - AdventuresInTheForgottenRealmsStructure.U1, AdventuresInTheForgottenRealmsStructure.U1, - AdventuresInTheForgottenRealmsStructure.U1, AdventuresInTheForgottenRealmsStructure.U1, - AdventuresInTheForgottenRealmsStructure.U1, AdventuresInTheForgottenRealmsStructure.U1, - AdventuresInTheForgottenRealmsStructure.U1, AdventuresInTheForgottenRealmsStructure.U1, - AdventuresInTheForgottenRealmsStructure.U1, AdventuresInTheForgottenRealmsStructure.U1, - AdventuresInTheForgottenRealmsStructure.U1, - AdventuresInTheForgottenRealmsStructure.U2, AdventuresInTheForgottenRealmsStructure.U2, - AdventuresInTheForgottenRealmsStructure.U2, AdventuresInTheForgottenRealmsStructure.U2, - AdventuresInTheForgottenRealmsStructure.U2 + AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, AAA, + BBB, BBB, BBB, BBB, BBB ); - private final RarityConfiguration rareRuns = new RarityConfiguration( - false, - AdventuresInTheForgottenRealmsStructure.R1, - AdventuresInTheForgottenRealmsStructure.R1, - AdventuresInTheForgottenRealmsStructure.R1, - AdventuresInTheForgottenRealmsStructure.R2 - ); - private final RarityConfiguration landRuns = new RarityConfiguration( - AdventuresInTheForgottenRealmsStructure.L1 - ); - - - @Override - public void shuffle() { - commonRuns.shuffle(); - uncommonRuns.shuffle(); - rareRuns.shuffle(); - landRuns.shuffle(); - } + private final RarityConfiguration rareRuns = new RarityConfiguration(R1, R1, R1, R2); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); @Override public List makeBooster() { diff --git a/Mage.Sets/src/mage/sets/DarkAscension.java b/Mage.Sets/src/mage/sets/DarkAscension.java index f05aa666531..930e9c466c5 100644 --- a/Mage.Sets/src/mage/sets/DarkAscension.java +++ b/Mage.Sets/src/mage/sets/DarkAscension.java @@ -2,9 +2,16 @@ package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + /** * * @author North @@ -201,4 +208,55 @@ public final class DarkAscension extends ExpansionSet { cards.add(new SetCardInfo("Young Wolf", 134, Rarity.COMMON, mage.cards.y.YoungWolf.class)); cards.add(new SetCardInfo("Zombie Apocalypse", 80, Rarity.RARE, mage.cards.z.ZombieApocalypse.class)); } + + @Override + public BoosterCollator createCollator() { + return new DarkAscensionCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/dka.html +// Using USA collation for common/uncommon, rare collation inferred from other sets +class DarkAscensionCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "66", "47", "100", "110", "86", "51", "21", "75", "148", "2", "52", "119", "102", "54", "49", "4", "87", "109", "148", "91", "40", "76", "110", "22", "106", "65", "43", "132", "17", "157", "76", "87", "40", "21", "49", "118", "75", "86", "54", "3", "119", "51", "47", "113", "2", "91", "59", "102", "27", "118", "43", "17", "109", "4", "59", "100", "66", "52", "132", "22", "157", "113", "106", "27", "65", "3"); + private final CardRun commonB = new CardRun(true, "103", "126", "29", "8", "73", "88", "60", "111", "19", "15", "29", "107", "35", "126", "150", "90", "73", "129", "6", "15", "68", "46", "155", "105", "72", "14", "129", "77", "38", "6", "121", "88", "134", "31", "111", "72", "103", "35", "19", "121", "90", "14", "31", "68", "105", "8", "60", "150", "134", "155", "38", "107", "46", "77"); + private final CardRun uncommonA = new CardRun(true, "5", "128", "101", "153", "45", "145", "12", "67", "78", "117", "97", "44", "153", "5", "136", "57", "128", "135", "101", "154", "53", "7", "12", "57", "116", "135", "97", "44", "58", "92", "12", "136", "145", "117", "5", "78", "116", "92", "53", "58", "145", "10", "97", "7", "116", "45", "67", "154", "58", "10", "92", "44", "117", "153", "7", "78", "128", "45", "101", "67", "136", "53", "10", "135", "154", "57"); + private final CardRun uncommonB = new CardRun(true, "83", "48", "16", "144", "127", "104", "42", "130", "48", "16", "84", "79", "141", "32", "130", "26", "127", "74", "83", "9", "42", "61", "144", "143", "108", "84", "26", "141", "127", "9", "48", "104", "74", "143", "79", "144", "108", "26", "32", "83", "104", "61", "42", "143", "16", "141", "130", "84", "32", "74", "108", "9", "79", "61"); + private final CardRun rare = new CardRun(false, "11", "18", "20", "23", "24", "25", "30", "33", "34", "36", "37", "39", "41", "56", "62", "63", "64", "69", "80", "82", "85", "89", "93", "95", "96", "112", "114", "115", "120", "123", "124", "149", "152", "156", "158", "11", "18", "20", "23", "24", "25", "30", "33", "34", "36", "37", "39", "41", "56", "62", "63", "64", "69", "80", "82", "85", "89", "93", "95", "96", "112", "114", "115", "120", "123", "124", "149", "152", "156", "158", "1", "28", "70", "99", "131", "137", "138", "139", "142", "151"); + private final CardRun doubleFaced = new CardRun(false, "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "13", "55", "94", "125", "50", "81", "122", "146", "50", "81", "122", "146", "50", "81", "122", "146", "50", "81", "122", "146", "50", "81", "122", "146", "50", "81", "122", "146", "50", "81", "122", "146", "50", "81", "122", "146", "50", "81", "122", "146", "71", "98", "133", "71", "98", "133", "71", "98", "133", "140", "147", "140", "147"); + private final CardRun land = new CardRun(false, "ISD_250", "ISD_251", "ISD_252", "ISD_253", "ISD_254", "ISD_255", "ISD_256", "ISD_257", "ISD_258", "ISD_259", "ISD_260", "ISD_261", "ISD_262", "ISD_263", "ISD_264"); + + private final BoosterStructure AAAAABBBB = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rare); + private final BoosterStructure D1 = new BoosterStructure(doubleFaced); + private final BoosterStructure L1 = new BoosterStructure(land); + + private final RarityConfiguration commonRuns = new RarityConfiguration(AAAAABBBB); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 1.65 A uncommons (33 / 20) + // 1.35 B uncommons (27 / 20) + // These numbers are the same for all sets with 40 uncommons in asymmetrical A/B print runs + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + private final RarityConfiguration dfcRuns = new RarityConfiguration(D1); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(dfcRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/Dominaria.java b/Mage.Sets/src/mage/sets/Dominaria.java index 98d533f1954..8d4a4d441c2 100644 --- a/Mage.Sets/src/mage/sets/Dominaria.java +++ b/Mage.Sets/src/mage/sets/Dominaria.java @@ -1,10 +1,16 @@ - package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + /** * * @author fireshoes @@ -310,4 +316,106 @@ public final class Dominaria extends ExpansionSet { cards.add(new SetCardInfo("Zahid, Djinn of the Lamp", 76, Rarity.RARE, mage.cards.z.ZahidDjinnOfTheLamp.class)); cards.add(new SetCardInfo("Zhalfirin Void", 249, Rarity.UNCOMMON, mage.cards.z.ZhalfirinVoid.class)); } + + @Override + public BoosterCollator createCollator() { + return new DominariaCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/dom.html +// Using USA collation for common/uncommon, rare collation inferred from other sets +class DominariaCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "5", "67", "125", "15", "62", "120", "10", "43", "151", "24", "45", "144", "32", "48", "115", "17", "44", "136", "20", "50", "117", "34", "53", "127", "2", "60", "126", "22", "73", "138", "11", "67", "120", "15", "71", "124", "20", "62", "151", "10", "43", "125", "5", "44", "136", "24", "50", "144", "11", "48", "117", "32", "45", "115", "22", "60", "126", "17", "53", "127", "34", "71", "138", "2", "73", "124"); + private final CardRun commonB = new CardRun(true, "79", "153", "87", "164", "77", "163", "104", "171", "89", "169", "80", "162", "112", "155", "83", "167", "92", "169", "101", "170", "105", "176", "87", "164", "77", "153", "104", "158", "89", "171", "79", "162", "80", "163", "83", "170", "105", "167", "101", "155", "112", "176", "92", "158", "87", "170", "79", "153", "104", "164", "77", "169", "112", "162", "83", "167", "89", "171", "92", "163", "80", "155", "105", "176", "101", "158"); + private final CardRun commonC1 = new CardRun(true, "47", "78", "3", "230", "177", "40", "85", "227", "142", "9", "168", "232", "91", "72", "236", "140", "19", "106", "221", "178", "46", "141", "3", "78", "177", "225", "47", "118", "212", "85", "9", "154", "227", "72", "84", "142", "232", "19", "168", "106", "230", "40", "141", "236", "91", "226", "178", "221", "140", "46", "84", "212", "154", "118", "225"); + private final CardRun commonC2 = new CardRun(true, "135", "27", "209", "94", "37", "189", "226", "139", "52", "110", "229", "29", "7", "157", "41", "134", "156", "216", "94", "29", "135", "157", "52", "27", "209", "139", "63", "7", "189", "134", "229", "110", "37", "41", "156", "216", "135", "29", "94", "63", "189", "209", "139", "157", "27", "52", "229", "7", "134", "41", "37", "110", "216", "156", "63"); + private final CardRun uncommonA = new CardRun(true, "235", "119", "179", "145", "99", "38", "219", "175", "243", "33", "116", "210", "70", "160", "152", "81", "23", "235", "186", "242", "14", "130", "210", "54", "159", "121", "90", "38", "219", "180", "245", "23", "145", "179", "65", "186", "116", "82", "75", "121", "175", "99", "14", "119", "235", "70", "159", "130", "81", "54", "243", "160", "33", "82", "152", "210", "65", "180", "145", "90", "75", "242", "159", "99", "245", "116", "219", "70", "175", "119", "81", "65", "243", "186", "82", "38", "130", "235", "75", "160", "33", "121", "70", "152", "179", "90", "23", "145", "180", "54", "159", "116", "14", "65", "242", "179", "99", "38", "119", "210", "245", "180", "152", "23", "54", "243", "175", "90", "14", "130", "219", "242", "160", "121", "82", "75", "245", "186", "81", "33"); + private final CardRun uncommonB = new CardRun(true, "231", "103", "188", "31", "64", "222", "249", "161", "28", "185", "150", "213", "97", "8", "181", "49", "220", "246", "188", "228", "56", "123", "218", "93", "185", "30", "74", "231", "244", "181", "31", "64", "128", "222", "107", "161", "137", "51", "213", "249", "103", "28", "49", "150", "220", "97", "188", "123", "56", "228", "137", "246", "30", "64", "244", "218", "107", "161", "128", "74", "213", "249", "93", "31", "51", "150", "222", "103", "185", "123", "56", "231", "97", "8", "220", "74", "137", "228", "107", "181", "150", "49", "218", "244", "93", "28", "51", "128", "213", "188", "8", "137", "64", "222", "246", "97", "30", "56", "31", "231", "103", "28", "123", "74", "220", "249", "161", "107", "246", "51", "228", "185", "8", "128", "49", "218", "244", "93", "30", "181"); + private final CardRun uncommonLegend = new CardRun(true, "203", "109", "204", "66", "191", "4", "205", "111", "206", "12", "196", "165", "202", "69", "203", "113", "190", "109", "208", "25", "194", "148", "196", "12", "204", "66", "208", "111", "191", "165", "202", "4", "205", "113", "194", "148", "206", "25", "190", "69"); + // Shalai (35), Josu Vess (95), Darigaaz (193) and Muldrotha (199) are on the non-legend sheet; boosters containing one of them will also contain an uncommon legend + private final CardRun rare = new CardRun(false, "6", "13", "18", "35", "39", "42", "55", "57", "61", "68", "88", "95", "98", "102", "114", "122", "129", "131", "133", "143", "147", "166", "173", "182", "183", "184", "187", "200", "201", "211", "214", "215", "217", "223", "233", "238", "239", "240", "241", "247", "248", "6", "13", "18", "35", "39", "42", "55", "57", "61", "68", "88", "95", "98", "102", "114", "122", "129", "131", "133", "143", "147", "166", "173", "182", "183", "184", "187", "200", "201", "211", "214", "215", "217", "223", "233", "238", "239", "240", "241", "247", "248", "1", "21", "100", "132", "193", "199", "207", "224", "237"); + private final CardRun rareLegend = new CardRun(false, "16", "36", "58", "76", "96", "108", "146", "172", "192", "195", "198", "234", "16", "36", "58", "76", "96", "108", "146", "172", "192", "195", "198", "234", "26", "59", "86", "149", "174", "197"); + private final CardRun land = new CardRun(false, "250", "251", "252", "253", "254", "255", "256", "257", "258", "259", "260", "261", "262", "263", "264", "265", "266", "267", "268", "269"); + + private final BoosterStructure AABBC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAAABBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBBC2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2 + ); + private final BoosterStructure ABL = new BoosterStructure(uncommonA, uncommonB, uncommonLegend, rare); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB, rareLegend); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB, rareLegend); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 3.27 A commons (36 / 11) + // 2.18 B commons (24 / 11) + // 2.73 C1 commons (30 / 11, or 60 / 11 in each C1 booster) + // 1.82 C2 commons (20 / 11, or 40 / 11 in each C2 booster) + // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs + // and with 10 common slots per booster + private final RarityConfiguration commonRuns = new RarityConfiguration( + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBBC2C2 + ); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 1.125 A uncommons (9 / 8) + // 1.125 B uncommons (9 / 8) + // 0.75 uncommon legend (6 / 8) + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + ABL, ABL, ABL, ABL, ABL, ABL, + AAB, ABB + ); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/DoubleMasters.java b/Mage.Sets/src/mage/sets/DoubleMasters.java index 21ab75bf72d..9b368b93aa6 100644 --- a/Mage.Sets/src/mage/sets/DoubleMasters.java +++ b/Mage.Sets/src/mage/sets/DoubleMasters.java @@ -23,7 +23,7 @@ public final class DoubleMasters extends ExpansionSet { } private DoubleMasters() { - super("Double Masters", "2XM", ExpansionSet.buildDate(2020, 8, 7), SetType.SUPPLEMENTAL, new DoubleMastersCollator()); + super("Double Masters", "2XM", ExpansionSet.buildDate(2020, 8, 7), SetType.SUPPLEMENTAL); this.blockName = "Reprint"; this.hasBasicLands = true; this.hasBoosters = true; @@ -418,6 +418,11 @@ public final class DoubleMasters extends ExpansionSet { cards.add(new SetCardInfo("Wurmcoil Engine", 368, Rarity.MYTHIC, mage.cards.w.WurmcoilEngine.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Yavimaya's Embrace", 229, Rarity.UNCOMMON, mage.cards.y.YavimayasEmbrace.class)); } + + @Override + public BoosterCollator createCollator() { + return new DoubleMastersCollator(); + } } // Booster collation info from https://www.lethe.xyz/mtg/collation/2xm.html @@ -428,352 +433,223 @@ public final class DoubleMasters extends ExpansionSet { // TODO: write a test, not sure how right now class DoubleMastersCollator implements BoosterCollator { - private static class DoubleMastersRun extends CardRun { - private static final DoubleMastersRun commonA = new DoubleMastersRun(true, "160", "108", "146", "79", "247", "165", "114", "111", "163", "29", "143", "105", "162", "135", "154", "78", "144", "151", "140", "84", "187", "304", "87", "133", "173", "95", "126", "28", "176", "90", "137", "165", "83", "159", "116", "168", "92", "121", "154", "79", "150", "181", "247", "146", "111", "160", "143", "96", "114", "108", "151", "78", "135", "95", "162", "144", "87", "140", "105", "163", "304", "84", "126", "173", "111", "133", "29", "187", "83", "137", "176", "90", "159", "150", "96", "247", "146", "165", "92", "116", "28", "160", "108", "121", "79", "168", "144", "162", "87", "163", "114", "84", "154", "304", "105", "135", "173", "95", "126", "29", "151", "83", "140", "181", "133", "78", "143", "187", "28", "150", "96", "159", "176", "90", "137", "168", "116", "92", "181", "121"); - private static final DoubleMastersRun commonB = new DoubleMastersRun(true, "250", "70", "259", "305", "45", "80", "261", "60", "288", "331", "294", "63", "255", "263", "46", "230", "262", "50", "257", "256", "44", "283", "237", "74", "157", "277", "59", "330", "280", "52", "254", "329", "250", "69", "239", "331", "45", "287", "288", "42", "115", "273", "40", "305", "269", "63", "294", "257", "50", "80", "230", "60", "256", "259", "46", "261", "283", "44", "237", "330", "70", "263", "255", "52", "262", "254", "40", "157", "287", "115", "69", "277", "273", "59", "329", "280", "74", "250", "257", "60", "331", "261", "45", "239", "269", "42", "288", "305", "63", "80", "283", "50", "294", "259", "70", "263", "237", "44", "255", "262", "46", "230", "157", "59", "256", "330", "52", "254", "277", "74", "280", "329", "69", "287", "115", "42", "239", "269", "40", "273"); - private static final DoubleMastersRun commonC = new DoubleMastersRun(true, "18", "35", "4", "17", "2", "30", "12", "27", "3", "13", "33", "30", "18", "12", "35", "27", "4", "3", "17", "13", "2", "33", "12", "4", "13", "18", "27", "17", "33", "30", "35", "3", "2", "18", "35", "4", "17", "2", "30", "12", "27", "3", "13", "33", "30", "18", "12", "35", "27", "4", "3", "17", "13", "2", "33", "12", "4", "13", "18", "27", "17", "33", "30", "35", "3", "2", "18", "35", "4", "17", "2", "30", "12", "27", "3", "13", "33", "30", "18", "12", "35", "27", "4", "3", "17", "13", "2", "33", "12", "4", "13", "18", "27", "17", "33", "30", "35", "3", "2", "18", "35", "4", "17", "2", "30", "12", "27", "3", "13", "33", "30", "18", "12", "35", "27", "4", "3", "17", "13", "2", "33"); - private static final DoubleMastersRun uncommonA = new DoubleMastersRun(true, "315", "244", "73", "274", "208", "147", "202", "169", "290", "102", "65", "285", "220", "67", "186", "246", "112", "222", "22", "301", "86", "62", "228", "161", "101", "302", "54", "184", "220", "307", "73", "93", "15", "119", "202", "291", "169", "323", "102", "244", "201", "172", "312", "290", "37", "208", "246", "6", "65", "307", "86", "186", "67", "222", "141", "15", "315", "22", "274", "161", "37", "101", "285", "228", "184", "112", "147", "302", "172", "54", "220", "141", "301", "6", "93", "119", "169", "323", "291", "312", "201", "62", "244", "184", "102", "15", "222", "37", "290", "112", "147", "65", "285", "101", "315", "67", "202", "186", "274", "208", "323", "73", "307", "228", "86", "161", "119", "246", "312", "6", "22", "172", "301", "62", "302", "141", "93", "291", "54", "201"); - private static final DoubleMastersRun uncommonB = new DoubleMastersRun(true, "217", "23", "49", "245", "91", "194", "148", "71", "16", "125", "238", "198", "180", "36", "278", "99", "224", "38", "232", "123", "68", "258", "229", "310", "120", "242", "188", "25", "66", "267", "138", "178", "281", "199", "89", "194", "241", "23", "49", "91", "245", "166", "134", "238", "217", "148", "36", "265", "16", "125", "198", "232", "71", "100", "267", "229", "180", "68", "278", "123", "25", "99", "241", "38", "120", "258", "199", "188", "224", "281", "310", "49", "23", "66", "138", "178", "245", "217", "166", "134", "242", "89", "36", "265", "148", "100", "242", "198", "180", "25", "238", "16", "194", "38", "91", "71", "125", "278", "229", "310", "68", "232", "123", "178", "99", "258", "188", "120", "267", "199", "89", "224", "241", "66", "134", "166", "281", "138", "100", "265"); - private static final DoubleMastersRun rareA = new DoubleMastersRun(false, "76", "231", "153", "77", "117", "118", "10", "43", "313", "158", "48", "85", "124", "252", "14", "167", "316", "318", "196", "127", "320", "130", "321", "97", "175", "271", "210", "272", "282", "26", "139", "103", "64", "325", "104", "179", "289", "32", "293", "326", "299", "327", "72", "109", "223", "225", "226", "75", "332", "113", "76", "231", "153", "77", "117", "118", "10", "43", "313", "158", "48", "85", "124", "252", "14", "167", "316", "318", "196", "127", "320", "130", "321", "97", "175", "271", "210", "272", "282", "26", "139", "103", "64", "325", "104", "179", "289", "32", "293", "326", "299", "327", "72", "109", "223", "225", "226", "75", "332", "113", "190", "8", "192", "240", "81", "314", "248", "164", "253", "51", "131", "204", "205", "20", "206", "136", "275", "214", "218", "303"); - private static final DoubleMastersRun rareB = new DoubleMastersRun(false, "122", "82", "317", "55", "264", "174", "324", "24", "284", "215", "328"); - private static final DoubleMastersRun rareC = new DoubleMastersRun(false, "189", "7", "311", "155", "236", "193", "11", "47", "249", "195", "128", "170", "260", "132", "203", "21", "268", "98", "57", "322", "209", "177", "279", "61", "212", "219", "292", "34", "183", "110", "149", "227", "306"); - private static final DoubleMastersRun rareD = new DoubleMastersRun(false, "309", "191", "233", "9", "156", "243", "88", "251", "319", "53", "129", "200", "171", "19", "266", "207", "58", "211", "276", "213", "142", "286", "106", "31", "221", "182", "39"); - private static final DoubleMastersRun rareE = new DoubleMastersRun(false, "5", "41", "152", "234", "235", "197", "94", "56", "1", "270", "107", "145", "295", "296", "297", "298", "300", "216", "185", "308"); - private static final DoubleMastersRun foilUncommonA = new DoubleMastersRun(false, "6", "119", "312", "244", "161", "246", "315", "86", "93", "15", "169", "201", "54", "172", "202", "208", "22", "274", "323", "101", "102", "62", "141", "65", "285", "67", "220", "290", "291", "147", "222", "301", "302", "73", "184", "37", "112", "186", "228", "307"); - private static final DoubleMastersRun foilUncommonB = new DoubleMastersRun(false, "310", "232", "120", "238", "241", "242", "245", "194", "123", "89", "91", "166", "49", "16", "125", "198", "199", "258", "265", "134", "267", "99", "23", "278", "100", "25", "281", "138", "178", "66", "217", "68", "180", "71", "36", "148", "224", "38", "188", "229"); - private static final DoubleMastersRun foilRareA = new DoubleMastersRun(false, "309", "231", "76", "189", "7", "153", "191", "233", "77", "9", "117", "311", "118", "155", "10", "236", "43", "193", "313", "156", "158", "243", "11", "122", "47", "82", "48", "85", "88", "124", "249", "251", "252", "14", "167", "195", "316", "317", "318", "196", "319", "127", "128", "53", "320", "170", "129", "260", "200", "171", "130", "321", "55", "132", "264", "203", "19", "266", "21", "174", "268", "207", "97", "98", "175", "57", "58", "271", "322", "209", "210", "211", "272", "276", "324", "177", "279", "24", "61", "282", "212", "26", "139", "284", "103", "64", "213", "142", "325", "104", "215", "286", "179", "219", "106", "289", "31", "32", "292", "293", "326", "221", "299", "34", "182", "327", "72", "109", "183", "223", "110", "149", "328", "225", "226", "227", "306", "75", "332", "113", "39"); - private static final DoubleMastersRun foilRareB = new DoubleMastersRun(false, "5", "41", "190", "8", "152", "234", "235", "192", "240", "81", "314", "248", "164", "253", "51", "197", "94", "131", "56", "204", "1", "205", "20", "206", "270", "136", "275", "214", "218", "107", "145", "295", "296", "297", "298", "300", "216", "303", "185", "308"); + private final CardRun commonA = new CardRun(true, "160", "108", "146", "79", "247", "165", "114", "111", "163", "29", "143", "105", "162", "135", "154", "78", "144", "151", "140", "84", "187", "304", "87", "133", "173", "95", "126", "28", "176", "90", "137", "165", "83", "159", "116", "168", "92", "121", "154", "79", "150", "181", "247", "146", "111", "160", "143", "96", "114", "108", "151", "78", "135", "95", "162", "144", "87", "140", "105", "163", "304", "84", "126", "173", "111", "133", "29", "187", "83", "137", "176", "90", "159", "150", "96", "247", "146", "165", "92", "116", "28", "160", "108", "121", "79", "168", "144", "162", "87", "163", "114", "84", "154", "304", "105", "135", "173", "95", "126", "29", "151", "83", "140", "181", "133", "78", "143", "187", "28", "150", "96", "159", "176", "90", "137", "168", "116", "92", "181", "121"); + private final CardRun commonB = new CardRun(true, "250", "70", "259", "305", "45", "80", "261", "60", "288", "331", "294", "63", "255", "263", "46", "230", "262", "50", "257", "256", "44", "283", "237", "74", "157", "277", "59", "330", "280", "52", "254", "329", "250", "69", "239", "331", "45", "287", "288", "42", "115", "273", "40", "305", "269", "63", "294", "257", "50", "80", "230", "60", "256", "259", "46", "261", "283", "44", "237", "330", "70", "263", "255", "52", "262", "254", "40", "157", "287", "115", "69", "277", "273", "59", "329", "280", "74", "250", "257", "60", "331", "261", "45", "239", "269", "42", "288", "305", "63", "80", "283", "50", "294", "259", "70", "263", "237", "44", "255", "262", "46", "230", "157", "59", "256", "330", "52", "254", "277", "74", "280", "329", "69", "287", "115", "42", "239", "269", "40", "273"); + private final CardRun commonC = new CardRun(true, "18", "35", "4", "17", "2", "30", "12", "27", "3", "13", "33", "30", "18", "12", "35", "27", "4", "3", "17", "13", "2", "33", "12", "4", "13", "18", "27", "17", "33", "30", "35", "3", "2", "18", "35", "4", "17", "2", "30", "12", "27", "3", "13", "33", "30", "18", "12", "35", "27", "4", "3", "17", "13", "2", "33", "12", "4", "13", "18", "27", "17", "33", "30", "35", "3", "2", "18", "35", "4", "17", "2", "30", "12", "27", "3", "13", "33", "30", "18", "12", "35", "27", "4", "3", "17", "13", "2", "33", "12", "4", "13", "18", "27", "17", "33", "30", "35", "3", "2", "18", "35", "4", "17", "2", "30", "12", "27", "3", "13", "33", "30", "18", "12", "35", "27", "4", "3", "17", "13", "2", "33"); + private final CardRun uncommonA = new CardRun(true, "315", "244", "73", "274", "208", "147", "202", "169", "290", "102", "65", "285", "220", "67", "186", "246", "112", "222", "22", "301", "86", "62", "228", "161", "101", "302", "54", "184", "220", "307", "73", "93", "15", "119", "202", "291", "169", "323", "102", "244", "201", "172", "312", "290", "37", "208", "246", "6", "65", "307", "86", "186", "67", "222", "141", "15", "315", "22", "274", "161", "37", "101", "285", "228", "184", "112", "147", "302", "172", "54", "220", "141", "301", "6", "93", "119", "169", "323", "291", "312", "201", "62", "244", "184", "102", "15", "222", "37", "290", "112", "147", "65", "285", "101", "315", "67", "202", "186", "274", "208", "323", "73", "307", "228", "86", "161", "119", "246", "312", "6", "22", "172", "301", "62", "302", "141", "93", "291", "54", "201"); + private final CardRun uncommonB = new CardRun(true, "217", "23", "49", "245", "91", "194", "148", "71", "16", "125", "238", "198", "180", "36", "278", "99", "224", "38", "232", "123", "68", "258", "229", "310", "120", "242", "188", "25", "66", "267", "138", "178", "281", "199", "89", "194", "241", "23", "49", "91", "245", "166", "134", "238", "217", "148", "36", "265", "16", "125", "198", "232", "71", "100", "267", "229", "180", "68", "278", "123", "25", "99", "241", "38", "120", "258", "199", "188", "224", "281", "310", "49", "23", "66", "138", "178", "245", "217", "166", "134", "242", "89", "36", "265", "148", "100", "242", "198", "180", "25", "238", "16", "194", "38", "91", "71", "125", "278", "229", "310", "68", "232", "123", "178", "99", "258", "188", "120", "267", "199", "89", "224", "241", "66", "134", "166", "281", "138", "100", "265"); + private final CardRun rareA = new CardRun(false, "76", "231", "153", "77", "117", "118", "10", "43", "313", "158", "48", "85", "124", "252", "14", "167", "316", "318", "196", "127", "320", "130", "321", "97", "175", "271", "210", "272", "282", "26", "139", "103", "64", "325", "104", "179", "289", "32", "293", "326", "299", "327", "72", "109", "223", "225", "226", "75", "332", "113", "76", "231", "153", "77", "117", "118", "10", "43", "313", "158", "48", "85", "124", "252", "14", "167", "316", "318", "196", "127", "320", "130", "321", "97", "175", "271", "210", "272", "282", "26", "139", "103", "64", "325", "104", "179", "289", "32", "293", "326", "299", "327", "72", "109", "223", "225", "226", "75", "332", "113", "190", "8", "192", "240", "81", "314", "248", "164", "253", "51", "131", "204", "205", "20", "206", "136", "275", "214", "218", "303"); + private final CardRun rareB = new CardRun(false, "122", "82", "317", "55", "264", "174", "324", "24", "284", "215", "328"); + private final CardRun rareC = new CardRun(false, "189", "7", "311", "155", "236", "193", "11", "47", "249", "195", "128", "170", "260", "132", "203", "21", "268", "98", "57", "322", "209", "177", "279", "61", "212", "219", "292", "34", "183", "110", "149", "227", "306"); + private final CardRun rareD = new CardRun(false, "309", "191", "233", "9", "156", "243", "88", "251", "319", "53", "129", "200", "171", "19", "266", "207", "58", "211", "276", "213", "142", "286", "106", "31", "221", "182", "39"); + private final CardRun rareE = new CardRun(false, "5", "41", "152", "234", "235", "197", "94", "56", "1", "270", "107", "145", "295", "296", "297", "298", "300", "216", "185", "308"); + private final CardRun foilUncommonA = new CardRun(false, "6", "119", "312", "244", "161", "246", "315", "86", "93", "15", "169", "201", "54", "172", "202", "208", "22", "274", "323", "101", "102", "62", "141", "65", "285", "67", "220", "290", "291", "147", "222", "301", "302", "73", "184", "37", "112", "186", "228", "307"); + private final CardRun foilUncommonB = new CardRun(false, "310", "232", "120", "238", "241", "242", "245", "194", "123", "89", "91", "166", "49", "16", "125", "198", "199", "258", "265", "134", "267", "99", "23", "278", "100", "25", "281", "138", "178", "66", "217", "68", "180", "71", "36", "148", "224", "38", "188", "229"); + private final CardRun foilRareA = new CardRun(false, "309", "231", "76", "189", "7", "153", "191", "233", "77", "9", "117", "311", "118", "155", "10", "236", "43", "193", "313", "156", "158", "243", "11", "122", "47", "82", "48", "85", "88", "124", "249", "251", "252", "14", "167", "195", "316", "317", "318", "196", "319", "127", "128", "53", "320", "170", "129", "260", "200", "171", "130", "321", "55", "132", "264", "203", "19", "266", "21", "174", "268", "207", "97", "98", "175", "57", "58", "271", "322", "209", "210", "211", "272", "276", "324", "177", "279", "24", "61", "282", "212", "26", "139", "284", "103", "64", "213", "142", "325", "104", "215", "286", "179", "219", "106", "289", "31", "32", "292", "293", "326", "221", "299", "34", "182", "327", "72", "109", "183", "223", "110", "149", "328", "225", "226", "227", "306", "75", "332", "113", "39"); + private final CardRun foilRareB = new CardRun(false, "5", "41", "190", "8", "152", "234", "235", "192", "240", "81", "314", "248", "164", "253", "51", "197", "94", "131", "56", "204", "1", "205", "20", "206", "270", "136", "275", "214", "218", "107", "145", "295", "296", "297", "298", "300", "216", "303", "185", "308"); - private DoubleMastersRun(boolean keepOrder, String... numbers) { - super(keepOrder, numbers); - } - } + private final BoosterStructure C1 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonC + ); + private final BoosterStructure C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC + ); + private final BoosterStructure C3 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB + ); + private final BoosterStructure U1 = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure U2 = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA, rareC); + private final BoosterStructure R2 = new BoosterStructure(rareA, rareD); + private final BoosterStructure R3 = new BoosterStructure(rareA, rareE); + private final BoosterStructure R4 = new BoosterStructure(rareB, rareC); + private final BoosterStructure R5 = new BoosterStructure(rareB, rareD); + private final BoosterStructure R6 = new BoosterStructure(rareB, rareE); - private static class DoubleMastersStructure extends BoosterStructure { - private static final DoubleMastersStructure C1 = new DoubleMastersStructure( - DoubleMastersRun.commonA, - DoubleMastersRun.commonA, - DoubleMastersRun.commonA, - DoubleMastersRun.commonA, - DoubleMastersRun.commonB, - DoubleMastersRun.commonB, - DoubleMastersRun.commonB, - DoubleMastersRun.commonC - ); - private static final DoubleMastersStructure C2 = new DoubleMastersStructure( - DoubleMastersRun.commonA, - DoubleMastersRun.commonA, - DoubleMastersRun.commonA, - DoubleMastersRun.commonB, - DoubleMastersRun.commonB, - DoubleMastersRun.commonB, - DoubleMastersRun.commonB, - DoubleMastersRun.commonC - ); - private static final DoubleMastersStructure C3 = new DoubleMastersStructure( - DoubleMastersRun.commonA, - DoubleMastersRun.commonA, - DoubleMastersRun.commonA, - DoubleMastersRun.commonA, - DoubleMastersRun.commonB, - DoubleMastersRun.commonB, - DoubleMastersRun.commonB, - DoubleMastersRun.commonB - ); - private static final DoubleMastersStructure U1 = new DoubleMastersStructure( - DoubleMastersRun.uncommonA, - DoubleMastersRun.uncommonB, - DoubleMastersRun.uncommonB - ); - private static final DoubleMastersStructure U2 = new DoubleMastersStructure( - DoubleMastersRun.uncommonA, - DoubleMastersRun.uncommonA, - DoubleMastersRun.uncommonB - ); - private static final DoubleMastersStructure R1 = new DoubleMastersStructure( - DoubleMastersRun.rareA, - DoubleMastersRun.rareC - ); - private static final DoubleMastersStructure R2 = new DoubleMastersStructure( - DoubleMastersRun.rareA, - DoubleMastersRun.rareD - ); - private static final DoubleMastersStructure R3 = new DoubleMastersStructure( - DoubleMastersRun.rareA, - DoubleMastersRun.rareE - ); - private static final DoubleMastersStructure R4 = new DoubleMastersStructure( - DoubleMastersRun.rareB, - DoubleMastersRun.rareC - ); - private static final DoubleMastersStructure R5 = new DoubleMastersStructure( - DoubleMastersRun.rareB, - DoubleMastersRun.rareD - ); - private static final DoubleMastersStructure R6 = new DoubleMastersStructure( - DoubleMastersRun.rareB, - DoubleMastersRun.rareE - ); - private static final DoubleMastersStructure F01 = new DoubleMastersStructure( - DoubleMastersRun.commonA, - DoubleMastersRun.commonB - ); - private static final DoubleMastersStructure F02 = new DoubleMastersStructure( - DoubleMastersRun.commonA, - DoubleMastersRun.commonC - ); - private static final DoubleMastersStructure F03 = new DoubleMastersStructure( - DoubleMastersRun.commonA, - DoubleMastersRun.foilUncommonA - ); - private static final DoubleMastersStructure F04 = new DoubleMastersStructure( - DoubleMastersRun.commonA, - DoubleMastersRun.foilUncommonB - ); - private static final DoubleMastersStructure F05 = new DoubleMastersStructure( - DoubleMastersRun.commonA, - DoubleMastersRun.foilRareA - ); - private static final DoubleMastersStructure F06 = new DoubleMastersStructure( - DoubleMastersRun.commonA, - DoubleMastersRun.foilRareB - ); - private static final DoubleMastersStructure F07 = new DoubleMastersStructure( - DoubleMastersRun.commonB, - DoubleMastersRun.commonC - ); - private static final DoubleMastersStructure F08 = new DoubleMastersStructure( - DoubleMastersRun.commonB, - DoubleMastersRun.foilUncommonA - ); - private static final DoubleMastersStructure F09 = new DoubleMastersStructure( - DoubleMastersRun.commonB, - DoubleMastersRun.foilUncommonB - ); - private static final DoubleMastersStructure F10 = new DoubleMastersStructure( - DoubleMastersRun.commonB, - DoubleMastersRun.foilRareA - ); - private static final DoubleMastersStructure F11 = new DoubleMastersStructure( - DoubleMastersRun.commonB, - DoubleMastersRun.foilRareB - ); - private static final DoubleMastersStructure F12 = new DoubleMastersStructure( - DoubleMastersRun.commonC, - DoubleMastersRun.foilUncommonA - ); - private static final DoubleMastersStructure F13 = new DoubleMastersStructure( - DoubleMastersRun.commonC, - DoubleMastersRun.foilUncommonB - ); - private static final DoubleMastersStructure F14 = new DoubleMastersStructure( - DoubleMastersRun.commonC, - DoubleMastersRun.foilRareA - ); - private static final DoubleMastersStructure F15 = new DoubleMastersStructure( - DoubleMastersRun.commonC, - DoubleMastersRun.foilRareB - ); - private static final DoubleMastersStructure F16 = new DoubleMastersStructure( - DoubleMastersRun.foilUncommonA, - DoubleMastersRun.foilUncommonB - ); - private static final DoubleMastersStructure F17 = new DoubleMastersStructure( - DoubleMastersRun.foilUncommonA, - DoubleMastersRun.foilRareA - ); - private static final DoubleMastersStructure F18 = new DoubleMastersStructure( - DoubleMastersRun.foilUncommonA, - DoubleMastersRun.foilRareB - ); - private static final DoubleMastersStructure F19 = new DoubleMastersStructure( - DoubleMastersRun.foilUncommonB, - DoubleMastersRun.foilRareA - ); - private static final DoubleMastersStructure F20 = new DoubleMastersStructure( - DoubleMastersRun.foilUncommonB, - DoubleMastersRun.foilRareB - ); - private static final DoubleMastersStructure F21 = new DoubleMastersStructure( - DoubleMastersRun.foilRareA, - DoubleMastersRun.foilRareB - ); - - - private DoubleMastersStructure(CardRun... runs) { - super(runs); - } - } + private final BoosterStructure F01 = new BoosterStructure(commonA, commonB); + private final BoosterStructure F02 = new BoosterStructure(commonA, commonC); + private final BoosterStructure F03 = new BoosterStructure(commonA, foilUncommonA); + private final BoosterStructure F04 = new BoosterStructure(commonA, foilUncommonB); + private final BoosterStructure F05 = new BoosterStructure(commonA, foilRareA); + private final BoosterStructure F06 = new BoosterStructure(commonA, foilRareB); + private final BoosterStructure F07 = new BoosterStructure(commonB, commonC); + private final BoosterStructure F08 = new BoosterStructure(commonB, foilUncommonA); + private final BoosterStructure F09 = new BoosterStructure(commonB, foilUncommonB); + private final BoosterStructure F10 = new BoosterStructure(commonB, foilRareA); + private final BoosterStructure F11 = new BoosterStructure(commonB, foilRareB); + private final BoosterStructure F12 = new BoosterStructure(commonC, foilUncommonA); + private final BoosterStructure F13 = new BoosterStructure(commonC, foilUncommonB); + private final BoosterStructure F14 = new BoosterStructure(commonC, foilRareA); + private final BoosterStructure F15 = new BoosterStructure(commonC, foilRareB); + private final BoosterStructure F16 = new BoosterStructure(foilUncommonA, foilUncommonB); + private final BoosterStructure F17 = new BoosterStructure(foilUncommonA, foilRareA); + private final BoosterStructure F18 = new BoosterStructure(foilUncommonA, foilRareB); + private final BoosterStructure F19 = new BoosterStructure(foilUncommonB, foilRareA); + private final BoosterStructure F20 = new BoosterStructure(foilUncommonB, foilRareB); + private final BoosterStructure F21 = new BoosterStructure(foilRareA, foilRareB); private final RarityConfiguration commonRuns = new RarityConfiguration( - false, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C1, DoubleMastersStructure.C2, DoubleMastersStructure.C1, DoubleMastersStructure.C2, - DoubleMastersStructure.C3, DoubleMastersStructure.C3, DoubleMastersStructure.C3 + C1, C2, C1, C2, + C1, C2, C1, C2, + C1, C2, C1, C2, + C1, C2, C1, C2, + C1, C2, C1, C2, + C1, C2, C1, C2, + C1, C2, C1, C2, + C1, C2, C1, C2, + C1, C2, C1, C2, + C1, C2, C1, C2, + C1, C2, C1, C2, + C3, C3, C3 ); private final RarityConfiguration uncommonRuns = new RarityConfiguration( - DoubleMastersStructure.U1, DoubleMastersStructure.U2 + U1, U2 ); private final RarityConfiguration rareRuns = new RarityConfiguration( - false, - DoubleMastersStructure.R1, DoubleMastersStructure.R2, DoubleMastersStructure.R3, - DoubleMastersStructure.R4, DoubleMastersStructure.R5, DoubleMastersStructure.R6 + R1, R2, R3, + R4, R5, R6 ); private final RarityConfiguration foilRuns = new RarityConfiguration( - false, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, - DoubleMastersStructure.F01, DoubleMastersStructure.F01, DoubleMastersStructure.F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, + F01, F01, F01, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, - DoubleMastersStructure.F02, DoubleMastersStructure.F02, DoubleMastersStructure.F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, + F02, F02, F02, - DoubleMastersStructure.F03, DoubleMastersStructure.F03, DoubleMastersStructure.F03, - DoubleMastersStructure.F03, DoubleMastersStructure.F03, DoubleMastersStructure.F03, - DoubleMastersStructure.F03, DoubleMastersStructure.F03, DoubleMastersStructure.F03, - DoubleMastersStructure.F03, DoubleMastersStructure.F03, DoubleMastersStructure.F03, - DoubleMastersStructure.F03, DoubleMastersStructure.F03, DoubleMastersStructure.F03, - DoubleMastersStructure.F03, + F03, F03, F03, + F03, F03, F03, + F03, F03, F03, + F03, F03, F03, + F03, F03, F03, + F03, - DoubleMastersStructure.F04, DoubleMastersStructure.F04, DoubleMastersStructure.F04, - DoubleMastersStructure.F04, DoubleMastersStructure.F04, DoubleMastersStructure.F04, - DoubleMastersStructure.F04, DoubleMastersStructure.F04, DoubleMastersStructure.F04, - DoubleMastersStructure.F04, DoubleMastersStructure.F04, DoubleMastersStructure.F04, - DoubleMastersStructure.F04, DoubleMastersStructure.F04, DoubleMastersStructure.F04, - DoubleMastersStructure.F04, + F04, F04, F04, + F04, F04, F04, + F04, F04, F04, + F04, F04, F04, + F04, F04, F04, + F04, - DoubleMastersStructure.F05, DoubleMastersStructure.F05, DoubleMastersStructure.F05, - DoubleMastersStructure.F05, DoubleMastersStructure.F05, DoubleMastersStructure.F05, - DoubleMastersStructure.F05, DoubleMastersStructure.F05, + F05, F05, F05, + F05, F05, F05, + F05, F05, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, - DoubleMastersStructure.F07, DoubleMastersStructure.F07, DoubleMastersStructure.F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, + F07, F07, F07, - DoubleMastersStructure.F08, DoubleMastersStructure.F08, DoubleMastersStructure.F08, - DoubleMastersStructure.F08, DoubleMastersStructure.F08, DoubleMastersStructure.F08, - DoubleMastersStructure.F08, DoubleMastersStructure.F08, DoubleMastersStructure.F08, - DoubleMastersStructure.F08, DoubleMastersStructure.F08, DoubleMastersStructure.F08, - DoubleMastersStructure.F08, DoubleMastersStructure.F08, DoubleMastersStructure.F08, - DoubleMastersStructure.F08, + F08, F08, F08, + F08, F08, F08, + F08, F08, F08, + F08, F08, F08, + F08, F08, F08, + F08, - DoubleMastersStructure.F09, DoubleMastersStructure.F09, DoubleMastersStructure.F09, - DoubleMastersStructure.F09, DoubleMastersStructure.F09, DoubleMastersStructure.F09, - DoubleMastersStructure.F09, DoubleMastersStructure.F09, DoubleMastersStructure.F09, - DoubleMastersStructure.F09, DoubleMastersStructure.F09, DoubleMastersStructure.F09, - DoubleMastersStructure.F09, DoubleMastersStructure.F09, DoubleMastersStructure.F09, - DoubleMastersStructure.F09, + F09, F09, F09, + F09, F09, F09, + F09, F09, F09, + F09, F09, F09, + F09, F09, F09, + F09, - DoubleMastersStructure.F10, DoubleMastersStructure.F10, DoubleMastersStructure.F10, - DoubleMastersStructure.F10, DoubleMastersStructure.F10, DoubleMastersStructure.F10, - DoubleMastersStructure.F10, DoubleMastersStructure.F10, + F10, F10, F10, + F10, F10, F10, + F10, F10, - DoubleMastersStructure.F12, DoubleMastersStructure.F12, DoubleMastersStructure.F12, - DoubleMastersStructure.F12, DoubleMastersStructure.F12, DoubleMastersStructure.F12, - DoubleMastersStructure.F12, DoubleMastersStructure.F12, DoubleMastersStructure.F12, - DoubleMastersStructure.F12, DoubleMastersStructure.F12, DoubleMastersStructure.F12, - DoubleMastersStructure.F12, DoubleMastersStructure.F12, DoubleMastersStructure.F12, - DoubleMastersStructure.F12, + F12, F12, F12, + F12, F12, F12, + F12, F12, F12, + F12, F12, F12, + F12, F12, F12, + F12, - DoubleMastersStructure.F13, DoubleMastersStructure.F13, DoubleMastersStructure.F13, - DoubleMastersStructure.F13, DoubleMastersStructure.F13, DoubleMastersStructure.F13, - DoubleMastersStructure.F13, DoubleMastersStructure.F13, DoubleMastersStructure.F13, - DoubleMastersStructure.F13, DoubleMastersStructure.F13, DoubleMastersStructure.F13, - DoubleMastersStructure.F13, DoubleMastersStructure.F13, DoubleMastersStructure.F13, - DoubleMastersStructure.F13, + F13, F13, F13, + F13, F13, F13, + F13, F13, F13, + F13, F13, F13, + F13, F13, F13, + F13, - DoubleMastersStructure.F14, DoubleMastersStructure.F14, DoubleMastersStructure.F14, - DoubleMastersStructure.F14, DoubleMastersStructure.F14, DoubleMastersStructure.F14, - DoubleMastersStructure.F14, DoubleMastersStructure.F14, + F14, F14, F14, + F14, F14, F14, + F14, F14, - DoubleMastersStructure.F16, DoubleMastersStructure.F16, DoubleMastersStructure.F16, - DoubleMastersStructure.F16, DoubleMastersStructure.F16, DoubleMastersStructure.F16, - DoubleMastersStructure.F16, DoubleMastersStructure.F16, DoubleMastersStructure.F16, - DoubleMastersStructure.F16, DoubleMastersStructure.F16, DoubleMastersStructure.F16, - DoubleMastersStructure.F16, DoubleMastersStructure.F16, DoubleMastersStructure.F16, - DoubleMastersStructure.F16, + F16, F16, F16, + F16, F16, F16, + F16, F16, F16, + F16, F16, F16, + F16, F16, F16, + F16, - DoubleMastersStructure.F17, DoubleMastersStructure.F17, DoubleMastersStructure.F17, - DoubleMastersStructure.F17, DoubleMastersStructure.F17, DoubleMastersStructure.F17, - DoubleMastersStructure.F17, DoubleMastersStructure.F17, + F17, F17, F17, + F17, F17, F17, + F17, F17, - DoubleMastersStructure.F19, DoubleMastersStructure.F19, DoubleMastersStructure.F19, - DoubleMastersStructure.F19, DoubleMastersStructure.F19, DoubleMastersStructure.F19, - DoubleMastersStructure.F19, DoubleMastersStructure.F19, + F19, F19, F19, + F19, F19, F19, + F19, F19, - DoubleMastersStructure.F06, DoubleMastersStructure.F11, DoubleMastersStructure.F15, - DoubleMastersStructure.F18, DoubleMastersStructure.F20, DoubleMastersStructure.F21 + F06, F11, F15, + F18, F20, F21 ); - @Override - public void shuffle() { - commonRuns.shuffle(); - uncommonRuns.shuffle(); - rareRuns.shuffle(); - foilRuns.shuffle(); - } - @Override public List makeBooster() { List booster = new ArrayList<>(); diff --git a/Mage.Sets/src/mage/sets/EternalMasters.java b/Mage.Sets/src/mage/sets/EternalMasters.java index a6c0a96d5c1..1604fd2c7b3 100644 --- a/Mage.Sets/src/mage/sets/EternalMasters.java +++ b/Mage.Sets/src/mage/sets/EternalMasters.java @@ -1,10 +1,16 @@ - package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + /** * @author fireshoes */ @@ -277,4 +283,100 @@ public final class EternalMasters extends ExpansionSet { cards.add(new SetCardInfo("Young Pyromancer", 155, Rarity.UNCOMMON, mage.cards.y.YoungPyromancer.class)); cards.add(new SetCardInfo("Zealous Persecution", 213, Rarity.UNCOMMON, mage.cards.z.ZealousPersecution.class)); } -} \ No newline at end of file + + @Override + public BoosterCollator createCollator() { + return new EternalMastersCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/ema.html +// Using USA collation for all rarities +// Foil rare sheet used for regular rares as regular rare sheet is not known +class EternalMastersCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "25", "85", "153", "4", "84", "117", "36", "104", "126", "1", "116", "129", "24", "107", "138", "27", "94", "136", "29", "111", "149", "8", "100", "139", "3", "80", "125", "14", "86", "152", "31", "103", "126", "4", "84", "153", "36", "85", "120", "25", "104", "117", "1", "111", "149", "27", "107", "129", "24", "94", "136", "8", "116", "139", "3", "80", "138", "29", "100", "152", "31", "103", "125", "14", "86", "120"); + private final CardRun commonB = new CardRun(true, "51", "162", "74", "189", "52", "176", "53", "188", "63", "184", "76", "179", "60", "161", "69", "183", "72", "156", "51", "164", "41", "194", "64", "184", "76", "176", "52", "189", "74", "188", "63", "162", "53", "161", "60", "156", "69", "183", "72", "194", "41", "179", "64", "164", "51", "189", "53", "184", "52", "188", "76", "176", "63", "161", "69", "162", "74", "156", "72", "183", "60", "194", "41", "179", "64", "164"); + private final CardRun commonC1 = new CardRun(true, "237", "165", "243", "144", "88", "236", "71", "6", "140", "115", "238", "109", "146", "20", "239", "47", "89", "128", "244", "23", "247", "237", "137", "75", "178", "110", "21", "144", "45", "243", "115", "163", "6", "140", "71", "88", "238", "128", "165", "239", "75", "109", "236", "137", "178", "20", "45", "89", "146", "21", "163", "47", "244", "110", "23"); + private final CardRun commonC2 = new CardRun(true, "185", "246", "122", "37", "97", "43", "245", "191", "102", "249", "59", "167", "175", "26", "130", "229", "247", "65", "18", "185", "245", "43", "102", "191", "246", "59", "37", "97", "122", "249", "175", "167", "65", "245", "18", "185", "229", "130", "102", "26", "43", "229", "59", "249", "97", "122", "37", "191", "246", "65", "167", "175", "18", "130", "26"); + private final CardRun uncommonA = new CardRun(true, "123", "205", "182", "32", "61", "172", "195", "192", "133", "79", "227", "11", "170", "201", "155", "180", "12", "151", "28", "166", "119", "54", "160", "5", "10", "226", "131", "190", "118", "90", "134", "200", "11", "79", "121", "182", "99", "135", "201", "32", "174", "224", "197", "227", "172", "48", "133", "205", "190", "119", "159", "28", "192", "61", "123", "195", "12", "170", "155", "226", "54", "166", "151", "180", "118", "192", "10", "121", "159", "48", "174", "135", "99", "200", "160", "32", "195", "224", "123", "172", "205", "79", "5", "133", "170", "131", "61", "134", "166", "201", "28", "155", "227", "180", "119", "182", "54", "90", "11", "197", "226", "151", "190", "12", "131", "99", "118", "5", "134", "159", "121", "48", "90", "135", "197", "174", "224", "10", "200", "160"); + private final CardRun uncommonB = new CardRun(true, "68", "101", "212", "15", "233", "66", "81", "30", "58", "141", "92", "213", "55", "105", "34", "83", "77", "113", "221", "217", "235", "142", "67", "242", "157", "13", "208", "44", "91", "230", "168", "73", "209", "40", "19", "58", "231", "78", "35", "218", "212", "81", "141", "95", "233", "68", "92", "30", "142", "101", "66", "15", "213", "34", "55", "105", "217", "83", "67", "235", "44", "113", "242", "221", "77", "95", "168", "78", "13", "68", "208", "230", "157", "91", "73", "141", "105", "19", "40", "209", "66", "231", "81", "30", "218", "101", "58", "83", "212", "77", "142", "92", "213", "15", "233", "113", "55", "34", "67", "221", "35", "217", "44", "91", "235", "168", "242", "13", "157", "208", "95", "218", "73", "19", "231", "78", "209", "40", "230", "35"); + private final CardRun rare = new CardRun(true, "114", "87", "186", "57", "106", "214", "56", "9", "93", "228", "22", "203", "62", "187", "148", "143", "215", "70", "108", "240", "232", "33", "206", "17", "193", "177", "145", "216", "82", "124", "223", "234", "38", "210", "16", "196", "207", "147", "220", "96", "143", "199", "241", "39", "211", "22", "198", "225", "150", "222", "7", "145", "9", "248", "42", "108", "33", "202", "219", "169", "186", "127", "147", "106", "132", "46", "56", "38", "228", "204", "171", "187", "114", "150", "154", "214", "50", "223", "39", "232", "158", "173", "193", "17", "169", "87", "215", "203", "93", "42", "234", "112", "181", "196", "96", "171", "124", "216", "206", "127", "46", "241", "98", "70", "198", "62", "173", "49", "220", "210", "16", "50", "248", "2", "82", "202", "132", "181", "7", "222", "211"); + private final CardRun foilCommon = new CardRun(true, "1", "80", "41", "156", "117", "236", "24", "249", "63", "178", "137", "3", "84", "43", "161", "120", "237", "25", "103", "64", "179", "138", "4", "85", "45", "162", "122", "238", "26", "104", "65", "183", "139", "6", "86", "47", "163", "125", "239", "27", "107", "69", "184", "140", "8", "88", "51", "164", "126", "243", "29", "109", "71", "185", "144", "14", "89", "52", "165", "128", "244", "229", "110", "72", "188", "146", "18", "94", "53", "167", "129", "245", "36", "111", "74", "189", "149", "20", "97", "59", "175", "130", "246", "37", "115", "75", "191", "152", "21", "100", "60", "176", "136", "247", "31", "116", "76", "194", "153", "23", "102"); + private final CardRun foilUncommon = new CardRun(true, "5", "78", "40", "195", "118", "218", "157", "30", "105", "135", "174", "10", "79", "44", "197", "119", "221", "159", "32", "212", "141", "180", "11", "81", "48", "200", "121", "224", "160", "34", "213", "142", "182", "12", "83", "54", "201", "123", "226", "166", "35", "217", "151", "190", "13", "90", "55", "205", "131", "227", "168", "113", "233", "155", "192", "15", "91", "58", "208", "133", "230", "170", "73", "235", "99", "67", "19", "92", "61", "209", "134", "231", "172", "77", "242", "101", "68", "28", "95", "66"); + + private final BoosterStructure AABBC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAAABBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rare); + private final BoosterStructure FC = new BoosterStructure(foilCommon); + private final BoosterStructure FU = new BoosterStructure(foilUncommon); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 3.27 A commons (36 / 11) + // 2.18 B commons (24 / 11) + // 2.73 C1 commons (30 / 11, or 60 / 11 in each C1 booster) + // 1.82 C2 commons (20 / 11, or 40 / 11 in each C2 booster) + // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs + // and with 10 common slots per booster + private final RarityConfiguration commonRuns = new RarityConfiguration( + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2 + ); + private final RarityConfiguration uncommonRuns = new RarityConfiguration(AAB, ABB); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + private final RarityConfiguration foilRuns = new RarityConfiguration( + FC, FC, FC, FC, FC, FC, FC, FC, FC, FC, + FU, FU, FU, + R1 + ); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(foilRuns.getNext().makeRun()); + return booster; + } +} diff --git a/Mage.Sets/src/mage/sets/Innistrad.java b/Mage.Sets/src/mage/sets/Innistrad.java index 1d6be5fd480..6f062c6cc04 100644 --- a/Mage.Sets/src/mage/sets/Innistrad.java +++ b/Mage.Sets/src/mage/sets/Innistrad.java @@ -1,9 +1,16 @@ package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + /** * @author BetaSteward_at_googlemail.com */ @@ -312,4 +319,90 @@ public final class Innistrad extends ExpansionSet { cards.add(new SetCardInfo("Wreath of Geists", 211, Rarity.UNCOMMON, mage.cards.w.WreathOfGeists.class)); } + @Override + public BoosterCollator createCollator() { + return new InnistradCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/isd.html +// Using USA collation for common/uncommon, rare collation inferred from other sets +class InnistradCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "103", "48", "57", "128", "11", "30", "96", "202", "160", "86", "55", "12", "151", "237", "179", "59", "42", "101", "96", "39", "128", "210", "55", "142", "30", "95", "124", "202", "59", "4", "160", "50", "190", "142", "86", "103", "184", "4", "131", "31", "50", "219", "210", "95", "135", "237", "101", "11", "63", "12", "184", "131", "57", "219", "116", "42", "124", "48", "190", "135", "63", "151", "31", "39", "116", "179"); + private final CardRun commonB = new CardRun(true, "127", "37", "198", "143", "54", "69", "224", "108", "93", "34", "154", "169", "54", "144", "111", "24", "65", "92", "154", "234", "175", "169", "34", "69", "111", "143", "65", "14", "154", "175", "24", "108", "198", "224", "65", "34", "144", "69", "170", "92", "108", "37", "143", "14", "175", "56", "198", "127", "234", "93", "144", "37", "54", "170", "111", "56", "14", "93", "127", "169", "224", "92", "24", "56", "234", "170"); + private final CardRun commonC1 = new CardRun(true, "204", "73", "132", "106", "80", "146", "197", "29", "40", "123", "73", "166", "195", "230", "107", "18", "216", "52", "132", "183", "123", "138", "40", "106", "204", "230", "43", "5", "167", "197", "120", "146", "196", "33", "43", "216", "81", "126", "167", "52", "183", "18", "29", "120", "166", "107", "81", "196", "80", "138", "5", "246", "195", "126", "33"); + private final CardRun commonC2 = new CardRun(true, "41", "173", "74", "206", "102", "7", "75", "153", "79", "113", "173", "148", "201", "118", "75", "83", "156", "41", "200", "91", "113", "102", "79", "7", "156", "173", "1", "91", "74", "246", "118", "153", "7", "206", "83", "201", "1", "74", "148", "118", "156", "200", "113", "206", "41", "79", "91", "153", "201", "75", "102", "1", "200", "148", "83"); + private final CardRun uncommonA = new CardRun(true, "125", "76", "133", "20", "22", "221", "98", "171", "66", "139", "100", "49", "32", "235", "133", "191", "125", "58", "229", "35", "137", "72", "98", "203", "221", "20", "76", "217", "150", "122", "72", "187", "88", "15", "240", "229", "161", "53", "235", "97", "100", "15", "232", "171", "139", "53", "217", "97", "240", "35", "150", "191", "88", "58", "227", "22", "66", "187", "137", "122", "161", "32", "232", "227", "49", "203"); + private final CardRun uncommonB = new CardRun(true, "62", "223", "157", "85", "28", "218", "16", "192", "60", "225", "157", "180", "28", "45", "110", "222", "172", "163", "27", "60", "162", "188", "223", "110", "70", "19", "163", "218", "192", "85", "172", "109", "45", "19", "158", "225", "233", "119", "188", "26", "62", "27", "158", "222", "211", "119", "70", "233", "162", "26", "16", "109", "211", "180"); + private final CardRun rare = new CardRun(false, "2", "6", "9", "10", "13", "17", "21", "25", "36", "44", "46", "61", "67", "71", "78", "82", "84", "89", "94", "99", "104", "115", "117", "121", "130", "134", "136", "140", "141", "147", "164", "174", "177", "186", "189", "194", "199", "205", "212", "220", "228", "231", "236", "238", "239", "241", "242", "243", "244", "245", "247", "248", "249", "2", "6", "9", "10", "13", "17", "21", "25", "36", "44", "46", "61", "67", "71", "78", "82", "84", "89", "94", "99", "104", "115", "117", "121", "130", "134", "136", "140", "141", "147", "164", "174", "177", "186", "189", "194", "199", "205", "212", "220", "228", "231", "236", "238", "239", "241", "242", "243", "244", "245", "247", "248", "249", "3", "23", "68", "77", "87", "105", "112", "129", "155", "178", "207", "213", "214", "215", "226"); + private final CardRun doubleFaced = new CardRun(false, "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "38", "51", "165", "168", "185", "209", "8", "47", "114", "145", "159", "182", "208", "8", "47", "114", "145", "159", "182", "208", "8", "47", "114", "145", "159", "182", "208", "8", "47", "114", "145", "159", "182", "208", "8", "47", "114", "145", "159", "182", "208", "8", "47", "114", "145", "159", "182", "208", "64", "90", "149", "152", "176", "193", "64", "90", "149", "152", "176", "193", "181"); + private final CardRun land = new CardRun(false, "250", "251", "252", "253", "254", "255", "256", "257", "258", "259", "260", "261", "262", "263", "264"); + + private final BoosterStructure AABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2 + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rare); + private final BoosterStructure D1 = new BoosterStructure(doubleFaced); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 2.95 A commons (162 / 55) + // 1.96 B commons (108 / 55) + // 2.46 C1 commons (135 / 55) + // 1.64 C2 commons (90 / 55) + private final RarityConfiguration commonRuns = new RarityConfiguration( + AABBC1C1C1C1C1, AABBC1C1C1C1C1, AABBC1C1C1C1C1, AABBC1C1C1C1C1, AABBC1C1C1C1C1, + AABBC1C1C1C1C1, AABBC1C1C1C1C1, AABBC1C1C1C1C1, AABBC1C1C1C1C1, AABBC1C1C1C1C1, + AABBC1C1C1C1C1, AABBC1C1C1C1C1, AABBC1C1C1C1C1, AABBC1C1C1C1C1, + AAABC1C1C1C1C1, AAABC1C1C1C1C1, AAABC1C1C1C1C1, AAABC1C1C1C1C1, AAABC1C1C1C1C1, + AAABC1C1C1C1C1, AAABC1C1C1C1C1, AAABC1C1C1C1C1, AAABC1C1C1C1C1, AAABC1C1C1C1C1, + AAABC1C1C1C1C1, AAABC1C1C1C1C1, AAABC1C1C1C1C1, + AAABBC2C2C2C2, AAABBC2C2C2C2, AAABBC2C2C2C2, AAABBC2C2C2C2, AAABBC2C2C2C2, + AAABBC2C2C2C2, AAABBC2C2C2C2, AAABBC2C2C2C2, AAABBC2C2C2C2, AAABBC2C2C2C2, + AAABBC2C2C2C2, AAABBC2C2C2C2, AAABBC2C2C2C2, AAABBC2C2C2C2, AAABBC2C2C2C2, + AAABBC2C2C2C2, AAABBC2C2C2C2, + AAAABBBC2C2, AAAABBBC2C2, AAAABBBC2C2, AAAABBBC2C2, AAAABBBC2C2, AAAABBBC2C2, + AAAABBBC2C2, AAAABBBC2C2, AAAABBBC2C2, AAAABBBC2C2, AAAABBBC2C2 + ); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 1.65 A uncommons (33 / 20) + // 1.35 B uncommons (27 / 20) + // These numbers are the same for all sets with 60 uncommons in asymmetrical A/B print runs + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + private final RarityConfiguration dfcRuns = new RarityConfiguration(D1); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(dfcRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java b/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java index 87425f69c4d..a4201ba0e33 100644 --- a/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java +++ b/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java @@ -1,9 +1,14 @@ package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -12,7 +17,7 @@ import java.util.List; */ public final class InnistradMidnightHunt extends ExpansionSet { - private static final List unfinished = Arrays.asList("Arlinn, the Pack's Hope", "Arlinn, the Moon's Fury", "Baithook Angler", "Hook-Haunt Drifter", "Baneblade Scoundrel", "Baneclaw Marauder", "Beloved Beggar", "Generous Soul", "Bird Admirer", "Wing Shredder", "Brimstone Vandal", "Brutal Cathar", "Moonrage Brute", "Burly Breaker", "Dire-Strain Demolisher", "Celestus Sanctifier", "Chaplain of Alms", "Chapel Shieldgeist", "Component Collector", "Covert Cutpurse", "Covetous Geist", "Covetous Castaway", "Ghostly Castigator", "Curse of Leeches", "Leeching Lurker", "Dennick, Pious Apprentice", "Dennick, Pious Apparition", "Devoted Grafkeeper", "Departed Soulkeeper", "Fangblade Brigand", "Fangblade Eviscerator", "Firmament Sage", "Galedrifter", "Waildrifter", "Gavony Dawnguard", "Graveyard Trespasser", "Graveyard Glutton", "Harvesttide Infiltrator", "Harvesttide Assailant", "Hound Tamer", "Untamed Pup", "Kessig Naturalist", "Lord of the Ulvenwald", "Lunarch Veteran", "Luminous Phantom", "Malevolent Hermit", "Benevolent Geist", "Mourning Patrol", "Morning Apparition", "Obsessive Astronomer", "Overwhelmed Archivist", "Archive Haunt", "Reckless Stormseeker", "Storm-Charged Slasher", "Shady Traveler", "Stalking Predator", "Shipwreck Sifters", "Spellrune Painter", "Spellrune Howler", "Sunrise Cavalier", "Sunstreak Phoenix", "Suspicious Stowaway", "Seafaring Werewolf", "Tavern Ruffian", "Tavern Smasher", "The Celestus", "Thraben Exorcism", "Tovolar, Dire Overlord", "Tovolar, the Midnight Scourge", "Tovolar's Huntmaster", "Tovolar's Packleader", "Unblinking Observer", "Vadrik, Astral Archmage", "Village Watch", "Village Reavers"); + private static final List unfinished = Arrays.asList("Arlinn, the Pack's Hope", "Arlinn, the Moon's Fury", "Baithook Angler", "Hook-Haunt Drifter", "Baneblade Scoundrel", "Baneclaw Marauder", "Beloved Beggar", "Generous Soul", "Bird Admirer", "Wing Shredder", "Brimstone Vandal", "Brutal Cathar", "Moonrage Brute", "Burly Breaker", "Dire-Strain Demolisher", "Celestus Sanctifier", "Chaplain of Alms", "Chapel Shieldgeist", "Component Collector", "Covert Cutpurse", "Covetous Geist", "Covetous Castaway", "Ghostly Castigator", "Curse of Leeches", "Leeching Lurker", "Dennick, Pious Apprentice", "Dennick, Pious Apparition", "Devoted Grafkeeper", "Departed Soulkeeper", "Fangblade Brigand", "Fangblade Eviscerator", "Firmament Sage", "Galedrifter", "Waildrifter", "Gavony Dawnguard", "Graveyard Trespasser", "Graveyard Glutton", "Harvesttide Infiltrator", "Harvesttide Assailant", "Hound Tamer", "Untamed Pup", "Kessig Naturalist", "Lord of the Ulvenwald", "Lunarch Veteran", "Luminous Phantom", "Malevolent Hermit", "Benevolent Geist", "Mourning Patrol", "Morning Apparition", "Obsessive Astronomer", "Outland Liberator", "Frenzied Trapbreaker", "Overwhelmed Archivist", "Archive Haunt", "Phantom Carriage", "Reckless Stormseeker", "Storm-Charged Slasher", "Shady Traveler", "Stalking Predator", "Shipwreck Sifters", "Spellrune Painter", "Spellrune Howler", "Sunrise Cavalier", "Sunstreak Phoenix", "Suspicious Stowaway", "Seafaring Werewolf", "Tavern Ruffian", "Tavern Smasher", "The Celestus", "Thraben Exorcism", "Tireless Hauler", "Dire-Strain Brawler", "Tovolar, Dire Overlord", "Tovolar, the Midnight Scourge", "Tovolar's Huntmaster", "Tovolar's Packleader", "Unblinking Observer", "Vadrik, Astral Archmage", "Village Watch", "Village Reavers"); private static final InnistradMidnightHunt instance = new InnistradMidnightHunt(); public static InnistradMidnightHunt getInstance() { @@ -32,14 +37,22 @@ public final class InnistradMidnightHunt extends ExpansionSet { this.numBoosterDoubleFaced = 1; cards.add(new SetCardInfo("Abandon the Post", 127, Rarity.COMMON, mage.cards.a.AbandonThePost.class)); + cards.add(new SetCardInfo("Adeline, Resplendent Cathar", 1, Rarity.RARE, mage.cards.a.AdelineResplendentCathar.class)); + cards.add(new SetCardInfo("Ambitious Farmhand", 2, Rarity.UNCOMMON, mage.cards.a.AmbitiousFarmhand.class)); cards.add(new SetCardInfo("Angelfire Ignition", 209, Rarity.RARE, mage.cards.a.AngelfireIgnition.class)); cards.add(new SetCardInfo("Arcane Infusion", 210, Rarity.UNCOMMON, mage.cards.a.ArcaneInfusion.class)); cards.add(new SetCardInfo("Archive Haunt", 68, Rarity.UNCOMMON, mage.cards.a.ArchiveHaunt.class)); cards.add(new SetCardInfo("Ardent Elementalist", 128, Rarity.COMMON, mage.cards.a.ArdentElementalist.class)); + cards.add(new SetCardInfo("Arlinn, the Moon's Fury", 211, Rarity.MYTHIC, mage.cards.a.ArlinnTheMoonsFury.class)); + cards.add(new SetCardInfo("Arlinn, the Pack's Hope", 211, Rarity.MYTHIC, mage.cards.a.ArlinnThePacksHope.class)); cards.add(new SetCardInfo("Arrogant Outlaw", 84, Rarity.COMMON, mage.cards.a.ArrogantOutlaw.class)); + cards.add(new SetCardInfo("Ashmouth Dragon", 159, Rarity.RARE, mage.cards.a.AshmouthDragon.class)); cards.add(new SetCardInfo("Augur of Autumn", 168, Rarity.RARE, mage.cards.a.AugurOfAutumn.class)); + cards.add(new SetCardInfo("Awoken Demon", 100, Rarity.COMMON, mage.cards.a.AwokenDemon.class)); cards.add(new SetCardInfo("Baithook Angler", 42, Rarity.COMMON, mage.cards.b.BaithookAngler.class)); cards.add(new SetCardInfo("Bat Whisperer", 86, Rarity.COMMON, mage.cards.b.BatWhisperer.class)); + cards.add(new SetCardInfo("Benevolent Geist", 61, Rarity.RARE, mage.cards.b.BenevolentGeist.class)); + cards.add(new SetCardInfo("Bereaved Survivor", 4, Rarity.UNCOMMON, mage.cards.b.BereavedSurvivor.class)); cards.add(new SetCardInfo("Bird Admirer", 169, Rarity.COMMON, mage.cards.b.BirdAdmirer.class)); cards.add(new SetCardInfo("Bladebrand", 87, Rarity.COMMON, mage.cards.b.Bladebrand.class)); cards.add(new SetCardInfo("Bladestitched Skaab", 212, Rarity.UNCOMMON, mage.cards.b.BladestitchedSkaab.class)); @@ -54,34 +67,54 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Briarbridge Tracker", 172, Rarity.RARE, mage.cards.b.BriarbridgeTracker.class)); cards.add(new SetCardInfo("Brimstone Vandal", 130, Rarity.COMMON, mage.cards.b.BrimstoneVandal.class)); cards.add(new SetCardInfo("Brood Weaver", 173, Rarity.UNCOMMON, mage.cards.b.BroodWeaver.class)); + cards.add(new SetCardInfo("Brutal Cathar", 7, Rarity.RARE, mage.cards.b.BrutalCathar.class)); cards.add(new SetCardInfo("Burly Breaker", 174, Rarity.UNCOMMON, mage.cards.b.BurlyBreaker.class)); cards.add(new SetCardInfo("Burn Down the House", 131, Rarity.RARE, mage.cards.b.BurnDownTheHouse.class)); + cards.add(new SetCardInfo("Burn the Accursed", 132, Rarity.COMMON, mage.cards.b.BurnTheAccursed.class)); cards.add(new SetCardInfo("Can't Stay Away", 213, Rarity.RARE, mage.cards.c.CantStayAway.class)); cards.add(new SetCardInfo("Candlegrove Witch", 8, Rarity.COMMON, mage.cards.c.CandlegroveWitch.class)); cards.add(new SetCardInfo("Candlelit Cavalry", 175, Rarity.COMMON, mage.cards.c.CandlelitCavalry.class)); cards.add(new SetCardInfo("Candletrap", 9, Rarity.COMMON, mage.cards.c.Candletrap.class)); cards.add(new SetCardInfo("Cathar Commando", 10, Rarity.COMMON, mage.cards.c.CatharCommando.class)); + cards.add(new SetCardInfo("Cathar's Call", 11, Rarity.UNCOMMON, mage.cards.c.CatharsCall.class)); cards.add(new SetCardInfo("Cathartic Pyre", 133, Rarity.UNCOMMON, mage.cards.c.CatharticPyre.class)); cards.add(new SetCardInfo("Celestus Sanctifier", 12, Rarity.COMMON, mage.cards.c.CelestusSanctifier.class)); cards.add(new SetCardInfo("Champion of the Perished", 91, Rarity.RARE, mage.cards.c.ChampionOfThePerished.class)); cards.add(new SetCardInfo("Chapel Shieldgeist", 13, Rarity.UNCOMMON, mage.cards.c.ChapelShieldgeist.class)); cards.add(new SetCardInfo("Chaplain of Alms", 13, Rarity.UNCOMMON, mage.cards.c.ChaplainOfAlms.class)); + cards.add(new SetCardInfo("Chilling Chronicle", 63, Rarity.UNCOMMON, mage.cards.c.ChillingChronicle.class)); cards.add(new SetCardInfo("Clarion Cathars", 14, Rarity.COMMON, mage.cards.c.ClarionCathars.class)); cards.add(new SetCardInfo("Clear Shot", 176, Rarity.UNCOMMON, mage.cards.c.ClearShot.class)); + cards.add(new SetCardInfo("Component Collector", 43, Rarity.COMMON, mage.cards.c.ComponentCollector.class)); cards.add(new SetCardInfo("Consider", 44, Rarity.COMMON, mage.cards.c.Consider.class)); + cards.add(new SetCardInfo("Consuming Blob", 177, Rarity.MYTHIC, mage.cards.c.ConsumingBlob.class)); cards.add(new SetCardInfo("Contortionist Troupe", 178, Rarity.UNCOMMON, mage.cards.c.ContortionistTroupe.class)); cards.add(new SetCardInfo("Corpse Cobble", 214, Rarity.UNCOMMON, mage.cards.c.CorpseCobble.class)); + cards.add(new SetCardInfo("Covert Cutpurse", 92, Rarity.UNCOMMON, mage.cards.c.CovertCutpurse.class)); + cards.add(new SetCardInfo("Covetous Castaway", 45, Rarity.UNCOMMON, mage.cards.c.CovetousCastaway.class)); + cards.add(new SetCardInfo("Covetous Geist", 92, Rarity.UNCOMMON, mage.cards.c.CovetousGeist.class)); + cards.add(new SetCardInfo("Crawl from the Cellar", 93, Rarity.COMMON, mage.cards.c.CrawlFromTheCellar.class)); + cards.add(new SetCardInfo("Creeping Inn", 264, Rarity.MYTHIC, mage.cards.c.CreepingInn.class)); cards.add(new SetCardInfo("Croaking Counterpart", 215, Rarity.RARE, mage.cards.c.CroakingCounterpart.class)); + cards.add(new SetCardInfo("Crossroads Candleguide", 253, Rarity.COMMON, mage.cards.c.CrossroadsCandleguide.class)); cards.add(new SetCardInfo("Curse of Shaken Faith", 134, Rarity.RARE, mage.cards.c.CurseOfShakenFaith.class)); cards.add(new SetCardInfo("Curse of Silence", 15, Rarity.RARE, mage.cards.c.CurseOfSilence.class)); cards.add(new SetCardInfo("Curse of Surveillance", 46, Rarity.RARE, mage.cards.c.CurseOfSurveillance.class)); + cards.add(new SetCardInfo("Dauntless Avenger", 4, Rarity.UNCOMMON, mage.cards.d.DauntlessAvenger.class)); + cards.add(new SetCardInfo("Dawnhart Mentor", 179, Rarity.UNCOMMON, mage.cards.d.DawnhartMentor.class)); cards.add(new SetCardInfo("Dawnhart Rejuvenator", 180, Rarity.COMMON, mage.cards.d.DawnhartRejuvenator.class)); cards.add(new SetCardInfo("Dawnhart Wardens", 216, Rarity.UNCOMMON, mage.cards.d.DawnhartWardens.class)); + cards.add(new SetCardInfo("Deathbonnet Hulk", 181, Rarity.UNCOMMON, mage.cards.d.DeathbonnetHulk.class)); + cards.add(new SetCardInfo("Deathbonnet Sprout", 181, Rarity.UNCOMMON, mage.cards.d.DeathbonnetSprout.class)); cards.add(new SetCardInfo("Defend the Celestus", 182, Rarity.UNCOMMON, mage.cards.d.DefendTheCelestus.class)); cards.add(new SetCardInfo("Defenestrate", 95, Rarity.COMMON, mage.cards.d.Defenestrate.class)); cards.add(new SetCardInfo("Delver of Secrets", 47, Rarity.UNCOMMON, mage.cards.d.DelverOfSecrets.class)); + cards.add(new SetCardInfo("Dennick, Pious Apparition", 217, Rarity.RARE, mage.cards.d.DennickPiousApparition.class)); + cards.add(new SetCardInfo("Dennick, Pious Apprentice", 217, Rarity.RARE, mage.cards.d.DennickPiousApprentice.class)); + cards.add(new SetCardInfo("Departed Soulkeeper", 218, Rarity.UNCOMMON, mage.cards.d.DepartedSoulkeeper.class)); cards.add(new SetCardInfo("Deserted Beach", 260, Rarity.RARE, mage.cards.d.DesertedBeach.class)); cards.add(new SetCardInfo("Devious Cover-Up", 48, Rarity.COMMON, mage.cards.d.DeviousCoverUp.class)); + cards.add(new SetCardInfo("Devoted Grafkeeper", 218, Rarity.UNCOMMON, mage.cards.d.DevotedGrafkeeper.class)); cards.add(new SetCardInfo("Dire-Strain Brawler", 203, Rarity.COMMON, mage.cards.d.DireStrainBrawler.class)); cards.add(new SetCardInfo("Dire-Strain Demolisher", 174, Rarity.UNCOMMON, mage.cards.d.DireStrainDemolisher.class)); cards.add(new SetCardInfo("Dire-Strain Rampage", 219, Rarity.RARE, mage.cards.d.DireStrainRampage.class)); @@ -92,8 +125,11 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Drownyard Amalgam", 50, Rarity.COMMON, mage.cards.d.DrownyardAmalgam.class)); cards.add(new SetCardInfo("Dryad's Revival", 183, Rarity.UNCOMMON, mage.cards.d.DryadsRevival.class)); cards.add(new SetCardInfo("Duel for Dominance", 184, Rarity.COMMON, mage.cards.d.DuelForDominance.class)); + cards.add(new SetCardInfo("Duelcraft Trainer", 16, Rarity.UNCOMMON, mage.cards.d.DuelcraftTrainer.class)); cards.add(new SetCardInfo("Duress", 98, Rarity.COMMON, mage.cards.d.Duress.class)); cards.add(new SetCardInfo("Eaten Alive", 99, Rarity.COMMON, mage.cards.e.EatenAlive.class)); + cards.add(new SetCardInfo("Eccentric Farmer", 185, Rarity.COMMON, mage.cards.e.EccentricFarmer.class)); + cards.add(new SetCardInfo("Ecstatic Awakener", 100, Rarity.COMMON, mage.cards.e.EcstaticAwakener.class)); cards.add(new SetCardInfo("Electric Revelation", 135, Rarity.COMMON, mage.cards.e.ElectricRevelation.class)); cards.add(new SetCardInfo("Embodiment of Flame", 141, Rarity.UNCOMMON, mage.cards.e.EmbodimentOfFlame.class)); cards.add(new SetCardInfo("Evolving Wilds", 261, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); @@ -103,6 +139,8 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Falkenrath Perforator", 136, Rarity.COMMON, mage.cards.f.FalkenrathPerforator.class)); cards.add(new SetCardInfo("Falkenrath Pit Fighter", 137, Rarity.RARE, mage.cards.f.FalkenrathPitFighter.class)); cards.add(new SetCardInfo("Famished Foragers", 138, Rarity.COMMON, mage.cards.f.FamishedForagers.class)); + cards.add(new SetCardInfo("Fangblade Brigand", 139, Rarity.UNCOMMON, mage.cards.f.FangbladeBrigand.class)); + cards.add(new SetCardInfo("Fangblade Eviscerator", 139, Rarity.UNCOMMON, mage.cards.f.FangbladeEviscerator.class)); cards.add(new SetCardInfo("Fateful Absence", 18, Rarity.RARE, mage.cards.f.FatefulAbsence.class)); cards.add(new SetCardInfo("Festival Crasher", 140, Rarity.COMMON, mage.cards.f.FestivalCrasher.class)); cards.add(new SetCardInfo("Field of Ruin", 262, Rarity.UNCOMMON, mage.cards.f.FieldOfRuin.class)); @@ -111,26 +149,36 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Flare of Faith", 19, Rarity.COMMON, mage.cards.f.FlareOfFaith.class)); cards.add(new SetCardInfo("Fleshtaker", 222, Rarity.UNCOMMON, mage.cards.f.Fleshtaker.class)); cards.add(new SetCardInfo("Flip the Switch", 54, Rarity.COMMON, mage.cards.f.FlipTheSwitch.class)); + cards.add(new SetCardInfo("Florian, Voldaren Scion", 223, Rarity.RARE, mage.cards.f.FlorianVoldarenScion.class)); cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Foul Play", 101, Rarity.UNCOMMON, mage.cards.f.FoulPlay.class)); + cards.add(new SetCardInfo("Frenzied Trapbreaker", 190, Rarity.UNCOMMON, mage.cards.f.FrenziedTrapbreaker.class)); + cards.add(new SetCardInfo("Galedrifter", 55, Rarity.COMMON, mage.cards.g.Galedrifter.class)); cards.add(new SetCardInfo("Galvanic Iteration", 224, Rarity.RARE, mage.cards.g.GalvanicIteration.class)); cards.add(new SetCardInfo("Gavony Dawnguard", 20, Rarity.UNCOMMON, mage.cards.g.GavonyDawnguard.class)); cards.add(new SetCardInfo("Gavony Silversmith", 21, Rarity.COMMON, mage.cards.g.GavonySilversmith.class)); cards.add(new SetCardInfo("Gavony Trapper", 22, Rarity.COMMON, mage.cards.g.GavonyTrapper.class)); cards.add(new SetCardInfo("Geistflame Reservoir", 142, Rarity.RARE, mage.cards.g.GeistflameReservoir.class)); + cards.add(new SetCardInfo("Geistwave", 56, Rarity.COMMON, mage.cards.g.Geistwave.class)); + cards.add(new SetCardInfo("Ghostly Castigator", 45, Rarity.UNCOMMON, mage.cards.g.GhostlyCastigator.class)); cards.add(new SetCardInfo("Ghoulcaller's Harvest", 225, Rarity.RARE, mage.cards.g.GhoulcallersHarvest.class)); + cards.add(new SetCardInfo("Ghoulish Procession", 102, Rarity.UNCOMMON, mage.cards.g.GhoulishProcession.class)); + cards.add(new SetCardInfo("Gisa, Glorious Resurrector", 103, Rarity.RARE, mage.cards.g.GisaGloriousResurrector.class)); cards.add(new SetCardInfo("Grafted Identity", 57, Rarity.RARE, mage.cards.g.GraftedIdentity.class)); cards.add(new SetCardInfo("Graveyard Glutton", 104, Rarity.RARE, mage.cards.g.GraveyardGlutton.class)); cards.add(new SetCardInfo("Graveyard Trespasser", 104, Rarity.RARE, mage.cards.g.GraveyardTrespasser.class)); cards.add(new SetCardInfo("Grizzly Ghoul", 226, Rarity.UNCOMMON, mage.cards.g.GrizzlyGhoul.class)); + cards.add(new SetCardInfo("Hallowed Respite", 227, Rarity.RARE, mage.cards.h.HallowedRespite.class)); cards.add(new SetCardInfo("Harvesttide Assailant", 143, Rarity.COMMON, mage.cards.h.HarvesttideAssailant.class)); cards.add(new SetCardInfo("Harvesttide Infiltrator", 143, Rarity.COMMON, mage.cards.h.HarvesttideInfiltrator.class)); + cards.add(new SetCardInfo("Harvesttide Sentry", 186, Rarity.COMMON, mage.cards.h.HarvesttideSentry.class)); cards.add(new SetCardInfo("Haunted Ridge", 263, Rarity.RARE, mage.cards.h.HauntedRidge.class)); cards.add(new SetCardInfo("Hedgewitch's Mask", 23, Rarity.COMMON, mage.cards.h.HedgewitchsMask.class)); cards.add(new SetCardInfo("Heirloom Mirror", 105, Rarity.UNCOMMON, mage.cards.h.HeirloomMirror.class)); cards.add(new SetCardInfo("Hobbling Zombie", 106, Rarity.COMMON, mage.cards.h.HobblingZombie.class)); cards.add(new SetCardInfo("Homestead Courage", 24, Rarity.COMMON, mage.cards.h.HomesteadCourage.class)); cards.add(new SetCardInfo("Hook-Haunt Drifter", 42, Rarity.COMMON, mage.cards.h.HookHauntDrifter.class)); + cards.add(new SetCardInfo("Hostile Hostel", 264, Rarity.MYTHIC, mage.cards.h.HostileHostel.class)); cards.add(new SetCardInfo("Hound Tamer", 187, Rarity.UNCOMMON, mage.cards.h.HoundTamer.class)); cards.add(new SetCardInfo("Howl of the Hunt", 188, Rarity.COMMON, mage.cards.h.HowlOfTheHunt.class)); cards.add(new SetCardInfo("Hungry for More", 228, Rarity.UNCOMMON, mage.cards.h.HungryForMore.class)); @@ -142,26 +190,57 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Island", 270, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Jack-o'-Lantern", 254, Rarity.COMMON, mage.cards.j.JackOLantern.class)); cards.add(new SetCardInfo("Jadar, Ghoulcaller of Nephalia", 108, Rarity.RARE, mage.cards.j.JadarGhoulcallerOfNephalia.class)); + cards.add(new SetCardInfo("Jerren, Corrupted Bishop", 109, Rarity.MYTHIC, mage.cards.j.JerrenCorruptedBishop.class)); cards.add(new SetCardInfo("Join the Dance", 229, Rarity.UNCOMMON, mage.cards.j.JoinTheDance.class)); + cards.add(new SetCardInfo("Katilda, Dawnhart Prime", 230, Rarity.RARE, mage.cards.k.KatildaDawnhartPrime.class)); cards.add(new SetCardInfo("Kessig Naturalist", 231, Rarity.UNCOMMON, mage.cards.k.KessigNaturalist.class)); cards.add(new SetCardInfo("Lambholt Harrier", 145, Rarity.COMMON, mage.cards.l.LambholtHarrier.class)); + cards.add(new SetCardInfo("Larder Zombie", 58, Rarity.COMMON, mage.cards.l.LarderZombie.class)); cards.add(new SetCardInfo("Lier, Disciple of the Drowned", 59, Rarity.MYTHIC, mage.cards.l.LierDiscipleOfTheDrowned.class)); + cards.add(new SetCardInfo("Liesa, Forgotten Archangel", 232, Rarity.RARE, mage.cards.l.LiesaForgottenArchangel.class)); + cards.add(new SetCardInfo("Light Up the Night", 146, Rarity.RARE, mage.cards.l.LightUpTheNight.class)); + cards.add(new SetCardInfo("Locked in the Cemetery", 60, Rarity.COMMON, mage.cards.l.LockedInTheCemetery.class)); + cards.add(new SetCardInfo("Lord of the Forsaken", 110, Rarity.MYTHIC, mage.cards.l.LordOfTheForsaken.class)); cards.add(new SetCardInfo("Lord of the Ulvenwald", 231, Rarity.UNCOMMON, mage.cards.l.LordOfTheUlvenwald.class)); cards.add(new SetCardInfo("Loyal Gryff", 26, Rarity.UNCOMMON, mage.cards.l.LoyalGryff.class)); + cards.add(new SetCardInfo("Luminous Phantom", 27, Rarity.COMMON, mage.cards.l.LuminousPhantom.class)); cards.add(new SetCardInfo("Lunar Frenzy", 147, Rarity.UNCOMMON, mage.cards.l.LunarFrenzy.class)); + cards.add(new SetCardInfo("Lunarch Veteran", 27, Rarity.COMMON, mage.cards.l.LunarchVeteran.class)); + cards.add(new SetCardInfo("Malevolent Hermit", 61, Rarity.RARE, mage.cards.m.MalevolentHermit.class)); cards.add(new SetCardInfo("Mask of Griselbrand", 111, Rarity.RARE, mage.cards.m.MaskOfGriselbrand.class)); + cards.add(new SetCardInfo("Memory Deluge", 62, Rarity.RARE, mage.cards.m.MemoryDeluge.class)); cards.add(new SetCardInfo("Might of the Old Ways", 189, Rarity.COMMON, mage.cards.m.MightOfTheOldWays.class)); + cards.add(new SetCardInfo("Moonrage Brute", 7, Rarity.RARE, mage.cards.m.MoonrageBrute.class)); + cards.add(new SetCardInfo("Moonrager's Slash", 148, Rarity.COMMON, mage.cards.m.MoonragersSlash.class)); cards.add(new SetCardInfo("Moonsilver Key", 255, Rarity.UNCOMMON, mage.cards.m.MoonsilverKey.class)); + cards.add(new SetCardInfo("Moonveil Regent", 149, Rarity.MYTHIC, mage.cards.m.MoonveilRegent.class)); cards.add(new SetCardInfo("Morbid Opportunist", 113, Rarity.UNCOMMON, mage.cards.m.MorbidOpportunist.class)); + cards.add(new SetCardInfo("Morkrut Behemoth", 114, Rarity.COMMON, mage.cards.m.MorkrutBehemoth.class)); + cards.add(new SetCardInfo("Morning Apparition", 28, Rarity.COMMON, mage.cards.m.MorningApparition.class)); cards.add(new SetCardInfo("Mountain", 274, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mounted Dreadknight", 150, Rarity.COMMON, mage.cards.m.MountedDreadknight.class)); + cards.add(new SetCardInfo("Mourning Patrol", 28, Rarity.COMMON, mage.cards.m.MourningPatrol.class)); + cards.add(new SetCardInfo("Mysterious Tome", 63, Rarity.UNCOMMON, mage.cards.m.MysteriousTome.class)); cards.add(new SetCardInfo("Mystic Monstrosity", 256, Rarity.UNCOMMON, mage.cards.m.MysticMonstrosity.class)); cards.add(new SetCardInfo("Mystic Skull", 256, Rarity.UNCOMMON, mage.cards.m.MysticSkull.class)); - cards.add(new SetCardInfo("Neblegast Intruder", 64, Rarity.UNCOMMON, mage.cards.n.NeblegastIntruder.class)); + cards.add(new SetCardInfo("Nebelgast Intruder", 64, Rarity.UNCOMMON, mage.cards.n.NebelgastIntruder.class)); + cards.add(new SetCardInfo("Necrosynthesis", 115, Rarity.UNCOMMON, mage.cards.n.Necrosynthesis.class)); + cards.add(new SetCardInfo("Neonate's Rush", 151, Rarity.COMMON, mage.cards.n.NeonatesRush.class)); + cards.add(new SetCardInfo("No Way Out", 116, Rarity.COMMON, mage.cards.n.NoWayOut.class)); + cards.add(new SetCardInfo("Novice Occultist", 117, Rarity.COMMON, mage.cards.n.NoviceOccultist.class)); cards.add(new SetCardInfo("Obsessive Astronomer", 152, Rarity.UNCOMMON, mage.cards.o.ObsessiveAstronomer.class)); cards.add(new SetCardInfo("Odric's Outrider", 29, Rarity.UNCOMMON, mage.cards.o.OdricsOutrider.class)); + cards.add(new SetCardInfo("Old Stickfingers", 234, Rarity.RARE, mage.cards.o.OldStickfingers.class)); + cards.add(new SetCardInfo("Olivia's Midnight Ambush", 118, Rarity.COMMON, mage.cards.o.OliviasMidnightAmbush.class)); + cards.add(new SetCardInfo("Ominous Roost", 65, Rarity.UNCOMMON, mage.cards.o.OminousRoost.class)); cards.add(new SetCardInfo("Organ Hoarder", 66, Rarity.COMMON, mage.cards.o.OrganHoarder.class)); + cards.add(new SetCardInfo("Ormendahl, the Corrupter", 109, Rarity.MYTHIC, mage.cards.o.OrmendahlTheCorrupter.class)); + cards.add(new SetCardInfo("Otherworldly Gaze", 67, Rarity.COMMON, mage.cards.o.OtherworldlyGaze.class)); + cards.add(new SetCardInfo("Outland Liberator", 190, Rarity.UNCOMMON, mage.cards.o.OutlandLiberator.class)); cards.add(new SetCardInfo("Overgrown Farmland", 265, Rarity.RARE, mage.cards.o.OvergrownFarmland.class)); cards.add(new SetCardInfo("Overwhelmed Archivist", 68, Rarity.UNCOMMON, mage.cards.o.OverwhelmedArchivist.class)); + cards.add(new SetCardInfo("Pack's Betrayal", 153, Rarity.COMMON, mage.cards.p.PacksBetrayal.class)); + cards.add(new SetCardInfo("Path to the Festival", 191, Rarity.COMMON, mage.cards.p.PathToTheFestival.class)); cards.add(new SetCardInfo("Patrician Geist", 69, Rarity.RARE, mage.cards.p.PatricianGeist.class)); cards.add(new SetCardInfo("Pestilent Wolf", 192, Rarity.COMMON, mage.cards.p.PestilentWolf.class)); cards.add(new SetCardInfo("Phantom Carriage", 70, Rarity.UNCOMMON, mage.cards.p.PhantomCarriage.class)); @@ -172,46 +251,79 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Poppet Factory", 71, Rarity.MYTHIC, mage.cards.p.PoppetFactory.class)); cards.add(new SetCardInfo("Poppet Stitcher", 71, Rarity.MYTHIC, mage.cards.p.PoppetStitcher.class)); cards.add(new SetCardInfo("Primal Adversary", 194, Rarity.MYTHIC, mage.cards.p.PrimalAdversary.class)); + cards.add(new SetCardInfo("Purifying Dragon", 155, Rarity.UNCOMMON, mage.cards.p.PurifyingDragon.class)); cards.add(new SetCardInfo("Raze the Effigy", 156, Rarity.COMMON, mage.cards.r.RazeTheEffigy.class)); cards.add(new SetCardInfo("Reckless Stormseeker", 157, Rarity.RARE, mage.cards.r.RecklessStormseeker.class)); + cards.add(new SetCardInfo("Rem Karolus, Stalwart Slayer", 235, Rarity.RARE, mage.cards.r.RemKarolusStalwartSlayer.class)); cards.add(new SetCardInfo("Return to Nature", 195, Rarity.COMMON, mage.cards.r.ReturnToNature.class)); + cards.add(new SetCardInfo("Revenge of the Drowned", 72, Rarity.COMMON, mage.cards.r.RevengeOfTheDrowned.class)); + cards.add(new SetCardInfo("Rise of the Ants", 196, Rarity.UNCOMMON, mage.cards.r.RiseOfTheAnts.class)); cards.add(new SetCardInfo("Rite of Harmony", 236, Rarity.RARE, mage.cards.r.RiteOfHarmony.class)); + cards.add(new SetCardInfo("Rite of Oblivion", 237, Rarity.UNCOMMON, mage.cards.r.RiteOfOblivion.class)); cards.add(new SetCardInfo("Ritual Guardian", 30, Rarity.COMMON, mage.cards.r.RitualGuardian.class)); cards.add(new SetCardInfo("Ritual of Hope", 31, Rarity.UNCOMMON, mage.cards.r.RitualOfHope.class)); cards.add(new SetCardInfo("Rockfall Vale", 266, Rarity.RARE, mage.cards.r.RockfallVale.class)); + cards.add(new SetCardInfo("Rootcoil Creeper", 238, Rarity.UNCOMMON, mage.cards.r.RootcoilCreeper.class)); cards.add(new SetCardInfo("Rotten Reunion", 119, Rarity.COMMON, mage.cards.r.RottenReunion.class)); cards.add(new SetCardInfo("Sacred Fire", 239, Rarity.UNCOMMON, mage.cards.s.SacredFire.class)); cards.add(new SetCardInfo("Saryth, the Viper's Fang", 197, Rarity.RARE, mage.cards.s.SarythTheVipersFang.class)); cards.add(new SetCardInfo("Seafaring Werewolf", 80, Rarity.RARE, mage.cards.s.SeafaringWerewolf.class)); + cards.add(new SetCardInfo("Search Party Captain", 32, Rarity.COMMON, mage.cards.s.SearchPartyCaptain.class)); + cards.add(new SetCardInfo("Seasoned Cathar", 2, Rarity.UNCOMMON, mage.cards.s.SeasonedCathar.class)); cards.add(new SetCardInfo("Secrets of the Key", 73, Rarity.COMMON, mage.cards.s.SecretsOfTheKey.class)); + cards.add(new SetCardInfo("Seize the Storm", 158, Rarity.UNCOMMON, mage.cards.s.SeizeTheStorm.class)); cards.add(new SetCardInfo("Shadowbeast Sighting", 198, Rarity.COMMON, mage.cards.s.ShadowbeastSighting.class)); cards.add(new SetCardInfo("Shady Traveler", 120, Rarity.COMMON, mage.cards.s.ShadyTraveler.class)); cards.add(new SetCardInfo("Shipwreck Marsh", 267, Rarity.RARE, mage.cards.s.ShipwreckMarsh.class)); + cards.add(new SetCardInfo("Shipwreck Sifters", 74, Rarity.COMMON, mage.cards.s.ShipwreckSifters.class)); + cards.add(new SetCardInfo("Siege Zombie", 121, Rarity.COMMON, mage.cards.s.SiegeZombie.class)); + cards.add(new SetCardInfo("Sigarda's Splendor", 33, Rarity.RARE, mage.cards.s.SigardasSplendor.class)); cards.add(new SetCardInfo("Sigarda, Champion of Light", 240, Rarity.MYTHIC, mage.cards.s.SigardaChampionOfLight.class)); + cards.add(new SetCardInfo("Sigardian Savior", 34, Rarity.MYTHIC, mage.cards.s.SigardianSavior.class)); + cards.add(new SetCardInfo("Silver Bolt", 258, Rarity.COMMON, mage.cards.s.SilverBolt.class)); + cards.add(new SetCardInfo("Siphon Insight", 241, Rarity.RARE, mage.cards.s.SiphonInsight.class)); + cards.add(new SetCardInfo("Skaab Wrangler", 75, Rarity.UNCOMMON, mage.cards.s.SkaabWrangler.class)); cards.add(new SetCardInfo("Slaughter Specialist", 122, Rarity.RARE, mage.cards.s.SlaughterSpecialist.class)); + cards.add(new SetCardInfo("Slogurk, the Overslime", 242, Rarity.RARE, mage.cards.s.SlogurkTheOverslime.class)); + cards.add(new SetCardInfo("Sludge Monster", 76, Rarity.RARE, mage.cards.s.SludgeMonster.class)); + cards.add(new SetCardInfo("Smoldering Egg", 159, Rarity.RARE, mage.cards.s.SmolderingEgg.class)); cards.add(new SetCardInfo("Snarling Wolf", 199, Rarity.COMMON, mage.cards.s.SnarlingWolf.class)); + cards.add(new SetCardInfo("Soul-Guide Gryff", 35, Rarity.COMMON, mage.cards.s.SoulGuideGryff.class)); cards.add(new SetCardInfo("Spectral Adversary", 77, Rarity.MYTHIC, mage.cards.s.SpectralAdversary.class)); cards.add(new SetCardInfo("Spellrune Howler", 160, Rarity.UNCOMMON, mage.cards.s.SpellruneHowler.class)); cards.add(new SetCardInfo("Spellrune Painter", 160, Rarity.UNCOMMON, mage.cards.s.SpellrunePainter.class)); cards.add(new SetCardInfo("Stalking Predator", 120, Rarity.COMMON, mage.cards.s.StalkingPredator.class)); cards.add(new SetCardInfo("Startle", 78, Rarity.COMMON, mage.cards.s.Startle.class)); + cards.add(new SetCardInfo("Stolen Vitality", 161, Rarity.COMMON, mage.cards.s.StolenVitality.class)); + cards.add(new SetCardInfo("Storm Skreelix", 243, Rarity.UNCOMMON, mage.cards.s.StormSkreelix.class)); + cards.add(new SetCardInfo("Storm the Festival", 200, Rarity.RARE, mage.cards.s.StormTheFestival.class)); cards.add(new SetCardInfo("Storm-Charged Slasher", 157, Rarity.RARE, mage.cards.s.StormChargedSlasher.class)); cards.add(new SetCardInfo("Stormrider Spirit", 79, Rarity.COMMON, mage.cards.s.StormriderSpirit.class)); + cards.add(new SetCardInfo("Strangling Grasp", 126, Rarity.UNCOMMON, mage.cards.s.StranglingGrasp.class)); cards.add(new SetCardInfo("Stromkirk Bloodthief", 123, Rarity.UNCOMMON, mage.cards.s.StromkirkBloodthief.class)); cards.add(new SetCardInfo("Stuffed Bear", 259, Rarity.COMMON, mage.cards.s.StuffedBear.class)); cards.add(new SetCardInfo("Sungold Barrage", 36, Rarity.COMMON, mage.cards.s.SungoldBarrage.class)); + cards.add(new SetCardInfo("Sungold Sentinel", 37, Rarity.RARE, mage.cards.s.SungoldSentinel.class)); cards.add(new SetCardInfo("Sunrise Cavalier", 244, Rarity.UNCOMMON, mage.cards.s.SunriseCavalier.class)); + cards.add(new SetCardInfo("Sunset Revelry", 38, Rarity.UNCOMMON, mage.cards.s.SunsetRevelry.class)); cards.add(new SetCardInfo("Sunstreak Phoenix", 162, Rarity.MYTHIC, mage.cards.s.SunstreakPhoenix.class)); cards.add(new SetCardInfo("Suspicious Stowaway", 80, Rarity.RARE, mage.cards.s.SuspiciousStowaway.class)); cards.add(new SetCardInfo("Swamp", 272, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Tainted Adversary", 124, Rarity.MYTHIC, mage.cards.t.TaintedAdversary.class)); + cards.add(new SetCardInfo("Tapping at the Window", 201, Rarity.COMMON, mage.cards.t.TappingAtTheWindow.class)); cards.add(new SetCardInfo("Tavern Ruffian", 163, Rarity.COMMON, mage.cards.t.TavernRuffian.class)); cards.add(new SetCardInfo("Tavern Smasher", 163, Rarity.COMMON, mage.cards.t.TavernSmasher.class)); + cards.add(new SetCardInfo("Teferi, Who Slows the Sunset", 245, Rarity.MYTHIC, mage.cards.t.TeferiWhoSlowsTheSunset.class)); + cards.add(new SetCardInfo("The Meathook Massacre", 112, Rarity.MYTHIC, mage.cards.t.TheMeathookMassacre.class)); cards.add(new SetCardInfo("Thermo-Alchemist", 164, Rarity.UNCOMMON, mage.cards.t.ThermoAlchemist.class)); cards.add(new SetCardInfo("Thraben Exorcism", 39, Rarity.COMMON, mage.cards.t.ThrabenExorcism.class)); cards.add(new SetCardInfo("Timberland Guide", 202, Rarity.COMMON, mage.cards.t.TimberlandGuide.class)); cards.add(new SetCardInfo("Tireless Hauler", 203, Rarity.COMMON, mage.cards.t.TirelessHauler.class)); + cards.add(new SetCardInfo("Tovolar's Huntmaster", 204, Rarity.RARE, mage.cards.t.TovolarsHuntmaster.class)); + cards.add(new SetCardInfo("Tovolar's Packleader", 204, Rarity.RARE, mage.cards.t.TovolarsPackleader.class)); cards.add(new SetCardInfo("Triskaidekaphile", 81, Rarity.RARE, mage.cards.t.Triskaidekaphile.class)); + cards.add(new SetCardInfo("Turn the Earth", 205, Rarity.UNCOMMON, mage.cards.t.TurnTheEarth.class)); + cards.add(new SetCardInfo("Unblinking Observer", 82, Rarity.COMMON, mage.cards.u.UnblinkingObserver.class)); cards.add(new SetCardInfo("Unnatural Growth", 206, Rarity.RARE, mage.cards.u.UnnaturalGrowth.class)); cards.add(new SetCardInfo("Unruly Mob", 40, Rarity.COMMON, mage.cards.u.UnrulyMob.class)); cards.add(new SetCardInfo("Untamed Pup", 187, Rarity.UNCOMMON, mage.cards.u.UntamedPup.class)); @@ -219,14 +331,99 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Vampire Interloper", 125, Rarity.COMMON, mage.cards.v.VampireInterloper.class)); cards.add(new SetCardInfo("Vampire Socialite", 249, Rarity.UNCOMMON, mage.cards.v.VampireSocialite.class)); cards.add(new SetCardInfo("Vanquish the Horde", 41, Rarity.RARE, mage.cards.v.VanquishTheHorde.class)); + cards.add(new SetCardInfo("Vengeful Strangler", 126, Rarity.UNCOMMON, mage.cards.v.VengefulStrangler.class)); cards.add(new SetCardInfo("Village Reavers", 165, Rarity.UNCOMMON, mage.cards.v.VillageReavers.class)); cards.add(new SetCardInfo("Village Watch", 165, Rarity.UNCOMMON, mage.cards.v.VillageWatch.class)); cards.add(new SetCardInfo("Vivisection", 83, Rarity.UNCOMMON, mage.cards.v.Vivisection.class)); cards.add(new SetCardInfo("Voldaren Ambusher", 166, Rarity.UNCOMMON, mage.cards.v.VoldarenAmbusher.class)); + cards.add(new SetCardInfo("Voldaren Stinger", 167, Rarity.COMMON, mage.cards.v.VoldarenStinger.class)); + cards.add(new SetCardInfo("Waildrifter", 55, Rarity.COMMON, mage.cards.w.Waildrifter.class)); + cards.add(new SetCardInfo("Wake to Slaughter", 250, Rarity.RARE, mage.cards.w.WakeToSlaughter.class)); cards.add(new SetCardInfo("Willow Geist", 207, Rarity.RARE, mage.cards.w.WillowGeist.class)); cards.add(new SetCardInfo("Wing Shredder", 169, Rarity.COMMON, mage.cards.w.WingShredder.class)); + cards.add(new SetCardInfo("Winterthorn Blessing", 251, Rarity.UNCOMMON, mage.cards.w.WinterthornBlessing.class)); cards.add(new SetCardInfo("Wrenn and Seven", 208, Rarity.MYTHIC, mage.cards.w.WrennAndSeven.class)); cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when mechanic is fully implemented } + /* + @Override + public BoosterCollator createCollator() { + return new InnistradMidnightHuntCollator(); + } + */ +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/mid.html +// Using USA collation for common/uncommon, rare collation inferred from other sets +class InnistradMidnightHuntCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "202", "23", "48", "125", "184", "153", "74", "116", "171", "259", "52", "87", "201", "135", "79", "98", "189", "253", "54", "114", "175", "35", "44", "121", "193", "136", "73", "86", "199", "19", "253", "67", "117", "186", "151", "82", "88", "170", "5", "50", "119", "192", "261", "58", "96", "191", "39", "56", "84", "195", "127", "78", "118", "188", "12", "43", "93", "184", "259", "79", "116", "171", "153", "74", "98", "202", "258", "52", "125", "189", "23", "135", "48", "87", "201", "35", "54", "121", "199", "254", "44", "114", "191", "19", "136", "73", "86", "193", "261", "82", "96", "175", "5", "254", "58", "117", "186", "151", "78", "119", "170", "39", "50", "88", "192", "258", "67", "118", "188", "127", "56", "84", "195", "12", "43", "93"); + private final CardRun commonB = new CardRun(true, "140", "198", "32", "145", "60", "22", "167", "106", "9", "128", "180", "287", "130", "66", "24", "161", "95", "40", "144", "185", "30", "148", "150", "21", "138", "72", "14", "156", "99", "36", "140", "60", "10", "132", "301", "8", "145", "106", "22", "167", "198", "32", "130", "66", "40", "128", "150", "9", "148", "95", "24", "161", "185", "14", "156", "72", "30", "132", "144", "36", "138", "99", "10", "140", "198", "32", "145", "60", "22", "128", "180", "21", "167", "106", "40", "130", "66", "24", "144", "95", "9", "148", "150", "8", "161", "185", "30", "138", "72", "14", "156", "99", "36", "132", "10", "21"); + private final CardRun commonDFC = new CardRun(true, "100", "55", "169", "293", "28", "120", "42", "305", "143", "27", "100", "55", "298", "163", "28", "120", "42", "203", "296", "27", "100", "55", "169", "143", "28", "291", "42", "203", "163", "27"); + private final CardRun uncommonA = new CardRun(true, "38", "53", "210", "97", "49", "26", "51", "115", "20", "70", "228", "113", "16", "75", "205", "90", "11", "83", "255", "123", "29", "65", "102", "31", "53", "262", "101", "38", "64", "210", "49", "20", "97", "51", "228", "90", "26", "75", "115", "16", "83", "205", "113", "11", "65", "102", "29", "70", "255", "31", "123", "64", "262", "97", "38", "53", "101", "20", "49", "210", "51", "115", "26", "75", "228", "113", "16", "83", "205", "90", "11", "65", "102", "31", "70", "255", "123", "29", "53", "262", "97", "38", "64", "210", "101", "49", "20", "51", "115", "26", "75", "228", "90", "16", "65", "205", "113", "11", "83", "102", "31", "70", "255", "123", "29", "64", "262", "101"); + private final CardRun uncommonB = new CardRun(true, "107", "229", "183", "237", "152", "238", "176", "220", "158", "216", "300", "222", "133", "244", "182", "226", "164", "251", "6", "212", "147", "247", "196", "239", "166", "249", "176", "243", "152", "214", "178", "154", "107", "221", "179", "238", "155", "229", "173", "237", "164", "216", "183", "220", "158", "239", "182", "222", "147", "226", "6", "244", "166", "212", "196", "251", "133", "247", "152", "237", "178", "243", "155", "249", "173", "214", "107", "229", "179", "238", "154", "221", "176", "220", "164", "308", "183", "226", "133", "244", "182", "222", "147", "212", "6", "247", "158", "251", "196", "239", "166", "243", "178", "249", "155", "221", "173", "154", "214"); + private final CardRun uncommonDFC1 = new CardRun(true, "190", "231", "139", "92", "302", "68", "160", "218", "299", "2", "165", "310", "190", "92", "292", "68", "187", "85", "160", "231", "174", "85", "165", "2", "303", "218", "139", "289", "187", "92", "295", "68", "174", "218", "297", "2"); + private final CardRun uncommonDFC2 = new CardRun(false, "3", "4", "13", "45", "47", "63", "105", "126", "141", "181", "256"); + private final CardRun rare = new CardRun(false, "1", "15", "18", "33", "37", "41", "46", "57", "62", "69", "76", "81", "89", "91", "103", "108", "111", "122", "131", "134", "137", "142", "146", "168", "172", "197", "200", "206", "207", "209", "213", "215", "219", "223", "224", "225", "227", "230", "232", "234", "235", "236", "241", "242", "248", "250", "252", "257", "260", "263", "265", "266", "267", "1", "15", "18", "33", "37", "41", "46", "57", "62", "69", "76", "81", "89", "91", "103", "108", "111", "122", "131", "134", "137", "142", "146", "168", "172", "197", "200", "206", "207", "209", "213", "215", "219", "223", "224", "225", "227", "230", "232", "234", "235", "236", "241", "242", "248", "250", "252", "257", "260", "263", "265", "266", "267", "25", "34", "59", "77", "110", "112", "124", "129", "149", "162", "177", "194", "208", "240", "245"); + private final CardRun rareDFC = new CardRun(false, "7", "61", "80", "94", "104", "157", "159", "204", "217", "233", "246", "7", "61", "80", "94", "104", "157", "159", "204", "217", "233", "246", "17", "71", "109", "211", "264"); + private final CardRun land = new CardRun(false, "268", "269", "270", "271", "272", "273", "274", "275", "276", "277"); + + private final BoosterStructure AAAAABBBBD = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonDFC + ); + private final BoosterStructure AAAAAABBBD = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonDFC + ); + private final BoosterStructure ABD1 = new BoosterStructure(uncommonA, uncommonB, uncommonDFC1, rare); + private final BoosterStructure BBD1 = new BoosterStructure(uncommonB, uncommonB, uncommonDFC1, rare); + private final BoosterStructure ABD2 = new BoosterStructure(uncommonA, uncommonB, uncommonDFC2, rare); + private final BoosterStructure BBD2 = new BoosterStructure(uncommonB, uncommonB, uncommonDFC2, rare); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB, rareDFC); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 5.8 A commons (58 / 10) + // 3.2 B commons (32 / 10) + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAAAABBBBD, AAAAABBBBD, + AAAAAABBBD, AAAAAABBBD, AAAAAABBBD, AAAAAABBBD, + AAAAAABBBD, AAAAAABBBD, AAAAAABBBD, AAAAAABBBD + ); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 0.98 A uncommons (81 / 83) + // 1.19 B uncommons (99 / 83) + // 0.43 DFC1 uncommon (36 / 83) + // 0.40 DFC2 uncommon (33 / 83) + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + ABD1, ABD1, ABD1, ABD1, ABD1, ABD1, + ABD1, ABD1, ABD1, ABD1, ABD1, ABD1, + ABD1, ABD1, ABD1, ABD1, ABD1, ABD1, + ABD1, ABD1, ABD1, ABD1, ABD1, ABD1, + ABD1, ABD1, ABD1, ABD1, ABD1, ABD1, + ABD1, ABD1, ABD1, ABD1, ABD1, BBD1, + + ABD2, ABD2, ABD2, ABD2, ABD2, ABD2, + ABD2, ABD2, ABD2, ABD2, ABD2, ABD2, + ABD2, ABD2, ABD2, ABD2, ABD2, ABD2, + ABD2, ABD2, ABD2, ABD2, ABD2, ABD2, + ABD2, ABD2, ABD2, ABD2, ABD2, ABD2, + ABD2, ABD2, BBD2, + + ABB, ABB, ABB, ABB, ABB, ABB, ABB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/Kaldheim.java b/Mage.Sets/src/mage/sets/Kaldheim.java index 4291b9b4b07..6e9f0891920 100644 --- a/Mage.Sets/src/mage/sets/Kaldheim.java +++ b/Mage.Sets/src/mage/sets/Kaldheim.java @@ -30,7 +30,7 @@ public final class Kaldheim extends ExpansionSet { private final List savedSpecialLand = new ArrayList<>(); private Kaldheim() { - super("Kaldheim", "KHM", ExpansionSet.buildDate(2021, 2, 5), SetType.EXPANSION, new KaldheimCollator()); + super("Kaldheim", "KHM", ExpansionSet.buildDate(2021, 2, 5), SetType.EXPANSION); this.blockName = "Kaldheim"; this.hasBasicLands = true; this.hasBoosters = true; @@ -492,101 +492,52 @@ public final class Kaldheim extends ExpansionSet { } return new ArrayList<>(savedSpecialLand); } + + @Override + public BoosterCollator createCollator() { + return new KaldheimCollator(); + } } // Booster collation info from https://www.lethe.xyz/mtg/collation/khm.html // Using USA collation for common/uncommon and JP for rare/mythic class KaldheimCollator implements BoosterCollator { - private static class KaldheimRun extends CardRun { - private static final KaldheimRun commonA = new KaldheimRun(true, "34","77","136","13","78","149","3","47","127","14","67","140","19","54","124","38","49","147","39","55","157","1","53","141","37","66","126","10","71","155","4","65","121","13","77","136","34","78","127","3","47","149","14","54","124","38","67","140","19","55","147","39","49","157","37","53","141","10","65","155","1","71","121","4","66","126"); - private static final KaldheimRun commonB = new KaldheimRun(true, "102","176","87","183","93","184","104","178","117","174","111","171","96","194","84","176","119","180","83","164","89","172","87","175","102","183","104","178","93","174","117","184","111","171","84","194","119","164","96","180","89","176","83","172","102","175","87","178","104","174","93","183","117","171","119","184","84","164","111","194","89","180","96","172","83","175"); - private static final KaldheimRun commonC1 = new KaldheimRun(true, "187","152","242","46","173","23","101","246","48","190","32","151","99","68","267","31","91","192","143","57","100","243","105","16","134","42","196","238","187","46","23","242","152","173","48","32","246","190","151","101","31","68","99","267","91","134","105","57","16","192","100","143","243","196","42"); - private static final KaldheimRun commonC2 = new KaldheimRun(true, "11","193","95","158","17","239","44","159","129","7","118","85","138","74","165","11","129","193","150","72","5","95","159","74","158","17","85","239","118","138","44","7","238","193","150","165","5","72","158","95","11","44","159","239","129","17","85","74","7","118","5","150","165","138","72"); - private static final KaldheimRun uncommonA = new KaldheimRun(true, "215","236","212","208","195","224","332","6","232","18","106","268","209","162","8","76","122","88","182","206","202","62","110","132","200","325","271","211","144","103","215","236","258","56","163","113","28","226","2","58","263","148","232","162","224","208","195","323","268","18","106","6","233","8","76","122","209","88","182","206","202","62","110","132","321","220","271","211","144","258","2","28","263","113","226","103","236","163","56","215","148","58","329","195","6","232","233","18","212","162","268","106","208","103","322","76","122","88","182","206","202","62","110","132","200","220","271","211","144","8","58","28","258","113","56","148","2","263","226","163"); - private static final KaldheimRun uncommonB = new KaldheimRun(true, "30","166","75","201","265","222","45","135","256","191","231","235","36","250","316","128","25","247","264","35","97","186","223","59","60","130","216","80","244","259","217","133","64","245","108","189","331","137","116","253","30","166","75","201","265","327","45","128","256","247","235","36","191","25","170","250","135","231","186","35","60","324","97","130","59","264","244","80","328","259","133","217","64","245","108","189","230","137","116","253","30","166","75","201","265","222","45","256","191","235","170","135","36","128","25","247","250","231","35","223","60","130","97","264","216","186","59","244","80","259","217","133","304","245","108","189","230","137","116","253"); - private static final KaldheimRun rareA = new KaldheimRun(false, "9","12","20","21","24","26","27","29","43","50","51","52","61","63","69","73","79","82","86","90","92","107","109","112","115","120","123","125","131","142","146","153","156","161","167","169","177","179","181","185","188","197","203","204","205","207","210","213","214","219","227","228","229","234","237","240","241","251","252","254","255","260","272","275","9","12","20","21","24","26","27","29","43","50","51","52","61","63","69","73","79","82","86","90","92","107","109","112","115","120","123","125","131","142","146","153","156","161","167","169","177","179","181","185","188","197","203","204","205","207","210","213","214","219","227","228","229","234","237","240","241","251","252","254","255","260","272","275","15","22","33","40","41","70","81","94","98","114","139","145","154","160","168","198","199","218","221","225"); - private static final KaldheimRun rareB = new KaldheimRun(false, "9","12","20","300","24","26","27","301","43","303","51","52","61","63","69","73","79","82","86","90","306","107","109","307","309","310","311","125","131","312","146","153","156","161","167","315","177","317","318","185","188","319","203","204","205","207","210","213","214","219","227","330","229","234","237","240","241","290","291","292","255","293","272","275","9","12","20","300","24","26","27","301","43","303","51","52","61","63","69","73","79","82","86","90","306","107","109","307","309","310","311","125","131","312","146","153","156","161","167","315","177","317","318","185","188","319","203","204","205","207","210","213","214","219","227","330","229","234","237","240","241","290","291","292","255","293","272","275","299","22","294","302","295","305","81","94","296","308","139","297","313","298","314","287","320","288","326","289"); - private static final KaldheimRun land = new KaldheimRun(true, "270","282","248","277","276","280","278","266","270","283","282","285","274","277","281","279","262","284","282","248","283","276","269","276","280","285","281","249","257","284","277","249","281","284","283","266","257","281","269","280","261","276","277","283","249","278","285","248","276","285","279","261","269","257","249","248","283","270","285","277","282","284","270","278","248","279","269","281","274","280","279","257","281","284","277","257","274","273","279","276","262","266","284","281","273","282","278","262","280","279","274","262","282","283","278","262","279","261","285","273","266","283","261","280","284","266","278","270","285","282","280","276","277","273","278","269","273","249","261","274"); + private final CardRun commonA = new CardRun(true, "34","77","136","13","78","149","3","47","127","14","67","140","19","54","124","38","49","147","39","55","157","1","53","141","37","66","126","10","71","155","4","65","121","13","77","136","34","78","127","3","47","149","14","54","124","38","67","140","19","55","147","39","49","157","37","53","141","10","65","155","1","71","121","4","66","126"); + private final CardRun commonB = new CardRun(true, "102","176","87","183","93","184","104","178","117","174","111","171","96","194","84","176","119","180","83","164","89","172","87","175","102","183","104","178","93","174","117","184","111","171","84","194","119","164","96","180","89","176","83","172","102","175","87","178","104","174","93","183","117","171","119","184","84","164","111","194","89","180","96","172","83","175"); + private final CardRun commonC1 = new CardRun(true, "187","152","242","46","173","23","101","246","48","190","32","151","99","68","267","31","91","192","143","57","100","243","105","16","134","42","196","238","187","46","23","242","152","173","48","32","246","190","151","101","31","68","99","267","91","134","105","57","16","192","100","143","243","196","42"); + private final CardRun commonC2 = new CardRun(true, "11","193","95","158","17","239","44","159","129","7","118","85","138","74","165","11","129","193","150","72","5","95","159","74","158","17","85","239","118","138","44","7","238","193","150","165","5","72","158","95","11","44","159","239","129","17","85","74","7","118","5","150","165","138","72"); + private final CardRun uncommonA = new CardRun(true, "215","236","212","208","195","224","332","6","232","18","106","268","209","162","8","76","122","88","182","206","202","62","110","132","200","325","271","211","144","103","215","236","258","56","163","113","28","226","2","58","263","148","232","162","224","208","195","323","268","18","106","6","233","8","76","122","209","88","182","206","202","62","110","132","321","220","271","211","144","258","2","28","263","113","226","103","236","163","56","215","148","58","329","195","6","232","233","18","212","162","268","106","208","103","322","76","122","88","182","206","202","62","110","132","200","220","271","211","144","8","58","28","258","113","56","148","2","263","226","163"); + private final CardRun uncommonB = new CardRun(true, "30","166","75","201","265","222","45","135","256","191","231","235","36","250","316","128","25","247","264","35","97","186","223","59","60","130","216","80","244","259","217","133","64","245","108","189","331","137","116","253","30","166","75","201","265","327","45","128","256","247","235","36","191","25","170","250","135","231","186","35","60","324","97","130","59","264","244","80","328","259","133","217","64","245","108","189","230","137","116","253","30","166","75","201","265","222","45","256","191","235","170","135","36","128","25","247","250","231","35","223","60","130","97","264","216","186","59","244","80","259","217","133","304","245","108","189","230","137","116","253"); + private final CardRun rareA = new CardRun(false, "9","12","20","21","24","26","27","29","43","50","51","52","61","63","69","73","79","82","86","90","92","107","109","112","115","120","123","125","131","142","146","153","156","161","167","169","177","179","181","185","188","197","203","204","205","207","210","213","214","219","227","228","229","234","237","240","241","251","252","254","255","260","272","275","9","12","20","21","24","26","27","29","43","50","51","52","61","63","69","73","79","82","86","90","92","107","109","112","115","120","123","125","131","142","146","153","156","161","167","169","177","179","181","185","188","197","203","204","205","207","210","213","214","219","227","228","229","234","237","240","241","251","252","254","255","260","272","275","15","22","33","40","41","70","81","94","98","114","139","145","154","160","168","198","199","218","221","225"); + private final CardRun rareB = new CardRun(false, "9","12","20","300","24","26","27","301","43","303","51","52","61","63","69","73","79","82","86","90","306","107","109","307","309","310","311","125","131","312","146","153","156","161","167","315","177","317","318","185","188","319","203","204","205","207","210","213","214","219","227","330","229","234","237","240","241","290","291","292","255","293","272","275","9","12","20","300","24","26","27","301","43","303","51","52","61","63","69","73","79","82","86","90","306","107","109","307","309","310","311","125","131","312","146","153","156","161","167","315","177","317","318","185","188","319","203","204","205","207","210","213","214","219","227","330","229","234","237","240","241","290","291","292","255","293","272","275","299","22","294","302","295","305","81","94","296","308","139","297","313","298","314","287","320","288","326","289"); + private final CardRun land = new CardRun(true, "270","282","248","277","276","280","278","266","270","283","282","285","274","277","281","279","262","284","282","248","283","276","269","276","280","285","281","249","257","284","277","249","281","284","283","266","257","281","269","280","261","276","277","283","249","278","285","248","276","285","279","261","269","257","249","248","283","270","285","277","282","284","270","278","248","279","269","281","274","280","279","257","281","284","277","257","274","273","279","276","262","266","284","281","273","282","278","262","280","279","274","262","282","283","278","262","279","261","285","273","266","283","261","280","284","266","278","270","285","282","280","276","277","273","278","269","273","249","261","274"); - private KaldheimRun(boolean keepOrder, String... numbers) { - super(keepOrder, numbers); - } - } - - private static class KaldheimStructure extends BoosterStructure { - private static final KaldheimStructure AABBC1C1C1C1C1C1 = new KaldheimStructure( - KaldheimRun.commonA, - KaldheimRun.commonA, - KaldheimRun.commonB, - KaldheimRun.commonB, - KaldheimRun.commonC1, - KaldheimRun.commonC1, - KaldheimRun.commonC1, - KaldheimRun.commonC1, - KaldheimRun.commonC1, - KaldheimRun.commonC1 - ); - private static final KaldheimStructure AAABBC1C1C1C1C1 = new KaldheimStructure( - KaldheimRun.commonA, - KaldheimRun.commonA, - KaldheimRun.commonA, - KaldheimRun.commonB, - KaldheimRun.commonB, - KaldheimRun.commonC1, - KaldheimRun.commonC1, - KaldheimRun.commonC1, - KaldheimRun.commonC1, - KaldheimRun.commonC1 - ); - private static final KaldheimStructure AAAABBBC2C2C2 = new KaldheimStructure( - KaldheimRun.commonA, - KaldheimRun.commonA, - KaldheimRun.commonA, - KaldheimRun.commonA, - KaldheimRun.commonB, - KaldheimRun.commonB, - KaldheimRun.commonB, - KaldheimRun.commonC2, - KaldheimRun.commonC2, - KaldheimRun.commonC2 - ); - private static final KaldheimStructure AAAABBC2C2C2C2 = new KaldheimStructure( - KaldheimRun.commonA, - KaldheimRun.commonA, - KaldheimRun.commonA, - KaldheimRun.commonA, - KaldheimRun.commonB, - KaldheimRun.commonB, - KaldheimRun.commonC2, - KaldheimRun.commonC2, - KaldheimRun.commonC2, - KaldheimRun.commonC2 - ); - private static final KaldheimStructure AAA = new KaldheimStructure( - KaldheimRun.uncommonA, - KaldheimRun.uncommonA, - KaldheimRun.uncommonA - ); - private static final KaldheimStructure BBB = new KaldheimStructure( - KaldheimRun.uncommonB, - KaldheimRun.uncommonB, - KaldheimRun.uncommonB - ); - private static final KaldheimStructure R1 = new KaldheimStructure( - KaldheimRun.rareA - ); - private static final KaldheimStructure R2 = new KaldheimStructure( - KaldheimRun.rareB - ); - private static final KaldheimStructure L1 = new KaldheimStructure( - KaldheimRun.land - ); - - private KaldheimStructure(CardRun... runs) { - super(runs); - } - } + private final BoosterStructure AABBC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAA = new BoosterStructure(uncommonA, uncommonA, uncommonA); + private final BoosterStructure BBB = new BoosterStructure(uncommonB, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA); + private final BoosterStructure R2 = new BoosterStructure(rareB); + private final BoosterStructure L1 = new BoosterStructure(land); // In order for equal numbers of each common to exist, the average booster must contain: // 3.27 A commons (36 / 11) @@ -596,53 +547,33 @@ class KaldheimCollator implements BoosterCollator { // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs // and with 10 common slots per booster private final RarityConfiguration commonRuns = new RarityConfiguration( - false, - KaldheimStructure.AABBC1C1C1C1C1C1, - KaldheimStructure.AABBC1C1C1C1C1C1, - KaldheimStructure.AABBC1C1C1C1C1C1, - KaldheimStructure.AABBC1C1C1C1C1C1, - KaldheimStructure.AABBC1C1C1C1C1C1, - KaldheimStructure.AAABBC1C1C1C1C1, - KaldheimStructure.AAABBC1C1C1C1C1, - KaldheimStructure.AAABBC1C1C1C1C1, - KaldheimStructure.AAABBC1C1C1C1C1, - KaldheimStructure.AAABBC1C1C1C1C1, - KaldheimStructure.AAABBC1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, - KaldheimStructure.AAAABBC2C2C2C2, - KaldheimStructure.AAAABBC2C2C2C2, - KaldheimStructure.AAAABBC2C2C2C2, - KaldheimStructure.AAAABBC2C2C2C2, - KaldheimStructure.AAAABBC2C2C2C2, - KaldheimStructure.AAAABBC2C2C2C2, - KaldheimStructure.AAAABBC2C2C2C2, - KaldheimStructure.AAAABBBC2C2C2, - KaldheimStructure.AAAABBBC2C2C2, - KaldheimStructure.AAAABBBC2C2C2, - KaldheimStructure.AAAABBBC2C2C2 + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2 ); - private final RarityConfiguration uncommonRuns = new RarityConfiguration( - KaldheimStructure.AAA, - KaldheimStructure.BBB - ); - private final RarityConfiguration rareRuns = new RarityConfiguration( - false, - KaldheimStructure.R1, - KaldheimStructure.R1, - KaldheimStructure.R2 - ); - private final RarityConfiguration landRuns = new RarityConfiguration( - KaldheimStructure.L1 - ); - - - @Override - public void shuffle() { - commonRuns.shuffle(); - uncommonRuns.shuffle(); - rareRuns.shuffle(); - landRuns.shuffle(); - } + private final RarityConfiguration uncommonRuns = new RarityConfiguration(AAA, BBB); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1, R1, R2); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); @Override public List makeBooster() { diff --git a/Mage.Sets/src/mage/sets/Legions.java b/Mage.Sets/src/mage/sets/Legions.java index fec891e7e1b..1b616701b7a 100644 --- a/Mage.Sets/src/mage/sets/Legions.java +++ b/Mage.Sets/src/mage/sets/Legions.java @@ -27,7 +27,7 @@ public final class Legions extends ExpansionSet { this.numBoosterCommon = 11; this.numBoosterUncommon = 3; this.numBoosterRare = 1; - this.ratioBoosterMythic = 8; + this.ratioBoosterMythic = 0; cards.add(new SetCardInfo("Akroma, Angel of Wrath", 1, Rarity.RARE, mage.cards.a.AkromaAngelOfWrath.class)); cards.add(new SetCardInfo("Akroma's Devoted", 2, Rarity.UNCOMMON, mage.cards.a.AkromasDevoted.class)); cards.add(new SetCardInfo("Aphetto Exterminator", 59, Rarity.UNCOMMON, mage.cards.a.AphettoExterminator.class)); diff --git a/Mage.Sets/src/mage/sets/MercadianMasques.java b/Mage.Sets/src/mage/sets/MercadianMasques.java index 866c8cb036a..a7398bf630e 100644 --- a/Mage.Sets/src/mage/sets/MercadianMasques.java +++ b/Mage.Sets/src/mage/sets/MercadianMasques.java @@ -1,375 +1,375 @@ -package mage.sets; - -import mage.cards.ExpansionSet; -import mage.constants.Rarity; -import mage.constants.SetType; - -/** - * @author North - */ -public final class MercadianMasques extends ExpansionSet { - - private static final MercadianMasques instance = new MercadianMasques(); - - public static MercadianMasques getInstance() { - return instance; - } - - private MercadianMasques() { - super("Mercadian Masques", "MMQ", ExpansionSet.buildDate(1999, 8, 25), SetType.EXPANSION); - this.blockName = "Masques"; - this.hasBoosters = true; - this.numBoosterLands = 0; - this.numBoosterCommon = 11; - this.numBoosterUncommon = 3; - this.numBoosterRare = 1; - this.ratioBoosterMythic = 0; - cards.add(new SetCardInfo("Aerial Caravan", 58, Rarity.RARE, mage.cards.a.AerialCaravan.class)); - cards.add(new SetCardInfo("Afterlife", 1, Rarity.UNCOMMON, mage.cards.a.Afterlife.class)); - cards.add(new SetCardInfo("Alabaster Wall", 2, Rarity.COMMON, mage.cards.a.AlabasterWall.class)); - cards.add(new SetCardInfo("Alley Grifters", 115, Rarity.COMMON, mage.cards.a.AlleyGrifters.class)); - cards.add(new SetCardInfo("Ancestral Mask", 229, Rarity.COMMON, mage.cards.a.AncestralMask.class)); - cards.add(new SetCardInfo("Armistice", 3, Rarity.RARE, mage.cards.a.Armistice.class)); - cards.add(new SetCardInfo("Arms Dealer", 172, Rarity.UNCOMMON, mage.cards.a.ArmsDealer.class)); - cards.add(new SetCardInfo("Arrest", 4, Rarity.UNCOMMON, mage.cards.a.Arrest.class)); - cards.add(new SetCardInfo("Assembly Hall", 286, Rarity.RARE, mage.cards.a.AssemblyHall.class)); - cards.add(new SetCardInfo("Ballista Squad", 5, Rarity.UNCOMMON, mage.cards.b.BallistaSquad.class)); - cards.add(new SetCardInfo("Balloon Peddler", 59, Rarity.COMMON, mage.cards.b.BalloonPeddler.class)); - cards.add(new SetCardInfo("Barbed Wire", 287, Rarity.UNCOMMON, mage.cards.b.BarbedWire.class)); - cards.add(new SetCardInfo("Battle Rampart", 173, Rarity.COMMON, mage.cards.b.BattleRampart.class)); - cards.add(new SetCardInfo("Battle Squadron", 174, Rarity.RARE, mage.cards.b.BattleSquadron.class)); - cards.add(new SetCardInfo("Bifurcate", 230, Rarity.RARE, mage.cards.b.Bifurcate.class)); - cards.add(new SetCardInfo("Black Market", 116, Rarity.RARE, mage.cards.b.BlackMarket.class)); - cards.add(new SetCardInfo("Blaster Mage", 175, Rarity.COMMON, mage.cards.b.BlasterMage.class)); - cards.add(new SetCardInfo("Blockade Runner", 60, Rarity.COMMON, mage.cards.b.BlockadeRunner.class)); - cards.add(new SetCardInfo("Blood Hound", 176, Rarity.RARE, mage.cards.b.BloodHound.class)); - cards.add(new SetCardInfo("Blood Oath", 177, Rarity.RARE, mage.cards.b.BloodOath.class)); - cards.add(new SetCardInfo("Boa Constrictor", 231, Rarity.UNCOMMON, mage.cards.b.BoaConstrictor.class)); - cards.add(new SetCardInfo("Bog Smugglers", 117, Rarity.COMMON, mage.cards.b.BogSmugglers.class)); - cards.add(new SetCardInfo("Bog Witch", 118, Rarity.COMMON, mage.cards.b.BogWitch.class)); - cards.add(new SetCardInfo("Brainstorm", 61, Rarity.COMMON, mage.cards.b.Brainstorm.class)); - cards.add(new SetCardInfo("Brawl", 178, Rarity.RARE, mage.cards.b.Brawl.class)); - cards.add(new SetCardInfo("Briar Patch", 232, Rarity.UNCOMMON, mage.cards.b.BriarPatch.class)); - cards.add(new SetCardInfo("Bribery", 62, Rarity.RARE, mage.cards.b.Bribery.class)); - cards.add(new SetCardInfo("Buoyancy", 63, Rarity.COMMON, mage.cards.b.Buoyancy.class)); - cards.add(new SetCardInfo("Cackling Witch", 119, Rarity.UNCOMMON, mage.cards.c.CacklingWitch.class)); - cards.add(new SetCardInfo("Caller of the Hunt", 233, Rarity.RARE, mage.cards.c.CallerOfTheHunt.class)); - cards.add(new SetCardInfo("Cateran Brute", 120, Rarity.COMMON, mage.cards.c.CateranBrute.class)); - cards.add(new SetCardInfo("Cateran Enforcer", 121, Rarity.UNCOMMON, mage.cards.c.CateranEnforcer.class)); - cards.add(new SetCardInfo("Cateran Kidnappers", 122, Rarity.UNCOMMON, mage.cards.c.CateranKidnappers.class)); - cards.add(new SetCardInfo("Cateran Overlord", 123, Rarity.RARE, mage.cards.c.CateranOverlord.class)); - cards.add(new SetCardInfo("Cateran Persuader", 124, Rarity.COMMON, mage.cards.c.CateranPersuader.class)); - cards.add(new SetCardInfo("Cateran Slaver", 125, Rarity.RARE, mage.cards.c.CateranSlaver.class)); - cards.add(new SetCardInfo("Cateran Summons", 126, Rarity.UNCOMMON, mage.cards.c.CateranSummons.class)); - cards.add(new SetCardInfo("Caustic Wasps", 234, Rarity.UNCOMMON, mage.cards.c.CausticWasps.class)); - cards.add(new SetCardInfo("Cave-In", 180, Rarity.RARE, mage.cards.c.CaveIn.class)); - cards.add(new SetCardInfo("Cavern Crawler", 181, Rarity.COMMON, mage.cards.c.CavernCrawler.class)); - cards.add(new SetCardInfo("Cave Sense", 179, Rarity.COMMON, mage.cards.c.CaveSense.class)); - cards.add(new SetCardInfo("Ceremonial Guard", 182, Rarity.COMMON, mage.cards.c.CeremonialGuard.class)); - cards.add(new SetCardInfo("Chambered Nautilus", 64, Rarity.UNCOMMON, mage.cards.c.ChamberedNautilus.class)); - cards.add(new SetCardInfo("Chameleon Spirit", 65, Rarity.UNCOMMON, mage.cards.c.ChameleonSpirit.class)); - cards.add(new SetCardInfo("Charisma", 66, Rarity.RARE, mage.cards.c.Charisma.class)); - cards.add(new SetCardInfo("Charm Peddler", 6, Rarity.COMMON, mage.cards.c.CharmPeddler.class)); - cards.add(new SetCardInfo("Charmed Griffin", 7, Rarity.UNCOMMON, mage.cards.c.CharmedGriffin.class)); - cards.add(new SetCardInfo("Cho-Arrim Alchemist", 8, Rarity.RARE, mage.cards.c.ChoArrimAlchemist.class)); - cards.add(new SetCardInfo("Cho-Arrim Bruiser", 9, Rarity.RARE, mage.cards.c.ChoArrimBruiser.class)); - cards.add(new SetCardInfo("Cho-Arrim Legate", 10, Rarity.UNCOMMON, mage.cards.c.ChoArrimLegate.class)); - cards.add(new SetCardInfo("Cho-Manno, Revolutionary", 11, Rarity.RARE, mage.cards.c.ChoMannoRevolutionary.class)); - cards.add(new SetCardInfo("Cho-Manno's Blessing", 12, Rarity.COMMON, mage.cards.c.ChoMannosBlessing.class)); - cards.add(new SetCardInfo("Cinder Elemental", 183, Rarity.UNCOMMON, mage.cards.c.CinderElemental.class)); - cards.add(new SetCardInfo("Clear the Land", 235, Rarity.RARE, mage.cards.c.ClearTheLand.class)); - cards.add(new SetCardInfo("Close Quarters", 184, Rarity.UNCOMMON, mage.cards.c.CloseQuarters.class)); - cards.add(new SetCardInfo("Cloud Sprite", 67, Rarity.COMMON, mage.cards.c.CloudSprite.class)); - cards.add(new SetCardInfo("Coastal Piracy", 68, Rarity.UNCOMMON, mage.cards.c.CoastalPiracy.class)); - cards.add(new SetCardInfo("Collective Unconscious", 236, Rarity.RARE, mage.cards.c.CollectiveUnconscious.class)); - cards.add(new SetCardInfo("Common Cause", 13, Rarity.RARE, mage.cards.c.CommonCause.class)); - cards.add(new SetCardInfo("Conspiracy", 127, Rarity.RARE, mage.cards.c.Conspiracy.class)); - cards.add(new SetCardInfo("Cornered Market", 14, Rarity.RARE, mage.cards.c.CorneredMarket.class)); - cards.add(new SetCardInfo("Corrupt Official", 128, Rarity.RARE, mage.cards.c.CorruptOfficial.class)); - cards.add(new SetCardInfo("Counterspell", 69, Rarity.COMMON, mage.cards.c.Counterspell.class)); - cards.add(new SetCardInfo("Cowardice", 70, Rarity.RARE, mage.cards.c.Cowardice.class)); - cards.add(new SetCardInfo("Crackdown", 15, Rarity.RARE, mage.cards.c.Crackdown.class)); - cards.add(new SetCardInfo("Crag Saurian", 185, Rarity.RARE, mage.cards.c.CragSaurian.class)); - cards.add(new SetCardInfo("Crash", 186, Rarity.COMMON, mage.cards.c.Crash.class)); - cards.add(new SetCardInfo("Credit Voucher", 289, Rarity.UNCOMMON, mage.cards.c.CreditVoucher.class)); - cards.add(new SetCardInfo("Crenellated Wall", 290, Rarity.UNCOMMON, mage.cards.c.CrenellatedWall.class)); - cards.add(new SetCardInfo("Crooked Scales", 291, Rarity.RARE, mage.cards.c.CrookedScales.class)); - cards.add(new SetCardInfo("Crossbow Infantry", 16, Rarity.COMMON, mage.cards.c.CrossbowInfantry.class)); - cards.add(new SetCardInfo("Crumbling Sanctuary", 292, Rarity.RARE, mage.cards.c.CrumblingSanctuary.class)); - cards.add(new SetCardInfo("Customs Depot", 71, Rarity.UNCOMMON, mage.cards.c.CustomsDepot.class)); - cards.add(new SetCardInfo("Dark Ritual", 129, Rarity.COMMON, mage.cards.d.DarkRitual.class)); - cards.add(new SetCardInfo("Darting Merfolk", 72, Rarity.COMMON, mage.cards.d.DartingMerfolk.class)); - cards.add(new SetCardInfo("Dawnstrider", 237, Rarity.RARE, mage.cards.d.Dawnstrider.class)); - cards.add(new SetCardInfo("Deadly Insect", 238, Rarity.COMMON, mage.cards.d.DeadlyInsect.class)); - cards.add(new SetCardInfo("Deathgazer", 130, Rarity.UNCOMMON, mage.cards.d.Deathgazer.class)); - cards.add(new SetCardInfo("Deepwood Drummer", 239, Rarity.COMMON, mage.cards.d.DeepwoodDrummer.class)); - cards.add(new SetCardInfo("Deepwood Elder", 240, Rarity.RARE, mage.cards.d.DeepwoodElder.class)); - cards.add(new SetCardInfo("Deepwood Ghoul", 131, Rarity.COMMON, mage.cards.d.DeepwoodGhoul.class)); - cards.add(new SetCardInfo("Deepwood Legate", 132, Rarity.UNCOMMON, mage.cards.d.DeepwoodLegate.class)); - cards.add(new SetCardInfo("Deepwood Tantiv", 241, Rarity.UNCOMMON, mage.cards.d.DeepwoodTantiv.class)); - cards.add(new SetCardInfo("Deepwood Wolverine", 242, Rarity.COMMON, mage.cards.d.DeepwoodWolverine.class)); - cards.add(new SetCardInfo("Dehydration", 73, Rarity.COMMON, mage.cards.d.Dehydration.class)); - cards.add(new SetCardInfo("Delraich", 133, Rarity.RARE, mage.cards.d.Delraich.class)); - cards.add(new SetCardInfo("Desert Twister", 243, Rarity.UNCOMMON, mage.cards.d.DesertTwister.class)); - cards.add(new SetCardInfo("Devout Witness", 17, Rarity.COMMON, mage.cards.d.DevoutWitness.class)); - cards.add(new SetCardInfo("Diplomatic Escort", 74, Rarity.UNCOMMON, mage.cards.d.DiplomaticEscort.class)); - cards.add(new SetCardInfo("Diplomatic Immunity", 75, Rarity.COMMON, mage.cards.d.DiplomaticImmunity.class)); - cards.add(new SetCardInfo("Disenchant", 18, Rarity.COMMON, mage.cards.d.Disenchant.class)); - cards.add(new SetCardInfo("Distorting Lens", 293, Rarity.RARE, mage.cards.d.DistortingLens.class)); - cards.add(new SetCardInfo("Drake Hatchling", 76, Rarity.COMMON, mage.cards.d.DrakeHatchling.class)); - cards.add(new SetCardInfo("Dust Bowl", 316, Rarity.RARE, mage.cards.d.DustBowl.class)); - cards.add(new SetCardInfo("Embargo", 77, Rarity.RARE, mage.cards.e.Embargo.class)); - cards.add(new SetCardInfo("Energy Flux", 78, Rarity.UNCOMMON, mage.cards.e.EnergyFlux.class)); - cards.add(new SetCardInfo("Enslaved Horror", 134, Rarity.UNCOMMON, mage.cards.e.EnslavedHorror.class)); - cards.add(new SetCardInfo("Extortion", 135, Rarity.RARE, mage.cards.e.Extortion.class)); - cards.add(new SetCardInfo("Extravagant Spirit", 79, Rarity.RARE, mage.cards.e.ExtravagantSpirit.class)); - cards.add(new SetCardInfo("Eye of Ramos", 294, Rarity.RARE, mage.cards.e.EyeOfRamos.class)); - cards.add(new SetCardInfo("False Demise", 80, Rarity.UNCOMMON, mage.cards.f.FalseDemise.class)); - cards.add(new SetCardInfo("Ferocity", 245, Rarity.COMMON, mage.cards.f.Ferocity.class)); - cards.add(new SetCardInfo("Flailing Manticore", 187, Rarity.RARE, mage.cards.f.FlailingManticore.class)); - cards.add(new SetCardInfo("Flailing Ogre", 188, Rarity.UNCOMMON, mage.cards.f.FlailingOgre.class)); - cards.add(new SetCardInfo("Flailing Soldier", 189, Rarity.COMMON, mage.cards.f.FlailingSoldier.class)); - cards.add(new SetCardInfo("Flaming Sword", 190, Rarity.COMMON, mage.cards.f.FlamingSword.class)); - cards.add(new SetCardInfo("Food Chain", 246, Rarity.RARE, mage.cards.f.FoodChain.class)); - cards.add(new SetCardInfo("Forced March", 136, Rarity.RARE, mage.cards.f.ForcedMarch.class)); - cards.add(new SetCardInfo("Forest", 347, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 348, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 349, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 350, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Foster", 247, Rarity.RARE, mage.cards.f.Foster.class)); - cards.add(new SetCardInfo("Fountain of Cho", 317, Rarity.UNCOMMON, mage.cards.f.FountainOfCho.class)); - cards.add(new SetCardInfo("Fountain Watch", 19, Rarity.RARE, mage.cards.f.FountainWatch.class)); - cards.add(new SetCardInfo("Fresh Volunteers", 20, Rarity.COMMON, mage.cards.f.FreshVolunteers.class)); - cards.add(new SetCardInfo("Furious Assault", 191, Rarity.COMMON, mage.cards.f.FuriousAssault.class)); - cards.add(new SetCardInfo("Game Preserve", 248, Rarity.RARE, mage.cards.g.GamePreserve.class)); - cards.add(new SetCardInfo("General's Regalia", 295, Rarity.RARE, mage.cards.g.GeneralsRegalia.class)); - cards.add(new SetCardInfo("Gerrard's Irregulars", 192, Rarity.COMMON, mage.cards.g.GerrardsIrregulars.class)); - cards.add(new SetCardInfo("Ghoul's Feast", 137, Rarity.UNCOMMON, mage.cards.g.GhoulsFeast.class)); - cards.add(new SetCardInfo("Giant Caterpillar", 249, Rarity.COMMON, mage.cards.g.GiantCaterpillar.class)); - cards.add(new SetCardInfo("Glowing Anemone", 81, Rarity.UNCOMMON, mage.cards.g.GlowingAnemone.class)); - cards.add(new SetCardInfo("Groundskeeper", 250, Rarity.UNCOMMON, mage.cards.g.Groundskeeper.class)); - cards.add(new SetCardInfo("Gush", 82, Rarity.COMMON, mage.cards.g.Gush.class)); - cards.add(new SetCardInfo("Hammer Mage", 193, Rarity.UNCOMMON, mage.cards.h.HammerMage.class)); - cards.add(new SetCardInfo("Haunted Crossroads", 138, Rarity.UNCOMMON, mage.cards.h.HauntedCrossroads.class)); - cards.add(new SetCardInfo("Heart of Ramos", 296, Rarity.RARE, mage.cards.h.HeartOfRamos.class)); - cards.add(new SetCardInfo("Henge Guardian", 297, Rarity.UNCOMMON, mage.cards.h.HengeGuardian.class)); - cards.add(new SetCardInfo("Henge of Ramos", 318, Rarity.UNCOMMON, mage.cards.h.HengeOfRamos.class)); - cards.add(new SetCardInfo("Hickory Woodlot", 319, Rarity.COMMON, mage.cards.h.HickoryWoodlot.class)); - cards.add(new SetCardInfo("High Market", 320, Rarity.RARE, mage.cards.h.HighMarket.class)); - cards.add(new SetCardInfo("High Seas", 83, Rarity.UNCOMMON, mage.cards.h.HighSeas.class)); - cards.add(new SetCardInfo("Highway Robber", 139, Rarity.COMMON, mage.cards.h.HighwayRobber.class)); - cards.add(new SetCardInfo("Hired Giant", 194, Rarity.UNCOMMON, mage.cards.h.HiredGiant.class)); - cards.add(new SetCardInfo("Honor the Fallen", 21, Rarity.RARE, mage.cards.h.HonorTheFallen.class)); - cards.add(new SetCardInfo("Hoodwink", 84, Rarity.COMMON, mage.cards.h.Hoodwink.class)); - cards.add(new SetCardInfo("Horned Troll", 251, Rarity.COMMON, mage.cards.h.HornedTroll.class)); - cards.add(new SetCardInfo("Horn of Plenty", 298, Rarity.RARE, mage.cards.h.HornOfPlenty.class)); - cards.add(new SetCardInfo("Horn of Ramos", 299, Rarity.RARE, mage.cards.h.HornOfRamos.class)); - cards.add(new SetCardInfo("Howling Wolf", 252, Rarity.COMMON, mage.cards.h.HowlingWolf.class)); - cards.add(new SetCardInfo("Hunted Wumpus", 253, Rarity.UNCOMMON, mage.cards.h.HuntedWumpus.class)); - cards.add(new SetCardInfo("Ignoble Soldier", 22, Rarity.UNCOMMON, mage.cards.i.IgnobleSoldier.class)); - cards.add(new SetCardInfo("Indentured Djinn", 85, Rarity.UNCOMMON, mage.cards.i.IndenturedDjinn.class)); - cards.add(new SetCardInfo("Instigator", 140, Rarity.RARE, mage.cards.i.Instigator.class)); - cards.add(new SetCardInfo("Insubordination", 141, Rarity.COMMON, mage.cards.i.Insubordination.class)); - cards.add(new SetCardInfo("Intimidation", 142, Rarity.UNCOMMON, mage.cards.i.Intimidation.class)); - cards.add(new SetCardInfo("Invigorate", 254, Rarity.COMMON, mage.cards.i.Invigorate.class)); - cards.add(new SetCardInfo("Inviolability", 23, Rarity.COMMON, mage.cards.i.Inviolability.class)); - cards.add(new SetCardInfo("Iron Lance", 300, Rarity.UNCOMMON, mage.cards.i.IronLance.class)); - cards.add(new SetCardInfo("Island", 335, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 336, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 337, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 338, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ivory Mask", 24, Rarity.RARE, mage.cards.i.IvoryMask.class)); - cards.add(new SetCardInfo("Jeweled Torque", 301, Rarity.UNCOMMON, mage.cards.j.JeweledTorque.class)); - cards.add(new SetCardInfo("Jhovall Queen", 25, Rarity.RARE, mage.cards.j.JhovallQueen.class)); - cards.add(new SetCardInfo("Jhovall Rider", 26, Rarity.UNCOMMON, mage.cards.j.JhovallRider.class)); - cards.add(new SetCardInfo("Karn's Touch", 86, Rarity.RARE, mage.cards.k.KarnsTouch.class)); - cards.add(new SetCardInfo("Kris Mage", 195, Rarity.COMMON, mage.cards.k.KrisMage.class)); - cards.add(new SetCardInfo("Kyren Archive", 302, Rarity.RARE, mage.cards.k.KyrenArchive.class)); - cards.add(new SetCardInfo("Kyren Glider", 196, Rarity.COMMON, mage.cards.k.KyrenGlider.class)); - cards.add(new SetCardInfo("Kyren Legate", 197, Rarity.UNCOMMON, mage.cards.k.KyrenLegate.class)); - cards.add(new SetCardInfo("Kyren Negotiations", 198, Rarity.UNCOMMON, mage.cards.k.KyrenNegotiations.class)); - cards.add(new SetCardInfo("Kyren Sniper", 199, Rarity.COMMON, mage.cards.k.KyrenSniper.class)); - cards.add(new SetCardInfo("Kyren Toy", 303, Rarity.RARE, mage.cards.k.KyrenToy.class)); - cards.add(new SetCardInfo("Land Grant", 255, Rarity.COMMON, mage.cards.l.LandGrant.class)); - cards.add(new SetCardInfo("Larceny", 143, Rarity.UNCOMMON, mage.cards.l.Larceny.class)); - cards.add(new SetCardInfo("Last Breath", 27, Rarity.UNCOMMON, mage.cards.l.LastBreath.class)); - cards.add(new SetCardInfo("Lava Runner", 200, Rarity.RARE, mage.cards.l.LavaRunner.class)); - cards.add(new SetCardInfo("Liability", 144, Rarity.RARE, mage.cards.l.Liability.class)); - cards.add(new SetCardInfo("Lightning Hounds", 201, Rarity.COMMON, mage.cards.l.LightningHounds.class)); - cards.add(new SetCardInfo("Lithophage", 202, Rarity.RARE, mage.cards.l.Lithophage.class)); - cards.add(new SetCardInfo("Lumbering Satyr", 257, Rarity.UNCOMMON, mage.cards.l.LumberingSatyr.class)); - cards.add(new SetCardInfo("Lunge", 203, Rarity.COMMON, mage.cards.l.Lunge.class)); - cards.add(new SetCardInfo("Lure", 258, Rarity.UNCOMMON, mage.cards.l.Lure.class)); - cards.add(new SetCardInfo("Maggot Therapy", 145, Rarity.COMMON, mage.cards.m.MaggotTherapy.class)); - cards.add(new SetCardInfo("Magistrate's Scepter", 304, Rarity.RARE, mage.cards.m.MagistratesScepter.class)); - cards.add(new SetCardInfo("Magistrate's Veto", 204, Rarity.UNCOMMON, mage.cards.m.MagistratesVeto.class)); - cards.add(new SetCardInfo("Megatherium", 259, Rarity.RARE, mage.cards.m.Megatherium.class)); - cards.add(new SetCardInfo("Mercadia's Downfall", 205, Rarity.UNCOMMON, mage.cards.m.MercadiasDownfall.class)); - cards.add(new SetCardInfo("Mercadian Atlas", 305, Rarity.RARE, mage.cards.m.MercadianAtlas.class)); - cards.add(new SetCardInfo("Mercadian Bazaar", 321, Rarity.UNCOMMON, mage.cards.m.MercadianBazaar.class)); - cards.add(new SetCardInfo("Mercadian Lift", 306, Rarity.RARE, mage.cards.m.MercadianLift.class)); - cards.add(new SetCardInfo("Midnight Ritual", 146, Rarity.RARE, mage.cards.m.MidnightRitual.class)); - cards.add(new SetCardInfo("Misdirection", 87, Rarity.RARE, mage.cards.m.Misdirection.class)); - cards.add(new SetCardInfo("Misshapen Fiend", 147, Rarity.COMMON, mage.cards.m.MisshapenFiend.class)); - cards.add(new SetCardInfo("Misstep", 88, Rarity.COMMON, mage.cards.m.Misstep.class)); - cards.add(new SetCardInfo("Molting Harpy", 148, Rarity.UNCOMMON, mage.cards.m.MoltingHarpy.class)); - cards.add(new SetCardInfo("Moment of Silence", 28, Rarity.COMMON, mage.cards.m.MomentOfSilence.class)); - cards.add(new SetCardInfo("Monkey Cage", 307, Rarity.RARE, mage.cards.m.MonkeyCage.class)); - cards.add(new SetCardInfo("Moonlit Wake", 29, Rarity.UNCOMMON, mage.cards.m.MoonlitWake.class)); - cards.add(new SetCardInfo("Mountain", 343, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 344, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 345, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 346, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Muzzle", 30, Rarity.COMMON, mage.cards.m.Muzzle.class)); - cards.add(new SetCardInfo("Natural Affinity", 260, Rarity.RARE, mage.cards.n.NaturalAffinity.class)); - cards.add(new SetCardInfo("Nether Spirit", 149, Rarity.RARE, mage.cards.n.NetherSpirit.class)); - cards.add(new SetCardInfo("Nightwind Glider", 31, Rarity.COMMON, mage.cards.n.NightwindGlider.class)); - cards.add(new SetCardInfo("Noble Purpose", 32, Rarity.UNCOMMON, mage.cards.n.NoblePurpose.class)); - cards.add(new SetCardInfo("Notorious Assassin", 150, Rarity.RARE, mage.cards.n.NotoriousAssassin.class)); - cards.add(new SetCardInfo("Ogre Taskmaster", 206, Rarity.UNCOMMON, mage.cards.o.OgreTaskmaster.class)); - cards.add(new SetCardInfo("Orim's Cure", 33, Rarity.COMMON, mage.cards.o.OrimsCure.class)); - cards.add(new SetCardInfo("Overtaker", 89, Rarity.RARE, mage.cards.o.Overtaker.class)); - cards.add(new SetCardInfo("Panacea", 308, Rarity.UNCOMMON, mage.cards.p.Panacea.class)); - cards.add(new SetCardInfo("Pangosaur", 261, Rarity.RARE, mage.cards.p.Pangosaur.class)); - cards.add(new SetCardInfo("Peat Bog", 322, Rarity.COMMON, mage.cards.p.PeatBog.class)); - cards.add(new SetCardInfo("Pious Warrior", 34, Rarity.COMMON, mage.cards.p.PiousWarrior.class)); - cards.add(new SetCardInfo("Plains", 331, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 332, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 333, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 334, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Port Inspector", 90, Rarity.COMMON, mage.cards.p.PortInspector.class)); - cards.add(new SetCardInfo("Power Matrix", 309, Rarity.RARE, mage.cards.p.PowerMatrix.class)); - cards.add(new SetCardInfo("Pretender's Claim", 151, Rarity.UNCOMMON, mage.cards.p.PretendersClaim.class)); - cards.add(new SetCardInfo("Primeval Shambler", 152, Rarity.UNCOMMON, mage.cards.p.PrimevalShambler.class)); - cards.add(new SetCardInfo("Puffer Extract", 310, Rarity.UNCOMMON, mage.cards.p.PufferExtract.class)); - cards.add(new SetCardInfo("Pulverize", 207, Rarity.RARE, mage.cards.p.Pulverize.class)); - cards.add(new SetCardInfo("Putrefaction", 153, Rarity.UNCOMMON, mage.cards.p.Putrefaction.class)); - cards.add(new SetCardInfo("Puppet's Verdict", 208, Rarity.RARE, mage.cards.p.PuppetsVerdict.class)); - cards.add(new SetCardInfo("Quagmire Lamprey", 154, Rarity.UNCOMMON, mage.cards.q.QuagmireLamprey.class)); - cards.add(new SetCardInfo("Rain of Tears", 155, Rarity.UNCOMMON, mage.cards.r.RainOfTears.class)); - cards.add(new SetCardInfo("Ramosian Captain", 35, Rarity.UNCOMMON, mage.cards.r.RamosianCaptain.class)); - cards.add(new SetCardInfo("Ramosian Commander", 36, Rarity.UNCOMMON, mage.cards.r.RamosianCommander.class)); - cards.add(new SetCardInfo("Ramosian Lieutenant", 37, Rarity.COMMON, mage.cards.r.RamosianLieutenant.class)); - cards.add(new SetCardInfo("Ramosian Rally", 38, Rarity.COMMON, mage.cards.r.RamosianRally.class)); - cards.add(new SetCardInfo("Ramosian Sergeant", 39, Rarity.COMMON, mage.cards.r.RamosianSergeant.class)); - cards.add(new SetCardInfo("Ramosian Sky Marshal", 40, Rarity.RARE, mage.cards.r.RamosianSkyMarshal.class)); - cards.add(new SetCardInfo("Rampart Crawler", 156, Rarity.COMMON, mage.cards.r.RampartCrawler.class)); - cards.add(new SetCardInfo("Rappelling Scouts", 41, Rarity.RARE, mage.cards.r.RappellingScouts.class)); - cards.add(new SetCardInfo("Remote Farm", 323, Rarity.COMMON, mage.cards.r.RemoteFarm.class)); - cards.add(new SetCardInfo("Renounce", 42, Rarity.UNCOMMON, mage.cards.r.Renounce.class)); - cards.add(new SetCardInfo("Revered Elder", 43, Rarity.COMMON, mage.cards.r.ReveredElder.class)); - cards.add(new SetCardInfo("Reverent Mantra", 44, Rarity.RARE, mage.cards.r.ReverentMantra.class)); - cards.add(new SetCardInfo("Revive", 262, Rarity.UNCOMMON, mage.cards.r.Revive.class)); - cards.add(new SetCardInfo("Righteous Aura", 45, Rarity.UNCOMMON, mage.cards.r.RighteousAura.class)); - cards.add(new SetCardInfo("Righteous Indignation", 46, Rarity.UNCOMMON, mage.cards.r.RighteousIndignation.class)); - cards.add(new SetCardInfo("Rishadan Airship", 91, Rarity.COMMON, mage.cards.r.RishadanAirship.class)); - cards.add(new SetCardInfo("Rishadan Brigand", 92, Rarity.RARE, mage.cards.r.RishadanBrigand.class)); - cards.add(new SetCardInfo("Rishadan Cutpurse", 93, Rarity.COMMON, mage.cards.r.RishadanCutpurse.class)); - cards.add(new SetCardInfo("Rishadan Footpad", 94, Rarity.UNCOMMON, mage.cards.r.RishadanFootpad.class)); - cards.add(new SetCardInfo("Rishadan Pawnshop", 311, Rarity.RARE, mage.cards.r.RishadanPawnshop.class)); - cards.add(new SetCardInfo("Rishadan Port", 324, Rarity.RARE, mage.cards.r.RishadanPort.class)); - cards.add(new SetCardInfo("Robber Fly", 209, Rarity.UNCOMMON, mage.cards.r.RobberFly.class)); - cards.add(new SetCardInfo("Rock Badger", 210, Rarity.UNCOMMON, mage.cards.r.RockBadger.class)); - cards.add(new SetCardInfo("Rouse", 157, Rarity.COMMON, mage.cards.r.Rouse.class)); - cards.add(new SetCardInfo("Rushwood Dryad", 263, Rarity.COMMON, mage.cards.r.RushwoodDryad.class)); - cards.add(new SetCardInfo("Rushwood Elemental", 264, Rarity.RARE, mage.cards.r.RushwoodElemental.class)); - cards.add(new SetCardInfo("Rushwood Grove", 325, Rarity.UNCOMMON, mage.cards.r.RushwoodGrove.class)); - cards.add(new SetCardInfo("Rushwood Herbalist", 265, Rarity.COMMON, mage.cards.r.RushwoodHerbalist.class)); - cards.add(new SetCardInfo("Rushwood Legate", 266, Rarity.UNCOMMON, mage.cards.r.RushwoodLegate.class)); - cards.add(new SetCardInfo("Saber Ants", 267, Rarity.UNCOMMON, mage.cards.s.SaberAnts.class)); - cards.add(new SetCardInfo("Sacred Prey", 268, Rarity.COMMON, mage.cards.s.SacredPrey.class)); - cards.add(new SetCardInfo("Sailmonger", 95, Rarity.UNCOMMON, mage.cards.s.Sailmonger.class)); - cards.add(new SetCardInfo("Sand Squid", 96, Rarity.RARE, mage.cards.s.SandSquid.class)); - cards.add(new SetCardInfo("Sandstone Needle", 326, Rarity.COMMON, mage.cards.s.SandstoneNeedle.class)); - cards.add(new SetCardInfo("Saprazzan Bailiff", 97, Rarity.RARE, mage.cards.s.SaprazzanBailiff.class)); - cards.add(new SetCardInfo("Saprazzan Breaker", 98, Rarity.UNCOMMON, mage.cards.s.SaprazzanBreaker.class)); - cards.add(new SetCardInfo("Saprazzan Cove", 327, Rarity.UNCOMMON, mage.cards.s.SaprazzanCove.class)); - cards.add(new SetCardInfo("Saprazzan Heir", 99, Rarity.RARE, mage.cards.s.SaprazzanHeir.class)); - cards.add(new SetCardInfo("Saprazzan Legate", 100, Rarity.UNCOMMON, mage.cards.s.SaprazzanLegate.class)); - cards.add(new SetCardInfo("Saprazzan Outrigger", 101, Rarity.COMMON, mage.cards.s.SaprazzanOutrigger.class)); - cards.add(new SetCardInfo("Saprazzan Raider", 102, Rarity.COMMON, mage.cards.s.SaprazzanRaider.class)); - cards.add(new SetCardInfo("Saprazzan Skerry", 328, Rarity.COMMON, mage.cards.s.SaprazzanSkerry.class)); - cards.add(new SetCardInfo("Scandalmonger", 158, Rarity.UNCOMMON, mage.cards.s.Scandalmonger.class)); - cards.add(new SetCardInfo("Security Detail", 47, Rarity.RARE, mage.cards.s.SecurityDetail.class)); - cards.add(new SetCardInfo("Seismic Mage", 211, Rarity.RARE, mage.cards.s.SeismicMage.class)); - cards.add(new SetCardInfo("Sever Soul", 159, Rarity.COMMON, mage.cards.s.SeverSoul.class)); - cards.add(new SetCardInfo("Shock Troops", 212, Rarity.COMMON, mage.cards.s.ShockTroops.class)); - cards.add(new SetCardInfo("Shoving Match", 103, Rarity.UNCOMMON, mage.cards.s.ShovingMatch.class)); - cards.add(new SetCardInfo("Silent Assassin", 160, Rarity.RARE, mage.cards.s.SilentAssassin.class)); - cards.add(new SetCardInfo("Silverglade Elemental", 269, Rarity.COMMON, mage.cards.s.SilvergladeElemental.class)); - cards.add(new SetCardInfo("Silverglade Pathfinder", 270, Rarity.UNCOMMON, mage.cards.s.SilvergladePathfinder.class)); - cards.add(new SetCardInfo("Sizzle", 213, Rarity.COMMON, mage.cards.s.Sizzle.class)); - cards.add(new SetCardInfo("Skulking Fugitive", 161, Rarity.COMMON, mage.cards.s.SkulkingFugitive.class)); - cards.add(new SetCardInfo("Skull of Ramos", 312, Rarity.RARE, mage.cards.s.SkullOfRamos.class)); - cards.add(new SetCardInfo("Snake Pit", 271, Rarity.UNCOMMON, mage.cards.s.SnakePit.class)); - cards.add(new SetCardInfo("Snorting Gahr", 272, Rarity.COMMON, mage.cards.s.SnortingGahr.class)); - cards.add(new SetCardInfo("Snuff Out", 162, Rarity.COMMON, mage.cards.s.SnuffOut.class)); - cards.add(new SetCardInfo("Soothing Balm", 48, Rarity.COMMON, mage.cards.s.SoothingBalm.class)); - cards.add(new SetCardInfo("Soothsaying", 104, Rarity.UNCOMMON, mage.cards.s.Soothsaying.class)); - cards.add(new SetCardInfo("Soul Channeling", 163, Rarity.COMMON, mage.cards.s.SoulChanneling.class)); - cards.add(new SetCardInfo("Specter's Wail", 164, Rarity.COMMON, mage.cards.s.SpectersWail.class)); - cards.add(new SetCardInfo("Spidersilk Armor", 273, Rarity.COMMON, mage.cards.s.SpidersilkArmor.class)); - cards.add(new SetCardInfo("Spiritual Focus", 49, Rarity.RARE, mage.cards.s.SpiritualFocus.class)); - cards.add(new SetCardInfo("Spontaneous Generation", 274, Rarity.RARE, mage.cards.s.SpontaneousGeneration.class)); - cards.add(new SetCardInfo("Squall", 275, Rarity.COMMON, mage.cards.s.Squall.class)); - cards.add(new SetCardInfo("Squallmonger", 276, Rarity.UNCOMMON, mage.cards.s.Squallmonger.class)); - cards.add(new SetCardInfo("Squee, Goblin Nabob", 214, Rarity.RARE, mage.cards.s.SqueeGoblinNabob.class)); - cards.add(new SetCardInfo("Squeeze", 105, Rarity.RARE, mage.cards.s.Squeeze.class)); - cards.add(new SetCardInfo("Stamina", 277, Rarity.UNCOMMON, mage.cards.s.Stamina.class)); - cards.add(new SetCardInfo("Statecraft", 106, Rarity.RARE, mage.cards.s.Statecraft.class)); - cards.add(new SetCardInfo("Steadfast Guard", 50, Rarity.COMMON, mage.cards.s.SteadfastGuard.class)); - cards.add(new SetCardInfo("Stinging Barrier", 107, Rarity.COMMON, mage.cards.s.StingingBarrier.class)); - cards.add(new SetCardInfo("Stone Rain", 215, Rarity.COMMON, mage.cards.s.StoneRain.class)); - cards.add(new SetCardInfo("Story Circle", 51, Rarity.UNCOMMON, mage.cards.s.StoryCircle.class)); - cards.add(new SetCardInfo("Strongarm Thug", 165, Rarity.UNCOMMON, mage.cards.s.StrongarmThug.class)); - cards.add(new SetCardInfo("Subterranean Hangar", 329, Rarity.UNCOMMON, mage.cards.s.SubterraneanHangar.class)); - cards.add(new SetCardInfo("Sustenance", 278, Rarity.UNCOMMON, mage.cards.s.Sustenance.class)); - cards.add(new SetCardInfo("Swamp", 339, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 340, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 341, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 342, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Task Force", 52, Rarity.COMMON, mage.cards.t.TaskForce.class)); - cards.add(new SetCardInfo("Tectonic Break", 216, Rarity.RARE, mage.cards.t.TectonicBreak.class)); - cards.add(new SetCardInfo("Territorial Dispute", 217, Rarity.RARE, mage.cards.t.TerritorialDispute.class)); - cards.add(new SetCardInfo("Thermal Glider", 53, Rarity.COMMON, mage.cards.t.ThermalGlider.class)); - cards.add(new SetCardInfo("Thieves' Auction", 218, Rarity.RARE, mage.cards.t.ThievesAuction.class)); - cards.add(new SetCardInfo("Thrashing Wumpus", 166, Rarity.RARE, mage.cards.t.ThrashingWumpus.class)); - cards.add(new SetCardInfo("Thunderclap", 219, Rarity.COMMON, mage.cards.t.Thunderclap.class)); - cards.add(new SetCardInfo("Thwart", 108, Rarity.UNCOMMON, mage.cards.t.Thwart.class)); - cards.add(new SetCardInfo("Tidal Bore", 109, Rarity.COMMON, mage.cards.t.TidalBore.class)); - cards.add(new SetCardInfo("Tidal Kraken", 110, Rarity.RARE, mage.cards.t.TidalKraken.class)); - cards.add(new SetCardInfo("Tiger Claws", 279, Rarity.COMMON, mage.cards.t.TigerClaws.class)); - cards.add(new SetCardInfo("Timid Drake", 111, Rarity.UNCOMMON, mage.cards.t.TimidDrake.class)); - cards.add(new SetCardInfo("Tonic Peddler", 54, Rarity.UNCOMMON, mage.cards.t.TonicPeddler.class)); - cards.add(new SetCardInfo("Tooth of Ramos", 313, Rarity.RARE, mage.cards.t.ToothOfRamos.class)); - cards.add(new SetCardInfo("Tower of the Magistrate", 330, Rarity.RARE, mage.cards.t.TowerOfTheMagistrate.class)); - cards.add(new SetCardInfo("Toymaker", 314, Rarity.UNCOMMON, mage.cards.t.Toymaker.class)); - cards.add(new SetCardInfo("Trade Routes", 112, Rarity.RARE, mage.cards.t.TradeRoutes.class)); - cards.add(new SetCardInfo("Tranquility", 280, Rarity.COMMON, mage.cards.t.Tranquility.class)); - cards.add(new SetCardInfo("Trap Runner", 55, Rarity.UNCOMMON, mage.cards.t.TrapRunner.class)); - cards.add(new SetCardInfo("Tremor", 220, Rarity.COMMON, mage.cards.t.Tremor.class)); - cards.add(new SetCardInfo("Two-Headed Dragon", 221, Rarity.RARE, mage.cards.t.TwoHeadedDragon.class)); - cards.add(new SetCardInfo("Undertaker", 167, Rarity.COMMON, mage.cards.u.Undertaker.class)); - cards.add(new SetCardInfo("Unmask", 168, Rarity.RARE, mage.cards.u.Unmask.class)); - cards.add(new SetCardInfo("Unnatural Hunger", 169, Rarity.RARE, mage.cards.u.UnnaturalHunger.class)); - cards.add(new SetCardInfo("Uphill Battle", 222, Rarity.UNCOMMON, mage.cards.u.UphillBattle.class)); - cards.add(new SetCardInfo("Vendetta", 170, Rarity.COMMON, mage.cards.v.Vendetta.class)); - cards.add(new SetCardInfo("Venomous Breath", 281, Rarity.UNCOMMON, mage.cards.v.VenomousBreath.class)); - cards.add(new SetCardInfo("Venomous Dragonfly", 282, Rarity.COMMON, mage.cards.v.VenomousDragonfly.class)); - cards.add(new SetCardInfo("Vernal Equinox", 283, Rarity.RARE, mage.cards.v.VernalEquinox.class)); - cards.add(new SetCardInfo("Vine Dryad", 284, Rarity.RARE, mage.cards.v.VineDryad.class)); - cards.add(new SetCardInfo("Vine Trellis", 285, Rarity.COMMON, mage.cards.v.VineTrellis.class)); - cards.add(new SetCardInfo("Volcanic Wind", 223, Rarity.UNCOMMON, mage.cards.v.VolcanicWind.class)); - cards.add(new SetCardInfo("Wall of Distortion", 171, Rarity.COMMON, mage.cards.w.WallOfDistortion.class)); - cards.add(new SetCardInfo("War Cadence", 224, Rarity.UNCOMMON, mage.cards.w.WarCadence.class)); - cards.add(new SetCardInfo("War Tax", 113, Rarity.UNCOMMON, mage.cards.w.WarTax.class)); - cards.add(new SetCardInfo("Warmonger", 225, Rarity.UNCOMMON, mage.cards.w.Warmonger.class)); - cards.add(new SetCardInfo("Warpath", 226, Rarity.UNCOMMON, mage.cards.w.Warpath.class)); - cards.add(new SetCardInfo("Waterfront Bouncer", 114, Rarity.COMMON, mage.cards.w.WaterfrontBouncer.class)); - cards.add(new SetCardInfo("Wave of Reckoning", 56, Rarity.RARE, mage.cards.w.WaveOfReckoning.class)); - cards.add(new SetCardInfo("Wild Jhovall", 227, Rarity.COMMON, mage.cards.w.WildJhovall.class)); - cards.add(new SetCardInfo("Wishmonger", 57, Rarity.UNCOMMON, mage.cards.w.Wishmonger.class)); - cards.add(new SetCardInfo("Word of Blasting", 228, Rarity.UNCOMMON, mage.cards.w.WordOfBlasting.class)); - cards.add(new SetCardInfo("Worry Beads", 315, Rarity.RARE, mage.cards.w.WorryBeads.class)); - } -} +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * @author North + */ +public final class MercadianMasques extends ExpansionSet { + + private static final MercadianMasques instance = new MercadianMasques(); + + public static MercadianMasques getInstance() { + return instance; + } + + private MercadianMasques() { + super("Mercadian Masques", "MMQ", ExpansionSet.buildDate(1999, 8, 25), SetType.EXPANSION); + this.blockName = "Masques"; + this.hasBoosters = true; + this.numBoosterLands = 0; + this.numBoosterCommon = 11; + this.numBoosterUncommon = 3; + this.numBoosterRare = 1; + this.ratioBoosterMythic = 0; + cards.add(new SetCardInfo("Aerial Caravan", 58, Rarity.RARE, mage.cards.a.AerialCaravan.class)); + cards.add(new SetCardInfo("Afterlife", 1, Rarity.UNCOMMON, mage.cards.a.Afterlife.class)); + cards.add(new SetCardInfo("Alabaster Wall", 2, Rarity.COMMON, mage.cards.a.AlabasterWall.class)); + cards.add(new SetCardInfo("Alley Grifters", 115, Rarity.COMMON, mage.cards.a.AlleyGrifters.class)); + cards.add(new SetCardInfo("Ancestral Mask", 229, Rarity.COMMON, mage.cards.a.AncestralMask.class)); + cards.add(new SetCardInfo("Armistice", 3, Rarity.RARE, mage.cards.a.Armistice.class)); + cards.add(new SetCardInfo("Arms Dealer", 172, Rarity.UNCOMMON, mage.cards.a.ArmsDealer.class)); + cards.add(new SetCardInfo("Arrest", 4, Rarity.UNCOMMON, mage.cards.a.Arrest.class)); + cards.add(new SetCardInfo("Assembly Hall", 286, Rarity.RARE, mage.cards.a.AssemblyHall.class)); + cards.add(new SetCardInfo("Ballista Squad", 5, Rarity.UNCOMMON, mage.cards.b.BallistaSquad.class)); + cards.add(new SetCardInfo("Balloon Peddler", 59, Rarity.COMMON, mage.cards.b.BalloonPeddler.class)); + cards.add(new SetCardInfo("Barbed Wire", 287, Rarity.UNCOMMON, mage.cards.b.BarbedWire.class)); + cards.add(new SetCardInfo("Battle Rampart", 173, Rarity.COMMON, mage.cards.b.BattleRampart.class)); + cards.add(new SetCardInfo("Battle Squadron", 174, Rarity.RARE, mage.cards.b.BattleSquadron.class)); + cards.add(new SetCardInfo("Bifurcate", 230, Rarity.RARE, mage.cards.b.Bifurcate.class)); + cards.add(new SetCardInfo("Black Market", 116, Rarity.RARE, mage.cards.b.BlackMarket.class)); + cards.add(new SetCardInfo("Blaster Mage", 175, Rarity.COMMON, mage.cards.b.BlasterMage.class)); + cards.add(new SetCardInfo("Blockade Runner", 60, Rarity.COMMON, mage.cards.b.BlockadeRunner.class)); + cards.add(new SetCardInfo("Blood Hound", 176, Rarity.RARE, mage.cards.b.BloodHound.class)); + cards.add(new SetCardInfo("Blood Oath", 177, Rarity.RARE, mage.cards.b.BloodOath.class)); + cards.add(new SetCardInfo("Boa Constrictor", 231, Rarity.UNCOMMON, mage.cards.b.BoaConstrictor.class)); + cards.add(new SetCardInfo("Bog Smugglers", 117, Rarity.COMMON, mage.cards.b.BogSmugglers.class)); + cards.add(new SetCardInfo("Bog Witch", 118, Rarity.COMMON, mage.cards.b.BogWitch.class)); + cards.add(new SetCardInfo("Brainstorm", 61, Rarity.COMMON, mage.cards.b.Brainstorm.class)); + cards.add(new SetCardInfo("Brawl", 178, Rarity.RARE, mage.cards.b.Brawl.class)); + cards.add(new SetCardInfo("Briar Patch", 232, Rarity.UNCOMMON, mage.cards.b.BriarPatch.class)); + cards.add(new SetCardInfo("Bribery", 62, Rarity.RARE, mage.cards.b.Bribery.class)); + cards.add(new SetCardInfo("Buoyancy", 63, Rarity.COMMON, mage.cards.b.Buoyancy.class)); + cards.add(new SetCardInfo("Cackling Witch", 119, Rarity.UNCOMMON, mage.cards.c.CacklingWitch.class)); + cards.add(new SetCardInfo("Caller of the Hunt", 233, Rarity.RARE, mage.cards.c.CallerOfTheHunt.class)); + cards.add(new SetCardInfo("Cateran Brute", 120, Rarity.COMMON, mage.cards.c.CateranBrute.class)); + cards.add(new SetCardInfo("Cateran Enforcer", 121, Rarity.UNCOMMON, mage.cards.c.CateranEnforcer.class)); + cards.add(new SetCardInfo("Cateran Kidnappers", 122, Rarity.UNCOMMON, mage.cards.c.CateranKidnappers.class)); + cards.add(new SetCardInfo("Cateran Overlord", 123, Rarity.RARE, mage.cards.c.CateranOverlord.class)); + cards.add(new SetCardInfo("Cateran Persuader", 124, Rarity.COMMON, mage.cards.c.CateranPersuader.class)); + cards.add(new SetCardInfo("Cateran Slaver", 125, Rarity.RARE, mage.cards.c.CateranSlaver.class)); + cards.add(new SetCardInfo("Cateran Summons", 126, Rarity.UNCOMMON, mage.cards.c.CateranSummons.class)); + cards.add(new SetCardInfo("Caustic Wasps", 234, Rarity.UNCOMMON, mage.cards.c.CausticWasps.class)); + cards.add(new SetCardInfo("Cave-In", 180, Rarity.RARE, mage.cards.c.CaveIn.class)); + cards.add(new SetCardInfo("Cavern Crawler", 181, Rarity.COMMON, mage.cards.c.CavernCrawler.class)); + cards.add(new SetCardInfo("Cave Sense", 179, Rarity.COMMON, mage.cards.c.CaveSense.class)); + cards.add(new SetCardInfo("Ceremonial Guard", 182, Rarity.COMMON, mage.cards.c.CeremonialGuard.class)); + cards.add(new SetCardInfo("Chambered Nautilus", 64, Rarity.UNCOMMON, mage.cards.c.ChamberedNautilus.class)); + cards.add(new SetCardInfo("Chameleon Spirit", 65, Rarity.UNCOMMON, mage.cards.c.ChameleonSpirit.class)); + cards.add(new SetCardInfo("Charisma", 66, Rarity.RARE, mage.cards.c.Charisma.class)); + cards.add(new SetCardInfo("Charm Peddler", 6, Rarity.COMMON, mage.cards.c.CharmPeddler.class)); + cards.add(new SetCardInfo("Charmed Griffin", 7, Rarity.UNCOMMON, mage.cards.c.CharmedGriffin.class)); + cards.add(new SetCardInfo("Cho-Arrim Alchemist", 8, Rarity.RARE, mage.cards.c.ChoArrimAlchemist.class)); + cards.add(new SetCardInfo("Cho-Arrim Bruiser", 9, Rarity.RARE, mage.cards.c.ChoArrimBruiser.class)); + cards.add(new SetCardInfo("Cho-Arrim Legate", 10, Rarity.UNCOMMON, mage.cards.c.ChoArrimLegate.class)); + cards.add(new SetCardInfo("Cho-Manno, Revolutionary", 11, Rarity.RARE, mage.cards.c.ChoMannoRevolutionary.class)); + cards.add(new SetCardInfo("Cho-Manno's Blessing", 12, Rarity.COMMON, mage.cards.c.ChoMannosBlessing.class)); + cards.add(new SetCardInfo("Cinder Elemental", 183, Rarity.UNCOMMON, mage.cards.c.CinderElemental.class)); + cards.add(new SetCardInfo("Clear the Land", 235, Rarity.RARE, mage.cards.c.ClearTheLand.class)); + cards.add(new SetCardInfo("Close Quarters", 184, Rarity.UNCOMMON, mage.cards.c.CloseQuarters.class)); + cards.add(new SetCardInfo("Cloud Sprite", 67, Rarity.COMMON, mage.cards.c.CloudSprite.class)); + cards.add(new SetCardInfo("Coastal Piracy", 68, Rarity.UNCOMMON, mage.cards.c.CoastalPiracy.class)); + cards.add(new SetCardInfo("Collective Unconscious", 236, Rarity.RARE, mage.cards.c.CollectiveUnconscious.class)); + cards.add(new SetCardInfo("Common Cause", 13, Rarity.RARE, mage.cards.c.CommonCause.class)); + cards.add(new SetCardInfo("Conspiracy", 127, Rarity.RARE, mage.cards.c.Conspiracy.class)); + cards.add(new SetCardInfo("Cornered Market", 14, Rarity.RARE, mage.cards.c.CorneredMarket.class)); + cards.add(new SetCardInfo("Corrupt Official", 128, Rarity.RARE, mage.cards.c.CorruptOfficial.class)); + cards.add(new SetCardInfo("Counterspell", 69, Rarity.COMMON, mage.cards.c.Counterspell.class)); + cards.add(new SetCardInfo("Cowardice", 70, Rarity.RARE, mage.cards.c.Cowardice.class)); + cards.add(new SetCardInfo("Crackdown", 15, Rarity.RARE, mage.cards.c.Crackdown.class)); + cards.add(new SetCardInfo("Crag Saurian", 185, Rarity.RARE, mage.cards.c.CragSaurian.class)); + cards.add(new SetCardInfo("Crash", 186, Rarity.COMMON, mage.cards.c.Crash.class)); + cards.add(new SetCardInfo("Credit Voucher", 289, Rarity.UNCOMMON, mage.cards.c.CreditVoucher.class)); + cards.add(new SetCardInfo("Crenellated Wall", 290, Rarity.UNCOMMON, mage.cards.c.CrenellatedWall.class)); + cards.add(new SetCardInfo("Crooked Scales", 291, Rarity.RARE, mage.cards.c.CrookedScales.class)); + cards.add(new SetCardInfo("Crossbow Infantry", 16, Rarity.COMMON, mage.cards.c.CrossbowInfantry.class)); + cards.add(new SetCardInfo("Crumbling Sanctuary", 292, Rarity.RARE, mage.cards.c.CrumblingSanctuary.class)); + cards.add(new SetCardInfo("Customs Depot", 71, Rarity.UNCOMMON, mage.cards.c.CustomsDepot.class)); + cards.add(new SetCardInfo("Dark Ritual", 129, Rarity.COMMON, mage.cards.d.DarkRitual.class)); + cards.add(new SetCardInfo("Darting Merfolk", 72, Rarity.COMMON, mage.cards.d.DartingMerfolk.class)); + cards.add(new SetCardInfo("Dawnstrider", 237, Rarity.RARE, mage.cards.d.Dawnstrider.class)); + cards.add(new SetCardInfo("Deadly Insect", 238, Rarity.COMMON, mage.cards.d.DeadlyInsect.class)); + cards.add(new SetCardInfo("Deathgazer", 130, Rarity.UNCOMMON, mage.cards.d.Deathgazer.class)); + cards.add(new SetCardInfo("Deepwood Drummer", 239, Rarity.COMMON, mage.cards.d.DeepwoodDrummer.class)); + cards.add(new SetCardInfo("Deepwood Elder", 240, Rarity.RARE, mage.cards.d.DeepwoodElder.class)); + cards.add(new SetCardInfo("Deepwood Ghoul", 131, Rarity.COMMON, mage.cards.d.DeepwoodGhoul.class)); + cards.add(new SetCardInfo("Deepwood Legate", 132, Rarity.UNCOMMON, mage.cards.d.DeepwoodLegate.class)); + cards.add(new SetCardInfo("Deepwood Tantiv", 241, Rarity.UNCOMMON, mage.cards.d.DeepwoodTantiv.class)); + cards.add(new SetCardInfo("Deepwood Wolverine", 242, Rarity.COMMON, mage.cards.d.DeepwoodWolverine.class)); + cards.add(new SetCardInfo("Dehydration", 73, Rarity.COMMON, mage.cards.d.Dehydration.class)); + cards.add(new SetCardInfo("Delraich", 133, Rarity.RARE, mage.cards.d.Delraich.class)); + cards.add(new SetCardInfo("Desert Twister", 243, Rarity.UNCOMMON, mage.cards.d.DesertTwister.class)); + cards.add(new SetCardInfo("Devout Witness", 17, Rarity.COMMON, mage.cards.d.DevoutWitness.class)); + cards.add(new SetCardInfo("Diplomatic Escort", 74, Rarity.UNCOMMON, mage.cards.d.DiplomaticEscort.class)); + cards.add(new SetCardInfo("Diplomatic Immunity", 75, Rarity.COMMON, mage.cards.d.DiplomaticImmunity.class)); + cards.add(new SetCardInfo("Disenchant", 18, Rarity.COMMON, mage.cards.d.Disenchant.class)); + cards.add(new SetCardInfo("Distorting Lens", 293, Rarity.RARE, mage.cards.d.DistortingLens.class)); + cards.add(new SetCardInfo("Drake Hatchling", 76, Rarity.COMMON, mage.cards.d.DrakeHatchling.class)); + cards.add(new SetCardInfo("Dust Bowl", 316, Rarity.RARE, mage.cards.d.DustBowl.class)); + cards.add(new SetCardInfo("Embargo", 77, Rarity.RARE, mage.cards.e.Embargo.class)); + cards.add(new SetCardInfo("Energy Flux", 78, Rarity.UNCOMMON, mage.cards.e.EnergyFlux.class)); + cards.add(new SetCardInfo("Enslaved Horror", 134, Rarity.UNCOMMON, mage.cards.e.EnslavedHorror.class)); + cards.add(new SetCardInfo("Extortion", 135, Rarity.RARE, mage.cards.e.Extortion.class)); + cards.add(new SetCardInfo("Extravagant Spirit", 79, Rarity.RARE, mage.cards.e.ExtravagantSpirit.class)); + cards.add(new SetCardInfo("Eye of Ramos", 294, Rarity.RARE, mage.cards.e.EyeOfRamos.class)); + cards.add(new SetCardInfo("False Demise", 80, Rarity.UNCOMMON, mage.cards.f.FalseDemise.class)); + cards.add(new SetCardInfo("Ferocity", 245, Rarity.COMMON, mage.cards.f.Ferocity.class)); + cards.add(new SetCardInfo("Flailing Manticore", 187, Rarity.RARE, mage.cards.f.FlailingManticore.class)); + cards.add(new SetCardInfo("Flailing Ogre", 188, Rarity.UNCOMMON, mage.cards.f.FlailingOgre.class)); + cards.add(new SetCardInfo("Flailing Soldier", 189, Rarity.COMMON, mage.cards.f.FlailingSoldier.class)); + cards.add(new SetCardInfo("Flaming Sword", 190, Rarity.COMMON, mage.cards.f.FlamingSword.class)); + cards.add(new SetCardInfo("Food Chain", 246, Rarity.RARE, mage.cards.f.FoodChain.class)); + cards.add(new SetCardInfo("Forced March", 136, Rarity.RARE, mage.cards.f.ForcedMarch.class)); + cards.add(new SetCardInfo("Forest", 347, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 348, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 349, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 350, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Foster", 247, Rarity.RARE, mage.cards.f.Foster.class)); + cards.add(new SetCardInfo("Fountain of Cho", 317, Rarity.UNCOMMON, mage.cards.f.FountainOfCho.class)); + cards.add(new SetCardInfo("Fountain Watch", 19, Rarity.RARE, mage.cards.f.FountainWatch.class)); + cards.add(new SetCardInfo("Fresh Volunteers", 20, Rarity.COMMON, mage.cards.f.FreshVolunteers.class)); + cards.add(new SetCardInfo("Furious Assault", 191, Rarity.COMMON, mage.cards.f.FuriousAssault.class)); + cards.add(new SetCardInfo("Game Preserve", 248, Rarity.RARE, mage.cards.g.GamePreserve.class)); + cards.add(new SetCardInfo("General's Regalia", 295, Rarity.RARE, mage.cards.g.GeneralsRegalia.class)); + cards.add(new SetCardInfo("Gerrard's Irregulars", 192, Rarity.COMMON, mage.cards.g.GerrardsIrregulars.class)); + cards.add(new SetCardInfo("Ghoul's Feast", 137, Rarity.UNCOMMON, mage.cards.g.GhoulsFeast.class)); + cards.add(new SetCardInfo("Giant Caterpillar", 249, Rarity.COMMON, mage.cards.g.GiantCaterpillar.class)); + cards.add(new SetCardInfo("Glowing Anemone", 81, Rarity.UNCOMMON, mage.cards.g.GlowingAnemone.class)); + cards.add(new SetCardInfo("Groundskeeper", 250, Rarity.UNCOMMON, mage.cards.g.Groundskeeper.class)); + cards.add(new SetCardInfo("Gush", 82, Rarity.COMMON, mage.cards.g.Gush.class)); + cards.add(new SetCardInfo("Hammer Mage", 193, Rarity.UNCOMMON, mage.cards.h.HammerMage.class)); + cards.add(new SetCardInfo("Haunted Crossroads", 138, Rarity.UNCOMMON, mage.cards.h.HauntedCrossroads.class)); + cards.add(new SetCardInfo("Heart of Ramos", 296, Rarity.RARE, mage.cards.h.HeartOfRamos.class)); + cards.add(new SetCardInfo("Henge Guardian", 297, Rarity.UNCOMMON, mage.cards.h.HengeGuardian.class)); + cards.add(new SetCardInfo("Henge of Ramos", 318, Rarity.UNCOMMON, mage.cards.h.HengeOfRamos.class)); + cards.add(new SetCardInfo("Hickory Woodlot", 319, Rarity.COMMON, mage.cards.h.HickoryWoodlot.class)); + cards.add(new SetCardInfo("High Market", 320, Rarity.RARE, mage.cards.h.HighMarket.class)); + cards.add(new SetCardInfo("High Seas", 83, Rarity.UNCOMMON, mage.cards.h.HighSeas.class)); + cards.add(new SetCardInfo("Highway Robber", 139, Rarity.COMMON, mage.cards.h.HighwayRobber.class)); + cards.add(new SetCardInfo("Hired Giant", 194, Rarity.UNCOMMON, mage.cards.h.HiredGiant.class)); + cards.add(new SetCardInfo("Honor the Fallen", 21, Rarity.RARE, mage.cards.h.HonorTheFallen.class)); + cards.add(new SetCardInfo("Hoodwink", 84, Rarity.COMMON, mage.cards.h.Hoodwink.class)); + cards.add(new SetCardInfo("Horned Troll", 251, Rarity.COMMON, mage.cards.h.HornedTroll.class)); + cards.add(new SetCardInfo("Horn of Plenty", 298, Rarity.RARE, mage.cards.h.HornOfPlenty.class)); + cards.add(new SetCardInfo("Horn of Ramos", 299, Rarity.RARE, mage.cards.h.HornOfRamos.class)); + cards.add(new SetCardInfo("Howling Wolf", 252, Rarity.COMMON, mage.cards.h.HowlingWolf.class)); + cards.add(new SetCardInfo("Hunted Wumpus", 253, Rarity.UNCOMMON, mage.cards.h.HuntedWumpus.class)); + cards.add(new SetCardInfo("Ignoble Soldier", 22, Rarity.UNCOMMON, mage.cards.i.IgnobleSoldier.class)); + cards.add(new SetCardInfo("Indentured Djinn", 85, Rarity.UNCOMMON, mage.cards.i.IndenturedDjinn.class)); + cards.add(new SetCardInfo("Instigator", 140, Rarity.RARE, mage.cards.i.Instigator.class)); + cards.add(new SetCardInfo("Insubordination", 141, Rarity.COMMON, mage.cards.i.Insubordination.class)); + cards.add(new SetCardInfo("Intimidation", 142, Rarity.UNCOMMON, mage.cards.i.Intimidation.class)); + cards.add(new SetCardInfo("Invigorate", 254, Rarity.COMMON, mage.cards.i.Invigorate.class)); + cards.add(new SetCardInfo("Inviolability", 23, Rarity.COMMON, mage.cards.i.Inviolability.class)); + cards.add(new SetCardInfo("Iron Lance", 300, Rarity.UNCOMMON, mage.cards.i.IronLance.class)); + cards.add(new SetCardInfo("Island", 335, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 336, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 337, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 338, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ivory Mask", 24, Rarity.RARE, mage.cards.i.IvoryMask.class)); + cards.add(new SetCardInfo("Jeweled Torque", 301, Rarity.UNCOMMON, mage.cards.j.JeweledTorque.class)); + cards.add(new SetCardInfo("Jhovall Queen", 25, Rarity.RARE, mage.cards.j.JhovallQueen.class)); + cards.add(new SetCardInfo("Jhovall Rider", 26, Rarity.UNCOMMON, mage.cards.j.JhovallRider.class)); + cards.add(new SetCardInfo("Karn's Touch", 86, Rarity.RARE, mage.cards.k.KarnsTouch.class)); + cards.add(new SetCardInfo("Kris Mage", 195, Rarity.COMMON, mage.cards.k.KrisMage.class)); + cards.add(new SetCardInfo("Kyren Archive", 302, Rarity.RARE, mage.cards.k.KyrenArchive.class)); + cards.add(new SetCardInfo("Kyren Glider", 196, Rarity.COMMON, mage.cards.k.KyrenGlider.class)); + cards.add(new SetCardInfo("Kyren Legate", 197, Rarity.UNCOMMON, mage.cards.k.KyrenLegate.class)); + cards.add(new SetCardInfo("Kyren Negotiations", 198, Rarity.UNCOMMON, mage.cards.k.KyrenNegotiations.class)); + cards.add(new SetCardInfo("Kyren Sniper", 199, Rarity.COMMON, mage.cards.k.KyrenSniper.class)); + cards.add(new SetCardInfo("Kyren Toy", 303, Rarity.RARE, mage.cards.k.KyrenToy.class)); + cards.add(new SetCardInfo("Land Grant", 255, Rarity.COMMON, mage.cards.l.LandGrant.class)); + cards.add(new SetCardInfo("Larceny", 143, Rarity.UNCOMMON, mage.cards.l.Larceny.class)); + cards.add(new SetCardInfo("Last Breath", 27, Rarity.UNCOMMON, mage.cards.l.LastBreath.class)); + cards.add(new SetCardInfo("Lava Runner", 200, Rarity.RARE, mage.cards.l.LavaRunner.class)); + cards.add(new SetCardInfo("Liability", 144, Rarity.RARE, mage.cards.l.Liability.class)); + cards.add(new SetCardInfo("Lightning Hounds", 201, Rarity.COMMON, mage.cards.l.LightningHounds.class)); + cards.add(new SetCardInfo("Lithophage", 202, Rarity.RARE, mage.cards.l.Lithophage.class)); + cards.add(new SetCardInfo("Lumbering Satyr", 257, Rarity.UNCOMMON, mage.cards.l.LumberingSatyr.class)); + cards.add(new SetCardInfo("Lunge", 203, Rarity.COMMON, mage.cards.l.Lunge.class)); + cards.add(new SetCardInfo("Lure", 258, Rarity.UNCOMMON, mage.cards.l.Lure.class)); + cards.add(new SetCardInfo("Maggot Therapy", 145, Rarity.COMMON, mage.cards.m.MaggotTherapy.class)); + cards.add(new SetCardInfo("Magistrate's Scepter", 304, Rarity.RARE, mage.cards.m.MagistratesScepter.class)); + cards.add(new SetCardInfo("Magistrate's Veto", 204, Rarity.UNCOMMON, mage.cards.m.MagistratesVeto.class)); + cards.add(new SetCardInfo("Megatherium", 259, Rarity.RARE, mage.cards.m.Megatherium.class)); + cards.add(new SetCardInfo("Mercadia's Downfall", 205, Rarity.UNCOMMON, mage.cards.m.MercadiasDownfall.class)); + cards.add(new SetCardInfo("Mercadian Atlas", 305, Rarity.RARE, mage.cards.m.MercadianAtlas.class)); + cards.add(new SetCardInfo("Mercadian Bazaar", 321, Rarity.UNCOMMON, mage.cards.m.MercadianBazaar.class)); + cards.add(new SetCardInfo("Mercadian Lift", 306, Rarity.RARE, mage.cards.m.MercadianLift.class)); + cards.add(new SetCardInfo("Midnight Ritual", 146, Rarity.RARE, mage.cards.m.MidnightRitual.class)); + cards.add(new SetCardInfo("Misdirection", 87, Rarity.RARE, mage.cards.m.Misdirection.class)); + cards.add(new SetCardInfo("Misshapen Fiend", 147, Rarity.COMMON, mage.cards.m.MisshapenFiend.class)); + cards.add(new SetCardInfo("Misstep", 88, Rarity.COMMON, mage.cards.m.Misstep.class)); + cards.add(new SetCardInfo("Molting Harpy", 148, Rarity.UNCOMMON, mage.cards.m.MoltingHarpy.class)); + cards.add(new SetCardInfo("Moment of Silence", 28, Rarity.COMMON, mage.cards.m.MomentOfSilence.class)); + cards.add(new SetCardInfo("Monkey Cage", 307, Rarity.RARE, mage.cards.m.MonkeyCage.class)); + cards.add(new SetCardInfo("Moonlit Wake", 29, Rarity.UNCOMMON, mage.cards.m.MoonlitWake.class)); + cards.add(new SetCardInfo("Mountain", 343, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 344, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 345, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 346, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Muzzle", 30, Rarity.COMMON, mage.cards.m.Muzzle.class)); + cards.add(new SetCardInfo("Natural Affinity", 260, Rarity.RARE, mage.cards.n.NaturalAffinity.class)); + cards.add(new SetCardInfo("Nether Spirit", 149, Rarity.RARE, mage.cards.n.NetherSpirit.class)); + cards.add(new SetCardInfo("Nightwind Glider", 31, Rarity.COMMON, mage.cards.n.NightwindGlider.class)); + cards.add(new SetCardInfo("Noble Purpose", 32, Rarity.UNCOMMON, mage.cards.n.NoblePurpose.class)); + cards.add(new SetCardInfo("Notorious Assassin", 150, Rarity.RARE, mage.cards.n.NotoriousAssassin.class)); + cards.add(new SetCardInfo("Ogre Taskmaster", 206, Rarity.UNCOMMON, mage.cards.o.OgreTaskmaster.class)); + cards.add(new SetCardInfo("Orim's Cure", 33, Rarity.COMMON, mage.cards.o.OrimsCure.class)); + cards.add(new SetCardInfo("Overtaker", 89, Rarity.RARE, mage.cards.o.Overtaker.class)); + cards.add(new SetCardInfo("Panacea", 308, Rarity.UNCOMMON, mage.cards.p.Panacea.class)); + cards.add(new SetCardInfo("Pangosaur", 261, Rarity.RARE, mage.cards.p.Pangosaur.class)); + cards.add(new SetCardInfo("Peat Bog", 322, Rarity.COMMON, mage.cards.p.PeatBog.class)); + cards.add(new SetCardInfo("Pious Warrior", 34, Rarity.COMMON, mage.cards.p.PiousWarrior.class)); + cards.add(new SetCardInfo("Plains", 331, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 332, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 333, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 334, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Port Inspector", 90, Rarity.COMMON, mage.cards.p.PortInspector.class)); + cards.add(new SetCardInfo("Power Matrix", 309, Rarity.RARE, mage.cards.p.PowerMatrix.class)); + cards.add(new SetCardInfo("Pretender's Claim", 151, Rarity.UNCOMMON, mage.cards.p.PretendersClaim.class)); + cards.add(new SetCardInfo("Primeval Shambler", 152, Rarity.UNCOMMON, mage.cards.p.PrimevalShambler.class)); + cards.add(new SetCardInfo("Puffer Extract", 310, Rarity.UNCOMMON, mage.cards.p.PufferExtract.class)); + cards.add(new SetCardInfo("Pulverize", 207, Rarity.RARE, mage.cards.p.Pulverize.class)); + cards.add(new SetCardInfo("Putrefaction", 153, Rarity.UNCOMMON, mage.cards.p.Putrefaction.class)); + cards.add(new SetCardInfo("Puppet's Verdict", 208, Rarity.RARE, mage.cards.p.PuppetsVerdict.class)); + cards.add(new SetCardInfo("Quagmire Lamprey", 154, Rarity.UNCOMMON, mage.cards.q.QuagmireLamprey.class)); + cards.add(new SetCardInfo("Rain of Tears", 155, Rarity.UNCOMMON, mage.cards.r.RainOfTears.class)); + cards.add(new SetCardInfo("Ramosian Captain", 35, Rarity.UNCOMMON, mage.cards.r.RamosianCaptain.class)); + cards.add(new SetCardInfo("Ramosian Commander", 36, Rarity.UNCOMMON, mage.cards.r.RamosianCommander.class)); + cards.add(new SetCardInfo("Ramosian Lieutenant", 37, Rarity.COMMON, mage.cards.r.RamosianLieutenant.class)); + cards.add(new SetCardInfo("Ramosian Rally", 38, Rarity.COMMON, mage.cards.r.RamosianRally.class)); + cards.add(new SetCardInfo("Ramosian Sergeant", 39, Rarity.COMMON, mage.cards.r.RamosianSergeant.class)); + cards.add(new SetCardInfo("Ramosian Sky Marshal", 40, Rarity.RARE, mage.cards.r.RamosianSkyMarshal.class)); + cards.add(new SetCardInfo("Rampart Crawler", 156, Rarity.COMMON, mage.cards.r.RampartCrawler.class)); + cards.add(new SetCardInfo("Rappelling Scouts", 41, Rarity.RARE, mage.cards.r.RappellingScouts.class)); + cards.add(new SetCardInfo("Remote Farm", 323, Rarity.COMMON, mage.cards.r.RemoteFarm.class)); + cards.add(new SetCardInfo("Renounce", 42, Rarity.UNCOMMON, mage.cards.r.Renounce.class)); + cards.add(new SetCardInfo("Revered Elder", 43, Rarity.COMMON, mage.cards.r.ReveredElder.class)); + cards.add(new SetCardInfo("Reverent Mantra", 44, Rarity.RARE, mage.cards.r.ReverentMantra.class)); + cards.add(new SetCardInfo("Revive", 262, Rarity.UNCOMMON, mage.cards.r.Revive.class)); + cards.add(new SetCardInfo("Righteous Aura", 45, Rarity.UNCOMMON, mage.cards.r.RighteousAura.class)); + cards.add(new SetCardInfo("Righteous Indignation", 46, Rarity.UNCOMMON, mage.cards.r.RighteousIndignation.class)); + cards.add(new SetCardInfo("Rishadan Airship", 91, Rarity.COMMON, mage.cards.r.RishadanAirship.class)); + cards.add(new SetCardInfo("Rishadan Brigand", 92, Rarity.RARE, mage.cards.r.RishadanBrigand.class)); + cards.add(new SetCardInfo("Rishadan Cutpurse", 93, Rarity.COMMON, mage.cards.r.RishadanCutpurse.class)); + cards.add(new SetCardInfo("Rishadan Footpad", 94, Rarity.UNCOMMON, mage.cards.r.RishadanFootpad.class)); + cards.add(new SetCardInfo("Rishadan Pawnshop", 311, Rarity.RARE, mage.cards.r.RishadanPawnshop.class)); + cards.add(new SetCardInfo("Rishadan Port", 324, Rarity.RARE, mage.cards.r.RishadanPort.class)); + cards.add(new SetCardInfo("Robber Fly", 209, Rarity.UNCOMMON, mage.cards.r.RobberFly.class)); + cards.add(new SetCardInfo("Rock Badger", 210, Rarity.UNCOMMON, mage.cards.r.RockBadger.class)); + cards.add(new SetCardInfo("Rouse", 157, Rarity.COMMON, mage.cards.r.Rouse.class)); + cards.add(new SetCardInfo("Rushwood Dryad", 263, Rarity.COMMON, mage.cards.r.RushwoodDryad.class)); + cards.add(new SetCardInfo("Rushwood Elemental", 264, Rarity.RARE, mage.cards.r.RushwoodElemental.class)); + cards.add(new SetCardInfo("Rushwood Grove", 325, Rarity.UNCOMMON, mage.cards.r.RushwoodGrove.class)); + cards.add(new SetCardInfo("Rushwood Herbalist", 265, Rarity.COMMON, mage.cards.r.RushwoodHerbalist.class)); + cards.add(new SetCardInfo("Rushwood Legate", 266, Rarity.UNCOMMON, mage.cards.r.RushwoodLegate.class)); + cards.add(new SetCardInfo("Saber Ants", 267, Rarity.UNCOMMON, mage.cards.s.SaberAnts.class)); + cards.add(new SetCardInfo("Sacred Prey", 268, Rarity.COMMON, mage.cards.s.SacredPrey.class)); + cards.add(new SetCardInfo("Sailmonger", 95, Rarity.UNCOMMON, mage.cards.s.Sailmonger.class)); + cards.add(new SetCardInfo("Sand Squid", 96, Rarity.RARE, mage.cards.s.SandSquid.class)); + cards.add(new SetCardInfo("Sandstone Needle", 326, Rarity.COMMON, mage.cards.s.SandstoneNeedle.class)); + cards.add(new SetCardInfo("Saprazzan Bailiff", 97, Rarity.RARE, mage.cards.s.SaprazzanBailiff.class)); + cards.add(new SetCardInfo("Saprazzan Breaker", 98, Rarity.UNCOMMON, mage.cards.s.SaprazzanBreaker.class)); + cards.add(new SetCardInfo("Saprazzan Cove", 327, Rarity.UNCOMMON, mage.cards.s.SaprazzanCove.class)); + cards.add(new SetCardInfo("Saprazzan Heir", 99, Rarity.RARE, mage.cards.s.SaprazzanHeir.class)); + cards.add(new SetCardInfo("Saprazzan Legate", 100, Rarity.UNCOMMON, mage.cards.s.SaprazzanLegate.class)); + cards.add(new SetCardInfo("Saprazzan Outrigger", 101, Rarity.COMMON, mage.cards.s.SaprazzanOutrigger.class)); + cards.add(new SetCardInfo("Saprazzan Raider", 102, Rarity.COMMON, mage.cards.s.SaprazzanRaider.class)); + cards.add(new SetCardInfo("Saprazzan Skerry", 328, Rarity.COMMON, mage.cards.s.SaprazzanSkerry.class)); + cards.add(new SetCardInfo("Scandalmonger", 158, Rarity.UNCOMMON, mage.cards.s.Scandalmonger.class)); + cards.add(new SetCardInfo("Security Detail", 47, Rarity.RARE, mage.cards.s.SecurityDetail.class)); + cards.add(new SetCardInfo("Seismic Mage", 211, Rarity.RARE, mage.cards.s.SeismicMage.class)); + cards.add(new SetCardInfo("Sever Soul", 159, Rarity.COMMON, mage.cards.s.SeverSoul.class)); + cards.add(new SetCardInfo("Shock Troops", 212, Rarity.COMMON, mage.cards.s.ShockTroops.class)); + cards.add(new SetCardInfo("Shoving Match", 103, Rarity.UNCOMMON, mage.cards.s.ShovingMatch.class)); + cards.add(new SetCardInfo("Silent Assassin", 160, Rarity.RARE, mage.cards.s.SilentAssassin.class)); + cards.add(new SetCardInfo("Silverglade Elemental", 269, Rarity.COMMON, mage.cards.s.SilvergladeElemental.class)); + cards.add(new SetCardInfo("Silverglade Pathfinder", 270, Rarity.UNCOMMON, mage.cards.s.SilvergladePathfinder.class)); + cards.add(new SetCardInfo("Sizzle", 213, Rarity.COMMON, mage.cards.s.Sizzle.class)); + cards.add(new SetCardInfo("Skulking Fugitive", 161, Rarity.COMMON, mage.cards.s.SkulkingFugitive.class)); + cards.add(new SetCardInfo("Skull of Ramos", 312, Rarity.RARE, mage.cards.s.SkullOfRamos.class)); + cards.add(new SetCardInfo("Snake Pit", 271, Rarity.UNCOMMON, mage.cards.s.SnakePit.class)); + cards.add(new SetCardInfo("Snorting Gahr", 272, Rarity.COMMON, mage.cards.s.SnortingGahr.class)); + cards.add(new SetCardInfo("Snuff Out", 162, Rarity.COMMON, mage.cards.s.SnuffOut.class)); + cards.add(new SetCardInfo("Soothing Balm", 48, Rarity.COMMON, mage.cards.s.SoothingBalm.class)); + cards.add(new SetCardInfo("Soothsaying", 104, Rarity.UNCOMMON, mage.cards.s.Soothsaying.class)); + cards.add(new SetCardInfo("Soul Channeling", 163, Rarity.COMMON, mage.cards.s.SoulChanneling.class)); + cards.add(new SetCardInfo("Specter's Wail", 164, Rarity.COMMON, mage.cards.s.SpectersWail.class)); + cards.add(new SetCardInfo("Spidersilk Armor", 273, Rarity.COMMON, mage.cards.s.SpidersilkArmor.class)); + cards.add(new SetCardInfo("Spiritual Focus", 49, Rarity.RARE, mage.cards.s.SpiritualFocus.class)); + cards.add(new SetCardInfo("Spontaneous Generation", 274, Rarity.RARE, mage.cards.s.SpontaneousGeneration.class)); + cards.add(new SetCardInfo("Squall", 275, Rarity.COMMON, mage.cards.s.Squall.class)); + cards.add(new SetCardInfo("Squallmonger", 276, Rarity.UNCOMMON, mage.cards.s.Squallmonger.class)); + cards.add(new SetCardInfo("Squee, Goblin Nabob", 214, Rarity.RARE, mage.cards.s.SqueeGoblinNabob.class)); + cards.add(new SetCardInfo("Squeeze", 105, Rarity.RARE, mage.cards.s.Squeeze.class)); + cards.add(new SetCardInfo("Stamina", 277, Rarity.UNCOMMON, mage.cards.s.Stamina.class)); + cards.add(new SetCardInfo("Statecraft", 106, Rarity.RARE, mage.cards.s.Statecraft.class)); + cards.add(new SetCardInfo("Steadfast Guard", 50, Rarity.COMMON, mage.cards.s.SteadfastGuard.class)); + cards.add(new SetCardInfo("Stinging Barrier", 107, Rarity.COMMON, mage.cards.s.StingingBarrier.class)); + cards.add(new SetCardInfo("Stone Rain", 215, Rarity.COMMON, mage.cards.s.StoneRain.class)); + cards.add(new SetCardInfo("Story Circle", 51, Rarity.UNCOMMON, mage.cards.s.StoryCircle.class)); + cards.add(new SetCardInfo("Strongarm Thug", 165, Rarity.UNCOMMON, mage.cards.s.StrongarmThug.class)); + cards.add(new SetCardInfo("Subterranean Hangar", 329, Rarity.UNCOMMON, mage.cards.s.SubterraneanHangar.class)); + cards.add(new SetCardInfo("Sustenance", 278, Rarity.UNCOMMON, mage.cards.s.Sustenance.class)); + cards.add(new SetCardInfo("Swamp", 339, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 340, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 341, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 342, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Task Force", 52, Rarity.COMMON, mage.cards.t.TaskForce.class)); + cards.add(new SetCardInfo("Tectonic Break", 216, Rarity.RARE, mage.cards.t.TectonicBreak.class)); + cards.add(new SetCardInfo("Territorial Dispute", 217, Rarity.RARE, mage.cards.t.TerritorialDispute.class)); + cards.add(new SetCardInfo("Thermal Glider", 53, Rarity.COMMON, mage.cards.t.ThermalGlider.class)); + cards.add(new SetCardInfo("Thieves' Auction", 218, Rarity.RARE, mage.cards.t.ThievesAuction.class)); + cards.add(new SetCardInfo("Thrashing Wumpus", 166, Rarity.RARE, mage.cards.t.ThrashingWumpus.class)); + cards.add(new SetCardInfo("Thunderclap", 219, Rarity.COMMON, mage.cards.t.Thunderclap.class)); + cards.add(new SetCardInfo("Thwart", 108, Rarity.UNCOMMON, mage.cards.t.Thwart.class)); + cards.add(new SetCardInfo("Tidal Bore", 109, Rarity.COMMON, mage.cards.t.TidalBore.class)); + cards.add(new SetCardInfo("Tidal Kraken", 110, Rarity.RARE, mage.cards.t.TidalKraken.class)); + cards.add(new SetCardInfo("Tiger Claws", 279, Rarity.COMMON, mage.cards.t.TigerClaws.class)); + cards.add(new SetCardInfo("Timid Drake", 111, Rarity.UNCOMMON, mage.cards.t.TimidDrake.class)); + cards.add(new SetCardInfo("Tonic Peddler", 54, Rarity.UNCOMMON, mage.cards.t.TonicPeddler.class)); + cards.add(new SetCardInfo("Tooth of Ramos", 313, Rarity.RARE, mage.cards.t.ToothOfRamos.class)); + cards.add(new SetCardInfo("Tower of the Magistrate", 330, Rarity.RARE, mage.cards.t.TowerOfTheMagistrate.class)); + cards.add(new SetCardInfo("Toymaker", 314, Rarity.UNCOMMON, mage.cards.t.Toymaker.class)); + cards.add(new SetCardInfo("Trade Routes", 112, Rarity.RARE, mage.cards.t.TradeRoutes.class)); + cards.add(new SetCardInfo("Tranquility", 280, Rarity.COMMON, mage.cards.t.Tranquility.class)); + cards.add(new SetCardInfo("Trap Runner", 55, Rarity.UNCOMMON, mage.cards.t.TrapRunner.class)); + cards.add(new SetCardInfo("Tremor", 220, Rarity.COMMON, mage.cards.t.Tremor.class)); + cards.add(new SetCardInfo("Two-Headed Dragon", 221, Rarity.RARE, mage.cards.t.TwoHeadedDragon.class)); + cards.add(new SetCardInfo("Undertaker", 167, Rarity.COMMON, mage.cards.u.Undertaker.class)); + cards.add(new SetCardInfo("Unmask", 168, Rarity.RARE, mage.cards.u.Unmask.class)); + cards.add(new SetCardInfo("Unnatural Hunger", 169, Rarity.RARE, mage.cards.u.UnnaturalHunger.class)); + cards.add(new SetCardInfo("Uphill Battle", 222, Rarity.UNCOMMON, mage.cards.u.UphillBattle.class)); + cards.add(new SetCardInfo("Vendetta", 170, Rarity.COMMON, mage.cards.v.Vendetta.class)); + cards.add(new SetCardInfo("Venomous Breath", 281, Rarity.UNCOMMON, mage.cards.v.VenomousBreath.class)); + cards.add(new SetCardInfo("Venomous Dragonfly", 282, Rarity.COMMON, mage.cards.v.VenomousDragonfly.class)); + cards.add(new SetCardInfo("Vernal Equinox", 283, Rarity.RARE, mage.cards.v.VernalEquinox.class)); + cards.add(new SetCardInfo("Vine Dryad", 284, Rarity.RARE, mage.cards.v.VineDryad.class)); + cards.add(new SetCardInfo("Vine Trellis", 285, Rarity.COMMON, mage.cards.v.VineTrellis.class)); + cards.add(new SetCardInfo("Volcanic Wind", 223, Rarity.UNCOMMON, mage.cards.v.VolcanicWind.class)); + cards.add(new SetCardInfo("Wall of Distortion", 171, Rarity.COMMON, mage.cards.w.WallOfDistortion.class)); + cards.add(new SetCardInfo("War Cadence", 224, Rarity.UNCOMMON, mage.cards.w.WarCadence.class)); + cards.add(new SetCardInfo("War Tax", 113, Rarity.UNCOMMON, mage.cards.w.WarTax.class)); + cards.add(new SetCardInfo("Warmonger", 225, Rarity.UNCOMMON, mage.cards.w.Warmonger.class)); + cards.add(new SetCardInfo("Warpath", 226, Rarity.UNCOMMON, mage.cards.w.Warpath.class)); + cards.add(new SetCardInfo("Waterfront Bouncer", 114, Rarity.COMMON, mage.cards.w.WaterfrontBouncer.class)); + cards.add(new SetCardInfo("Wave of Reckoning", 56, Rarity.RARE, mage.cards.w.WaveOfReckoning.class)); + cards.add(new SetCardInfo("Wild Jhovall", 227, Rarity.COMMON, mage.cards.w.WildJhovall.class)); + cards.add(new SetCardInfo("Wishmonger", 57, Rarity.UNCOMMON, mage.cards.w.Wishmonger.class)); + cards.add(new SetCardInfo("Word of Blasting", 228, Rarity.UNCOMMON, mage.cards.w.WordOfBlasting.class)); + cards.add(new SetCardInfo("Worry Beads", 315, Rarity.RARE, mage.cards.w.WorryBeads.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/MidnightHuntCommander.java b/Mage.Sets/src/mage/sets/MidnightHuntCommander.java index acee3f4116b..94c82fcad2f 100644 --- a/Mage.Sets/src/mage/sets/MidnightHuntCommander.java +++ b/Mage.Sets/src/mage/sets/MidnightHuntCommander.java @@ -19,6 +19,151 @@ public final class MidnightHuntCommander extends ExpansionSet { super("Midnight Hunt Commander", "MIC", ExpansionSet.buildDate(2021, 9, 24), SetType.SUPPLEMENTAL); this.hasBasicLands = false; + cards.add(new SetCardInfo("Abzan Falconer", 77, Rarity.UNCOMMON, mage.cards.a.AbzanFalconer.class)); + cards.add(new SetCardInfo("Aetherspouts", 97, Rarity.RARE, mage.cards.a.Aetherspouts.class)); + cards.add(new SetCardInfo("Ainok Bond-Kin", 78, Rarity.COMMON, mage.cards.a.AinokBondKin.class)); + cards.add(new SetCardInfo("Angel of Glory's Rise", 79, Rarity.RARE, mage.cards.a.AngelOfGlorysRise.class)); + cards.add(new SetCardInfo("Arcane Signet", 157, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); + cards.add(new SetCardInfo("Army of the Damned", 106, Rarity.MYTHIC, mage.cards.a.ArmyOfTheDamned.class)); cards.add(new SetCardInfo("Avacyn's Memorial", 31, Rarity.MYTHIC, mage.cards.a.AvacynsMemorial.class)); + cards.add(new SetCardInfo("Avacyn's Pilgrim", 132, Rarity.COMMON, mage.cards.a.AvacynsPilgrim.class)); + cards.add(new SetCardInfo("Bastion Protector", 80, Rarity.RARE, mage.cards.b.BastionProtector.class)); + cards.add(new SetCardInfo("Beast Within", 133, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class)); + cards.add(new SetCardInfo("Bestial Menace", 134, Rarity.UNCOMMON, mage.cards.b.BestialMenace.class)); + cards.add(new SetCardInfo("Biogenic Upgrade", 135, Rarity.UNCOMMON, mage.cards.b.BiogenicUpgrade.class)); + cards.add(new SetCardInfo("Blighted Woodland", 166, Rarity.UNCOMMON, mage.cards.b.BlightedWoodland.class)); + cards.add(new SetCardInfo("Bojuka Bog", 167, Rarity.COMMON, mage.cards.b.BojukaBog.class)); + cards.add(new SetCardInfo("Butcher of Malakir", 107, Rarity.RARE, mage.cards.b.ButcherOfMalakir.class)); + cards.add(new SetCardInfo("Canopy Vista", 168, Rarity.RARE, mage.cards.c.CanopyVista.class)); + cards.add(new SetCardInfo("Celebrate the Harvest", 24, Rarity.RARE, mage.cards.c.CelebrateTheHarvest.class)); + cards.add(new SetCardInfo("Celestial Judgment", 5, Rarity.RARE, mage.cards.c.CelestialJudgment.class)); + cards.add(new SetCardInfo("Cemetery Reaper", 108, Rarity.RARE, mage.cards.c.CemeteryReaper.class)); + cards.add(new SetCardInfo("Champion of Lambholt", 136, Rarity.RARE, mage.cards.c.ChampionOfLambholt.class)); + cards.add(new SetCardInfo("Charcoal Diamond", 158, Rarity.COMMON, mage.cards.c.CharcoalDiamond.class)); + cards.add(new SetCardInfo("Choked Estuary", 169, Rarity.RARE, mage.cards.c.ChokedEstuary.class)); + cards.add(new SetCardInfo("Citadel Siege", 81, Rarity.RARE, mage.cards.c.CitadelSiege.class)); + cards.add(new SetCardInfo("Cleansing Nova", 82, Rarity.RARE, mage.cards.c.CleansingNova.class)); + cards.add(new SetCardInfo("Cleaver Skaab", 11, Rarity.RARE, mage.cards.c.CleaverSkaab.class)); + cards.add(new SetCardInfo("Command Tower", 170, Rarity.COMMON, mage.cards.c.CommandTower.class)); + cards.add(new SetCardInfo("Commander's Sphere", 159, Rarity.COMMON, mage.cards.c.CommandersSphere.class)); + cards.add(new SetCardInfo("Corpse Augur", 109, Rarity.UNCOMMON, mage.cards.c.CorpseAugur.class)); + cards.add(new SetCardInfo("Crowded Crypt", 17, Rarity.RARE, mage.cards.c.CrowdedCrypt.class)); + cards.add(new SetCardInfo("Curse of Clinging Webs", 25, Rarity.RARE, mage.cards.c.CurseOfClingingWebs.class)); + cards.add(new SetCardInfo("Curse of Conformity", 6, Rarity.RARE, mage.cards.c.CurseOfConformity.class)); + cards.add(new SetCardInfo("Curse of Obsession", 35, Rarity.RARE, mage.cards.c.CurseOfObsession.class)); + cards.add(new SetCardInfo("Curse of Unbinding", 12, Rarity.RARE, mage.cards.c.CurseOfUnbinding.class)); + cards.add(new SetCardInfo("Curse of the Restless Dead", 18, Rarity.RARE, mage.cards.c.CurseOfTheRestlessDead.class)); + cards.add(new SetCardInfo("Custodi Soulbinders", 83, Rarity.RARE, mage.cards.c.CustodiSoulbinders.class)); + cards.add(new SetCardInfo("Dark Salvation", 110, Rarity.RARE, mage.cards.d.DarkSalvation.class)); + cards.add(new SetCardInfo("Darkwater Catacombs", 171, Rarity.RARE, mage.cards.d.DarkwaterCatacombs.class)); + cards.add(new SetCardInfo("Dearly Departed", 84, Rarity.RARE, mage.cards.d.DearlyDeparted.class)); + cards.add(new SetCardInfo("Death Baron", 111, Rarity.RARE, mage.cards.d.DeathBaron.class)); + cards.add(new SetCardInfo("Death's Presence", 137, Rarity.RARE, mage.cards.d.DeathsPresence.class)); + cards.add(new SetCardInfo("Dimir Aqueduct", 172, Rarity.UNCOMMON, mage.cards.d.DimirAqueduct.class)); + cards.add(new SetCardInfo("Diregraf Captain", 148, Rarity.UNCOMMON, mage.cards.d.DiregrafCaptain.class)); + cards.add(new SetCardInfo("Diregraf Colossus", 112, Rarity.RARE, mage.cards.d.DiregrafColossus.class)); + cards.add(new SetCardInfo("Distant Melody", 98, Rarity.COMMON, mage.cards.d.DistantMelody.class)); + cards.add(new SetCardInfo("Dread Summons", 113, Rarity.RARE, mage.cards.d.DreadSummons.class)); + cards.add(new SetCardInfo("Dreadhorde Invasion", 114, Rarity.RARE, mage.cards.d.DreadhordeInvasion.class)); + cards.add(new SetCardInfo("Drown in Dreams", 13, Rarity.RARE, mage.cards.d.DrownInDreams.class)); + cards.add(new SetCardInfo("Eater of Hope", 115, Rarity.RARE, mage.cards.e.EaterOfHope.class)); + cards.add(new SetCardInfo("Elite Scaleguard", 85, Rarity.UNCOMMON, mage.cards.e.EliteScaleguard.class)); + cards.add(new SetCardInfo("Eloise, Nephalia Sleuth", 3, Rarity.MYTHIC, mage.cards.e.EloiseNephaliaSleuth.class)); + cards.add(new SetCardInfo("Empty the Laboratory", 14, Rarity.RARE, mage.cards.e.EmptyTheLaboratory.class)); + cards.add(new SetCardInfo("Endless Ranks of the Dead", 116, Rarity.RARE, mage.cards.e.EndlessRanksOfTheDead.class)); + cards.add(new SetCardInfo("Enduring Scalelord", 149, Rarity.UNCOMMON, mage.cards.e.EnduringScalelord.class)); + cards.add(new SetCardInfo("Eternal Skylord", 99, Rarity.UNCOMMON, mage.cards.e.EternalSkylord.class)); + cards.add(new SetCardInfo("Eternal Witness", 138, Rarity.UNCOMMON, mage.cards.e.EternalWitness.class)); + cards.add(new SetCardInfo("Exotic Orchard", 173, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); + cards.add(new SetCardInfo("Feed the Swarm", 117, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); + cards.add(new SetCardInfo("Fleshbag Marauder", 118, Rarity.UNCOMMON, mage.cards.f.FleshbagMarauder.class)); + cards.add(new SetCardInfo("Forgotten Creation", 100, Rarity.RARE, mage.cards.f.ForgottenCreation.class)); + cards.add(new SetCardInfo("Fortified Village", 174, Rarity.RARE, mage.cards.f.FortifiedVillage.class)); + cards.add(new SetCardInfo("Ghouls' Night Out", 19, Rarity.RARE, mage.cards.g.GhoulsNightOut.class)); + cards.add(new SetCardInfo("Gisa and Geralf", 150, Rarity.MYTHIC, mage.cards.g.GisaAndGeralf.class)); + cards.add(new SetCardInfo("Gleaming Overseer", 151, Rarity.UNCOMMON, mage.cards.g.GleamingOverseer.class)); + cards.add(new SetCardInfo("Go for the Throat", 119, Rarity.UNCOMMON, mage.cards.g.GoForTheThroat.class)); + cards.add(new SetCardInfo("Gravespawn Sovereign", 120, Rarity.RARE, mage.cards.g.GravespawnSovereign.class)); + cards.add(new SetCardInfo("Growth Spasm", 139, Rarity.COMMON, mage.cards.g.GrowthSpasm.class)); + cards.add(new SetCardInfo("Gyre Sage", 140, Rarity.RARE, mage.cards.g.GyreSage.class)); + cards.add(new SetCardInfo("Havengul Runebinder", 101, Rarity.RARE, mage.cards.h.HavengulRunebinder.class)); + cards.add(new SetCardInfo("Herald of War", 86, Rarity.RARE, mage.cards.h.HeraldOfWar.class)); + cards.add(new SetCardInfo("Heron's Grace Champion", 152, Rarity.RARE, mage.cards.h.HeronsGraceChampion.class)); + cards.add(new SetCardInfo("Heronblade Elite", 26, Rarity.RARE, mage.cards.h.HeronbladeElite.class)); + cards.add(new SetCardInfo("Hordewing Skaab", 15, Rarity.RARE, mage.cards.h.HordewingSkaab.class)); + cards.add(new SetCardInfo("Hour of Eternity", 102, Rarity.RARE, mage.cards.h.HourOfEternity.class)); + cards.add(new SetCardInfo("Hour of Reckoning", 87, Rarity.RARE, mage.cards.h.HourOfReckoning.class)); + cards.add(new SetCardInfo("Inspiring Call", 141, Rarity.UNCOMMON, mage.cards.i.InspiringCall.class)); + cards.add(new SetCardInfo("Juniper Order Ranger", 153, Rarity.UNCOMMON, mage.cards.j.JuniperOrderRanger.class)); + cards.add(new SetCardInfo("Kessig Cagebreakers", 142, Rarity.RARE, mage.cards.k.KessigCagebreakers.class)); + cards.add(new SetCardInfo("Knight of the White Orchid", 88, Rarity.RARE, mage.cards.k.KnightOfTheWhiteOrchid.class)); + cards.add(new SetCardInfo("Krosan Verge", 175, Rarity.UNCOMMON, mage.cards.k.KrosanVerge.class)); + cards.add(new SetCardInfo("Kurbis, Harvest Celebrant", 27, Rarity.RARE, mage.cards.k.KurbisHarvestCelebrant.class)); + cards.add(new SetCardInfo("Kyler, Sigardian Emissary", 4, Rarity.MYTHIC, mage.cards.k.KylerSigardianEmissary.class)); + cards.add(new SetCardInfo("Leinore, Autumn Sovereign", 1, Rarity.MYTHIC, mage.cards.l.LeinoreAutumnSovereign.class)); + cards.add(new SetCardInfo("Lifecrafter's Bestiary", 160, Rarity.RARE, mage.cards.l.LifecraftersBestiary.class)); + cards.add(new SetCardInfo("Liliana's Devotee", 122, Rarity.UNCOMMON, mage.cards.l.LilianasDevotee.class)); + cards.add(new SetCardInfo("Liliana's Mastery", 123, Rarity.RARE, mage.cards.l.LilianasMastery.class)); + cards.add(new SetCardInfo("Liliana, Death's Majesty", 121, Rarity.MYTHIC, mage.cards.l.LilianaDeathsMajesty.class)); + cards.add(new SetCardInfo("Lord of the Accursed", 124, Rarity.UNCOMMON, mage.cards.l.LordOfTheAccursed.class)); + cards.add(new SetCardInfo("Midnight Reaper", 125, Rarity.RARE, mage.cards.m.MidnightReaper.class)); + cards.add(new SetCardInfo("Mikaeus, the Lunarch", 89, Rarity.MYTHIC, mage.cards.m.MikaeusTheLunarch.class)); + cards.add(new SetCardInfo("Moorland Rescuer", 7, Rarity.RARE, mage.cards.m.MoorlandRescuer.class)); + cards.add(new SetCardInfo("Mortuary Mire", 176, Rarity.COMMON, mage.cards.m.MortuaryMire.class)); + cards.add(new SetCardInfo("Myriad Landscape", 177, Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class)); + cards.add(new SetCardInfo("Odric, Master Tactician", 90, Rarity.RARE, mage.cards.o.OdricMasterTactician.class)); + cards.add(new SetCardInfo("Open the Graves", 126, Rarity.RARE, mage.cards.o.OpenTheGraves.class)); + cards.add(new SetCardInfo("Orzhov Advokist", 91, Rarity.UNCOMMON, mage.cards.o.OrzhovAdvokist.class)); + cards.add(new SetCardInfo("Overseer of the Damned", 127, Rarity.RARE, mage.cards.o.OverseerOfTheDamned.class)); + cards.add(new SetCardInfo("Path of Ancestry", 178, Rarity.COMMON, mage.cards.p.PathOfAncestry.class)); + cards.add(new SetCardInfo("Prowling Geistcatcher", 21, Rarity.RARE, mage.cards.p.ProwlingGeistcatcher.class)); + cards.add(new SetCardInfo("Ravenous Rotbelly", 22, Rarity.RARE, mage.cards.r.RavenousRotbelly.class)); + cards.add(new SetCardInfo("Return to Dust", 92, Rarity.UNCOMMON, mage.cards.r.ReturnToDust.class)); + cards.add(new SetCardInfo("Riders of Gavony", 93, Rarity.RARE, mage.cards.r.RidersOfGavony.class)); + cards.add(new SetCardInfo("Rogue's Passage", 179, Rarity.UNCOMMON, mage.cards.r.RoguesPassage.class)); + cards.add(new SetCardInfo("Rooftop Storm", 103, Rarity.RARE, mage.cards.r.RooftopStorm.class)); + cards.add(new SetCardInfo("Ruinous Intrusion", 28, Rarity.RARE, mage.cards.r.RuinousIntrusion.class)); + cards.add(new SetCardInfo("Ruthless Deathfang", 154, Rarity.UNCOMMON, mage.cards.r.RuthlessDeathfang.class)); + cards.add(new SetCardInfo("Selesnya Sanctuary", 180, Rarity.UNCOMMON, mage.cards.s.SelesnyaSanctuary.class)); + cards.add(new SetCardInfo("Shamanic Revelation", 143, Rarity.RARE, mage.cards.s.ShamanicRevelation.class)); + cards.add(new SetCardInfo("Sigarda's Vanguard", 8, Rarity.RARE, mage.cards.s.SigardasVanguard.class)); + cards.add(new SetCardInfo("Sigarda, Heron's Grace", 155, Rarity.MYTHIC, mage.cards.s.SigardaHeronsGrace.class)); + cards.add(new SetCardInfo("Sigardian Zealot", 29, Rarity.RARE, mage.cards.s.SigardianZealot.class)); + cards.add(new SetCardInfo("Sky Diamond", 161, Rarity.COMMON, mage.cards.s.SkyDiamond.class)); + cards.add(new SetCardInfo("Sol Ring", 162, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); + cards.add(new SetCardInfo("Somberwald Beastmaster", 30, Rarity.RARE, mage.cards.s.SomberwaldBeastmaster.class)); + cards.add(new SetCardInfo("Somberwald Sage", 144, Rarity.RARE, mage.cards.s.SomberwaldSage.class)); + cards.add(new SetCardInfo("Spark Reaper", 128, Rarity.COMMON, mage.cards.s.SparkReaper.class)); + cards.add(new SetCardInfo("Stalwart Pathlighter", 9, Rarity.RARE, mage.cards.s.StalwartPathlighter.class)); + cards.add(new SetCardInfo("Stitcher Geralf", 104, Rarity.MYTHIC, mage.cards.s.StitcherGeralf.class)); + cards.add(new SetCardInfo("Sungrass Prairie", 181, Rarity.RARE, mage.cards.s.SungrassPrairie.class)); + cards.add(new SetCardInfo("Sunken Hollow", 182, Rarity.RARE, mage.cards.s.SunkenHollow.class)); + cards.add(new SetCardInfo("Swiftfoot Boots", 163, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class)); + cards.add(new SetCardInfo("Swords to Plowshares", 94, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); + cards.add(new SetCardInfo("Syphon Flesh", 129, Rarity.UNCOMMON, mage.cards.s.SyphonFlesh.class)); + cards.add(new SetCardInfo("Tainted Isle", 183, Rarity.UNCOMMON, mage.cards.t.TaintedIsle.class)); + cards.add(new SetCardInfo("Talisman of Dominance", 164, Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class)); + cards.add(new SetCardInfo("Talisman of Unity", 165, Rarity.UNCOMMON, mage.cards.t.TalismanOfUnity.class)); + cards.add(new SetCardInfo("Temple of Deceit", 184, Rarity.RARE, mage.cards.t.TempleOfDeceit.class)); + cards.add(new SetCardInfo("Temple of Plenty", 185, Rarity.RARE, mage.cards.t.TempleOfPlenty.class)); + cards.add(new SetCardInfo("Temple of the False God", 186, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class)); + cards.add(new SetCardInfo("Tomb Tyrant", 23, Rarity.RARE, mage.cards.t.TombTyrant.class)); + cards.add(new SetCardInfo("Trostani's Summoner", 156, Rarity.UNCOMMON, mage.cards.t.TrostanisSummoner.class)); + cards.add(new SetCardInfo("Unbreakable Formation", 95, Rarity.RARE, mage.cards.u.UnbreakableFormation.class)); + cards.add(new SetCardInfo("Unclaimed Territory", 187, Rarity.UNCOMMON, mage.cards.u.UnclaimedTerritory.class)); + cards.add(new SetCardInfo("Undead Alchemist", 105, Rarity.RARE, mage.cards.u.UndeadAlchemist.class)); + cards.add(new SetCardInfo("Undead Augur", 130, Rarity.UNCOMMON, mage.cards.u.UndeadAugur.class)); + cards.add(new SetCardInfo("Verdurous Gearhulk", 145, Rarity.MYTHIC, mage.cards.v.VerdurousGearhulk.class)); + cards.add(new SetCardInfo("Victory's Envoy", 96, Rarity.RARE, mage.cards.v.VictorysEnvoy.class)); + cards.add(new SetCardInfo("Visions of Dominance", 37, Rarity.RARE, mage.cards.v.VisionsOfDominance.class)); + cards.add(new SetCardInfo("Visions of Dread", 34, Rarity.RARE, mage.cards.v.VisionsOfDread.class)); + cards.add(new SetCardInfo("Visions of Duplicity", 33, Rarity.RARE, mage.cards.v.VisionsOfDuplicity.class)); + cards.add(new SetCardInfo("Visions of Glory", 32, Rarity.RARE, mage.cards.v.VisionsOfGlory.class)); + cards.add(new SetCardInfo("Visions of Ruin", 36, Rarity.RARE, mage.cards.v.VisionsOfRuin.class)); + cards.add(new SetCardInfo("Wall of Mourning", 10, Rarity.RARE, mage.cards.w.WallOfMourning.class)); + cards.add(new SetCardInfo("Wild Beastmaster", 146, Rarity.RARE, mage.cards.w.WildBeastmaster.class)); + cards.add(new SetCardInfo("Wilhelt, the Rotcleaver", 2, Rarity.MYTHIC, mage.cards.w.WilheltTheRotcleaver.class)); + cards.add(new SetCardInfo("Yavimaya Elder", 147, Rarity.COMMON, mage.cards.y.YavimayaElder.class)); + cards.add(new SetCardInfo("Zombie Apocalypse", 131, Rarity.RARE, mage.cards.z.ZombieApocalypse.class)); } } diff --git a/Mage.Sets/src/mage/sets/ModernHorizons2.java b/Mage.Sets/src/mage/sets/ModernHorizons2.java index 62e7730904f..f8d7b6843e7 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons2.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons2.java @@ -26,7 +26,7 @@ public final class ModernHorizons2 extends ExpansionSet { } private ModernHorizons2() { - super("Modern Horizons 2", "MH2", ExpansionSet.buildDate(2021, 6, 11), SetType.SUPPLEMENTAL_MODERN_LEGAL, new ModernHorizons2Collator()); + super("Modern Horizons 2", "MH2", ExpansionSet.buildDate(2021, 6, 11), SetType.SUPPLEMENTAL_MODERN_LEGAL); this.blockName = "Modern Horizons 2"; this.hasBasicLands = true; this.hasBoosters = true; @@ -559,6 +559,11 @@ public final class ModernHorizons2 extends ExpansionSet { cards.removeIf(cardInfo -> cardInfo.getCardNumberAsInt() >= 262); return cards; } + + @Override + public BoosterCollator createCollator() { + return new ModernHorizons2Collator(); + } } // Booster collation info from https://www.lethe.xyz/mtg/collation/mh2.html @@ -566,190 +571,118 @@ public final class ModernHorizons2 extends ExpansionSet { // TODO: add reprint variants (waiting on more info for this) class ModernHorizons2Collator implements BoosterCollator { - private static class ModernHorizons2Run extends CardRun { - private static final ModernHorizons2Run commonA = new ModernHorizons2Run(true, "62", "134", "38", "156", "136", "168", "43", "170", "145", "65", "154", "112", "49", "169", "131", "51", "155", "144", "46", "181", "114", "40", "173", "111", "57", "159", "140", "54", "149", "139", "62", "161", "136", "38", "168", "134", "63", "146", "156", "65", "145", "170", "43", "131", "169", "51", "114", "154", "49", "112", "155", "46", "140", "181", "40", "144", "173", "63", "139", "159", "57", "111", "161", "54", "146", "149"); - private static final ModernHorizons2Run commonB = new ModernHorizons2Run(true, "15", "395", "8", "95", "21", "89", "381", "88", "36", "86", "4", "109", "20", "91", "17", "107", "18", "78", "33", "99", "329", "76", "15", "95", "8", "101", "21", "88", "6", "86", "4", "343", "36", "109", "17", "91", "18", "78", "330", "107", "15", "99", "19", "399", "33", "76", "8", "88", "382", "86", "6", "101", "4", "348", "36", "91", "17", "89", "18", "107", "20", "78", "19", "99", "33", "101"); - private static final ModernHorizons2Run commonC1 = new ModernHorizons2Run(true, "3", "246", "24", "253", "190", "103", "226", "187", "239", "252", "230", "255", "11", "213", "249", "83", "256", "104", "194", "13", "82", "24", "257", "193", "245", "3", "196", "235", "246", "188", "222", "190", "253", "187", "252", "230", "226", "255", "11", "239", "103", "249", "194", "104", "13", "82", "213", "256", "196", "245", "83", "193", "257", "188", "235"); - private static final ModernHorizons2Run commonC2 = new ModernHorizons2Run(true, "424", "163", "349", "167", "37", "152", "430", "200", "354", "135", "247", "258", "55", "217", "127", "351", "163", "122", "215", "152", "66", "356", "42", "408", "200", "147", "232", "37", "406", "247", "55", "258", "217", "128", "335", "167", "215", "411", "66", "413", "122", "135", "421", "147", "232", "389", "127", "339", "258", "247", "217", "222", "392", "128", "42"); - private static final ModernHorizons2Run uncommonA = new ModernHorizons2Run(true, "5", "429", "172", "125", "110", "369", "2", "228", "165", "123", "28", "185", "64", "150", "98", "14", "164", "376", "94", "237", "191", "143", "251", "108", "360", "221", "113", "179", "85", "124", "241", "16", "79", "56", "195", "77", "220", "2", "110", "174", "50", "5", "203", "404", "229", "172", "125", "28", "228", "150", "361", "123", "98", "64", "165", "143", "94", "14", "164", "191", "237", "251", "212", "221", "403", "124", "179", "16", "85", "184", "113", "241", "79", "56", "364", "77", "174", "220", "50", "2", "110", "150", "28", "115", "229", "5", "350", "203", "172", "428", "64", "98", "123", "165", "185", "94", "14", "143", "212", "164", "221", "362", "251", "108", "124", "237", "358", "16", "113", "184", "220", "85", "195", "174", "79", "241", "56", "77", "115", "50"); - private static final ModernHorizons2Run uncommonB = new ModernHorizons2Run(true, "74", "373", "141", "60", "183", "31", "70", "233", "10", "130", "211", "72", "180", "133", "41", "160", "346", "25", "121", "48", "210", "90", "177", "201", "34", "73", "9", "434", "119", "105", "1", "426", "53", "84", "175", "45", "141", "327", "60", "142", "209", "74", "61", "158", "233", "72", "133", "180", "10", "130", "375", "160", "31", "90", "48", "183", "210", "70", "384", "100", "41", "121", "201", "9", "73", "240", "177", "34", "45", "84", "415", "61", "119", "1", "53", "347", "223", "141", "60", "209", "142", "7", "158", "74", "133", "180", "31", "394", "211", "10", "233", "130", "183", "70", "100", "48", "160", "90", "25", "374", "121", "41", "177", "9", "240", "73", "201", "34", "175", "45", "223", "84", "338", "1", "119", "105", "61", "158", "142", "7"); - private static final ModernHorizons2Run rareA = new ModernHorizons2Run(false, "12", "12", "22", "22", "23", "23", "26", "26", "27", "27", "29", "29", "30", "32", "35", "35", "39", "39", "44", "44", "47", "47", "52", "58", "58", "59", "59", "67", "68", "68", "69", "71", "71", "75", "80", "80", "81", "81", "87", "92", "92", "93", "93", "96", "96", "97", "97", "102", "106", "106", "116", "116", "117", "117", "118", "118", "120", "120", "126", "129", "129", "132", "132", "137", "137", "138", "148", "148", "151", "153", "153", "157", "162", "162", "166", "166", "171", "171", "176", "176", "178", "182", "182", "186", "186", "189", "189", "192", "197", "198", "198", "199", "202", "204", "204", "205", "205", "206", "206", "207", "207", "208", "208", "214", "214", "216", "216", "218", "218", "219", "219", "224", "224", "225", "225", "227", "231", "231", "234", "236", "236", "238", "242", "242", "243", "243", "244", "244", "248", "248", "250", "250", "254", "254", "259", "259", "260", "260", "261", "261"); - private static final ModernHorizons2Run rareB = new ModernHorizons2Run(false, "328", "328", "331", "331", "332", "332", "333", "334", "334", "336", "336", "337", "340", "340", "341", "341", "342", "344", "344", "345", "345", "352", "352", "353", "353", "355", "355", "357", "357", "359", "359", "363", "365", "366", "366", "367", "368", "370", "370", "371", "371", "372", "372", "377", "377", "378", "378", "379", "380", "380", "383", "383", "385", "385", "386", "386", "388", "388", "390", "390", "391", "391", "393", "396", "396", "397", "397", "398", "398", "400", "400", "401", "401", "402", "405", "405", "407", "407", "409", "409", "410", "412", "412", "414", "414", "417", "417", "418", "418", "420", "422", "422", "425", "425", "427", "427", "431", "432", "432", "433", "435", "435", "436", "436", "437", "437", "438", "438", "439", "439", "440", "440", "441", "441"); - private static final ModernHorizons2Run rareC = new ModernHorizons2Run(false, "304", "305", "306", "307", "309", "310", "311", "312", "313", "315", "316", "317", "318", "323", "324"); - private static final ModernHorizons2Run reprintUncommon = new ModernHorizons2Run(false, "262", "264", "266", "267", "268", "269", "272", "274", "276", "278", "280", "282", "284", "285", "288", "296", "297", "299", "300", "302"); - private static final ModernHorizons2Run reprintRare = new ModernHorizons2Run(false, "263", "265", "270", "271", "273", "275", "277", "279", "283", "286", "289", "290", "292", "293", "294", "295", "298", "303"); - private static final ModernHorizons2Run reprintMythic = new ModernHorizons2Run(false, "281", "287", "291", "301"); + private final CardRun commonA = new CardRun(true, "62", "134", "38", "156", "136", "168", "43", "170", "145", "65", "154", "112", "49", "169", "131", "51", "155", "144", "46", "181", "114", "40", "173", "111", "57", "159", "140", "54", "149", "139", "62", "161", "136", "38", "168", "134", "63", "146", "156", "65", "145", "170", "43", "131", "169", "51", "114", "154", "49", "112", "155", "46", "140", "181", "40", "144", "173", "63", "139", "159", "57", "111", "161", "54", "146", "149"); + private final CardRun commonB = new CardRun(true, "15", "395", "8", "95", "21", "89", "381", "88", "36", "86", "4", "109", "20", "91", "17", "107", "18", "78", "33", "99", "329", "76", "15", "95", "8", "101", "21", "88", "6", "86", "4", "343", "36", "109", "17", "91", "18", "78", "330", "107", "15", "99", "19", "399", "33", "76", "8", "88", "382", "86", "6", "101", "4", "348", "36", "91", "17", "89", "18", "107", "20", "78", "19", "99", "33", "101"); + private final CardRun commonC1 = new CardRun(true, "3", "246", "24", "253", "190", "103", "226", "187", "239", "252", "230", "255", "11", "213", "249", "83", "256", "104", "194", "13", "82", "24", "257", "193", "245", "3", "196", "235", "246", "188", "222", "190", "253", "187", "252", "230", "226", "255", "11", "239", "103", "249", "194", "104", "13", "82", "213", "256", "196", "245", "83", "193", "257", "188", "235"); + private final CardRun commonC2 = new CardRun(true, "424", "163", "349", "167", "37", "152", "430", "200", "354", "135", "247", "258", "55", "217", "127", "351", "163", "122", "215", "152", "66", "356", "42", "408", "200", "147", "232", "37", "406", "247", "55", "258", "217", "128", "335", "167", "215", "411", "66", "413", "122", "135", "421", "147", "232", "389", "127", "339", "258", "247", "217", "222", "392", "128", "42"); + private final CardRun uncommonA = new CardRun(true, "5", "429", "172", "125", "110", "369", "2", "228", "165", "123", "28", "185", "64", "150", "98", "14", "164", "376", "94", "237", "191", "143", "251", "108", "360", "221", "113", "179", "85", "124", "241", "16", "79", "56", "195", "77", "220", "2", "110", "174", "50", "5", "203", "404", "229", "172", "125", "28", "228", "150", "361", "123", "98", "64", "165", "143", "94", "14", "164", "191", "237", "251", "212", "221", "403", "124", "179", "16", "85", "184", "113", "241", "79", "56", "364", "77", "174", "220", "50", "2", "110", "150", "28", "115", "229", "5", "350", "203", "172", "428", "64", "98", "123", "165", "185", "94", "14", "143", "212", "164", "221", "362", "251", "108", "124", "237", "358", "16", "113", "184", "220", "85", "195", "174", "79", "241", "56", "77", "115", "50"); + private final CardRun uncommonB = new CardRun(true, "74", "373", "141", "60", "183", "31", "70", "233", "10", "130", "211", "72", "180", "133", "41", "160", "346", "25", "121", "48", "210", "90", "177", "201", "34", "73", "9", "434", "119", "105", "1", "426", "53", "84", "175", "45", "141", "327", "60", "142", "209", "74", "61", "158", "233", "72", "133", "180", "10", "130", "375", "160", "31", "90", "48", "183", "210", "70", "384", "100", "41", "121", "201", "9", "73", "240", "177", "34", "45", "84", "415", "61", "119", "1", "53", "347", "223", "141", "60", "209", "142", "7", "158", "74", "133", "180", "31", "394", "211", "10", "233", "130", "183", "70", "100", "48", "160", "90", "25", "374", "121", "41", "177", "9", "240", "73", "201", "34", "175", "45", "223", "84", "338", "1", "119", "105", "61", "158", "142", "7"); + private final CardRun rareA = new CardRun(false, "12", "12", "22", "22", "23", "23", "26", "26", "27", "27", "29", "29", "30", "32", "35", "35", "39", "39", "44", "44", "47", "47", "52", "58", "58", "59", "59", "67", "68", "68", "69", "71", "71", "75", "80", "80", "81", "81", "87", "92", "92", "93", "93", "96", "96", "97", "97", "102", "106", "106", "116", "116", "117", "117", "118", "118", "120", "120", "126", "129", "129", "132", "132", "137", "137", "138", "148", "148", "151", "153", "153", "157", "162", "162", "166", "166", "171", "171", "176", "176", "178", "182", "182", "186", "186", "189", "189", "192", "197", "198", "198", "199", "202", "204", "204", "205", "205", "206", "206", "207", "207", "208", "208", "214", "214", "216", "216", "218", "218", "219", "219", "224", "224", "225", "225", "227", "231", "231", "234", "236", "236", "238", "242", "242", "243", "243", "244", "244", "248", "248", "250", "250", "254", "254", "259", "259", "260", "260", "261", "261"); + private final CardRun rareB = new CardRun(false, "328", "328", "331", "331", "332", "332", "333", "334", "334", "336", "336", "337", "340", "340", "341", "341", "342", "344", "344", "345", "345", "352", "352", "353", "353", "355", "355", "357", "357", "359", "359", "363", "365", "366", "366", "367", "368", "370", "370", "371", "371", "372", "372", "377", "377", "378", "378", "379", "380", "380", "383", "383", "385", "385", "386", "386", "388", "388", "390", "390", "391", "391", "393", "396", "396", "397", "397", "398", "398", "400", "400", "401", "401", "402", "405", "405", "407", "407", "409", "409", "410", "412", "412", "414", "414", "417", "417", "418", "418", "420", "422", "422", "425", "425", "427", "427", "431", "432", "432", "433", "435", "435", "436", "436", "437", "437", "438", "438", "439", "439", "440", "440", "441", "441"); + private final CardRun rareC = new CardRun(false, "304", "305", "306", "307", "309", "310", "311", "312", "313", "315", "316", "317", "318", "323", "324"); + private final CardRun reprint = new CardRun(false, "262", "264", "266", "267", "268", "269", "272", "274", "276", "278", "280", "282", "284", "285", "288", "296", "297", "299", "300", "302", "262", "264", "266", "267", "268", "269", "272", "274", "276", "278", "280", "282", "284", "285", "288", "296", "297", "299", "300", "302", "262", "264", "266", "267", "268", "269", "272", "274", "276", "278", "280", "282", "284", "285", "288", "296", "297", "299", "300", "302", "262", "264", "266", "267", "268", "269", "272", "274", "276", "278", "280", "282", "284", "285", "288", "296", "297", "299", "300", "302", "263", "265", "270", "271", "273", "275", "277", "279", "283", "286", "289", "290", "292", "293", "294", "295", "298", "303", "263", "265", "270", "271", "273", "275", "277", "279", "283", "286", "289", "290", "292", "293", "294", "295", "298", "303", "281", "287", "291", "301"); - private ModernHorizons2Run(boolean keepOrder, String... numbers) { - super(keepOrder, numbers); - } - } - - private static class ModernHorizons2Structure extends BoosterStructure { - private static final ModernHorizons2Structure C1 = new ModernHorizons2Structure( - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonC1, - ModernHorizons2Run.commonC1, - ModernHorizons2Run.commonC1, - ModernHorizons2Run.commonC1, - ModernHorizons2Run.commonC1, - ModernHorizons2Run.commonC1 - ); - private static final ModernHorizons2Structure C2 = new ModernHorizons2Structure( - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonC1, - ModernHorizons2Run.commonC1, - ModernHorizons2Run.commonC1, - ModernHorizons2Run.commonC1, - ModernHorizons2Run.commonC1 - ); - private static final ModernHorizons2Structure C3 = new ModernHorizons2Structure( - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonC2, - ModernHorizons2Run.commonC2, - ModernHorizons2Run.commonC2, - ModernHorizons2Run.commonC2 - ); - private static final ModernHorizons2Structure C4 = new ModernHorizons2Structure( - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonC2, - ModernHorizons2Run.commonC2, - ModernHorizons2Run.commonC2, - ModernHorizons2Run.commonC2 - ); - private static final ModernHorizons2Structure C5 = new ModernHorizons2Structure( - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonA, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonB, - ModernHorizons2Run.commonC2, - ModernHorizons2Run.commonC2, - ModernHorizons2Run.commonC2 - ); - private static final ModernHorizons2Structure U1 = new ModernHorizons2Structure( - ModernHorizons2Run.uncommonA, - ModernHorizons2Run.uncommonA, - ModernHorizons2Run.uncommonA - ); - private static final ModernHorizons2Structure U2 = new ModernHorizons2Structure( - ModernHorizons2Run.uncommonB, - ModernHorizons2Run.uncommonB, - ModernHorizons2Run.uncommonB - ); - private static final ModernHorizons2Structure R1 = new ModernHorizons2Structure( - ModernHorizons2Run.rareA - ); - private static final ModernHorizons2Structure R2 = new ModernHorizons2Structure( - ModernHorizons2Run.rareB - ); - private static final ModernHorizons2Structure R3 = new ModernHorizons2Structure( - ModernHorizons2Run.rareC - ); - private static final ModernHorizons2Structure RP1 = new ModernHorizons2Structure( - ModernHorizons2Run.reprintUncommon - ); - private static final ModernHorizons2Structure RP2 = new ModernHorizons2Structure( - ModernHorizons2Run.reprintRare - ); - private static final ModernHorizons2Structure RP3 = new ModernHorizons2Structure( - ModernHorizons2Run.reprintMythic - ); - - private ModernHorizons2Structure(CardRun... runs) { - super(runs); - } - } + private final BoosterStructure AAABC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, + commonC1, commonC1, commonC1, commonC1, commonC1,commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAA = new BoosterStructure(uncommonA, uncommonA, uncommonA); + private final BoosterStructure BBB = new BoosterStructure(uncommonB, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA); + private final BoosterStructure R2 = new BoosterStructure(rareB); + private final BoosterStructure R3 = new BoosterStructure(rareC); + private final BoosterStructure RP1 = new BoosterStructure(reprint); + // In order for equal numbers of each common to exist, the average booster must contain: + // 3.27 A commons (36 / 11) + // 2.18 B commons (24 / 11) + // 2.73 C1 commons (30 / 11, or 60 / 11 in each C1 booster) + // 1.82 C2 commons (20 / 11, or 40 / 11 in each C2 booster) + // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs + // and with 10 common slots per booster private final RarityConfiguration commonRuns = new RarityConfiguration( - false, - ModernHorizons2Structure.C1, - ModernHorizons2Structure.C2, - ModernHorizons2Structure.C3, - ModernHorizons2Structure.C4, - ModernHorizons2Structure.C5, - ModernHorizons2Structure.C1, - ModernHorizons2Structure.C2, - ModernHorizons2Structure.C3, - ModernHorizons2Structure.C4, - ModernHorizons2Structure.C5, - ModernHorizons2Structure.C1, - ModernHorizons2Structure.C2 - ); - private final RarityConfiguration uncommonRuns = new RarityConfiguration( - ModernHorizons2Structure.U1, - ModernHorizons2Structure.U2 - ); - private final RarityConfiguration rareRuns = new RarityConfiguration( - false, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, ModernHorizons2Structure.R1, - ModernHorizons2Structure.R1, - ModernHorizons2Structure.R2, ModernHorizons2Structure.R2, - ModernHorizons2Structure.R3 - ); - private final RarityConfiguration reprintRuns = new RarityConfiguration( - false, - ModernHorizons2Structure.RP1, ModernHorizons2Structure.RP1, - ModernHorizons2Structure.RP1, ModernHorizons2Structure.RP1, - ModernHorizons2Structure.RP2, ModernHorizons2Structure.RP2, - ModernHorizons2Structure.RP3 - ); + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, - @Override - public void shuffle() { - commonRuns.shuffle(); - uncommonRuns.shuffle(); - rareRuns.shuffle(); - reprintRuns.shuffle(); - } + AAABBBC2C2C2C2, + AAABBBC2C2C2C2, + AAABBBC2C2C2C2, + AAABBBC2C2C2C2, + AAABBBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2 + ); + private final RarityConfiguration uncommonRuns = new RarityConfiguration(AAA, BBB); + private final RarityConfiguration rareRuns = new RarityConfiguration( + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, R1, R1, + R1, + R2, R2, + R3 + ); + private final RarityConfiguration reprintRuns = new RarityConfiguration(RP1); @Override public List makeBooster() { diff --git a/Mage.Sets/src/mage/sets/ModernMasters.java b/Mage.Sets/src/mage/sets/ModernMasters.java index 9b87034f21c..ef1807cd2b9 100644 --- a/Mage.Sets/src/mage/sets/ModernMasters.java +++ b/Mage.Sets/src/mage/sets/ModernMasters.java @@ -1,9 +1,16 @@ package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + /** * @author LevelX2 */ @@ -256,4 +263,106 @@ public final class ModernMasters extends ExpansionSet { cards.add(new SetCardInfo("Yosei, the Morning Star", 35, Rarity.MYTHIC, mage.cards.y.YoseiTheMorningStar.class)); } + @Override + public BoosterCollator createCollator() { + return new ModernMastersCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/mma.html +// Using USA collation for all rarities +// Foil rare sheet used for regular rares as regular rare sheet is not known +class ModernMastersCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "156", "6", "207", "69", "215", "81", "61", "148", "3", "200", "112", "157", "36", "79", "11", "21", "107", "41", "146", "86", "207", "2", "50", "119", "156", "101", "34", "139", "41", "145", "79", "3", "121", "98", "69", "157", "21", "112", "101", "146", "50", "86", "34", "119", "145", "136", "61", "103", "6", "215", "168", "81", "39", "200", "11", "107", "148", "98", "36", "121", "2", "168", "136", "39", "103", "139"); + private final CardRun commonB = new CardRun(true, "73", "51", "113", "140", "18", "94", "68", "212", "169", "87", "113", "42", "140", "7", "224", "126", "73", "19", "167", "60", "169", "116", "85", "224", "147", "126", "18", "60", "94", "137", "212", "12", "51", "116", "85", "18", "68", "140", "137", "73", "42", "167", "7", "87", "51", "126", "19", "224", "42", "94", "113", "169", "7", "212", "87", "147", "60", "12", "137", "167", "85", "19", "68", "116", "147", "12"); + private final CardRun commonC1 = new CardRun(true, "77", "9", "122", "44", "151", "127", "161", "23", "71", "53", "105", "214", "143", "8", "104", "199", "131", "45", "17", "80", "59", "161", "132", "71", "100", "33", "44", "155", "201", "127", "17", "77", "65", "105", "165", "53", "9", "80", "131", "201", "151", "45", "104", "23", "122", "165", "100", "65", "33", "143", "132", "8", "199", "59", "155"); + private final CardRun commonC2 = new CardRun(true, "202", "163", "40", "58", "114", "57", "196", "173", "109", "95", "209", "163", "58", "115", "196", "92", "142", "40", "114", "95", "163", "27", "214", "99", "109", "24", "142", "202", "115", "57", "28", "173", "58", "27", "99", "209", "114", "92", "28", "202", "109", "196", "27", "57", "95", "173", "24", "92", "115", "40", "28", "209", "142", "99", "24"); + private final CardRun uncommonA = new CardRun(true, "66", "191", "211", "227", "144", "90", "192", "29", "64", "133", "228", "91", "184", "159", "10", "67", "227", "190", "162", "211", "206", "133", "181", "225", "159", "110", "66", "22", "191", "184", "154", "226", "37", "72", "194", "129", "144", "213", "138", "192", "206", "64", "16", "225", "179", "90", "162", "110", "10", "190", "213", "37", "194", "91", "138", "29", "226", "181", "67", "16", "154", "72", "179", "228", "22", "129"); + private final CardRun uncommonB = new CardRun(true, "83", "229", "55", "124", "25", "222", "78", "195", "62", "171", "32", "93", "210", "124", "54", "15", "222", "149", "88", "118", "55", "30", "185", "152", "62", "134", "205", "25", "158", "83", "43", "135", "175", "15", "152", "88", "54", "134", "185", "78", "171", "229", "32", "118", "195", "93", "158", "210", "43", "175", "205", "135", "30", "149"); + private final CardRun rare = new CardRun(true, "26", "96", "117", "178", "218", "141", "150", "111", "219", "197", "89", "204", "47", "70", "178", "198", "84", "13", "108", "75", "123", "177", "172", "4", "164", "120", "74", "31", "46", "186", "153", "97", "26", "197", "187", "76", "170", "160", "221", "188", "5", "56", "102", "38", "1", "193", "203", "97", "4", "141", "180", "125", "84", "20", "49", "189", "52", "123", "76", "172", "5", "106", "56", "31", "174", "47", "208", "220", "177", "38", "223", "49", "108", "117", "102", "82", "176", "48", "128", "96", "187", "63", "223", "182", "111", "188", "153", "203", "204", "130", "216", "189", "14", "1", "52", "166", "46", "217", "174", "183", "198", "74", "63", "125", "219", "160", "180", "130", "176", "20", "193", "220", "164", "106", "208", "170", "14", "221", "82", "35", "186"); + private final CardRun foilCommon = new CardRun(true, "201", "79", "132", "8", "168", "41", "80", "12", "139", "103", "50", "215", "24", "148", "92", "142", "196", "59", "114", "212", "104", "146", "169", "28", "81", "40", "21", "207", "131", "101", "58", "19", "157", "73", "119", "140", "200", "57", "27", "199", "112", "151", "77", "224", "107", "33", "61", "94", "202", "121", "143", "87", "23", "60", "98", "214", "65", "173", "3", "105", "39", "209", "7", "36", "115", "167", "99", "69", "6", "116", "163", "95", "145", "9", "53", "136", "155", "113", "161", "68", "34", "100", "156", "45", "18", "122", "71", "51", "126", "165", "2", "85", "127", "147", "42", "17", "137", "44", "109", "86", "11"); + private final CardRun foilUncommon = new CardRun(true, "144", "206", "190", "124", "158", "67", "22", "83", "227", "25", "175", "210", "229", "78", "211", "37", "10", "162", "213", "134", "90", "29", "185", "171", "88", "64", "15", "228", "30", "159", "37", "191", "124", "195", "67", "91", "25", "78", "134", "162", "62", "149", "229", "22", "64", "138", "110", "175", "192", "90", "152", "91", "135", "154", "129", "15", "55", "30", "184", "54", "225", "133", "152", "228", "110", "83", "149", "93", "226", "154", "210", "181", "138", "54", "158", "184", "225", "72", "66", "227", "16", "205", "118", "62", "194", "179", "10", "211", "185", "222", "43", "206", "192", "144", "29", "195", "88", "205", "179", "191", "32", "129", "213", "226", "55", "222", "133", "190", "72", "135", "93", "32", "43", "171", "194", "181", "66", "118", "159", "16"); + + private final BoosterStructure AABBC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAAABBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rare); + private final BoosterStructure FC = new BoosterStructure(foilCommon); + private final BoosterStructure FU = new BoosterStructure(foilUncommon); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 3.27 A commons (36 / 11) + // 2.18 B commons (24 / 11) + // 2.73 C1 commons (30 / 11, or 60 / 11 in each C1 booster) + // 1.82 C2 commons (20 / 11, or 40 / 11 in each C2 booster) + // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs + // and with 10 common slots per booster + private final RarityConfiguration commonRuns = new RarityConfiguration( + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2 + ); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 1.65 A uncommons (33 / 20) + // 1.35 B uncommons (27 / 20) + // These numbers are the same for all sets with 60 uncommons in asymmetrical A/B print runs + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + private final RarityConfiguration foilRuns = new RarityConfiguration( + FC, FC, FC, FC, FC, FC, FC, FC, FC, FC, + FU, FU, FU, + R1 + ); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(foilRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/OathOfTheGatewatch.java b/Mage.Sets/src/mage/sets/OathOfTheGatewatch.java index fe5f9f44728..025e70c4c5b 100644 --- a/Mage.Sets/src/mage/sets/OathOfTheGatewatch.java +++ b/Mage.Sets/src/mage/sets/OathOfTheGatewatch.java @@ -28,7 +28,7 @@ public final class OathOfTheGatewatch extends ExpansionSet { this.blockName = "Battle for Zendikar"; this.parentSet = BattleForZendikar.getInstance(); this.hasBoosters = true; - this.hasBasicLands = true; + this.hasBasicLands = false; this.numBoosterLands = 1; this.numBoosterCommon = 10; this.numBoosterUncommon = 3; @@ -213,10 +213,10 @@ public final class OathOfTheGatewatch extends ExpansionSet { cards.add(new SetCardInfo("Wandering Fumarole", 182, Rarity.RARE, mage.cards.w.WanderingFumarole.class)); cards.add(new SetCardInfo("Warden of Geometries", 11, Rarity.COMMON, mage.cards.w.WardenOfGeometries.class)); cards.add(new SetCardInfo("Warping Wail", 12, Rarity.UNCOMMON, mage.cards.w.WarpingWail.class)); - cards.add(new SetCardInfo("Wastes", "183a", Rarity.LAND, mage.cards.w.Wastes.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Wastes", "184a", Rarity.LAND, mage.cards.w.Wastes.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Wastes", 183, Rarity.LAND, mage.cards.w.Wastes.class, FULL_ART_BFZ_VARIOUS)); - cards.add(new SetCardInfo("Wastes", 184, Rarity.LAND, mage.cards.w.Wastes.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Wastes", "183a", Rarity.COMMON, mage.cards.w.Wastes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wastes", "184a", Rarity.COMMON, mage.cards.w.Wastes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wastes", 183, Rarity.COMMON, mage.cards.w.Wastes.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Wastes", 184, Rarity.COMMON, mage.cards.w.Wastes.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Weapons Trainer", 160, Rarity.UNCOMMON, mage.cards.w.WeaponsTrainer.class)); cards.add(new SetCardInfo("Witness the End", 82, Rarity.COMMON, mage.cards.w.WitnessTheEnd.class)); cards.add(new SetCardInfo("World Breaker", 126, Rarity.MYTHIC, mage.cards.w.WorldBreaker.class)); @@ -225,6 +225,16 @@ public final class OathOfTheGatewatch extends ExpansionSet { cards.add(new SetCardInfo("Zulaport Chainmage", 93, Rarity.COMMON, mage.cards.z.ZulaportChainmage.class)); } + @Override + public List getCardsByRarity(Rarity rarity) { + List cards = super.getCardsByRarity(rarity); + if (rarity == Rarity.COMMON) { + // only the full-art versions of Wastes are found in boosters + cards.removeIf(cardInfo -> cardInfo.getCardNumber().contains("a")); + } + return cards; + } + @Override public List getSpecialLand() { if (savedSpecialLand.isEmpty()) { @@ -237,4 +247,4 @@ public final class OathOfTheGatewatch extends ExpansionSet { return new ArrayList<>(savedSpecialLand); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/RiseOfTheEldrazi.java b/Mage.Sets/src/mage/sets/RiseOfTheEldrazi.java index 9e7f6cb6f2c..294115614ce 100644 --- a/Mage.Sets/src/mage/sets/RiseOfTheEldrazi.java +++ b/Mage.Sets/src/mage/sets/RiseOfTheEldrazi.java @@ -1,10 +1,16 @@ - package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + /** * * @author BetaSteward_at_googlemail.com @@ -277,4 +283,124 @@ public final class RiseOfTheEldrazi extends ExpansionSet { cards.add(new SetCardInfo("Zulaport Enforcer", 133, Rarity.COMMON, mage.cards.z.ZulaportEnforcer.class)); } + @Override + public BoosterCollator createCollator() { + return new RiseOfTheEldraziCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/roe.html +// Using USA collation +class RiseOfTheEldraziCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "153", "16", "186", "60", "228", "148", "111", "145", "29", "213", "72", "110", "138", "36", "174", "133", "88", "222", "56", "138", "42", "203", "142", "121", "223", "88", "213", "43", "97", "56", "145", "222", "111", "187", "29", "149", "60", "16", "148", "74", "186", "130", "41", "133", "166", "223", "65", "36", "203", "149", "110", "142", "41", "65", "228", "187", "97", "72", "153", "42", "130", "174", "43", "166", "74", "121"); + private final CardRun commonB = new CardRun(true, "144", "30", "196", "83", "98", "22", "195", "135", "59", "106", "209", "30", "147", "83", "118", "195", "106", "201", "22", "144", "76", "123", "154", "44", "199", "95", "59", "76", "209", "144", "44", "19", "123", "54", "98", "201", "135", "76", "199", "106", "147", "22", "59", "196", "19", "135", "118", "30", "201", "54", "147", "95", "44", "196", "118", "154", "83", "199", "98", "19", "54", "209", "95", "154", "195", "123"); + private final CardRun commonC1 = new CardRun(true, "132", "50", "85", "207", "136", "116", "175", "26", "5", "85", "99", "155", "78", "207", "15", "150", "108", "86", "27", "210", "164", "5", "202", "99", "79", "23", "182", "136", "114", "155", "175", "78", "23", "132", "182", "15", "164", "114", "68", "210", "171", "193", "50", "86", "150", "27", "68", "202", "108", "26", "79", "171", "116", "193", "5"); + private final CardRun commonC2 = new CardRun(true, "87", "173", "73", "102", "34", "161", "208", "18", "200", "93", "105", "34", "159", "87", "24", "102", "208", "93", "18", "161", "13", "67", "46", "159", "208", "73", "200", "34", "67", "161", "126", "18", "105", "13", "194", "73", "24", "173", "102", "200", "13", "87", "194", "24", "159", "126", "67", "46", "93", "173", "194", "105", "126", "13", "46"); + private final CardRun uncommonA = new CardRun(true, "168", "8", "189", "63", "28", "127", "190", "168", "10", "40", "162", "226", "119", "204", "77", "8", "53", "103", "216", "163", "63", "48", "9", "204", "122", "28", "221", "146", "61", "189", "143", "122", "53", "226", "2", "71", "178", "143", "103", "77", "48", "216", "61", "178", "2", "127", "146", "37", "221", "71", "179", "162", "94", "9", "66", "40", "119", "179", "220", "163", "10", "190", "37", "94", "66", "220"); + private final CardRun uncommonB = new CardRun(true, "137", "80", "224", "49", "115", "217", "205", "137", "92", "45", "185", "115", "70", "49", "134", "217", "104", "181", "20", "81", "180", "170", "92", "14", "128", "157", "109", "181", "35", "58", "167", "104", "157", "224", "180", "35", "81", "131", "80", "170", "128", "188", "45", "167", "109", "20", "185", "58", "134", "205", "14", "70", "131", "188"); + private final CardRun rareA = new CardRun(true, "158", "47", "198", "12", "129", "7", "52", "191", "215", "64", "160", "113", "219", "39", "84", "3", "218", "31", "125", "212", "158", "184", "25", "112", "75", "156", "84", "11", "225", "31", "211", "219", "107", "172", "25", "191", "227", "62", "214", "3", "160", "7", "57", "52", "107", "156", "125", "184", "6", "225", "64", "47", "211", "215", "152", "39", "198", "129", "11", "112", "62", "172", "21", "218", "227", "57"); + private final CardRun rareB = new CardRun(true, "38", "176", "91", "139", "140", "183", "117", "51", "90", "169", "124", "177", "100", "55", "38", "141", "197", "89", "206", "117", "151", "17", "1", "82", "96", "140", "69", "183", "100", "169", "192", "197", "165", "90", "17", "91", "101", "139", "4", "124", "32", "176", "141", "69", "177", "82", "33", "151", "96", "206", "101", "89", "165", "32", "120"); + private final CardRun land = new CardRun(false, "229", "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242", "243", "244", "245", "246", "247", "248"); + + private final BoosterStructure AAABC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABC2C2C2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, + commonC2, commonC2, commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBC2C2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBBC2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2 + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA); + private final BoosterStructure R2 = new BoosterStructure(rareB); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 3.27 A commons (36 / 11) + // 2.18 B commons (24 / 11) + // 2.73 C1 commons (30 / 11, or 60 / 11 in each C1 booster) + // 1.82 C2 commons (20 / 11, or 40 / 11 in each C2 booster) + // These numbers are the same as all sets with 101 commons in A/B/C1/C2 print runs + // The only difference is ROE has two overprinted commons instead of one short-printed + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + + AAABC2C2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBBC2C2C2C2, + AAABBBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBBC2C2 + ); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 1.65 A uncommons (33 / 20) + // 1.35 B uncommons (27 / 20) + // These numbers are the same for all sets with 60 uncommons in asymmetrical A/B print runs + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + private final RarityConfiguration rareRuns = new RarityConfiguration( + R1, R1, R1, R1, R1, R1, + R2, R2, R2, R2, R2 + ); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/ScarsOfMirrodin.java b/Mage.Sets/src/mage/sets/ScarsOfMirrodin.java index 0742778d39d..f868cad144d 100644 --- a/Mage.Sets/src/mage/sets/ScarsOfMirrodin.java +++ b/Mage.Sets/src/mage/sets/ScarsOfMirrodin.java @@ -1,10 +1,16 @@ - package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + /** * * @author nantuko84 @@ -277,4 +283,124 @@ public final class ScarsOfMirrodin extends ExpansionSet { cards.add(new SetCardInfo("Wurmcoil Engine", 223, Rarity.MYTHIC, mage.cards.w.WurmcoilEngine.class)); } + @Override + public BoosterCollator createCollator() { + return new ScarsOfMirrodinCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/som.html +// Using USA collation +class ScarsOfMirrodinCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "70", "8", "189", "38", "106", "166", "65", "23", "221", "112", "138", "75", "84", "168", "13", "29", "157", "68", "117", "156", "89", "210", "70", "44", "200", "10", "170", "63", "130", "4", "168", "38", "157", "88", "8", "204", "65", "29", "221", "106", "23", "200", "130", "166", "75", "36", "189", "89", "13", "204", "4", "138", "68", "44", "202", "84", "112", "210", "63", "10", "156", "36", "88", "202", "117", "170"); + private final CardRun commonB = new CardRun(true, "114", "58", "207", "109", "18", "146", "107", "37", "142", "114", "147", "41", "2", "192", "107", "52", "146", "125", "18", "209", "129", "178", "58", "91", "192", "55", "52", "207", "125", "2", "186", "91", "142", "129", "37", "209", "109", "103", "178", "41", "18", "207", "58", "186", "103", "129", "146", "41", "91", "178", "55", "37", "147", "125", "186", "114", "52", "209", "55", "103", "192", "109", "107", "147", "2", "142"); + private final CardRun commonC1 = new CardRun(true, "116", "54", "184", "97", "15", "160", "113", "51", "203", "54", "153", "82", "21", "155", "120", "45", "187", "60", "97", "153", "46", "15", "133", "45", "165", "227", "92", "184", "31", "7", "60", "116", "187", "227", "96", "219", "51", "21", "191", "120", "77", "165", "82", "219", "133", "92", "203", "46", "77", "155", "113", "7", "96", "31", "191"); + private final CardRun commonC2 = new CardRun(true, "197", "56", "27", "102", "136", "19", "140", "20", "76", "49", "131", "222", "158", "56", "136", "80", "20", "131", "140", "100", "158", "160", "80", "49", "20", "134", "222", "27", "218", "42", "100", "76", "197", "140", "102", "131", "42", "80", "218", "19", "197", "158", "134", "42", "56", "100", "19", "222", "136", "27", "49", "102", "134", "76", "218"); + private final CardRun uncommonA = new CardRun(true, "78", "108", "171", "90", "47", "185", "101", "132", "217", "3", "214", "71", "167", "127", "53", "159", "85", "161", "71", "216", "26", "149", "62", "164", "124", "47", "161", "61", "81", "3", "185", "26", "143", "62", "50", "213", "90", "216", "124", "214", "30", "167", "17", "139", "61", "1", "171", "85", "30", "217", "81", "108", "139", "1", "149", "78", "127", "159", "101", "53", "213", "17", "132", "164", "50", "143"); + private final CardRun uncommonB = new CardRun(true, "59", "151", "115", "9", "211", "40", "144", "59", "198", "83", "174", "128", "48", "199", "5", "9", "152", "148", "67", "99", "151", "34", "16", "211", "174", "87", "115", "198", "40", "74", "181", "215", "83", "111", "190", "34", "162", "16", "144", "67", "152", "111", "181", "87", "148", "5", "74", "199", "128", "190", "99", "215", "48", "162"); + private final CardRun rareA = new CardRun(true, "180", "95", "119", "201", "177", "66", "122", "14", "205", "226", "220", "43", "223", "95", "121", "69", "212", "22", "183", "229", "173", "28", "73", "194", "33", "98", "205", "25", "179", "224", "182", "122", "93", "201", "22", "180", "43", "64", "188", "229", "212", "119", "145", "28", "69", "194", "14", "135", "163", "93", "183", "224", "220", "25", "39", "188", "66", "182", "121", "98", "145", "73", "11", "226", "163", "33"); + private final CardRun rareB = new CardRun(true, "154", "104", "225", "193", "24", "172", "118", "105", "175", "72", "169", "228", "141", "208", "35", "150", "86", "126", "118", "12", "195", "176", "72", "32", "137", "105", "123", "150", "24", "196", "225", "141", "57", "154", "206", "6", "228", "104", "110", "137", "126", "175", "35", "169", "94", "195", "172", "57", "12", "86", "206", "79", "110", "196", "32"); + private final CardRun land = new CardRun(false, "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242", "243", "244", "245", "246", "247", "248", "249"); + + private final BoosterStructure AAABC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABC2C2C2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, + commonC2, commonC2, commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBC2C2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBBC2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2 + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA); + private final BoosterStructure R2 = new BoosterStructure(rareB); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 3.27 A commons (36 / 11) + // 2.18 B commons (24 / 11) + // 2.73 C1 commons (30 / 11, or 60 / 11 in each C1 booster) + // 1.82 C2 commons (20 / 11, or 40 / 11 in each C2 booster) + // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs + // and with 10 common slots per booster + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + + AAABC2C2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBBC2C2C2C2, + AAABBBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBBC2C2 + ); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 1.65 A uncommons (33 / 20) + // 1.35 B uncommons (27 / 20) + // These numbers are the same for all sets with 60 uncommons in asymmetrical A/B print runs + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + private final RarityConfiguration rareRuns = new RarityConfiguration( + R1, R1, R1, R1, R1, R1, + R2, R2, R2, R2, R2 + ); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/ShardsOfAlara.java b/Mage.Sets/src/mage/sets/ShardsOfAlara.java index 252bc48035e..17cd13b1de7 100644 --- a/Mage.Sets/src/mage/sets/ShardsOfAlara.java +++ b/Mage.Sets/src/mage/sets/ShardsOfAlara.java @@ -1,10 +1,16 @@ - package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + /** * * @author BetaSteward_at_googlemail.com @@ -278,4 +284,114 @@ public final class ShardsOfAlara extends ExpansionSet { cards.add(new SetCardInfo("Yoked Plowbeast", 31, Rarity.COMMON, mage.cards.y.YokedPlowbeast.class)); } + @Override + public BoosterCollator createCollator() { + return new ShardsOfAlaraCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/ala.html +// Using USA collation +class ShardsOfAlaraCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "134", "116", "42", "18", "71", "195", "126", "118", "32", "11", "92", "128", "116", "37", "31", "65", "165", "126", "110", "46", "22", "74", "127", "115", "62", "30", "67", "165", "147", "104", "59", "18", "92", "144", "118", "42", "1", "73", "195", "128", "111", "59", "11", "65", "134", "104", "62", "22", "71", "176", "144", "115", "46", "30", "73", "127", "110", "32", "31", "74", "176", "147", "111", "37", "1", "67"); + private final CardRun commonB = new CardRun(true, "189", "130", "93", "28", "198", "47", "136", "203", "88", "12", "169", "102", "130", "26", "93", "153", "61", "86", "203", "47", "28", "139", "207", "130", "97", "88", "198", "26", "61", "203", "36", "24", "86", "189", "136", "93", "12", "153", "102", "87", "207", "36", "28", "88", "169", "139", "97", "24", "189", "61", "136", "153", "47", "26", "87", "198", "139", "102", "24", "169", "36", "86", "207", "12", "97", "87"); + private final CardRun commonC1 = new CardRun(true, "221", "13", "149", "90", "213", "108", "8", "133", "34", "156", "107", "4", "90", "221", "214", "66", "125", "57", "8", "121", "218", "54", "149", "15", "83", "227", "33", "186", "94", "141", "4", "66", "215", "34", "107", "227", "81", "213", "133", "13", "54", "108", "156", "141", "214", "83", "94", "15", "125", "33", "215", "121", "186", "81", "57"); + private final CardRun commonC2 = new CardRun(true, "223", "75", "162", "120", "132", "52", "216", "208", "225", "218", "173", "120", "152", "75", "52", "20", "162", "225", "77", "132", "212", "158", "105", "224", "162", "35", "77", "223", "216", "132", "208", "75", "158", "52", "224", "212", "152", "105", "208", "35", "216", "20", "223", "173", "152", "224", "77", "120", "35", "158", "20", "225", "212", "173", "105"); + private final CardRun uncommonA = new CardRun(true, "161", "129", "43", "184", "228", "171", "5", "226", "205", "114", "202", "76", "142", "155", "43", "197", "27", "222", "168", "106", "188", "85", "142", "180", "40", "209", "229", "5", "220", "168", "161", "112", "80", "184", "145", "155", "44", "27", "228", "157", "175", "106", "76", "205", "222", "129", "197", "40", "19", "209", "220", "157", "114", "175", "85", "145", "188", "229", "44", "180", "19", "171", "112", "226", "202", "80"); + private final CardRun uncommonB = new CardRun(true, "82", "124", "23", "181", "29", "137", "98", "68", "174", "39", "123", "99", "78", "167", "53", "117", "2", "181", "200", "39", "113", "124", "68", "177", "41", "201", "3", "99", "151", "58", "123", "117", "72", "200", "53", "2", "137", "82", "167", "113", "201", "29", "72", "190", "41", "78", "23", "151", "174", "98", "177", "3", "58", "190"); + private final CardRun rareA = new CardRun(true, "55", "100", "183", "89", "159", "148", "6", "219", "70", "192", "95", "48", "187", "49", "183", "140", "21", "217", "70", "204", "100", "164", "95", "49", "148", "187", "25", "211", "163", "89", "160", "150", "109", "17", "48", "199", "25", "138", "163", "64", "206", "91", "103", "219", "45", "199", "6", "140", "159", "55", "164", "64", "109", "217", "17", "160", "138", "103", "206", "45", "204", "211", "21", "91", "150", "192"); + private final CardRun rareB = new CardRun(true, "69", "196", "10", "84", "122", "194", "51", "16", "210", "131", "96", "56", "154", "146", "122", "7", "191", "14", "50", "170", "84", "135", "119", "193", "38", "79", "10", "182", "131", "51", "185", "63", "101", "143", "178", "14", "56", "119", "9", "135", "69", "172", "38", "96", "146", "179", "7", "50", "63", "166", "101", "16", "60", "79", "143"); + private final CardRun land = new CardRun(false, "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242", "243", "244", "245", "246", "247", "248", "249"); + + private final BoosterStructure AAABC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC2C2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBBC2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2 + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA); + private final BoosterStructure R2 = new BoosterStructure(rareB); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 3.27 A commons (36 / 11) + // 2.18 B commons (24 / 11) + // 2.73 C1 commons (30 / 11, or 60 / 11 in each C1 booster) + // 1.82 C2 commons (20 / 11, or 40 / 11 in each C2 booster) + // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs + // and with 10 common slots per booster + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + + AAABBC2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBBC2C2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBBC2C2, + AAAABBBBC2C2 + ); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 1.65 A uncommons (33 / 20) + // 1.35 B uncommons (27 / 20) + // These numbers are the same for all sets with 60 uncommons in asymmetrical A/B print runs + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + private final RarityConfiguration rareRuns = new RarityConfiguration( + R1, R1, R1, R1, R1, R1, + R2, R2, R2, R2, R2 + ); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java b/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java index 364aaedab82..d9c1aaa788b 100644 --- a/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java +++ b/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java @@ -29,7 +29,7 @@ public final class StrixhavenSchoolOfMages extends ExpansionSet { } private StrixhavenSchoolOfMages() { - super("Strixhaven: School of Mages", "STX", ExpansionSet.buildDate(2021, 4, 23), SetType.EXPANSION, new StrixhavenSchoolOfMagesCollator()); + super("Strixhaven: School of Mages", "STX", ExpansionSet.buildDate(2021, 4, 23), SetType.EXPANSION); this.blockName = "Strixhaven: School of Mages"; this.hasBoosters = true; this.hasBasicLands = true; @@ -479,154 +479,85 @@ public final class StrixhavenSchoolOfMages extends ExpansionSet { .stream() .forEach(cardInfo -> inBoosterMap.put("STA_" + cardInfo.getCardNumber(), cardInfo)); } + + @Override + public BoosterCollator createCollator() { + return new StrixhavenSchoolOfMagesCollator(); + } } // Booster collation info from https://www.lethe.xyz/mtg/collation/stx.html // Using Belgian collation plus other info inferred from various sources class StrixhavenSchoolOfMagesCollator implements BoosterCollator { - private static class StrixhavenSchoolOfMagesRun extends CardRun { - private static final StrixhavenSchoolOfMagesRun commonA = new StrixhavenSchoolOfMagesRun(true, "249", "206", "182", "226", "237", "255", "210", "209", "239", "226", "251", "185", "219", "243", "206", "164", "215", "238", "256", "184", "239", "208", "254", "237", "185", "223", "251", "206", "166", "182", "164", "238", "204", "233", "184", "210", "182", "252", "243", "254", "208", "194", "255", "243", "249", "201", "204", "194", "235", "239", "166", "251", "223", "252", "237", "208", "233", "241", "219", "255", "201", "194", "164", "252", "170", "223", "241", "215", "166", "249", "237", "238", "184", "210", "243", "209", "235", "252", "204", "210", "185", "233", "249", "256", "239", "235", "209", "170", "201", "226", "241", "215", "206", "256", "208", "254", "185", "251", "226", "170", "184", "256", "235", "233", "209", "238", "254", "219", "201", "241", "182", "164", "194", "223", "170", "204", "166", "219", "215", "255"); - private static final StrixhavenSchoolOfMagesRun commonB = new StrixhavenSchoolOfMagesRun(true, "79", "9", "111", "136", "52", "85", "12", "118", "131", "40", "90", "34", "117", "141", "60", "69", "30", "93", "140", "36", "74", "16", "97", "143", "40", "87", "11", "99", "121", "39", "75", "30", "106", "122", "38", "79", "22", "97", "131", "51", "90", "23", "112", "145", "50", "87", "18", "103", "124", "43", "68", "34", "102", "142", "49", "77", "16", "93", "140", "52", "73", "32", "116", "144", "60", "63", "22", "109", "124", "55", "69", "9", "106", "143", "39", "73", "23", "116", "142", "61", "84", "8", "99", "122", "43", "74", "32", "117", "145", "36", "75", "19", "112", "141", "61", "77", "8", "109", "144", "50", "84", "18", "118", "137", "55", "68", "12", "111", "121", "38", "63", "19", "103", "136", "51", "85", "11", "102", "137", "49"); - private static final StrixhavenSchoolOfMagesRun commonC = new StrixhavenSchoolOfMagesRun(true, "263", "268", "270", "271", "273", "275"); - private static final StrixhavenSchoolOfMagesRun uncommonA = new StrixhavenSchoolOfMagesRun(true, "257", "65", "56", "132", "92", "125", "72", "62", "104", "89", "123", "54", "10", "135", "114", "53", "105", "188", "45", "115", "47", "70", "100", "129", "88", "260", "46", "76", "257", "110", "65", "13", "139", "78", "92", "26", "72", "15", "56", "89", "134", "28", "70", "91", "132", "105", "31", "123", "88", "104", "135", "54", "115", "25", "76", "13", "62", "125", "35", "188", "65", "260", "10", "114", "129", "47", "100", "15", "257", "53", "26", "110", "139", "28", "134", "56", "35", "45", "91", "72", "10", "31", "132", "54", "89", "15", "78", "46", "123", "25", "92", "135", "104", "70", "125", "105", "260", "62", "188", "129", "114", "88", "13", "53", "26", "115", "47", "76", "28", "139", "100", "35", "91", "45", "78", "134", "31", "46", "25", "110"); - private static final StrixhavenSchoolOfMagesRun uncommonB = new StrixhavenSchoolOfMagesRun(true, "229", "218", "216", "222", "212", "198", "71", "177", "202", "138", "258", "162", "81", "175", "213", "224", "220", "176", "227", "107", "247", "186", "169", "216", "261", "197", "242", "126", "262", "198", "24", "225", "207", "229", "190", "202", "81", "178", "200", "193", "41", "162", "71", "171", "59", "212", "218", "222", "227", "138", "231", "220", "258", "175", "169", "186", "107", "193", "250", "197", "176", "171", "126", "261", "247", "227", "213", "177", "262", "207", "190", "224", "225", "24", "216", "200", "242", "71", "178", "41", "212", "59", "198", "81", "231", "220", "229", "218", "202", "169", "175", "222", "193", "197", "250", "213", "186", "126", "177", "190", "258", "138", "162", "107", "261", "224", "200", "176", "178", "24", "247", "262", "207", "171", "225", "59", "231", "242", "41", "250"); - private static final StrixhavenSchoolOfMagesRun rareA = new StrixhavenSchoolOfMagesRun(false, "6", "14", "17", "20", "27", "29", "33", "37", "42", "44", "48", "58", "64", "66", "80", "82", "86", "94", "96", "98", "101", "113", "119", "127", "130", "133", "146", "147", "150", "152", "154", "155", "157", "158", "159", "160", "161", "165", "172", "173", "174", "179", "180", "181", "199", "205", "214", "217", "221", "228", "232", "234", "244", "246", "248", "253", "259", "264", "265", "266", "267", "269", "272", "274", "6", "14", "17", "20", "27", "29", "33", "37", "42", "44", "48", "58", "64", "66", "80", "82", "86", "94", "96", "98", "101", "113", "119", "127", "130", "133", "146", "147", "150", "152", "154", "155", "157", "158", "159", "160", "161", "165", "172", "173", "174", "179", "180", "181", "199", "205", "214", "217", "221", "228", "232", "234", "244", "246", "248", "253", "259", "264", "265", "266", "267", "269", "272", "274", "21", "83", "95", "128", "148", "149", "151", "153", "156", "163", "167", "168", "189", "191", "192", "196", "203", "230", "240", "245"); - private static final StrixhavenSchoolOfMagesRun commonLesson = new StrixhavenSchoolOfMagesRun(false, "1", "2", "3", "4", "183", "187", "195", "211", "236"); - private static final StrixhavenSchoolOfMagesRun rareLesson = new StrixhavenSchoolOfMagesRun(false, "5", "7", "57", "67", "108", "120", "7", "57", "67", "108", "120"); - private static final StrixhavenSchoolOfMagesRun uncommonArchive = new StrixhavenSchoolOfMagesRun(true, "18", "29", "19", "41", "4", "57", "46", "23", "35", "3", "20", "49", "37", "30", "41", "24", "9", "44", "4", "29", "3", "46", "35", "18", "57", "41", "9", "19", "24", "44", "51", "30", "23", "37", "18", "49", "20", "29", "4", "19", "35", "46", "51", "23", "30", "18", "57", "3", "37", "49", "41", "24", "9", "44", "20", "4", "23", "37", "30", "3", "35", "46", "57", "29", "9", "29", "49", "19", "51", "44", "4", "24", "41", "18", "46", "3", "20", "57", "19", "35", "44", "23", "24", "9", "51", "30", "49", "37", "20", "51"); - private static final StrixhavenSchoolOfMagesRun rareArchive = new StrixhavenSchoolOfMagesRun(false, "5", "6", "7", "8", "10", "13", "14", "15", "16", "21", "26", "28", "31", "32", "34", "38", "39", "42", "45", "47", "48", "52", "53", "56", "58", "59", "60", "61", "62", "63"); - private static final StrixhavenSchoolOfMagesRun mythicArchive = new StrixhavenSchoolOfMagesRun(false, "1", "2", "11", "12", "17", "22", "25", "27", "33", "36", "40", "43", "50", "54", "55"); + private final CardRun commonA = new CardRun(true, "249", "206", "182", "226", "237", "255", "210", "209", "239", "226", "251", "185", "219", "243", "206", "164", "215", "238", "256", "184", "239", "208", "254", "237", "185", "223", "251", "206", "166", "182", "164", "238", "204", "233", "184", "210", "182", "252", "243", "254", "208", "194", "255", "243", "249", "201", "204", "194", "235", "239", "166", "251", "223", "252", "237", "208", "233", "241", "219", "255", "201", "194", "164", "252", "170", "223", "241", "215", "166", "249", "237", "238", "184", "210", "243", "209", "235", "252", "204", "210", "185", "233", "249", "256", "239", "235", "209", "170", "201", "226", "241", "215", "206", "256", "208", "254", "185", "251", "226", "170", "184", "256", "235", "233", "209", "238", "254", "219", "201", "241", "182", "164", "194", "223", "170", "204", "166", "219", "215", "255"); + private final CardRun commonB = new CardRun(true, "79", "9", "111", "136", "52", "85", "12", "118", "131", "40", "90", "34", "117", "141", "60", "69", "30", "93", "140", "36", "74", "16", "97", "143", "40", "87", "11", "99", "121", "39", "75", "30", "106", "122", "38", "79", "22", "97", "131", "51", "90", "23", "112", "145", "50", "87", "18", "103", "124", "43", "68", "34", "102", "142", "49", "77", "16", "93", "140", "52", "73", "32", "116", "144", "60", "63", "22", "109", "124", "55", "69", "9", "106", "143", "39", "73", "23", "116", "142", "61", "84", "8", "99", "122", "43", "74", "32", "117", "145", "36", "75", "19", "112", "141", "61", "77", "8", "109", "144", "50", "84", "18", "118", "137", "55", "68", "12", "111", "121", "38", "63", "19", "103", "136", "51", "85", "11", "102", "137", "49"); + private final CardRun commonC = new CardRun(false, "263", "268", "270", "271", "273", "275"); + private final CardRun uncommonA = new CardRun(true, "257", "65", "56", "132", "92", "125", "72", "62", "104", "89", "123", "54", "10", "135", "114", "53", "105", "188", "45", "115", "47", "70", "100", "129", "88", "260", "46", "76", "257", "110", "65", "13", "139", "78", "92", "26", "72", "15", "56", "89", "134", "28", "70", "91", "132", "105", "31", "123", "88", "104", "135", "54", "115", "25", "76", "13", "62", "125", "35", "188", "65", "260", "10", "114", "129", "47", "100", "15", "257", "53", "26", "110", "139", "28", "134", "56", "35", "45", "91", "72", "10", "31", "132", "54", "89", "15", "78", "46", "123", "25", "92", "135", "104", "70", "125", "105", "260", "62", "188", "129", "114", "88", "13", "53", "26", "115", "47", "76", "28", "139", "100", "35", "91", "45", "78", "134", "31", "46", "25", "110"); + private final CardRun uncommonB = new CardRun(true, "229", "218", "216", "222", "212", "198", "71", "177", "202", "138", "258", "162", "81", "175", "213", "224", "220", "176", "227", "107", "247", "186", "169", "216", "261", "197", "242", "126", "262", "198", "24", "225", "207", "229", "190", "202", "81", "178", "200", "193", "41", "162", "71", "171", "59", "212", "218", "222", "227", "138", "231", "220", "258", "175", "169", "186", "107", "193", "250", "197", "176", "171", "126", "261", "247", "227", "213", "177", "262", "207", "190", "224", "225", "24", "216", "200", "242", "71", "178", "41", "212", "59", "198", "81", "231", "220", "229", "218", "202", "169", "175", "222", "193", "197", "250", "213", "186", "126", "177", "190", "258", "138", "162", "107", "261", "224", "200", "176", "178", "24", "247", "262", "207", "171", "225", "59", "231", "242", "41", "250"); + private final CardRun rareA = new CardRun(false, "6", "14", "17", "20", "27", "29", "33", "37", "42", "44", "48", "58", "64", "66", "80", "82", "86", "94", "96", "98", "101", "113", "119", "127", "130", "133", "146", "147", "150", "152", "154", "155", "157", "158", "159", "160", "161", "165", "172", "173", "174", "179", "180", "181", "199", "205", "214", "217", "221", "228", "232", "234", "244", "246", "248", "253", "259", "264", "265", "266", "267", "269", "272", "274", "6", "14", "17", "20", "27", "29", "33", "37", "42", "44", "48", "58", "64", "66", "80", "82", "86", "94", "96", "98", "101", "113", "119", "127", "130", "133", "146", "147", "150", "152", "154", "155", "157", "158", "159", "160", "161", "165", "172", "173", "174", "179", "180", "181", "199", "205", "214", "217", "221", "228", "232", "234", "244", "246", "248", "253", "259", "264", "265", "266", "267", "269", "272", "274", "21", "83", "95", "128", "148", "149", "151", "153", "156", "163", "167", "168", "189", "191", "192", "196", "203", "230", "240", "245"); + private final CardRun commonLesson = new CardRun(true, "4", "187", "183", "195", "211", "3", "1", "236", "2", "187", "195", "4", "183", "211", "1", "2", "236", "3", "183", "187", "4", "195", "2", "211", "3", "1", "236", "187", "183", "195", "4", "1", "211", "3", "2", "236", "187", "4", "183", "195", "3", "1", "2", "211", "236", "187", "195", "4", "183", "2", "1", "211", "3", "187", "183", "4", "236", "195", "1", "3", "211", "236", "2", "187", "4", "195", "183", "3", "211", "1", "2", "236", "187", "4", "195", "183", "211", "3", "1", "2", "236", "183", "195", "4", "187", "1", "211", "3", "2", "236", "187", "4", "195", "183", "2", "211", "3", "1", "187", "236", "4", "183", "195", "211", "1", "236", "3", "2", "195", "187", "183", "4", "2", "236", "3", "211", "1"); + private final CardRun rareLesson = new CardRun(false, "5", "7", "57", "67", "108", "120", "7", "57", "67", "108", "120"); + private final CardRun uncommonArchive = new CardRun(true, "18", "29", "19", "41", "4", "57", "46", "23", "35", "3", "20", "49", "37", "30", "41", "24", "9", "44", "4", "29", "3", "46", "35", "18", "57", "41", "9", "19", "24", "44", "51", "30", "23", "37", "18", "49", "20", "29", "4", "19", "35", "46", "51", "23", "30", "18", "57", "3", "37", "49", "41", "24", "9", "44", "20", "4", "23", "37", "30", "3", "35", "46", "57", "29", "9", "29", "49", "19", "51", "44", "4", "24", "41", "18", "46", "3", "20", "57", "19", "35", "44", "23", "24", "9", "51", "30", "49", "37", "20", "51"); + private final CardRun rareArchive = new CardRun(false, "5", "6", "7", "8", "10", "13", "14", "15", "16", "21", "26", "28", "31", "32", "34", "38", "39", "42", "45", "47", "48", "52", "53", "56", "58", "59", "60", "61", "62", "63", "5", "6", "7", "8", "10", "13", "14", "15", "16", "21", "26", "28", "31", "32", "34", "38", "39", "42", "45", "47", "48", "52", "53", "56", "58", "59", "60", "61", "62", "63", "1", "2", "11", "12", "17", "22", "25", "27", "33", "36", "40", "43", "50", "54", "55"); - private StrixhavenSchoolOfMagesRun(boolean keepOrder, String... numbers) { - super(keepOrder, numbers); - } - } - - private static class StrixhavenSchoolOfMagesStructure extends BoosterStructure { - private static final StrixhavenSchoolOfMagesStructure C1 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonC, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB - ); - private static final StrixhavenSchoolOfMagesStructure C2 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB - ); - private static final StrixhavenSchoolOfMagesStructure C3 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonC, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB - ); - private static final StrixhavenSchoolOfMagesStructure C4 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonA, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB, - StrixhavenSchoolOfMagesRun.commonB - ); - private static final StrixhavenSchoolOfMagesStructure U1 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.uncommonA, - StrixhavenSchoolOfMagesRun.uncommonB, - StrixhavenSchoolOfMagesRun.uncommonB - ); - private static final StrixhavenSchoolOfMagesStructure U2 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.uncommonA, - StrixhavenSchoolOfMagesRun.uncommonA, - StrixhavenSchoolOfMagesRun.uncommonB - ); - private static final StrixhavenSchoolOfMagesStructure R1 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.rareA - ); - private static final StrixhavenSchoolOfMagesStructure L1 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.commonLesson - ); - private static final StrixhavenSchoolOfMagesStructure L2 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.rareLesson - ); - private static final StrixhavenSchoolOfMagesStructure A1 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.uncommonArchive - ); - private static final StrixhavenSchoolOfMagesStructure A2 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.rareArchive - ); - private static final StrixhavenSchoolOfMagesStructure A3 = new StrixhavenSchoolOfMagesStructure( - StrixhavenSchoolOfMagesRun.mythicArchive - ); - - private StrixhavenSchoolOfMagesStructure(CardRun... runs) { - super(runs); - } - } + private final BoosterStructure AABBBBBBC = new BoosterStructure( + commonA, commonA, + commonB, commonB, commonB, commonB, commonB, commonB, + commonC + ); + private final BoosterStructure AAABBBBBC = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, commonB, commonB, + commonC + ); + private final BoosterStructure AAABBBBBB = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, commonB, commonB, commonB + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA); + private final BoosterStructure L1 = new BoosterStructure(commonLesson); + private final BoosterStructure L2 = new BoosterStructure(rareLesson); + private final BoosterStructure A1 = new BoosterStructure(uncommonArchive); + private final BoosterStructure A2 = new BoosterStructure(rareArchive); + // In order for equal numbers of each common to exist, the average booster must contain: + // 2.81 A commons (45 / 16) + // 5.63 B commons (90 / 16) + // 0.56 C commons ( 9 / 16) private final RarityConfiguration commonRuns = new RarityConfiguration( - false, - StrixhavenSchoolOfMagesStructure.C1, - StrixhavenSchoolOfMagesStructure.C2, - StrixhavenSchoolOfMagesStructure.C3, - StrixhavenSchoolOfMagesStructure.C4 - ); - private final RarityConfiguration uncommonRuns = new RarityConfiguration( - StrixhavenSchoolOfMagesStructure.U1, - StrixhavenSchoolOfMagesStructure.U2 - ); - private final RarityConfiguration rareRuns = new RarityConfiguration( - StrixhavenSchoolOfMagesStructure.R1 + AABBBBBBC, + AABBBBBBC, + AABBBBBBC, + AAABBBBBC, + AAABBBBBC, + AAABBBBBC, + AAABBBBBC, + AAABBBBBC, + AAABBBBBC, + AAABBBBBB, + AAABBBBBB, + AAABBBBBB, + AAABBBBBB, + AAABBBBBB, + AAABBBBBB, + AAABBBBBB ); + private final RarityConfiguration uncommonRuns = new RarityConfiguration(AAB, ABB); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + + // The ratio of common to rare/mythic Lesson cards hasn't been officially disclosed + // rare/mythic Lessons were observed in 37 out of 468 packs, which is very close to 2/25 private final RarityConfiguration lessonRuns = new RarityConfiguration( - false, - StrixhavenSchoolOfMagesStructure.L1, StrixhavenSchoolOfMagesStructure.L1, - StrixhavenSchoolOfMagesStructure.L1, StrixhavenSchoolOfMagesStructure.L1, - StrixhavenSchoolOfMagesStructure.L1, StrixhavenSchoolOfMagesStructure.L1, - StrixhavenSchoolOfMagesStructure.L1, StrixhavenSchoolOfMagesStructure.L1, - StrixhavenSchoolOfMagesStructure.L1, StrixhavenSchoolOfMagesStructure.L1, - StrixhavenSchoolOfMagesStructure.L1, StrixhavenSchoolOfMagesStructure.L1, - StrixhavenSchoolOfMagesStructure.L1, StrixhavenSchoolOfMagesStructure.L1, - StrixhavenSchoolOfMagesStructure.L1, - - StrixhavenSchoolOfMagesStructure.L2 + L1, L1, L1, L1, L1, + L1, L1, L1, L1, L1, + L1, L1, L1, L1, L1, + L1, L1, L1, L1, L1, + L1, L1, L1, L2, L2 ); - private final RarityConfiguration archiveRuns = new RarityConfiguration( - false, - StrixhavenSchoolOfMagesStructure.A1, StrixhavenSchoolOfMagesStructure.A1, - - StrixhavenSchoolOfMagesStructure.A2, StrixhavenSchoolOfMagesStructure.A2, - StrixhavenSchoolOfMagesStructure.A2, StrixhavenSchoolOfMagesStructure.A2, - - StrixhavenSchoolOfMagesStructure.A3 - ); - - @Override - public void shuffle() { - commonRuns.shuffle(); - uncommonRuns.shuffle(); - rareRuns.shuffle(); - lessonRuns.shuffle(); - archiveRuns.shuffle(); - } + private final RarityConfiguration archiveRuns = new RarityConfiguration(A1, A1, A2); @Override public List makeBooster() { diff --git a/Mage.Sets/src/mage/sets/TherosBeyondDeath.java b/Mage.Sets/src/mage/sets/TherosBeyondDeath.java index 65ca9b2bf8b..fdf322c4a7d 100644 --- a/Mage.Sets/src/mage/sets/TherosBeyondDeath.java +++ b/Mage.Sets/src/mage/sets/TherosBeyondDeath.java @@ -23,7 +23,7 @@ public final class TherosBeyondDeath extends ExpansionSet { } private TherosBeyondDeath() { - super("Theros Beyond Death", "THB", ExpansionSet.buildDate(2020, 1, 24), SetType.EXPANSION, new TherosBeyondDeathCollator()); + super("Theros Beyond Death", "THB", ExpansionSet.buildDate(2020, 1, 24), SetType.EXPANSION); this.blockName = "Theros Beyond Death"; this.hasBoosters = true; this.numBoosterLands = 1; @@ -392,113 +392,56 @@ public final class TherosBeyondDeath extends ExpansionSet { cards.add(new SetCardInfo("Wolfwillow Haven", 357, Rarity.UNCOMMON, mage.cards.w.WolfwillowHaven.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Wrap in Flames", 164, Rarity.COMMON, mage.cards.w.WrapInFlames.class)); } + + @Override + public BoosterCollator createCollator() { + return new TherosBeyondDeathCollator(); + } } // Booster collation info from https://www.lethe.xyz/mtg/collation/thb.html // Using USA collation for common/uncommon, rare collation inferred from other sets class TherosBeyondDeathCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "155", "29", "79", "127", "38", "57", "159", "41", "66", "140", "30", "78", "163", "28", "56", "137", "25", "68", "144", "20", "67", "146", "26", "49", "134", "40", "61", "159", "29", "51", "164", "17", "57", "149", "38", "66", "127", "30", "47", "144", "36", "79", "155", "41", "67", "137", "28", "78", "140", "25", "56", "163", "20", "49", "146", "40", "68", "134", "17", "51", "149", "26", "47", "164", "36", "61"); + private final CardRun commonB = new CardRun(true, "186", "85", "191", "116", "201", "103", "202", "115", "184", "120", "194", "110", "192", "88", "177", "113", "171", "86", "195", "109", "179", "114", "202", "85", "201", "103", "184", "116", "186", "115", "192", "110", "191", "114", "177", "120", "194", "88", "171", "113", "179", "86", "195", "109", "201", "85", "184", "116", "202", "110", "186", "103", "191", "115", "192", "114", "179", "113", "194", "109", "195", "86", "177", "88", "171", "120"); + private final CardRun commonC1 = new CardRun(true, "203", "154", "106", "77", "10", "174", "58", "16", "141", "238", "122", "46", "173", "152", "22", "240", "100", "74", "200", "142", "97", "11", "48", "203", "241", "154", "106", "35", "82", "174", "77", "10", "240", "141", "100", "58", "238", "122", "232", "152", "22", "46", "111", "173", "241", "16", "74", "97", "48", "11", "200", "142", "111", "82", "35"); + private final CardRun commonC2 = new CardRun(true, "44", "96", "197", "145", "232", "34", "126", "204", "249", "54", "135", "231", "187", "175", "44", "143", "95", "96", "197", "135", "107", "6", "32", "204", "126", "34", "54", "249", "145", "231", "187", "96", "6", "143", "44", "107", "34", "175", "135", "249", "95", "197", "54", "204", "126", "32", "6", "175", "95", "231", "145", "107", "187", "32", "143"); + private final CardRun uncommonA = new CardRun(true, "223", "65", "153", "8", "112", "227", "99", "167", "33", "138", "4", "189", "228", "45", "59", "180", "105", "1", "136", "196", "206", "139", "83", "89", "233", "31", "131", "91", "219", "193", "27", "133", "64", "199", "213", "264", "42", "153", "205", "8", "136", "4", "189", "33", "223", "2", "138", "112", "27", "233", "260", "180", "31", "59", "99", "131", "105", "267", "81", "139", "228", "167", "133", "219", "65", "1", "83", "125", "206", "193", "42", "91", "227", "89", "199", "153", "8", "81", "213", "64", "112", "223", "4", "136", "205", "105", "139", "99", "65", "2", "180", "228", "59", "1", "233", "45", "189", "227", "33", "196", "83", "138", "206", "42", "219", "167", "131", "31", "89", "193", "91", "125", "213", "199", "81", "27", "2", "64", "133", "205"); + private final CardRun uncommonB = new CardRun(true, "226", "101", "128", "183", "21", "234", "87", "50", "242", "176", "239", "132", "9", "216", "62", "119", "172", "160", "104", "69", "168", "225", "130", "237", "63", "15", "102", "166", "5", "129", "121", "53", "239", "70", "182", "128", "21", "234", "92", "69", "101", "160", "23", "230", "75", "130", "104", "172", "50", "7", "162", "87", "183", "226", "62", "216", "258", "132", "176", "237", "263", "15", "242", "63", "5", "225", "168", "129", "121", "53", "230", "21", "70", "102", "166", "128", "92", "234", "23", "183", "160", "104", "75", "226", "162", "7", "239", "182", "9", "132", "101", "69", "172", "216", "242", "50", "176", "87", "225", "62", "15", "168", "119", "237", "130", "5", "70", "102", "166", "63", "23", "129", "121", "53", "182", "7", "162", "230", "92", "75"); + private final CardRun rareA = new CardRun(false, "207", "84", "165", "3", "43", "209", "210", "212", "214", "169", "90", "12", "13", "215", "94", "217", "98", "218", "19", "24", "222", "243", "178", "55", "181", "108", "188", "235", "148", "60", "151", "198", "236", "37", "156", "157", "39", "158", "244", "245", "246", "247", "248", "72", "73", "124", "170", "76", "117", "118", "161", "80", "123", "207", "84", "165", "3", "43", "209", "210", "212", "214", "169", "90", "12", "13", "215", "94", "217", "98", "218", "19", "24", "222", "243", "178", "55", "181", "108", "188", "235", "148", "60", "151", "198", "236", "37", "156", "157", "39", "158", "244", "245", "246", "247", "248", "72", "73", "124", "170", "76", "117", "118", "161", "80", "123", "14", "18", "52", "71", "93", "147", "150", "185", "190", "208", "211", "220", "221", "224", "229"); + private final CardRun rareB = new CardRun(false, "207", "84", "165", "3", "43", "209", "210", "212", "214", "169", "90", "12", "13", "215", "94", "217", "98", "218", "19", "24", "222", "243", "178", "55", "181", "108", "188", "235", "148", "60", "151", "198", "236", "37", "156", "157", "39", "158", "244", "245", "246", "247", "248", "72", "73", "124", "170", "76", "117", "118", "161", "80", "123", "207", "84", "165", "3", "43", "209", "210", "212", "214", "169", "90", "12", "13", "215", "94", "217", "98", "218", "19", "24", "222", "243", "178", "55", "181", "108", "188", "235", "148", "60", "151", "198", "236", "37", "156", "157", "39", "158", "244", "245", "246", "247", "248", "72", "73", "124", "170", "76", "117", "118", "161", "80", "123", "255", "259", "52", "261", "262", "147", "265", "266", "190", "256", "257", "268", "221", "224", "229"); + private final CardRun land = new CardRun(false, "250", "251", "252", "253", "254"); - private static class TherosBeyondDeathRun extends CardRun { - private static final TherosBeyondDeathRun commonA = new TherosBeyondDeathRun(true, "155", "29", "79", "127", "38", "57", "159", "41", "66", "140", "30", "78", "163", "28", "56", "137", "25", "68", "144", "20", "67", "146", "26", "49", "134", "40", "61", "159", "29", "51", "164", "17", "57", "149", "38", "66", "127", "30", "47", "144", "36", "79", "155", "41", "67", "137", "28", "78", "140", "25", "56", "163", "20", "49", "146", "40", "68", "134", "17", "51", "149", "26", "47", "164", "36", "61"); - private static final TherosBeyondDeathRun commonB = new TherosBeyondDeathRun(true, "186", "85", "191", "116", "201", "103", "202", "115", "184", "120", "194", "110", "192", "88", "177", "113", "171", "86", "195", "109", "179", "114", "202", "85", "201", "103", "184", "116", "186", "115", "192", "110", "191", "114", "177", "120", "194", "88", "171", "113", "179", "86", "195", "109", "201", "85", "184", "116", "202", "110", "186", "103", "191", "115", "192", "114", "179", "113", "194", "109", "195", "86", "177", "88", "171", "120"); - private static final TherosBeyondDeathRun commonC1 = new TherosBeyondDeathRun(true, "203", "154", "106", "77", "10", "174", "58", "16", "141", "238", "122", "46", "173", "152", "22", "240", "100", "74", "200", "142", "97", "11", "48", "203", "241", "154", "106", "35", "82", "174", "77", "10", "240", "141", "100", "58", "238", "122", "232", "152", "22", "46", "111", "173", "241", "16", "74", "97", "48", "11", "200", "142", "111", "82", "35"); - private static final TherosBeyondDeathRun commonC2 = new TherosBeyondDeathRun(true, "44", "96", "197", "145", "232", "34", "126", "204", "249", "54", "135", "231", "187", "175", "44", "143", "95", "96", "197", "135", "107", "6", "32", "204", "126", "34", "54", "249", "145", "231", "187", "96", "6", "143", "44", "107", "34", "175", "135", "249", "95", "197", "54", "204", "126", "32", "6", "175", "95", "231", "145", "107", "187", "32", "143"); - private static final TherosBeyondDeathRun uncommonA = new TherosBeyondDeathRun(true, "223", "65", "153", "8", "112", "227", "99", "167", "33", "138", "4", "189", "228", "45", "59", "180", "105", "1", "136", "196", "206", "139", "83", "89", "233", "31", "131", "91", "219", "193", "27", "133", "64", "199", "213", "264", "42", "153", "205", "8", "136", "4", "189", "33", "223", "2", "138", "112", "27", "233", "260", "180", "31", "59", "99", "131", "105", "267", "81", "139", "228", "167", "133", "219", "65", "1", "83", "125", "206", "193", "42", "91", "227", "89", "199", "153", "8", "81", "213", "64", "112", "223", "4", "136", "205", "105", "139", "99", "65", "2", "180", "228", "59", "1", "233", "45", "189", "227", "33", "196", "83", "138", "206", "42", "219", "167", "131", "31", "89", "193", "91", "125", "213", "199", "81", "27", "2", "64", "133", "205"); - private static final TherosBeyondDeathRun uncommonB = new TherosBeyondDeathRun(true, "226", "101", "128", "183", "21", "234", "87", "50", "242", "176", "239", "132", "9", "216", "62", "119", "172", "160", "104", "69", "168", "225", "130", "237", "63", "15", "102", "166", "5", "129", "121", "53", "239", "70", "182", "128", "21", "234", "92", "69", "101", "160", "23", "230", "75", "130", "104", "172", "50", "7", "162", "87", "183", "226", "62", "216", "258", "132", "176", "237", "263", "15", "242", "63", "5", "225", "168", "129", "121", "53", "230", "21", "70", "102", "166", "128", "92", "234", "23", "183", "160", "104", "75", "226", "162", "7", "239", "182", "9", "132", "101", "69", "172", "216", "242", "50", "176", "87", "225", "62", "15", "168", "119", "237", "130", "5", "70", "102", "166", "63", "23", "129", "121", "53", "182", "7", "162", "230", "92", "75"); - private static final TherosBeyondDeathRun rareA = new TherosBeyondDeathRun(false, "207", "84", "165", "3", "43", "209", "210", "212", "214", "169", "90", "12", "13", "215", "94", "217", "98", "218", "19", "24", "222", "243", "178", "55", "181", "108", "188", "235", "148", "60", "151", "198", "236", "37", "156", "157", "39", "158", "244", "245", "246", "247", "248", "72", "73", "124", "170", "76", "117", "118", "161", "80", "123", "207", "84", "165", "3", "43", "209", "210", "212", "214", "169", "90", "12", "13", "215", "94", "217", "98", "218", "19", "24", "222", "243", "178", "55", "181", "108", "188", "235", "148", "60", "151", "198", "236", "37", "156", "157", "39", "158", "244", "245", "246", "247", "248", "72", "73", "124", "170", "76", "117", "118", "161", "80", "123", "14", "18", "52", "71", "93", "147", "150", "185", "190", "208", "211", "220", "221", "224", "229"); - private static final TherosBeyondDeathRun rareB = new TherosBeyondDeathRun(false, "207", "84", "165", "3", "43", "209", "210", "212", "214", "169", "90", "12", "13", "215", "94", "217", "98", "218", "19", "24", "222", "243", "178", "55", "181", "108", "188", "235", "148", "60", "151", "198", "236", "37", "156", "157", "39", "158", "244", "245", "246", "247", "248", "72", "73", "124", "170", "76", "117", "118", "161", "80", "123", "207", "84", "165", "3", "43", "209", "210", "212", "214", "169", "90", "12", "13", "215", "94", "217", "98", "218", "19", "24", "222", "243", "178", "55", "181", "108", "188", "235", "148", "60", "151", "198", "236", "37", "156", "157", "39", "158", "244", "245", "246", "247", "248", "72", "73", "124", "170", "76", "117", "118", "161", "80", "123", "255", "259", "52", "261", "262", "147", "265", "266", "190", "256", "257", "268", "221", "224", "229"); - private static final TherosBeyondDeathRun land = new TherosBeyondDeathRun(false, "250", "251", "252", "253", "254"); - - private TherosBeyondDeathRun(boolean keepOrder, String... numbers) { - super(keepOrder, numbers); - } - } - - private static class TherosBeyondDeathStructure extends BoosterStructure { - private static final TherosBeyondDeathStructure AABBC1C1C1C1C1C1 = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonC1, - TherosBeyondDeathRun.commonC1, - TherosBeyondDeathRun.commonC1, - TherosBeyondDeathRun.commonC1, - TherosBeyondDeathRun.commonC1, - TherosBeyondDeathRun.commonC1 - ); - private static final TherosBeyondDeathStructure AAABBC1C1C1C1C1 = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonC1, - TherosBeyondDeathRun.commonC1, - TherosBeyondDeathRun.commonC1, - TherosBeyondDeathRun.commonC1, - TherosBeyondDeathRun.commonC1 - ); - private static final TherosBeyondDeathStructure AAAABBC2C2C2C2 = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonC2, - TherosBeyondDeathRun.commonC2, - TherosBeyondDeathRun.commonC2, - TherosBeyondDeathRun.commonC2 - ); - private static final TherosBeyondDeathStructure AAAABBBC2C2C2 = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonC2, - TherosBeyondDeathRun.commonC2, - TherosBeyondDeathRun.commonC2 - ); - private static final TherosBeyondDeathStructure AAAABBBBC2C2 = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonA, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonB, - TherosBeyondDeathRun.commonC2, - TherosBeyondDeathRun.commonC2 - ); - private static final TherosBeyondDeathStructure ABB = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.uncommonA, - TherosBeyondDeathRun.uncommonB, - TherosBeyondDeathRun.uncommonB - ); - private static final TherosBeyondDeathStructure AAB = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.uncommonA, - TherosBeyondDeathRun.uncommonA, - TherosBeyondDeathRun.uncommonB - ); - private static final TherosBeyondDeathStructure R1 = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.rareA - ); - private static final TherosBeyondDeathStructure R2 = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.rareB - ); - private static final TherosBeyondDeathStructure L1 = new TherosBeyondDeathStructure( - TherosBeyondDeathRun.land - ); - - private TherosBeyondDeathStructure(CardRun... runs) { - super(runs); - } - } + private final BoosterStructure AABBC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAAABBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBBC2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2 + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA); + private final BoosterStructure R2 = new BoosterStructure(rareB); + private final BoosterStructure L1 = new BoosterStructure(land); // In order for equal numbers of each common to exist, the average booster must contain: // 3.27 A commons (36 / 11) @@ -508,53 +451,33 @@ class TherosBeyondDeathCollator implements BoosterCollator { // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs // and with 10 common slots per booster private final RarityConfiguration commonRuns = new RarityConfiguration( - false, - TherosBeyondDeathStructure.AABBC1C1C1C1C1C1, - TherosBeyondDeathStructure.AABBC1C1C1C1C1C1, - TherosBeyondDeathStructure.AABBC1C1C1C1C1C1, - TherosBeyondDeathStructure.AABBC1C1C1C1C1C1, - TherosBeyondDeathStructure.AABBC1C1C1C1C1C1, - TherosBeyondDeathStructure.AAABBC1C1C1C1C1, - TherosBeyondDeathStructure.AAABBC1C1C1C1C1, - TherosBeyondDeathStructure.AAABBC1C1C1C1C1, - TherosBeyondDeathStructure.AAABBC1C1C1C1C1, - TherosBeyondDeathStructure.AAABBC1C1C1C1C1, - TherosBeyondDeathStructure.AAABBC1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, - TherosBeyondDeathStructure.AAAABBC2C2C2C2, - TherosBeyondDeathStructure.AAAABBC2C2C2C2, - TherosBeyondDeathStructure.AAAABBC2C2C2C2, - TherosBeyondDeathStructure.AAAABBC2C2C2C2, - TherosBeyondDeathStructure.AAAABBC2C2C2C2, - TherosBeyondDeathStructure.AAAABBC2C2C2C2, - TherosBeyondDeathStructure.AAAABBC2C2C2C2, - TherosBeyondDeathStructure.AAAABBC2C2C2C2, - TherosBeyondDeathStructure.AAAABBBC2C2C2, - TherosBeyondDeathStructure.AAAABBBC2C2C2, - TherosBeyondDeathStructure.AAAABBBBC2C2 + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBBC2C2 ); - private final RarityConfiguration uncommonRuns = new RarityConfiguration( - TherosBeyondDeathStructure.ABB, - TherosBeyondDeathStructure.AAB - ); - private final RarityConfiguration rareRuns = new RarityConfiguration( - false, - TherosBeyondDeathStructure.R1, - TherosBeyondDeathStructure.R1, - TherosBeyondDeathStructure.R2 - ); - private final RarityConfiguration landRuns = new RarityConfiguration( - TherosBeyondDeathStructure.L1 - ); - - - @Override - public void shuffle() { - commonRuns.shuffle(); - uncommonRuns.shuffle(); - rareRuns.shuffle(); - landRuns.shuffle(); - } + private final RarityConfiguration uncommonRuns = new RarityConfiguration(AAB, ABB); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1, R1, R2); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); @Override public List makeBooster() { diff --git a/Mage.Sets/src/mage/sets/TimeSpiralRemastered.java b/Mage.Sets/src/mage/sets/TimeSpiralRemastered.java index bd8b39451bf..431c3b07311 100644 --- a/Mage.Sets/src/mage/sets/TimeSpiralRemastered.java +++ b/Mage.Sets/src/mage/sets/TimeSpiralRemastered.java @@ -28,9 +28,9 @@ public class TimeSpiralRemastered extends ExpansionSet { private final List savedSpecialBonus = new ArrayList<>(); private TimeSpiralRemastered() { - super("Time Spiral Remastered", "TSR", ExpansionSet.buildDate(2021, 3, 19), SetType.SUPPLEMENTAL, new TimeSpiralRemasteredCollator()); + super("Time Spiral Remastered", "TSR", ExpansionSet.buildDate(2021, 3, 19), SetType.SUPPLEMENTAL); this.hasBoosters = true; - this.hasBasicLands = true; + this.hasBasicLands = false; this.maxCardNumberInBooster = 410; this.numBoosterCommon = 10; this.numBoosterUncommon = 3; @@ -461,153 +461,94 @@ public class TimeSpiralRemastered extends ExpansionSet { return new ArrayList<>(savedSpecialBonus); } + + @Override + public BoosterCollator createCollator() { + return new TimeSpiralRemasteredCollator(); + } } // Booster collation info from https://www.lethe.xyz/mtg/collation/tsr.html // Using USA collation for common/uncommon, rare and bonus sheet are standard class TimeSpiralRemasteredCollator implements BoosterCollator { - private static class TimeSpiralRemasteredRun extends CardRun { - private static final TimeSpiralRemasteredRun commonA = new TimeSpiralRemasteredRun(true, "176", "65", "177", "94", "192", "70", "86", "191", "84", "168", "80", "69", "173", "73", "154", "87", "197", "92", "166", "67", "183", "78", "171", "81", "155", "95", "190", "70", "191", "58", "168", "63", "162", "89", "157", "65", "173", "69", "159", "73", "176", "94", "192", "86", "197", "84", "166", "67", "177", "92", "171", "80", "162", "81", "155", "78", "154", "87", "157", "95", "190", "58", "159", "63", "183", "89"); - private static final TimeSpiralRemasteredRun commonB = new TimeSpiralRemasteredRun(true, "110", "245", "135", "241", "104", "226", "233", "108", "236", "107", "207", "229", "101", "201", "116", "199", "123", "246", "130", "240", "109", "245", "103", "202", "118", "221", "117", "230", "105", "212", "148", "243", "134", "237", "135", "226", "110", "236", "108", "233", "107", "241", "101", "229", "116", "201", "118", "199", "104", "246", "109", "240", "130", "207", "103", "202", "117", "221", "105", "230", "134", "243", "148", "212", "123", "237"); - private static final TimeSpiralRemasteredRun commonC1 = new TimeSpiralRemasteredRun(true, "44", "223", "20", "140", "53", "21", "181", "17", "184", "2", "205", "151", "42", "119", "74", "1", "161", "272", "10", "228", "12", "68", "6", "285", "7", "48", "53", "20", "133", "44", "223", "21", "140", "151", "2", "184", "17", "74", "42", "181", "228", "1", "10", "119", "12", "68", "285", "48", "272", "6", "205", "161", "7", "133", "185"); - private static final TimeSpiralRemasteredRun commonC2 = new TimeSpiralRemasteredRun(true, "25", "147", "31", "64", "269", "47", "206", "43", "178", "132", "263", "18", "200", "59", "26", "145", "9", "66", "50", "14", "147", "28", "232", "49", "122", "71", "22", "25", "165", "132", "31", "206", "43", "47", "269", "64", "18", "178", "200", "26", "263", "59", "9", "145", "14", "66", "50", "165", "28", "122", "232", "22", "49", "71", "185"); - private static final TimeSpiralRemasteredRun uncommonA = new TimeSpiralRemasteredRun(true, "217", "283", "196", "242", "126", "194", "13", "222", "248", "40", "128", "195", "204", "5", "189", "113", "56", "249", "218", "180", "149", "276", "16", "258", "224", "79", "163", "142", "19", "61", "273", "153", "85", "30", "111", "170", "57", "231", "24", "99", "143", "279", "100", "174", "213", "55", "131", "46", "244", "126", "196", "82", "219", "102", "188", "208", "33", "120", "60", "186", "217", "283", "169", "124", "242", "194", "61", "218", "16", "153", "142", "258", "13", "224", "249", "113", "189", "79", "248", "204", "19", "149", "163", "5", "276", "222", "195", "40", "56", "180", "128", "273", "85", "30", "111", "170", "231", "57", "24", "143", "99", "279", "174", "213", "100", "131", "46", "244", "124", "55", "208", "188", "120", "60", "186", "33", "219", "169", "102", "82"); - private static final TimeSpiralRemasteredRun uncommonB = new TimeSpiralRemasteredRun(true, "38", "152", "141", "29", "72", "225", "11", "158", "88", "271", "23", "76", "247", "160", "139", "250", "216", "288", "264", "45", "93", "252", "137", "275", "214", "39", "54", "129", "193", "227", "35", "112", "83", "282", "38", "164", "254", "115", "90", "37", "211", "152", "72", "141", "29", "247", "250", "158", "88", "11", "271", "225", "76", "139", "23", "160", "216", "288", "93", "45", "252", "137", "275", "214", "54", "264", "39", "227", "129", "193", "35", "282", "83", "112", "37", "254", "211", "38", "115", "90", "164", "29", "72", "152", "141", "225", "11", "88", "271", "158", "247", "23", "139", "76", "250", "160", "288", "252", "216", "93", "45", "137", "264", "214", "54", "275", "227", "129", "39", "193", "112", "35", "282", "83", "254", "211", "115", "37", "164", "90"); - private static final TimeSpiralRemasteredRun rare = new TimeSpiralRemasteredRun(false, "3", "4", "8", "27", "32", "34", "41", "51", "62", "75", "77", "96", "97", "98", "114", "125", "127", "136", "138", "144", "146", "156", "167", "172", "175", "179", "182", "187", "203", "209", "215", "220", "234", "238", "239", "251", "253", "255", "256", "257", "259", "260", "265", "266", "268", "270", "274", "277", "278", "281", "284", "286", "287", "3", "4", "8", "27", "32", "34", "41", "51", "62", "75", "77", "96", "97", "98", "114", "125", "127", "136", "138", "144", "146", "156", "167", "172", "175", "179", "182", "187", "203", "209", "215", "220", "234", "238", "239", "251", "253", "255", "256", "257", "259", "260", "265", "266", "268", "270", "274", "277", "278", "281", "284", "286", "287", "15", "36", "52", "91", "106", "121", "150", "198", "210", "235", "261", "262", "267", "280", "289"); - private static final TimeSpiralRemasteredRun special = new TimeSpiralRemasteredRun(false, "290", "291", "292", "293", "294", "295", "296", "297", "298", "299", "300", "301", "302", "303", "304", "305", "306", "307", "308", "309", "310", "311", "312", "313", "314", "315", "316", "317", "318", "319", "320", "321", "322", "323", "324", "325", "326", "327", "328", "329", "330", "331", "332", "333", "334", "335", "336", "337", "338", "339", "340", "341", "342", "343", "344", "345", "346", "347", "348", "349", "350", "351", "352", "353", "354", "355", "356", "357", "358", "359", "360", "361", "362", "363", "364", "365", "366", "367", "368", "369", "370", "371", "372", "373", "374", "375", "376", "377", "378", "379", "380", "381", "382", "383", "384", "385", "386", "387", "388", "389", "390", "391", "392", "393", "394", "395", "396", "397", "398", "399", "400", "401", "402", "403", "404", "405", "406", "407", "408", "409", "410"); + private final CardRun commonA = new CardRun(true, "176", "65", "177", "94", "192", "70", "86", "191", "84", "168", "80", "69", "173", "73", "154", "87", "197", "92", "166", "67", "183", "78", "171", "81", "155", "95", "190", "70", "191", "58", "168", "63", "162", "89", "157", "65", "173", "69", "159", "73", "176", "94", "192", "86", "197", "84", "166", "67", "177", "92", "171", "80", "162", "81", "155", "78", "154", "87", "157", "95", "190", "58", "159", "63", "183", "89"); + private final CardRun commonB = new CardRun(true, "110", "245", "135", "241", "104", "226", "233", "108", "236", "107", "207", "229", "101", "201", "116", "199", "123", "246", "130", "240", "109", "245", "103", "202", "118", "221", "117", "230", "105", "212", "148", "243", "134", "237", "135", "226", "110", "236", "108", "233", "107", "241", "101", "229", "116", "201", "118", "199", "104", "246", "109", "240", "130", "207", "103", "202", "117", "221", "105", "230", "134", "243", "148", "212", "123", "237"); + private final CardRun commonC1 = new CardRun(true, "44", "223", "20", "140", "53", "21", "181", "17", "184", "2", "205", "151", "42", "119", "74", "1", "161", "272", "10", "228", "12", "68", "6", "285", "7", "48", "53", "20", "133", "44", "223", "21", "140", "151", "2", "184", "17", "74", "42", "181", "228", "1", "10", "119", "12", "68", "285", "48", "272", "6", "205", "161", "7", "133", "185"); + private final CardRun commonC2 = new CardRun(true, "25", "147", "31", "64", "269", "47", "206", "43", "178", "132", "263", "18", "200", "59", "26", "145", "9", "66", "50", "14", "147", "28", "232", "49", "122", "71", "22", "25", "165", "132", "31", "206", "43", "47", "269", "64", "18", "178", "200", "26", "263", "59", "9", "145", "14", "66", "50", "165", "28", "122", "232", "22", "49", "71", "185"); + private final CardRun uncommonA = new CardRun(true, "217", "283", "196", "242", "126", "194", "13", "222", "248", "40", "128", "195", "204", "5", "189", "113", "56", "249", "218", "180", "149", "276", "16", "258", "224", "79", "163", "142", "19", "61", "273", "153", "85", "30", "111", "170", "57", "231", "24", "99", "143", "279", "100", "174", "213", "55", "131", "46", "244", "126", "196", "82", "219", "102", "188", "208", "33", "120", "60", "186", "217", "283", "169", "124", "242", "194", "61", "218", "16", "153", "142", "258", "13", "224", "249", "113", "189", "79", "248", "204", "19", "149", "163", "5", "276", "222", "195", "40", "56", "180", "128", "273", "85", "30", "111", "170", "231", "57", "24", "143", "99", "279", "174", "213", "100", "131", "46", "244", "124", "55", "208", "188", "120", "60", "186", "33", "219", "169", "102", "82"); + private final CardRun uncommonB = new CardRun(true, "38", "152", "141", "29", "72", "225", "11", "158", "88", "271", "23", "76", "247", "160", "139", "250", "216", "288", "264", "45", "93", "252", "137", "275", "214", "39", "54", "129", "193", "227", "35", "112", "83", "282", "38", "164", "254", "115", "90", "37", "211", "152", "72", "141", "29", "247", "250", "158", "88", "11", "271", "225", "76", "139", "23", "160", "216", "288", "93", "45", "252", "137", "275", "214", "54", "264", "39", "227", "129", "193", "35", "282", "83", "112", "37", "254", "211", "38", "115", "90", "164", "29", "72", "152", "141", "225", "11", "88", "271", "158", "247", "23", "139", "76", "250", "160", "288", "252", "216", "93", "45", "137", "264", "214", "54", "275", "227", "129", "39", "193", "112", "35", "282", "83", "254", "211", "115", "37", "164", "90"); + private final CardRun rare = new CardRun(false, "3", "4", "8", "27", "32", "34", "41", "51", "62", "75", "77", "96", "97", "98", "114", "125", "127", "136", "138", "144", "146", "156", "167", "172", "175", "179", "182", "187", "203", "209", "215", "220", "234", "238", "239", "251", "253", "255", "256", "257", "259", "260", "265", "266", "268", "270", "274", "277", "278", "281", "284", "286", "287", "3", "4", "8", "27", "32", "34", "41", "51", "62", "75", "77", "96", "97", "98", "114", "125", "127", "136", "138", "144", "146", "156", "167", "172", "175", "179", "182", "187", "203", "209", "215", "220", "234", "238", "239", "251", "253", "255", "256", "257", "259", "260", "265", "266", "268", "270", "274", "277", "278", "281", "284", "286", "287", "15", "36", "52", "91", "106", "121", "150", "198", "210", "235", "261", "262", "267", "280", "289"); + private final CardRun special = new CardRun(false, "290", "291", "292", "293", "294", "295", "296", "297", "298", "299", "300", "301", "302", "303", "304", "305", "306", "307", "308", "309", "310", "311", "312", "313", "314", "315", "316", "317", "318", "319", "320", "321", "322", "323", "324", "325", "326", "327", "328", "329", "330", "331", "332", "333", "334", "335", "336", "337", "338", "339", "340", "341", "342", "343", "344", "345", "346", "347", "348", "349", "350", "351", "352", "353", "354", "355", "356", "357", "358", "359", "360", "361", "362", "363", "364", "365", "366", "367", "368", "369", "370", "371", "372", "373", "374", "375", "376", "377", "378", "379", "380", "381", "382", "383", "384", "385", "386", "387", "388", "389", "390", "391", "392", "393", "394", "395", "396", "397", "398", "399", "400", "401", "402", "403", "404", "405", "406", "407", "408", "409", "410"); - private TimeSpiralRemasteredRun(boolean keepOrder, String... numbers) { - super(keepOrder, numbers); - } - } - - private static class TimeSpiralRemasteredStructure extends BoosterStructure { - private static final TimeSpiralRemasteredStructure C1 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1 - ); - private static final TimeSpiralRemasteredStructure C2 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1 - ); - private static final TimeSpiralRemasteredStructure C3 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1, - TimeSpiralRemasteredRun.commonC1 - ); - private static final TimeSpiralRemasteredStructure C4 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2 - ); - private static final TimeSpiralRemasteredStructure C5 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2 - ); - private static final TimeSpiralRemasteredStructure C6 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonA, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonB, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2, - TimeSpiralRemasteredRun.commonC2 - ); - private static final TimeSpiralRemasteredStructure U1 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.uncommonA, - TimeSpiralRemasteredRun.uncommonA, - TimeSpiralRemasteredRun.uncommonA - ); - private static final TimeSpiralRemasteredStructure U2 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.uncommonB, - TimeSpiralRemasteredRun.uncommonB, - TimeSpiralRemasteredRun.uncommonB - ); - private static final TimeSpiralRemasteredStructure R1 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.rare - ); - private static final TimeSpiralRemasteredStructure S1 = new TimeSpiralRemasteredStructure( - TimeSpiralRemasteredRun.special - ); - - private TimeSpiralRemasteredStructure(CardRun... runs) { - super(runs); - } - } + private final BoosterStructure AAABBBC1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, + commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AABBBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, + commonB, commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBC2C2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AABBBC2C2C2C2C2 = new BoosterStructure( + commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAA = new BoosterStructure(uncommonA, uncommonA, uncommonA); + private final BoosterStructure BBB = new BoosterStructure(uncommonB, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rare); + private final BoosterStructure S1 = new BoosterStructure(special); + // In order for equal numbers of each common to exist, the average booster must contain: + // 2.73 A commons (30 / 11) + // 2.73 B commons (30 / 11) + // 2.27 C1 commons (25 / 11, or 50 / 11 in each C1 booster) + // 2.27 C2 commons (25 / 11, or 50 / 11 in each C2 booster) private final RarityConfiguration commonRuns = new RarityConfiguration( - false, - TimeSpiralRemasteredStructure.C1, - TimeSpiralRemasteredStructure.C2, - TimeSpiralRemasteredStructure.C3, - TimeSpiralRemasteredStructure.C4, - TimeSpiralRemasteredStructure.C5, - TimeSpiralRemasteredStructure.C6 - ); - private final RarityConfiguration uncommonRuns = new RarityConfiguration( - false, - TimeSpiralRemasteredStructure.U1, - TimeSpiralRemasteredStructure.U1, - TimeSpiralRemasteredStructure.U1, - TimeSpiralRemasteredStructure.U2, - TimeSpiralRemasteredStructure.U2 - ); - private final RarityConfiguration rareRuns = new RarityConfiguration( - TimeSpiralRemasteredStructure.R1 - ); - private final RarityConfiguration specialRuns = new RarityConfiguration( - TimeSpiralRemasteredStructure.S1 - ); + AAABBBC1C1C1C1, + AAABBBC1C1C1C1, + AAABBBC1C1C1C1, + AAABBBC1C1C1C1, + AAABBBC1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AABBBC1C1C1C1C1, + AABBBC1C1C1C1C1, + AABBBC1C1C1C1C1, - @Override - public void shuffle() { - commonRuns.shuffle(); - uncommonRuns.shuffle(); - rareRuns.shuffle(); - specialRuns.shuffle(); - } + AAABBBC2C2C2C2, + AAABBBC2C2C2C2, + AAABBBC2C2C2C2, + AAABBBC2C2C2C2, + AAABBBC2C2C2C2, + AAABBC2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBC2C2C2C2C2, + AABBBC2C2C2C2C2, + AABBBC2C2C2C2C2, + AABBBC2C2C2C2C2 + ); + private final RarityConfiguration uncommonRuns = new RarityConfiguration(AAA, AAA, AAA, BBB, BBB); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + private final RarityConfiguration specialRuns = new RarityConfiguration(S1); @Override public List makeBooster() { diff --git a/Mage.Sets/src/mage/sets/WarOfTheSpark.java b/Mage.Sets/src/mage/sets/WarOfTheSpark.java index 4b6f2b67a2c..e86c4dbfe29 100644 --- a/Mage.Sets/src/mage/sets/WarOfTheSpark.java +++ b/Mage.Sets/src/mage/sets/WarOfTheSpark.java @@ -1,9 +1,16 @@ package mage.sets; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + public final class WarOfTheSpark extends ExpansionSet { private static final WarOfTheSpark instance = new WarOfTheSpark(); @@ -336,4 +343,113 @@ public final class WarOfTheSpark extends ExpansionSet { cards.add(new SetCardInfo("Wardscale Crocodile", 183, Rarity.COMMON, mage.cards.w.WardscaleCrocodile.class)); cards.add(new SetCardInfo("Widespread Brutality", 226, Rarity.RARE, mage.cards.w.WidespreadBrutality.class)); } + + @Override + public BoosterCollator createCollator() { + return new WarOfTheSparkCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/war.html +// Using USA collation for common/uncommon, rare collation inferred from other sets +class WarOfTheSparkCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "57", "129", "19", "69", "113", "39", "40", "134", "7", "58", "141", "14", "72", "130", "23", "46", "114", "5", "47", "118", "33", "63", "148", "19", "40", "132", "29", "70", "129", "25", "67", "128", "22", "57", "142", "39", "72", "113", "7", "47", "134", "14", "60", "141", "5", "67", "114", "21", "58", "118", "33", "69", "148", "25", "70", "132", "29", "63", "130", "22", "46", "142", "23", "60", "128", "21"); + private final CardRun commonB = new CardRun(true, "175", "103", "176", "108", "165", "95", "183", "88", "182", "106", "158", "104", "161", "110", "177", "76", "175", "81", "168", "108", "174", "95", "166", "94", "176", "107", "183", "106", "161", "103", "158", "104", "165", "110", "182", "88", "176", "108", "166", "76", "177", "81", "174", "95", "175", "94", "168", "107", "182", "106", "158", "103", "183", "88", "161", "104", "165", "110", "177", "81", "166", "107", "168", "76", "174", "94"); + private final CardRun commonC1 = new CardRun(true, "243", "154", "8", "42", "117", "101", "136", "74", "156", "240", "48", "11", "96", "123", "20", "9", "241", "73", "8", "112", "131", "173", "239", "120", "84", "38", "64", "11", "136", "109", "42", "144", "154", "243", "101", "74", "20", "117", "64", "156", "123", "96", "9", "48", "239", "120", "109", "38", "131", "241", "173", "84", "73", "144", "112"); + private final CardRun commonC2 = new CardRun(true, "145", "149", "93", "242", "162", "105", "149", "10", "75", "151", "240", "44", "139", "153", "242", "93", "179", "75", "105", "35", "162", "145", "71", "77", "246", "153", "36", "151", "139", "44", "149", "10", "179", "145", "35", "93", "246", "71", "36", "77", "162", "35", "44", "246", "151", "75", "10", "242", "105", "179", "139", "71", "77", "36", "153"); + private final CardRun uncommonA = new CardRun(true, "159", "205", "49", "188", "116", "189", "4", "204", "171", "121", "215", "6", "200", "87", "202", "212", "41", "147", "190", "31", "78", "205", "178", "192", "111", "188", "55", "26", "204", "122", "4", "189", "87", "215", "159", "116", "49", "200", "78", "147", "202", "171", "212", "6", "111", "121", "41", "178", "31", "192", "55", "122", "190", "26"); + private final CardRun uncommonB = new CardRun(true, "245", "237", "206", "124", "27", "198", "115", "3", "102", "195", "126", "59", "222", "157", "65", "247", "225", "90", "167", "193", "17", "185", "245", "98", "155", "199", "170", "43", "210", "237", "80", "157", "201", "52", "90", "238", "15", "206", "124", "198", "3", "27", "65", "222", "193", "167", "102", "195", "59", "199", "115", "43", "247", "225", "155", "80", "17", "52", "126", "170", "201", "98", "185", "238", "15", "210"); + private final CardRun uncommonPlaneswalker = new CardRun(false, "32", "37", "56", "61", "83", "100", "135", "146", "150", "164", "227", "228", "229", "230", "231", "232", "233", "234", "235", "236"); + private final CardRun rare = new CardRun(false, "18", "24", "28", "30", "34", "45", "50", "62", "66", "68", "79", "82", "85", "86", "89", "99", "125", "137", "138", "140", "152", "172", "181", "186", "187", "194", "196", "197", "203", "209", "214", "216", "218", "219", "223", "224", "226", "244", "248", "249", "18", "24", "28", "30", "34", "45", "50", "62", "66", "68", "79", "82", "85", "86", "89", "99", "125", "137", "138", "140", "152", "172", "181", "186", "187", "194", "196", "197", "203", "209", "214", "216", "218", "219", "223", "224", "226", "244", "248", "249", "12", "16", "51", "53", "91", "92", "127", "133", "160", "163", "208", "213"); + private final CardRun rarePlaneswalker = new CardRun(false, "1", "2", "54", "119", "143", "169", "180", "184", "191", "211", "217", "220", "221", "1", "2", "54", "119", "143", "169", "180", "184", "191", "211", "217", "220", "221", "13", "97", "207"); + private final CardRun land = new CardRun(false, "250", "251", "252", "253", "254", "255", "256", "257", "258", "259", "260", "261", "262", "263", "264"); + + private final BoosterStructure AABBC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAAABBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBBC2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2 + ); + private final BoosterStructure ABP = new BoosterStructure(uncommonA, uncommonB, uncommonPlaneswalker, rare); + private final BoosterStructure BBP = new BoosterStructure(uncommonB, uncommonB, uncommonPlaneswalker, rare); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB, rarePlaneswalker); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB, rarePlaneswalker); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 3.27 A commons (36 / 11) + // 2.18 B commons (24 / 11) + // 2.73 C1 commons (30 / 11, or 60 / 11 in each C1 booster) + // 1.82 C2 commons (20 / 11, or 40 / 11 in each C2 booster) + // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs + // and with 10 common slots per booster + private final RarityConfiguration commonRuns = new RarityConfiguration( + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AABBC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBC2C2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBBC2C2 + ); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 1.01 A uncommons (81 / 80) + // 1.24 B uncommons (99 / 80) + // 0.75 uncommon planeswalker (60 / 80) + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, + ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, + ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, + ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, + ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, ABP, + ABP, ABP, ABP, ABP, BBP, BBP, BBP, BBP, BBP, BBP, + + AAB, AAB, AAB, AAB, AAB, AAB, AAB, ABB, ABB, ABB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/Zendikar.java b/Mage.Sets/src/mage/sets/Zendikar.java index 5656790fcc0..9521768efba 100644 --- a/Mage.Sets/src/mage/sets/Zendikar.java +++ b/Mage.Sets/src/mage/sets/Zendikar.java @@ -3,9 +3,16 @@ package mage.sets; import mage.ObjectColor; import mage.cards.CardGraphicInfo; import mage.cards.ExpansionSet; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.ArrayList; +import java.util.List; + /** * @author BetaSteward_at_googlemail.com */ @@ -298,4 +305,124 @@ public final class Zendikar extends ExpansionSet { cards.add(new SetCardInfo("Zendikar Farguide", 194, Rarity.COMMON, mage.cards.z.ZendikarFarguide.class)); } + @Override + public BoosterCollator createCollator() { + return new ZendikarCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/zen.html +// Using USA collation +class ZendikarCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "87", "189", "58", "118", "7", "222", "163", "66", "14", "132", "195", "106", "192", "77", "118", "23", "222", "189", "48", "3", "145", "202", "98", "192", "60", "226", "216", "94", "169", "78", "7", "119", "195", "90", "181", "60", "132", "216", "87", "169", "77", "23", "226", "207", "94", "181", "78", "148", "14", "90", "193", "58", "31", "145", "207", "98", "163", "48", "148", "3", "106", "193", "66", "31", "119", "202"); + private final CardRun commonB = new CardRun(true, "113", "173", "141", "97", "225", "22", "165", "150", "49", "113", "206", "20", "173", "152", "97", "75", "18", "188", "151", "225", "85", "201", "36", "165", "152", "115", "49", "20", "188", "141", "76", "97", "206", "22", "174", "152", "85", "49", "36", "173", "151", "75", "113", "201", "188", "22", "150", "115", "225", "20", "174", "141", "85", "76", "206", "18", "165", "151", "115", "75", "36", "174", "150", "201", "76", "18"); + private final CardRun commonC1 = new CardRun(true, "155", "194", "28", "73", "167", "112", "35", "65", "138", "158", "91", "121", "44", "29", "166", "103", "37", "72", "112", "138", "167", "104", "136", "194", "35", "44", "84", "149", "158", "21", "183", "73", "117", "136", "179", "28", "67", "103", "29", "72", "204", "155", "183", "104", "149", "166", "37", "65", "91", "21", "67", "84", "121", "179", "117"); + private final CardRun commonC2 = new CardRun(true, "147", "70", "171", "93", "5", "128", "227", "27", "86", "43", "50", "129", "30", "171", "80", "27", "128", "70", "93", "147", "227", "5", "50", "26", "186", "125", "51", "86", "171", "27", "43", "129", "5", "147", "50", "227", "86", "26", "43", "80", "186", "128", "51", "30", "125", "70", "26", "93", "186", "129", "51", "80", "125", "204", "30"); + private final CardRun uncommonA = new CardRun(true, "32", "55", "114", "139", "185", "2", "52", "108", "127", "156", "197", "17", "40", "89", "124", "190", "38", "56", "116", "137", "160", "209", "17", "55", "108", "139", "156", "38", "46", "114", "127", "185", "209", "16", "71", "116", "133", "157", "32", "56", "89", "137", "180", "205", "16", "52", "95", "142", "160", "34", "46", "101", "133", "180", "197", "2", "40", "95", "124", "157", "34", "71", "101", "142", "190", "205"); + private final CardRun uncommonB = new CardRun(true, "130", "33", "102", "64", "214", "198", "153", "176", "59", "109", "215", "19", "102", "47", "177", "210", "146", "164", "198", "33", "74", "217", "105", "130", "208", "24", "47", "215", "144", "177", "88", "19", "214", "59", "153", "161", "4", "224", "109", "208", "144", "164", "64", "210", "88", "146", "24", "176", "217", "4", "74", "161", "105", "224"); + private final CardRun rareA = new CardRun(true, "191", "140", "45", "82", "123", "229", "187", "100", "41", "9", "218", "120", "172", "25", "92", "42", "213", "6", "187", "83", "122", "218", "53", "1", "100", "8", "45", "229", "159", "126", "96", "219", "200", "54", "9", "143", "172", "223", "170", "82", "122", "68", "6", "220", "41", "8", "143", "159", "99", "219", "1", "68", "123", "92", "213", "12", "191", "126", "83", "42", "220", "168", "54", "96", "25", "223"); + private final CardRun rareB = new CardRun(true, "175", "211", "135", "162", "134", "61", "10", "107", "69", "203", "175", "212", "39", "154", "196", "62", "79", "184", "11", "211", "57", "228", "81", "131", "162", "62", "13", "182", "79", "212", "135", "63", "11", "81", "196", "221", "178", "134", "203", "61", "110", "15", "199", "10", "182", "221", "184", "131", "69", "228", "110", "39", "63", "111", "15"); + private final CardRun land = new CardRun(false, "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242", "243", "244", "245", "246", "247", "248", "249"); + + private final BoosterStructure AAABC1C1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, + commonC1, commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABBC1C1C1C1C1 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC1, commonC1, commonC1, commonC1, commonC1 + ); + private final BoosterStructure AAABC2C2C2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, + commonC2, commonC2, commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBC2C2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, + commonC2, commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBBC2C2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAABBBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBC2C2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, + commonC2, commonC2, commonC2 + ); + private final BoosterStructure AAAABBBBC2C2 = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, + commonC2, commonC2 + ); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure R1 = new BoosterStructure(rareA); + private final BoosterStructure R2 = new BoosterStructure(rareB); + private final BoosterStructure L1 = new BoosterStructure(land); + + // In order for equal numbers of each common to exist, the average booster must contain: + // 3.27 A commons (36 / 11) + // 2.18 B commons (24 / 11) + // 2.73 C1 commons (30 / 11, or 60 / 11 in each C1 booster) + // 1.82 C2 commons (20 / 11, or 40 / 11 in each C2 booster) + // These numbers are the same for all sets with 101 commons in A/B/C1/C2 print runs + // and with 10 common slots per booster + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABC1C1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + AAABBC1C1C1C1C1, + + AAABC2C2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBC2C2C2C2C2, + AAABBBC2C2C2C2, + AAABBBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBC2C2C2, + AAAABBBBC2C2 + ); + // In order for equal numbers of each uncommon to exist, the average booster must contain: + // 1.65 A uncommons (33 / 20) + // 1.35 B uncommons (27 / 20) + // These numbers are the same for all sets with 60 uncommons in asymmetrical A/B print runs + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, AAB, + ABB, ABB, ABB, ABB, ABB, ABB, ABB + ); + private final RarityConfiguration rareRuns = new RarityConfiguration( + R1, R1, R1, R1, R1, R1, + R2, R2, R2, R2, R2 + ); + private final RarityConfiguration landRuns = new RarityConfiguration(L1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + booster.addAll(landRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Tests/pom.xml b/Mage.Tests/pom.xml index 5e39abef7d1..3c73adbaf39 100644 --- a/Mage.Tests/pom.xml +++ b/Mage.Tests/pom.xml @@ -34,11 +34,6 @@ mage-server ${mage-version} - - junit - junit - test - ${project.groupId} mage-game-twoplayerduel @@ -52,11 +47,6 @@ compile - - log4j - log4j - jar - ${project.groupId} mage-game-freeforall @@ -65,7 +55,7 @@ com.sun.xml.bind jaxb-impl - 2.3.3 + 3.0.2 @@ -73,6 +63,10 @@ jaxb-runtime 3.0.2 + + net.java.truevfs + truevfs-profile-base + diff --git a/Mage.Tests/src/test/data/images.zip b/Mage.Tests/src/test/data/images.zip new file mode 100644 index 00000000000..4453745230d Binary files /dev/null and b/Mage.Tests/src/test/data/images.zip differ diff --git a/Mage.Tests/src/test/data/scryfall-card.json b/Mage.Tests/src/test/data/scryfall-card.json new file mode 100644 index 00000000000..80cde7e0cf2 --- /dev/null +++ b/Mage.Tests/src/test/data/scryfall-card.json @@ -0,0 +1,154 @@ +{ + "object": "card", + "id": "8093f8b0-5d50-48ca-b616-e92535a47138", + "oracle_id": "80b07882-e144-4815-8b6f-04b3ab343d97", + "multiverse_ids": [ + 409843, + 409844 + ], + "mtgo_id": 60370, + "mtgo_foil_id": 60371, + "tcgplayer_id": 116486, + "cardmarket_id": 289120, + "name": "Accursed Witch // Infectious Curse", + "lang": "en", + "released_at": "2016-04-08", + "uri": "https://api.scryfall.com/cards/8093f8b0-5d50-48ca-b616-e92535a47138", + "scryfall_uri": "https://scryfall.com/card/soi/97/accursed-witch-infectious-curse?utm_source=api", + "layout": "transform", + "highres_image": true, + "image_status": "highres_scan", + "cmc": 4.0, + "type_line": "Creature — Human Shaman // Enchantment — Aura Curse", + "color_identity": [ + "B" + ], + "keywords": [ + "Enchant" + ], + "card_faces": [ + { + "object": "card_face", + "name": "Accursed Witch", + "mana_cost": "{3}{B}", + "type_line": "Creature — Human Shaman", + "oracle_text": "Spells your opponents cast that target Accursed Witch cost {1} less to cast.\nWhen Accursed Witch dies, return it to the battlefield transformed under your control attached to target opponent.", + "colors": [ + "B" + ], + "power": "4", + "toughness": "2", + "artist": "Wesley Burt", + "artist_id": "b98c9d94-8bdb-49af-871d-7bac92274535", + "illustration_id": "e648ea98-8935-4b00-b3d9-d4d1e98026d8", + "image_uris": { + "small": "https://c1.scryfall.com/file/scryfall-cards/small/front/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328", + "normal": "https://c1.scryfall.com/file/scryfall-cards/normal/front/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328", + "large": "https://c1.scryfall.com/file/scryfall-cards/large/front/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328", + "png": "https://c1.scryfall.com/file/scryfall-cards/png/front/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.png?1576384328", + "art_crop": "https://c1.scryfall.com/file/scryfall-cards/art_crop/front/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328", + "border_crop": "https://c1.scryfall.com/file/scryfall-cards/border_crop/front/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328" + } + }, + { + "object": "card_face", + "name": "Infectious Curse", + "mana_cost": "", + "type_line": "Enchantment — Aura Curse", + "oracle_text": "Enchant player\nSpells you cast that target enchanted player cost {1} less to cast.\nAt the beginning of enchanted player's upkeep, that player loses 1 life and you gain 1 life.", + "colors": [ + "B" + ], + "color_indicator": [ + "B" + ], + "artist": "Wesley Burt", + "artist_id": "b98c9d94-8bdb-49af-871d-7bac92274535", + "illustration_id": "828863bf-ddb7-4719-bcfd-2a20c667829f", + "image_uris": { + "small": "https://c1.scryfall.com/file/scryfall-cards/small/back/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328", + "normal": "https://c1.scryfall.com/file/scryfall-cards/normal/back/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328", + "large": "https://c1.scryfall.com/file/scryfall-cards/large/back/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328", + "png": "https://c1.scryfall.com/file/scryfall-cards/png/back/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.png?1576384328", + "art_crop": "https://c1.scryfall.com/file/scryfall-cards/art_crop/back/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328", + "border_crop": "https://c1.scryfall.com/file/scryfall-cards/border_crop/back/8/0/8093f8b0-5d50-48ca-b616-e92535a47138.jpg?1576384328" + } + } + ], + "legalities": { + "standard": "not_legal", + "future": "not_legal", + "historic": "not_legal", + "gladiator": "not_legal", + "pioneer": "legal", + "modern": "legal", + "legacy": "legal", + "pauper": "not_legal", + "vintage": "legal", + "penny": "not_legal", + "commander": "legal", + "brawl": "not_legal", + "historicbrawl": "not_legal", + "paupercommander": "restricted", + "duel": "legal", + "oldschool": "not_legal", + "premodern": "not_legal" + }, + "games": [ + "paper", + "mtgo" + ], + "reserved": false, + "foil": true, + "nonfoil": true, + "finishes": [ + "nonfoil", + "foil" + ], + "oversized": false, + "promo": false, + "reprint": false, + "variation": false, + "set_id": "5e914d7e-c1e9-446c-a33d-d093c02b2743", + "set": "soi", + "set_name": "Shadows over Innistrad", + "set_type": "expansion", + "set_uri": "https://api.scryfall.com/sets/5e914d7e-c1e9-446c-a33d-d093c02b2743", + "set_search_uri": "https://api.scryfall.com/cards/search?order=set&q=e%3Asoi&unique=prints", + "scryfall_set_uri": "https://scryfall.com/sets/soi?utm_source=api", + "rulings_uri": "https://api.scryfall.com/cards/8093f8b0-5d50-48ca-b616-e92535a47138/rulings", + "prints_search_uri": "https://api.scryfall.com/cards/search?order=released&q=oracleid%3A80b07882-e144-4815-8b6f-04b3ab343d97&unique=prints", + "collector_number": "97", + "digital": false, + "rarity": "uncommon", + "card_back_id": "0aeebaf5-8c7d-4636-9e82-8c27447861f7", + "artist": "Wesley Burt", + "artist_ids": [ + "b98c9d94-8bdb-49af-871d-7bac92274535" + ], + "border_color": "black", + "frame": "2015", + "frame_effects": [ + "sunmoondfc" + ], + "full_art": false, + "textless": false, + "booster": true, + "story_spotlight": false, + "edhrec_rank": 6115, + "prices": { + "usd": "0.31", + "usd_foil": "0.77", + "usd_etched": null, + "eur": "0.16", + "eur_foil": null, + "tix": "0.03" + }, + "related_uris": { + "gatherer": "https://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=409843", + "tcgplayer_infinite_articles": "https://infinite.tcgplayer.com/search?contentMode=article&game=magic&partner=scryfall&q=Accursed+Witch+%2F%2F+Infectious+Curse&utm_campaign=affiliate&utm_medium=api&utm_source=scryfall", + "tcgplayer_infinite_decks": "https://infinite.tcgplayer.com/search?contentMode=deck&game=magic&partner=scryfall&q=Accursed+Witch+%2F%2F+Infectious+Curse&utm_campaign=affiliate&utm_medium=api&utm_source=scryfall", + "edhrec": "https://edhrec.com/route/?cc=Accursed+Witch", + "mtgtop8": "https://mtgtop8.com/search?MD_check=1&SB_check=1&cards=Accursed+Witch" + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/data/users-db-sample.h2.mv.db b/Mage.Tests/src/test/data/users-db-sample.h2.mv.db new file mode 100644 index 00000000000..91b77c4d569 Binary files /dev/null and b/Mage.Tests/src/test/data/users-db-sample.h2.mv.db differ diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java index 8c00b671fdb..a5fc11b7f37 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java @@ -1,110 +1,110 @@ - -package org.mage.test.cards.abilities.keywords; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.counters.CounterType; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ - -public class CumulativeUpkeepTest extends CardTestPlayerBase { - - @Test - public void basicTest() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); - // Flying; fear - // Cumulative upkeep {B} - addCard(Zone.HAND, playerA, "Phobian Phantasm"); // Creature {1}{B}{B} 3/3 - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phobian Phantasm"); - - // Phobian Phantasm - CumulativeUpkeepAbility: Cumulative upkeep {B} - setChoice(playerA, true); // Pay {B}? - attack(3, playerA, "Phobian Phantasm"); - checkPermanentCounters("Age counters", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Phobian Phantasm", CounterType.AGE, 1); - - setChoice(playerA, true); // Pay {B}{B}? - attack(5, playerA, "Phobian Phantasm"); - checkPermanentCounters("Age counters", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Phobian Phantasm", CounterType.AGE, 2); - - setChoice(playerA, false); // Pay {B}{B}{B}? - - setStopAt(7, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Phobian Phantasm", 1); - - assertLife(playerA, 20); - assertLife(playerB, 14); - } - - - /** - I changed control of a Illusions of Grandeur to an AI after cumulative upkeep had triggered but before it resolved. - I chose not to pay the upkeep cost and then either the AI sacrificed it or I sacrificed it, neither of which should happen. - I can't sacrifice it because it's not under my control. The AI can't sacrifice it because they are not instructed to do so. - - Here is the reminder text for cumulative upkeep: - At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it. - */ - @Test - public void controlChangeTest() { - setStrictChooseMode(true); - - // Whenever Kor Celebrant or another creature enters the battlefield under your control, you gain 1 life. - addCard(Zone.HAND, playerB, "Kor Celebrant", 1); // Creature {2}{W} - addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); - - addCard(Zone.BATTLEFIELD, playerA, "Island", 6); - // Cumulative upkeep {2} - // When Illusions of Grandeur enters the battlefield, you gain 20 life. - // When Illusions of Grandeur leaves the battlefield, you lose 20 life. - addCard(Zone.HAND, playerA, "Illusions of Grandeur"); // Enchantment {3}{U} - - // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. - addCard(Zone.HAND, playerA, "Puca's Mischief"); // Enchantment {3}{U} - - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Illusions of Grandeur"); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Kor Celebrant"); - - // Illusions of Grandeur - CumulativeUpkeepAbility: Cumulative upkeep {2} - setChoice(playerA, true); // Pay {2}? - - castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Puca's Mischief"); - - setChoice(playerA, "Cumulative upkeep"); // Triggered list (total 2) which trigger goes first on the stack - addTarget(playerA, "Illusions of Grandeur"); // Own target permanent of Puca's Mischief - addTarget(playerA, "Kor Celebrant"); // Opponent's target permanent of Puca's Mischief - - setChoice(playerA, true); // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. - setChoice(playerA, false); // Pay {2}{2}? - - checkPermanentCounters("Age counters", 5, PhaseStep.PRECOMBAT_MAIN, playerB, "Illusions of Grandeur", CounterType.AGE, 2); - - setStopAt(5, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertLife(playerA, 40); - assertLife(playerB, 21); - - assertPermanentCount(playerA, "Kor Celebrant", 1); - assertPermanentCount(playerB, "Illusions of Grandeur", 1); - - - } - - + +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class CumulativeUpkeepTest extends CardTestPlayerBase { + + @Test + public void basicTest() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + // Flying; fear + // Cumulative upkeep {B} + addCard(Zone.HAND, playerA, "Phobian Phantasm"); // Creature {1}{B}{B} 3/3 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phobian Phantasm"); + + // Phobian Phantasm - CumulativeUpkeepAbility: Cumulative upkeep {B} + setChoice(playerA, true); // Pay {B}? + attack(3, playerA, "Phobian Phantasm"); + checkPermanentCounters("Age counters", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Phobian Phantasm", CounterType.AGE, 1); + + setChoice(playerA, true); // Pay {B}{B}? + attack(5, playerA, "Phobian Phantasm"); + checkPermanentCounters("Age counters", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Phobian Phantasm", CounterType.AGE, 2); + + setChoice(playerA, false); // Pay {B}{B}{B}? + + setStopAt(7, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Phobian Phantasm", 1); + + assertLife(playerA, 20); + assertLife(playerB, 14); + } + + + /** + I changed control of a Illusions of Grandeur to an AI after cumulative upkeep had triggered but before it resolved. + I chose not to pay the upkeep cost and then either the AI sacrificed it or I sacrificed it, neither of which should happen. + I can't sacrifice it because it's not under my control. The AI can't sacrifice it because they are not instructed to do so. + + Here is the reminder text for cumulative upkeep: + At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it. + */ + @Test + public void controlChangeTest() { + setStrictChooseMode(true); + + // Whenever Kor Celebrant or another creature enters the battlefield under your control, you gain 1 life. + addCard(Zone.HAND, playerB, "Kor Celebrant", 1); // Creature {2}{W} + addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); + + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + // Cumulative upkeep {2} + // When Illusions of Grandeur enters the battlefield, you gain 20 life. + // When Illusions of Grandeur leaves the battlefield, you lose 20 life. + addCard(Zone.HAND, playerA, "Illusions of Grandeur"); // Enchantment {3}{U} + + // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. + addCard(Zone.HAND, playerA, "Puca's Mischief"); // Enchantment {3}{U} + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Illusions of Grandeur"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Kor Celebrant"); + + // Illusions of Grandeur - CumulativeUpkeepAbility: Cumulative upkeep {2} + setChoice(playerA, true); // Pay {2}? + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Puca's Mischief"); + + setChoice(playerA, "Cumulative upkeep"); // Triggered list (total 2) which trigger goes first on the stack + addTarget(playerA, "Illusions of Grandeur"); // Own target permanent of Puca's Mischief + addTarget(playerA, "Kor Celebrant"); // Opponent's target permanent of Puca's Mischief + + setChoice(playerA, true); // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. + setChoice(playerA, false); // Pay {2}{2}? + + checkPermanentCounters("Age counters", 5, PhaseStep.PRECOMBAT_MAIN, playerB, "Illusions of Grandeur", CounterType.AGE, 2); + + setStopAt(5, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertLife(playerA, 40); + assertLife(playerB, 21); + + assertPermanentCount(playerA, "Kor Celebrant", 1); + assertPermanentCount(playerB, "Illusions of Grandeur", 1); + + + } + + } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EntwineTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EntwineTest.java index 787d9d59f03..1946604d6f0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EntwineTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EntwineTest.java @@ -1,179 +1,179 @@ -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 LevelX2, JayDi85 - */ - -public class EntwineTest extends CardTestPlayerBase { - - @Test - public void test_CastWithoutEntwine() { - // Choose one — - //• Barbed Lightning deals 3 damage to target creature. - //• Barbed Lightning deals 3 damage to target player or planeswalker. - // Entwine {2} (Choose both if you pay the entwine cost.) - addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - // - addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); - setChoice(playerA, false); // not use Entwine - setModeChoice(playerA, "1"); // target creature - addTarget(playerA, "Balduvian Bears"); - - setStrictChooseMode(true); - setStopAt(1, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - assertLife(playerA, 20); - assertPermanentCount(playerA, "Balduvian Bears", 0); - assertTappedCount("Mountain", true, 3); - } - - @Test - public void test_CastEntwine_Normal() { - // Choose one — - //• Barbed Lightning deals 3 damage to target creature. - //• Barbed Lightning deals 3 damage to target player or planeswalker. - // Entwine {2} (Choose both if you pay the entwine cost.) - addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 2); - // - addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); - setChoice(playerA, true); // use Entwine - addTarget(playerA, "Balduvian Bears"); - addTarget(playerA, playerA); - - setStrictChooseMode(true); - setStopAt(1, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - assertLife(playerA, 20 - 3); - assertPermanentCount(playerA, "Balduvian Bears", 0); - assertTappedCount("Mountain", true, 3 + 2); - } - - @Test - public void test_CastEntwine_CostReduction() { - addCustomEffect_SpellCostModification(playerA, -4); - - // Choose one — - //• Barbed Lightning deals 3 damage to target creature. - //• Barbed Lightning deals 3 damage to target player or planeswalker. - // Entwine {2} (Choose both if you pay the entwine cost.) - addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); // -4 as cost reduction - // - addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); - setChoice(playerA, true); // use Entwine - addTarget(playerA, "Balduvian Bears"); - addTarget(playerA, playerA); - - setStrictChooseMode(true); - setStopAt(1, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - assertLife(playerA, 20 - 3); - assertPermanentCount(playerA, "Balduvian Bears", 0); - assertTappedCount("Mountain", true, 1); - } - - @Test - public void test_CastEntwine_CostIncreasing() { - addCustomEffect_SpellCostModification(playerA, 5); - - // Choose one — - //• Barbed Lightning deals 3 damage to target creature. - //• Barbed Lightning deals 3 damage to target player or planeswalker. - // Entwine {2} (Choose both if you pay the entwine cost.) - addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 2 + 5); - // - addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); - setChoice(playerA, true); // use Entwine - addTarget(playerA, "Balduvian Bears"); - addTarget(playerA, playerA); - - setStrictChooseMode(true); - setStopAt(1, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - assertLife(playerA, 20 - 3); - assertPermanentCount(playerA, "Balduvian Bears", 0); - assertTappedCount("Mountain", true, 3 + 2 + 5); - } - - @Test - public void test_CastEntwine_FreeFromHand() { - // You may cast nonland cards from your hand without paying their mana costs. - addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); - - // Choose one — - //• Barbed Lightning deals 3 damage to target creature. - //• Barbed Lightning deals 3 damage to target player or planeswalker. - // Entwine {2} (Choose both if you pay the entwine cost.) - addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); // only Entwine pay need - // - addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); - setChoice(playerA, true); // cast for free - setChoice(playerA, true); // use Entwine - addTarget(playerA, "Balduvian Bears"); - addTarget(playerA, playerA); - - setStrictChooseMode(true); - setStopAt(1, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - assertLife(playerA, 20 - 3); - assertPermanentCount(playerA, "Balduvian Bears", 0); - assertTappedCount("Plains", true, 2); - } - - @Test - public void test_ToothAndNail() { - setStrictChooseMode(true); - - addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1); - addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1); - - addCard(Zone.BATTLEFIELD, playerA, "Forest", 9); - // Choose one - - // Search your library for up to two creature cards, reveal them, put them into your hand, then shuffle your library; - // or put up to two creature cards from your hand onto the battlefield. - // Entwine {2} - addCard(Zone.HAND, playerA, "Tooth and Nail"); // Sorcery {5}{G}{G} - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tooth and Nail"); - setChoice(playerA, true); // Message: Pay Entwine {2} ? - addTarget(playerA, "Silvercoat Lion^Pillarfield Ox"); - setChoice(playerA, "Silvercoat Lion^Pillarfield Ox"); - - setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - execute(); - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Silvercoat Lion", 1); - assertPermanentCount(playerA, "Pillarfield Ox", 1); - } -} +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 LevelX2, JayDi85 + */ + +public class EntwineTest extends CardTestPlayerBase { + + @Test + public void test_CastWithoutEntwine() { + // Choose one — + //• Barbed Lightning deals 3 damage to target creature. + //• Barbed Lightning deals 3 damage to target player or planeswalker. + // Entwine {2} (Choose both if you pay the entwine cost.) + addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); + setChoice(playerA, false); // not use Entwine + setModeChoice(playerA, "1"); // target creature + addTarget(playerA, "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertPermanentCount(playerA, "Balduvian Bears", 0); + assertTappedCount("Mountain", true, 3); + } + + @Test + public void test_CastEntwine_Normal() { + // Choose one — + //• Barbed Lightning deals 3 damage to target creature. + //• Barbed Lightning deals 3 damage to target player or planeswalker. + // Entwine {2} (Choose both if you pay the entwine cost.) + addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 2); + // + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); + setChoice(playerA, true); // use Entwine + addTarget(playerA, "Balduvian Bears"); + addTarget(playerA, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20 - 3); + assertPermanentCount(playerA, "Balduvian Bears", 0); + assertTappedCount("Mountain", true, 3 + 2); + } + + @Test + public void test_CastEntwine_CostReduction() { + addCustomEffect_SpellCostModification(playerA, -4); + + // Choose one — + //• Barbed Lightning deals 3 damage to target creature. + //• Barbed Lightning deals 3 damage to target player or planeswalker. + // Entwine {2} (Choose both if you pay the entwine cost.) + addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); // -4 as cost reduction + // + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); + setChoice(playerA, true); // use Entwine + addTarget(playerA, "Balduvian Bears"); + addTarget(playerA, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20 - 3); + assertPermanentCount(playerA, "Balduvian Bears", 0); + assertTappedCount("Mountain", true, 1); + } + + @Test + public void test_CastEntwine_CostIncreasing() { + addCustomEffect_SpellCostModification(playerA, 5); + + // Choose one — + //• Barbed Lightning deals 3 damage to target creature. + //• Barbed Lightning deals 3 damage to target player or planeswalker. + // Entwine {2} (Choose both if you pay the entwine cost.) + addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 2 + 5); + // + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); + setChoice(playerA, true); // use Entwine + addTarget(playerA, "Balduvian Bears"); + addTarget(playerA, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20 - 3); + assertPermanentCount(playerA, "Balduvian Bears", 0); + assertTappedCount("Mountain", true, 3 + 2 + 5); + } + + @Test + public void test_CastEntwine_FreeFromHand() { + // You may cast nonland cards from your hand without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + + // Choose one — + //• Barbed Lightning deals 3 damage to target creature. + //• Barbed Lightning deals 3 damage to target player or planeswalker. + // Entwine {2} (Choose both if you pay the entwine cost.) + addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); // only Entwine pay need + // + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning"); + setChoice(playerA, true); // cast for free + setChoice(playerA, true); // use Entwine + addTarget(playerA, "Balduvian Bears"); + addTarget(playerA, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20 - 3); + assertPermanentCount(playerA, "Balduvian Bears", 0); + assertTappedCount("Plains", true, 2); + } + + @Test + public void test_ToothAndNail() { + setStrictChooseMode(true); + + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1); + addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 9); + // Choose one - + // Search your library for up to two creature cards, reveal them, put them into your hand, then shuffle your library; + // or put up to two creature cards from your hand onto the battlefield. + // Entwine {2} + addCard(Zone.HAND, playerA, "Tooth and Nail"); // Sorcery {5}{G}{G} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tooth and Nail"); + setChoice(playerA, true); // Message: Pay Entwine {2} ? + addTarget(playerA, "Silvercoat Lion^Pillarfield Ox"); + setChoice(playerA, "Silvercoat Lion^Pillarfield Ox"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Silvercoat Lion", 1); + assertPermanentCount(playerA, "Pillarfield Ox", 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HexproofTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HexproofTest.java index fafc7c99fd2..00c6bf24775 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HexproofTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HexproofTest.java @@ -3,13 +3,15 @@ package org.mage.test.cards.abilities.keywords; import mage.abilities.keyword.HexproofAbility; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Assert; import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; /** - * @author LevelX2 + * @author LevelX2, JayDi85 */ -public class HexproofTest extends CardTestPlayerBase { +public class HexproofTest extends CardTestPlayerBaseWithAIHelps { /** * Tests one target gets hexproof @@ -103,4 +105,162 @@ public class HexproofTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Knight of Grace", 0); } + + @Test + public void test_Human_CanTargetValid() { + // +1: Target player discards a card. + addCard(Zone.BATTLEFIELD, playerA, "Liliana Vess", 1); + addCard(Zone.HAND, playerA, "Balduvian Bears", 1); + addCard(Zone.HAND, playerA, "Swamp", 1); + addCard(Zone.HAND, playerB, "Matter Reshaper", 1); + addCard(Zone.HAND, playerB, "Mountain", 1); + // + // You have hexproof. (You can't be the target of spells or abilities your opponents control.) + addCard(Zone.BATTLEFIELD, playerB, "Leyline of Sanctity", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + addTarget(playerA, playerA); + setChoice(playerA, "Swamp"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Swamp", 1); + assertCounterCount(playerA, "Liliana Vess", CounterType.LOYALTY, 5 + 1); + } + + @Test + public void test_Human_CantTargetInvalid() { + // +1: Target player discards a card. + addCard(Zone.BATTLEFIELD, playerA, "Liliana Vess", 1); + addCard(Zone.HAND, playerA, "Balduvian Bears", 1); + addCard(Zone.HAND, playerA, "Swamp", 1); + addCard(Zone.HAND, playerB, "Matter Reshaper", 1); + addCard(Zone.HAND, playerB, "Mountain", 1); + // + // You have hexproof. (You can't be the target of spells or abilities your opponents control.) + addCard(Zone.BATTLEFIELD, playerB, "Leyline of Sanctity", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + addTarget(playerA, playerB); + + try { + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + Assert.fail("must throw exception on execute"); + } catch (Throwable e) { + if (!e.getMessage().contains("setup good targets")) { + Assert.fail("must thow error about bad targets, but got:\n" + e.getMessage()); + } + } + } + + @Test + public void test_AI_MustTargetOnlyValid_1() { + // +1: Target player discards a card. + addCard(Zone.BATTLEFIELD, playerA, "Liliana Vess", 1); + addCard(Zone.HAND, playerA, "Balduvian Bears", 1); + addCard(Zone.HAND, playerA, "Swamp", 1); + addCard(Zone.HAND, playerB, "Matter Reshaper", 1); + addCard(Zone.HAND, playerB, "Mountain", 1); + // + // You have hexproof. (You can't be the target of spells or abilities your opponents control.) + addCard(Zone.BATTLEFIELD, playerB, "Leyline of Sanctity", 1); + + // ai must not use +1 on itself (due bad score) and must not use on opponent (due hexproof) + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // no discarded cards + assertGraveyardCount(playerA, 0); + assertGraveyardCount(playerB, 0); + // no activated abilities + assertCounterCount(playerA, "Liliana Vess", CounterType.LOYALTY, 5 - 2); // search library for -2 + } + + @Test + public void test_AI_MustTargetOnlyValid_2() { + // +1: Target player discards a card. + addCard(Zone.BATTLEFIELD, playerA, "Liliana Vess", 1); + addCard(Zone.HAND, playerA, "Balduvian Bears", 1); + addCard(Zone.HAND, playerA, "Swamp", 1); + addCard(Zone.HAND, playerB, "Matter Reshaper", 1); + addCard(Zone.HAND, playerB, "Mountain", 1); + // + // You and permanents you control gain hexproof from blue and from black until end of turn. + addCard(Zone.HAND, playerB, "Veil of Summer", 1); // instant {G} + addCard(Zone.BATTLEFIELD, playerB, "Forest", 1); + + // prepare hexproof + castSpell(1, PhaseStep.UPKEEP, playerB, "Veil of Summer"); + + // ai must not use +1 on itself (due bad score) and must not use on opponent (due hexproof) + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // no discarded cards + assertGraveyardCount(playerA, 0); + assertGraveyardCount(playerB, 1); // Veil of Summer + // no activated abilities + assertCounterCount(playerA, "Liliana Vess", CounterType.LOYALTY, 5 - 2); // search library for -2 + } + + @Test + public void test_AI_Logs() { + addCard(Zone.HAND, playerA, "Lightning Bolt", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerB, 20 - 3 * 3); + } + + @Test + public void test_RulesModificationForPlayers() { + // This turn and next turn, creatures can't attack, and players and permanents can't be the targets + // of spells or activated abilities. + addCard(Zone.HAND, playerA, "Peace Talks", 1); // {1}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", true); + + // activate restriction + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}", 2); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Peace Talks"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // playable doesn't check illegal targets, so it will be active + // ai can cast on turn 3 only + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkLife("after 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, 20); + aiPlayStep(2, PhaseStep.PRECOMBAT_MAIN, playerA); + checkLife("after 2", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, 20); + aiPlayStep(3, PhaseStep.PRECOMBAT_MAIN, playerA); + checkLife("after 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, 20 - 3); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java index a3c7ccfce2a..125476f40e4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java @@ -1,540 +1,540 @@ -package org.mage.test.cards.abilities.keywords; - -import mage.cards.Card; -import mage.constants.EmptyNames; -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 LevelX2 - */ -public class ManifestTest extends CardTestPlayerBase { - - /** - * Tests that ETB triggered abilities did not trigger for manifested cards - */ - @Test - public void testETBTriggeredAbilities() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // Manifest the top card of your library {1}{W} - addCard(Zone.HAND, playerA, "Soul Summons"); - - // Tranquil Cove enters the battlefield tapped. - // When Tranquil Cove enters the battlefield, you gain 1 life. - // {T}: Add {W} or {U}. - addCard(Zone.LIBRARY, playerA, "Tranquil Cove"); - skipInitShuffling(); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Summons"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); - // not tapped - assertTapped(EmptyNames.FACE_DOWN_CREATURE.toString(), false); - } - - /** - * If Doomwake Giant gets manifested, it's Constellation trigger may not - * trigger - */ - @Test - public void testETBTriggeredAbilities2() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // Manifest the top card of your library {1}{W} - addCard(Zone.HAND, playerA, "Soul Summons"); - - // Constellation - When Doomwake Giant or another enchantment enters the battlefield - // under your control, creatures your opponents control get -1/-1 until end of turn. - addCard(Zone.LIBRARY, playerA, "Doomwake Giant"); - - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); - skipInitShuffling(); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Summons"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); - // PlayerB's Silvercoat Lion should not have get -1/-1/ - assertPermanentCount(playerB, "Silvercoat Lion", 1); - assertPowerToughness(playerB, "Silvercoat Lion", 2, 2); - } - - /** - * If Doomwake Giant gets manifested, it's Constellation trigger may not - * trigger - */ - @Test - public void testETBTriggeredAbilities3() { - addCard(Zone.BATTLEFIELD, playerB, "Island", 2); - // Exile target creature. Its controller manifests the top card of their library {1}{U} - addCard(Zone.HAND, playerB, "Reality Shift"); - - // Constellation - When Doomwake Giant or another enchantment enters the battlefield - // under your control, creatures your opponents control get -1/-1 until end of turn. - addCard(Zone.LIBRARY, playerA, "Doomwake Giant"); - - addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); - - skipInitShuffling(); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - assertGraveyardCount(playerB, "Reality Shift", 1); - assertExileCount("Silvercoat Lion", 1); - // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); - // PlayerA's Pillarfield Ox should not have get -1/-1/ - assertPermanentCount(playerB, "Pillarfield Ox", 1); - assertPowerToughness(playerB, "Pillarfield Ox", 2, 4); - } - - /** - * If Doomwake Giant gets manifested, it's Constellation trigger may not - * trigger - */ - @Test - public void testNylea() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerB, "Island", 2); - // Exile target creature. Its controller manifests the top card of their library {1}{U} - addCard(Zone.HAND, playerB, "Reality Shift"); - - // As long as your devotion to white is less than five, Nylea isn't a creature. - // (Each {G} in the mana costs of permanents you control counts towards your devotion to green.) - addCard(Zone.LIBRARY, playerA, "Nylea, God of the Hunt"); - - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); - - skipInitShuffling(); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - assertGraveyardCount(playerB, "Reality Shift", 1); - assertExileCount("Silvercoat Lion", 1); - // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); - - } - - /* - Had a Foundry Street Denizen and another creature out. - Opponent Reality Shift'ed the other creature, manifested card was a red creature. This pumped the foundry street denizen even though it shouldn't. - */ - @Test - public void testColorOfManifestedCardDoesNotCount() { - addCard(Zone.BATTLEFIELD, playerB, "Island", 2); - // Exile target creature. Its controller manifests the top card of their library {1}{U} - addCard(Zone.HAND, playerB, "Reality Shift"); - - // Gore Swine {2}{R} - // 4/1 - addCard(Zone.LIBRARY, playerA, "Gore Swine"); - - // Whenever another red creature enters the battlefield under your control, Foundry Street Denizen gets +1/+0 until end of turn. - addCard(Zone.BATTLEFIELD, playerA, "Foundry Street Denizen"); - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); - - skipInitShuffling(); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - assertGraveyardCount(playerB, "Reality Shift", 1); - assertExileCount("Silvercoat Lion", 1); - // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); - assertPowerToughness(playerA, "Foundry Street Denizen", 1, 1); - - } - - /* - I casted a Silence the Believers on a manifested card. It moved to the exile zone face-down. - */ - @Test - public void testCardGetsExiledFaceUp() { - addCard(Zone.BATTLEFIELD, playerB, "Island", 2); - addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4); - // Exile target creature. Its controller manifests the top card of their library {1}{U} - addCard(Zone.HAND, playerB, "Reality Shift"); - // Silence the Believers - Instant {2}{B}{B} - // Strive — Silence the Believers costs more to cast for each target beyond the first. - // Exile any number of target creatures and all Auras attached to them. - addCard(Zone.HAND, playerB, "Silence the Believers"); - // Gore Swine {2}{R} - // 4/1 - addCard(Zone.LIBRARY, playerA, "Gore Swine"); - - // Whenever another red creature enters the battlefield under your control, Foundry Street Denizen gets +1/+0 until end of turn. - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); - - skipInitShuffling(); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); - // showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); - // showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Silence the Believers", EmptyNames.FACE_DOWN_CREATURE.toString()); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - assertGraveyardCount(playerB, "Reality Shift", 1); - assertExileCount("Silvercoat Lion", 1); - assertExileCount("Gore Swine", 1); - // no facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); - - for (Card card : currentGame.getExile().getAllCards(currentGame)) { - if (card.getName().equals("Gore Swine")) { - Assert.assertTrue("Gore Swine may not be face down in exile", !card.isFaceDown(currentGame)); - } - } - - } - - // Qarsi High Priest went to manifest Illusory Gains, - // but it made me choose a target for gains, then enchanted the card to that creature. - @Test - public void testManifestAura() { - - addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. - addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); - - addCard(Zone.LIBRARY, playerB, "Illusory Gains", 1); - addCard(Zone.LIBRARY, playerB, "Mountain", 1); - - skipInitShuffling(); - - activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); - setChoice(playerB, "Silvercoat Lion"); - - setStopAt(2, PhaseStep.BEGIN_COMBAT); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - - assertGraveyardCount(playerB, "Illusory Gains", 0); - assertGraveyardCount(playerB, "Silvercoat Lion", 1); - - // a facedown creature is on the battlefield - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - - } - - // Check if a Megamorph card is manifested and turned face up by their megamorph ability - // it gets the +1/+1 counter. - // 701.33c - // If a card with morph is manifested, its controller may turn that card face up using - // either the procedure described in rule 702.36e to turn a face-down permanent with morph face up - // or the procedure described above to turn a manifested permanent face up. - @Test - public void testManifestMegamorph_TurnUpByMegamorphCost() { - addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - addCard(Zone.BATTLEFIELD, playerB, "Forest", 6); - // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. - addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); - - // Reach (This creature can block creatures with flying.) - // Megamorph {5}{G} - addCard(Zone.LIBRARY, playerB, "Aerie Bowmasters", 1); - addCard(Zone.LIBRARY, playerB, "Mountain", 1); - - skipInitShuffling(); - - activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); - setChoice(playerB, "Silvercoat Lion"); - - activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{5}{G}: Turn"); - - setStrictChooseMode(true); - setStopAt(2, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - - assertGraveyardCount(playerB, "Silvercoat Lion", 1); - - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); - assertPermanentCount(playerB, "Aerie Bowmasters", 1); - assertPowerToughness(playerB, "Aerie Bowmasters", 4, 5); // 3/4 and the +1/+1 counter from Megamorph - Permanent aerie = getPermanent("Aerie Bowmasters", playerB); - Assert.assertTrue("Aerie Bowmasters has to be green", aerie != null && aerie.getColor(currentGame).isGreen()); - } - - @Test - public void testManifestMegamorph_TurnUpBySimpleCost() { - addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - addCard(Zone.BATTLEFIELD, playerB, "Forest", 4); - // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. - addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); - - // {2}{G}{G} - // Reach (This creature can block creatures with flying.) - // Megamorph {5}{G} - addCard(Zone.LIBRARY, playerB, "Aerie Bowmasters", 1); - addCard(Zone.LIBRARY, playerB, "Mountain", 1); - - skipInitShuffling(); - - activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); - setChoice(playerB, "Silvercoat Lion"); - - activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{2}{G}{G}: Turn"); - - setStrictChooseMode(true); - setStopAt(2, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - - assertGraveyardCount(playerB, "Silvercoat Lion", 1); - - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); - assertPermanentCount(playerB, "Aerie Bowmasters", 1); - assertPowerToughness(playerB, "Aerie Bowmasters", 3, 4); // 3/4 without counter (megamorph not used) - Permanent aerie = getPermanent("Aerie Bowmasters", playerB); - Assert.assertTrue("Aerie Bowmasters has to be green", aerie != null && aerie.getColor(currentGame).isGreen()); - } - - /** - * When a Forest came manifested into play my Courser of Kruphix gained me a - * life. - */ - @Test - public void testManifestForest() { - - addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - // Play with the top card of your library revealed. - // You may play the top card of your library if it's a land card. - // Whenever a land enters the battlefield under your control, you gain 1 life. - addCard(Zone.BATTLEFIELD, playerB, "Courser of Kruphix", 1); - - // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. - addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); - - addCard(Zone.LIBRARY, playerB, "Forest", 1); - - skipInitShuffling(); - - activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); - setChoice(playerB, "Silvercoat Lion"); - - setStopAt(2, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - - assertGraveyardCount(playerB, "Silvercoat Lion", 1); - - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - - } - - /** - * Whisperwood Elemental - Its sacrifice ability doesn't work.. - */ - @Test - public void testWhisperwoodElemental() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - // Seismic Rupture deals 2 damage to each creature without flying. - addCard(Zone.HAND, playerA, "Seismic Rupture", 1); - - // At the beginning of your end step, manifest the top card of your library. - // Sacrifice Whisperwood Elemental: Until end of turn, face-up, nontoken creatures you control gain "When this creature dies, manifest the top card of your library." - addCard(Zone.BATTLEFIELD, playerB, "Whisperwood Elemental", 1); - - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 2); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Sacrifice"); - - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Seismic Rupture"); - setChoice(playerB, "When {this} dies"); // Order of triggers - - setStopAt(1, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - - assertGraveyardCount(playerA, "Seismic Rupture", 1); - assertGraveyardCount(playerB, "Whisperwood Elemental", 1); - assertGraveyardCount(playerB, "Silvercoat Lion", 2); - - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); - - } - - /** - * I sacrificed a manifested face-down Smothering Abomination to Nantuko - * Husk and it made me draw a card. - */ - @Test - public void testDiesTriggeredAbilitiesOfManifestedCreatures() { - - addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - - // Sacrifice a creature: Nantuko Husk gets +2/+2 until end of turn. - addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk", 1); - - // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. - addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); - - // Devoid - // Flying - // At the beginning of your upkeep, sacrifice a creature - // Whenever you sacrifice a creature, draw a card. - addCard(Zone.LIBRARY, playerB, "Mountain", 1); - addCard(Zone.LIBRARY, playerB, "Smothering Abomination", 1); - addCard(Zone.LIBRARY, playerB, "Mountain", 1); - - skipInitShuffling(); - - activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); - setChoice(playerB, "Silvercoat Lion"); - - activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Sacrifice a creature"); - setChoice(playerB, EmptyNames.FACE_DOWN_CREATURE.toString()); - - setStrictChooseMode(true); - setStopAt(2, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - - assertPermanentCount(playerB, "Qarsi High Priest", 1); - assertPermanentCount(playerB, "Nantuko Husk", 1); - - assertGraveyardCount(playerB, "Silvercoat Lion", 1); - assertGraveyardCount(playerB, "Smothering Abomination", 1); - - assertPowerToughness(playerB, "Nantuko Husk", 4, 4); - - assertHandCount(playerB, "Mountain", 1); - - } - - @Test - public void test_ManifestSorceryAndBlinkIt() { - - addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); - addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); - - // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. - addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); - - // Exile target creature you control, then return that card to the battlefield under your control. - addCard(Zone.HAND, playerB, "Cloudshift", 1); //Instant {W} - - - // Devoid - // Flying - // At the beginning of your upkeep, sacrifice a creature - // Whenever you sacrifice a creature, draw a card. - addCard(Zone.LIBRARY, playerB, "Mountain", 1); - addCard(Zone.LIBRARY, playerB, "Lightning Bolt", 1); - addCard(Zone.LIBRARY, playerB, "Mountain", 1); - - skipInitShuffling(); - - activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); - setChoice(playerB, "Silvercoat Lion"); - - waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN, playerB); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cloudshift", EmptyNames.FACE_DOWN_CREATURE.toString()); - - setStrictChooseMode(true); - setStopAt(2, PhaseStep.END_TURN); - execute(); - assertAllCommandsUsed(); - - // no life gain - assertLife(playerA, 20); - assertLife(playerB, 20); - - assertPermanentCount(playerB, "Qarsi High Priest", 1); - - assertGraveyardCount(playerB, "Silvercoat Lion", 1); - assertGraveyardCount(playerB, "Cloudshift", 1); - - assertPermanentCount(playerB, "Lightning Bolt", 0); - assertExileCount(playerB, "Lightning Bolt", 1); - - assertHandCount(playerB, "Mountain", 1); - - } -} +package org.mage.test.cards.abilities.keywords; + +import mage.cards.Card; +import mage.constants.EmptyNames; +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 LevelX2 + */ +public class ManifestTest extends CardTestPlayerBase { + + /** + * Tests that ETB triggered abilities did not trigger for manifested cards + */ + @Test + public void testETBTriggeredAbilities() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // Manifest the top card of your library {1}{W} + addCard(Zone.HAND, playerA, "Soul Summons"); + + // Tranquil Cove enters the battlefield tapped. + // When Tranquil Cove enters the battlefield, you gain 1 life. + // {T}: Add {W} or {U}. + addCard(Zone.LIBRARY, playerA, "Tranquil Cove"); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Summons"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + // a facedown creature is on the battlefield + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + // not tapped + assertTapped(EmptyNames.FACE_DOWN_CREATURE.toString(), false); + } + + /** + * If Doomwake Giant gets manifested, it's Constellation trigger may not + * trigger + */ + @Test + public void testETBTriggeredAbilities2() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // Manifest the top card of your library {1}{W} + addCard(Zone.HAND, playerA, "Soul Summons"); + + // Constellation - When Doomwake Giant or another enchantment enters the battlefield + // under your control, creatures your opponents control get -1/-1 until end of turn. + addCard(Zone.LIBRARY, playerA, "Doomwake Giant"); + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Summons"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + // a facedown creature is on the battlefield + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + // PlayerB's Silvercoat Lion should not have get -1/-1/ + assertPermanentCount(playerB, "Silvercoat Lion", 1); + assertPowerToughness(playerB, "Silvercoat Lion", 2, 2); + } + + /** + * If Doomwake Giant gets manifested, it's Constellation trigger may not + * trigger + */ + @Test + public void testETBTriggeredAbilities3() { + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + // Exile target creature. Its controller manifests the top card of their library {1}{U} + addCard(Zone.HAND, playerB, "Reality Shift"); + + // Constellation - When Doomwake Giant or another enchantment enters the battlefield + // under your control, creatures your opponents control get -1/-1 until end of turn. + addCard(Zone.LIBRARY, playerA, "Doomwake Giant"); + + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + assertGraveyardCount(playerB, "Reality Shift", 1); + assertExileCount("Silvercoat Lion", 1); + // a facedown creature is on the battlefield + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + // PlayerA's Pillarfield Ox should not have get -1/-1/ + assertPermanentCount(playerB, "Pillarfield Ox", 1); + assertPowerToughness(playerB, "Pillarfield Ox", 2, 4); + } + + /** + * If Doomwake Giant gets manifested, it's Constellation trigger may not + * trigger + */ + @Test + public void testNylea() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + // Exile target creature. Its controller manifests the top card of their library {1}{U} + addCard(Zone.HAND, playerB, "Reality Shift"); + + // As long as your devotion to white is less than five, Nylea isn't a creature. + // (Each {G} in the mana costs of permanents you control counts towards your devotion to green.) + addCard(Zone.LIBRARY, playerA, "Nylea, God of the Hunt"); + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + assertGraveyardCount(playerB, "Reality Shift", 1); + assertExileCount("Silvercoat Lion", 1); + // a facedown creature is on the battlefield + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + + } + + /* + Had a Foundry Street Denizen and another creature out. + Opponent Reality Shift'ed the other creature, manifested card was a red creature. This pumped the foundry street denizen even though it shouldn't. + */ + @Test + public void testColorOfManifestedCardDoesNotCount() { + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + // Exile target creature. Its controller manifests the top card of their library {1}{U} + addCard(Zone.HAND, playerB, "Reality Shift"); + + // Gore Swine {2}{R} + // 4/1 + addCard(Zone.LIBRARY, playerA, "Gore Swine"); + + // Whenever another red creature enters the battlefield under your control, Foundry Street Denizen gets +1/+0 until end of turn. + addCard(Zone.BATTLEFIELD, playerA, "Foundry Street Denizen"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + assertGraveyardCount(playerB, "Reality Shift", 1); + assertExileCount("Silvercoat Lion", 1); + // a facedown creature is on the battlefield + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPowerToughness(playerA, "Foundry Street Denizen", 1, 1); + + } + + /* + I casted a Silence the Believers on a manifested card. It moved to the exile zone face-down. + */ + @Test + public void testCardGetsExiledFaceUp() { + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4); + // Exile target creature. Its controller manifests the top card of their library {1}{U} + addCard(Zone.HAND, playerB, "Reality Shift"); + // Silence the Believers - Instant {2}{B}{B} + // Strive — Silence the Believers costs more to cast for each target beyond the first. + // Exile any number of target creatures and all Auras attached to them. + addCard(Zone.HAND, playerB, "Silence the Believers"); + // Gore Swine {2}{R} + // 4/1 + addCard(Zone.LIBRARY, playerA, "Gore Swine"); + + // Whenever another red creature enters the battlefield under your control, Foundry Street Denizen gets +1/+0 until end of turn. + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); + // showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); + // showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Silence the Believers", EmptyNames.FACE_DOWN_CREATURE.toString()); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + assertGraveyardCount(playerB, "Reality Shift", 1); + assertExileCount("Silvercoat Lion", 1); + assertExileCount("Gore Swine", 1); + // no facedown creature is on the battlefield + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + + for (Card card : currentGame.getExile().getAllCards(currentGame)) { + if (card.getName().equals("Gore Swine")) { + Assert.assertTrue("Gore Swine may not be face down in exile", !card.isFaceDown(currentGame)); + } + } + + } + + // Qarsi High Priest went to manifest Illusory Gains, + // but it made me choose a target for gains, then enchanted the card to that creature. + @Test + public void testManifestAura() { + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. + addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + addCard(Zone.LIBRARY, playerB, "Illusory Gains", 1); + addCard(Zone.LIBRARY, playerB, "Mountain", 1); + + skipInitShuffling(); + + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); + setChoice(playerB, "Silvercoat Lion"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertGraveyardCount(playerB, "Illusory Gains", 0); + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + + // a facedown creature is on the battlefield + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + + } + + // Check if a Megamorph card is manifested and turned face up by their megamorph ability + // it gets the +1/+1 counter. + // 701.33c + // If a card with morph is manifested, its controller may turn that card face up using + // either the procedure described in rule 702.36e to turn a face-down permanent with morph face up + // or the procedure described above to turn a manifested permanent face up. + @Test + public void testManifestMegamorph_TurnUpByMegamorphCost() { + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 6); + // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. + addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + // Reach (This creature can block creatures with flying.) + // Megamorph {5}{G} + addCard(Zone.LIBRARY, playerB, "Aerie Bowmasters", 1); + addCard(Zone.LIBRARY, playerB, "Mountain", 1); + + skipInitShuffling(); + + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); + setChoice(playerB, "Silvercoat Lion"); + + activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{5}{G}: Turn"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerB, "Aerie Bowmasters", 1); + assertPowerToughness(playerB, "Aerie Bowmasters", 4, 5); // 3/4 and the +1/+1 counter from Megamorph + Permanent aerie = getPermanent("Aerie Bowmasters", playerB); + Assert.assertTrue("Aerie Bowmasters has to be green", aerie != null && aerie.getColor(currentGame).isGreen()); + } + + @Test + public void testManifestMegamorph_TurnUpBySimpleCost() { + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 4); + // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. + addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + // {2}{G}{G} + // Reach (This creature can block creatures with flying.) + // Megamorph {5}{G} + addCard(Zone.LIBRARY, playerB, "Aerie Bowmasters", 1); + addCard(Zone.LIBRARY, playerB, "Mountain", 1); + + skipInitShuffling(); + + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); + setChoice(playerB, "Silvercoat Lion"); + + activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{2}{G}{G}: Turn"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerB, "Aerie Bowmasters", 1); + assertPowerToughness(playerB, "Aerie Bowmasters", 3, 4); // 3/4 without counter (megamorph not used) + Permanent aerie = getPermanent("Aerie Bowmasters", playerB); + Assert.assertTrue("Aerie Bowmasters has to be green", aerie != null && aerie.getColor(currentGame).isGreen()); + } + + /** + * When a Forest came manifested into play my Courser of Kruphix gained me a + * life. + */ + @Test + public void testManifestForest() { + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + // Play with the top card of your library revealed. + // You may play the top card of your library if it's a land card. + // Whenever a land enters the battlefield under your control, you gain 1 life. + addCard(Zone.BATTLEFIELD, playerB, "Courser of Kruphix", 1); + + // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. + addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + addCard(Zone.LIBRARY, playerB, "Forest", 1); + + skipInitShuffling(); + + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); + setChoice(playerB, "Silvercoat Lion"); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + + } + + /** + * Whisperwood Elemental - Its sacrifice ability doesn't work.. + */ + @Test + public void testWhisperwoodElemental() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // Seismic Rupture deals 2 damage to each creature without flying. + addCard(Zone.HAND, playerA, "Seismic Rupture", 1); + + // At the beginning of your end step, manifest the top card of your library. + // Sacrifice Whisperwood Elemental: Until end of turn, face-up, nontoken creatures you control gain "When this creature dies, manifest the top card of your library." + addCard(Zone.BATTLEFIELD, playerB, "Whisperwood Elemental", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Sacrifice"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Seismic Rupture"); + setChoice(playerB, "When {this} dies"); // Order of triggers + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertGraveyardCount(playerA, "Seismic Rupture", 1); + assertGraveyardCount(playerB, "Whisperwood Elemental", 1); + assertGraveyardCount(playerB, "Silvercoat Lion", 2); + + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); + + } + + /** + * I sacrificed a manifested face-down Smothering Abomination to Nantuko + * Husk and it made me draw a card. + */ + @Test + public void testDiesTriggeredAbilitiesOfManifestedCreatures() { + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + + // Sacrifice a creature: Nantuko Husk gets +2/+2 until end of turn. + addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk", 1); + + // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. + addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + // Devoid + // Flying + // At the beginning of your upkeep, sacrifice a creature + // Whenever you sacrifice a creature, draw a card. + addCard(Zone.LIBRARY, playerB, "Mountain", 1); + addCard(Zone.LIBRARY, playerB, "Smothering Abomination", 1); + addCard(Zone.LIBRARY, playerB, "Mountain", 1); + + skipInitShuffling(); + + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); + setChoice(playerB, "Silvercoat Lion"); + + activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Sacrifice a creature"); + setChoice(playerB, EmptyNames.FACE_DOWN_CREATURE.toString()); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertPermanentCount(playerB, "Qarsi High Priest", 1); + assertPermanentCount(playerB, "Nantuko Husk", 1); + + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + assertGraveyardCount(playerB, "Smothering Abomination", 1); + + assertPowerToughness(playerB, "Nantuko Husk", 4, 4); + + assertHandCount(playerB, "Mountain", 1); + + } + + @Test + public void test_ManifestSorceryAndBlinkIt() { + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + + // {1}{B}, {T}, Sacrifice another creature: Manifest the top card of your library. + addCard(Zone.BATTLEFIELD, playerB, "Qarsi High Priest", 1); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + // Exile target creature you control, then return that card to the battlefield under your control. + addCard(Zone.HAND, playerB, "Cloudshift", 1); //Instant {W} + + + // Devoid + // Flying + // At the beginning of your upkeep, sacrifice a creature + // Whenever you sacrifice a creature, draw a card. + addCard(Zone.LIBRARY, playerB, "Mountain", 1); + addCard(Zone.LIBRARY, playerB, "Lightning Bolt", 1); + addCard(Zone.LIBRARY, playerB, "Mountain", 1); + + skipInitShuffling(); + + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{B}, {T}, Sacrifice another creature"); + setChoice(playerB, "Silvercoat Lion"); + + waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN, playerB); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cloudshift", EmptyNames.FACE_DOWN_CREATURE.toString()); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + // no life gain + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertPermanentCount(playerB, "Qarsi High Priest", 1); + + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + assertGraveyardCount(playerB, "Cloudshift", 1); + + assertPermanentCount(playerB, "Lightning Bolt", 0); + assertExileCount(playerB, "Lightning Bolt", 1); + + assertHandCount(playerB, "Mountain", 1); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OverloadTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OverloadTest.java index 7e74f4420ee..101fe368ca1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OverloadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OverloadTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; @@ -7,44 +6,147 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class OverloadTest extends CardTestPlayerBase { /** * My opponent cast an overloaded Vandalblast, and Xmage would not let me * cast Mental Misstep on it. - * + *

* The CMC of a card never changes, and Vandalblast's CMC is always 1. - * + *

* 4/15/2013 Casting a spell with overload doesn't change that spell's mana * cost. You just pay the overload cost instead. */ @Test - public void testCastByOverloadDoesNotChangeCMC() { - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + public void test_CastByOverloadDoesNotChangeCMC() { // Destroy target artifact you don't control. // Overload {4}{R} (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.") addCard(Zone.HAND, playerA, "Vandalblast"); - - addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // // Counter target spell with converted mana cost 1. addCard(Zone.HAND, playerB, "Mental Misstep", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 1); addCard(Zone.BATTLEFIELD, playerB, "War Horn", 2); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vandalblast with overload"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Mental Misstep", "Vandalblast"); + setChoice(playerB, false); // pay mana instead life + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Vandalblast", 1); assertGraveyardCount(playerB, "Mental Misstep", 1); - assertPermanentCount(playerB, "War Horn", 2); - } + @Test + public void test_CyclonicRift_NormalPlay() { + // Return target nonland permanent you don't control to its owner's hand. + // Overload {6}{U} (You may cast this spell for its overload cost. If you do, change its text + // by replacing all instances of "target" with "each.") + addCard(Zone.HAND, playerA, "Cyclonic Rift"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + // + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); + + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Cyclonic Rift", true); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Cyclonic Rift with overload", true); + + // cast and remove 1 target + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cyclonic Rift"); + addTarget(playerA, "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Cyclonic Rift", 1); + assertPermanentCount(playerB, "Swamp", 1); + assertHandCount(playerB, "Balduvian Bears", 1); + assertPermanentCount(playerB, "Spectral Bears", 1); + } + + @Test + public void test_CyclonicRift_OverloadPlay() { + // Return target nonland permanent you don't control to its owner's hand. + // Overload {6}{U} (You may cast this spell for its overload cost. If you do, change its text + // by replacing all instances of "target" with "each.") + addCard(Zone.HAND, playerA, "Cyclonic Rift"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + // + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); + + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Cyclonic Rift", true); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Cyclonic Rift with overload", true); + + // cast and remove all possible targets (all bears) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cyclonic Rift with overload"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Cyclonic Rift", 1); + assertPermanentCount(playerB, "Swamp", 1); + assertHandCount(playerB, "Balduvian Bears", 1); + assertHandCount(playerB, "Spectral Bears", 1); + } + + @Test + public void test_CyclonicRift_CantUseAlternativeSpellOnFreeCast() { + // bug: https://github.com/magefree/mage/issues/6925 + // Casting Overloaded Cyclonic Rift with Isochron Scepter - This should not be possible, + // You can't pay two alternate costs for the same thing. + + // Return target nonland permanent you don't control to its owner's hand. + // Overload {6}{U} (You may cast this spell for its overload cost. If you do, change its text + // by replacing all instances of "target" with "each.") + addCard(Zone.HAND, playerA, "Cyclonic Rift"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + // + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); + // + // Imprint - When Isochron Scepter enters the battlefield, you may exile an instant card with converted mana cost 2 or less from your hand. + // {2}, {T}: You may copy the exiled card. If you do, you may cast the copy without paying its mana cost. + addCard(Zone.HAND, playerA, "Isochron Scepter"); // {2} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // prepare scepter for imprint + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Isochron Scepter"); + setChoice(playerA, true); // use imprint + setChoice(playerA, "Cyclonic Rift"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // cast rift for free + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}:"); + setChoice(playerA, true); // create copy + setChoice(playerA, true); // use free cast + //setChoice(playerA, "Cast Cyclonic Rift with overload"); // must be NO choices, cause only normal cast allows here + addTarget(playerA, "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertExileCount(playerA, "Cyclonic Rift", 1); + assertGraveyardCount(playerA, "Cyclonic Rift", 0); // imprinted copy discarded + assertPermanentCount(playerB, "Swamp", 1); + assertHandCount(playerB, "Balduvian Bears", 1); + assertPermanentCount(playerB, "Spectral Bears", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ConditionalAsThoughTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ConditionalAsThoughTest.java new file mode 100644 index 00000000000..6077a23cf19 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ConditionalAsThoughTest.java @@ -0,0 +1,136 @@ +package org.mage.test.cards.conditional; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneAllEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCardInOpponentsGraveyard; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class ConditionalAsThoughTest extends CardTestPlayerBase { + + @Test + public void test_PlayFromNotOwnHandZoneAllEffect() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromLibrary(playerB); + + addCustomCardWithAbility("play any library on any creature", playerA, new SimpleStaticAbility( + Zone.ALL, + new ConditionalAsThoughEffect( + new PlayFromNotOwnHandZoneAllEffect( + StaticFilters.FILTER_CARD, + Zone.LIBRARY, + false, + TargetController.ANY, + Duration.EndOfTurn + ), + new PermanentsOnTheBattlefieldCondition( + StaticFilters.FILTER_PERMANENT_CREATURE, + ComparisonType.MORE_THAN, + 0 + ) + ) + )); + addCard(Zone.HAND, playerA, "Grizzly Bears"); + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion"); + addCard(Zone.LIBRARY, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + + // can't play lib's card before good condition + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false); + + // make good condition - now we can play any lib's card + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPlayableAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", false); + checkPlayableAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", true); + checkPlayableAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + } + + @Test(expected = IllegalArgumentException.class) + public void test_TargetCardInLibrary_CantUseAsAbilityTarget() { + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new InfoEffect("test"), + new ManaCostsImpl("{R}") + ); + ability.addTarget(new TargetCardInLibrary()); + } + + @Test + public void test_PlayFromNotOwnHandZoneTargetEffect() { + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new ConditionalAsThoughEffect( + new PlayFromNotOwnHandZoneTargetEffect( + Zone.GRAVEYARD, + TargetController.ANY, + Duration.EndOfTurn + ), + new PermanentsOnTheBattlefieldCondition( + StaticFilters.FILTER_PERMANENT_CREATURE, + ComparisonType.MORE_THAN, + 0 + ) + ).setText("allow target cast"), + new ManaCostsImpl("{R}") + ); + ability.addTarget(new TargetCardInOpponentsGraveyard(StaticFilters.FILTER_CARD)); + addCustomCardWithAbility("play any opponent hand", playerA, ability); + + addCard(Zone.HAND, playerA, "Grizzly Bears"); + addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion"); + addCard(Zone.GRAVEYARD, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + + // can't play grave before + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false); + + // activate target effect + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: Allow"); + addTarget(playerA, "Lightning Bolt"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // can't play grave after but without good condition + checkPlayableAbility("after bad", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true); + checkPlayableAbility("after bad", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false); + checkPlayableAbility("after bad", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false); + + // make good condition - now we can play opponent's grave + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPlayableAbility("after good", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", false); + checkPlayableAbility("after good", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false); + checkPlayableAbility("after good", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/FerventChampionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/FerventChampionTest.java index 2370ee55c17..b74dfe0b12a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/FerventChampionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/FerventChampionTest.java @@ -1,39 +1,39 @@ -package org.mage.test.cards.cost.modification; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ - -public class FerventChampionTest extends CardTestPlayerBase { - - @Test - public void testFerventChampion() { - setStrictChooseMode(true); - // First strike, Haste - // Whenever Fervent Champion attacks, another target attacking Knight you control gets +1/+0 until end of turn. - // Equip abilities you activate that target Fervent Champion cost {3} less to activate. - addCard(Zone.BATTLEFIELD, playerA, "Fervent Champion"); - - // Equipped creature gets +2/+2 and has protection from red and from blue. - // Whenever equipped creature deals combat damage to a player, Sword of Fire - // and Ice deals 2 damage to any target and you draw a card. - // Equip {2} - addCard(Zone.BATTLEFIELD, playerA, "Sword of Fire and Ice", 1); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip"); - addTarget(playerA, "Fervent Champion"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerA, "Fervent Champion", 3,3); - } +package org.mage.test.cards.cost.modification; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class FerventChampionTest extends CardTestPlayerBase { + + @Test + public void testFerventChampion() { + setStrictChooseMode(true); + // First strike, Haste + // Whenever Fervent Champion attacks, another target attacking Knight you control gets +1/+0 until end of turn. + // Equip abilities you activate that target Fervent Champion cost {3} less to activate. + addCard(Zone.BATTLEFIELD, playerA, "Fervent Champion"); + + // Equipped creature gets +2/+2 and has protection from red and from blue. + // Whenever equipped creature deals combat damage to a player, Sword of Fire + // and Ice deals 2 damage to any target and you draw a card. + // Equip {2} + addCard(Zone.BATTLEFIELD, playerA, "Sword of Fire and Ice", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip"); + addTarget(playerA, "Fervent Champion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerA, "Fervent Champion", 3,3); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/PrimordialMistTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/PrimordialMistTest.java index bf7a287c1dd..0167be3c333 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/PrimordialMistTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/PrimordialMistTest.java @@ -1,56 +1,56 @@ -package org.mage.test.cards.facedown; - -import mage.constants.EmptyNames; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class PrimordialMistTest extends CardTestPlayerBase { - - /** - * I have Brine Elemental played face down as a morph, an artifact which has - * been manifested and Kadena which has been turned face by Ixidron. I can't - * seem to activate Primordial Mist's second ability for any of these kinds - * of face down creatures: - */ - @Test - public void test_ExileAndCastMorphFaceDownCard() { - setStrictChooseMode(true); - - // At the beginning of your end step, you may manifest the top card of your library. - // Exile a face-down permanent you control face-up: You may play that card this turn - addCard(Zone.BATTLEFIELD, playerA, "Primordial Mist"); - // Morph {5}{U}{U} - // When Brine Elemental is turned face up, each opponent skips their next untap step. - addCard(Zone.HAND, playerA, "Brine Elemental"); // Creature {5}{U}{U} (5/4) - addCard(Zone.BATTLEFIELD, playerA, "Island", 9); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brine Elemental"); - setChoice(playerA, true); // cast it face down as 2/2 creature - - waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Exile a face-down permanent you control"); - setChoice(playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); - - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Brine Elemental"); - setChoice(playerA, false); // cast it face down as 2/2 creature - - setChoice(playerA, true); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertExileCount(playerA, 0); - - assertPowerToughness(playerA, "Brine Elemental", 5, 4); - - } -} +package org.mage.test.cards.facedown; + +import mage.constants.EmptyNames; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class PrimordialMistTest extends CardTestPlayerBase { + + /** + * I have Brine Elemental played face down as a morph, an artifact which has + * been manifested and Kadena which has been turned face by Ixidron. I can't + * seem to activate Primordial Mist's second ability for any of these kinds + * of face down creatures: + */ + @Test + public void test_ExileAndCastMorphFaceDownCard() { + setStrictChooseMode(true); + + // At the beginning of your end step, you may manifest the top card of your library. + // Exile a face-down permanent you control face-up: You may play that card this turn + addCard(Zone.BATTLEFIELD, playerA, "Primordial Mist"); + // Morph {5}{U}{U} + // When Brine Elemental is turned face up, each opponent skips their next untap step. + addCard(Zone.HAND, playerA, "Brine Elemental"); // Creature {5}{U}{U} (5/4) + addCard(Zone.BATTLEFIELD, playerA, "Island", 9); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brine Elemental"); + setChoice(playerA, true); // cast it face down as 2/2 creature + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Exile a face-down permanent you control"); + setChoice(playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Brine Elemental"); + setChoice(playerA, false); // cast it face down as 2/2 creature + + setChoice(playerA, true); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertExileCount(playerA, 0); + + assertPowerToughness(playerA, "Brine Elemental", 5, 4); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/TriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/TriggerTest.java index 54035a4cfe1..2a673dd635a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/TriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/TriggerTest.java @@ -1,62 +1,62 @@ - -package org.mage.test.cards.facedown; - -import mage.cards.Card; -import mage.constants.EmptyNames; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Assert; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ - - - -public class TriggerTest extends CardTestPlayerBase { - - /** - * Midnight Reaper triggers when dies face down #7063 - * Ixidron has turned Midnight Reaper and Balduvian Bears face down: - * - */ - - // test that cards imprinted using Summoner's Egg are face down - @Test - public void testReaperDoesNotTriggerDiesTriggerFaceDown() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Island", 5); - // As Ixidron enters the battlefield, turn all other nontoken creatures face down. - // Ixidron's power and toughness are each equal to the number of face-down creatures on the battlefield. - addCard(Zone.HAND, playerA, "Ixidron"); // Creature {3}{U}{U} (*/*) - // Whenever a nontoken creature you control dies, Midnight Reaper deals 1 damage to you and you draw a card. - addCard(Zone.BATTLEFIELD, playerA, "Midnight Reaper"); // Creature {2}{B} - - addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); - addCard(Zone.HAND, playerB, "Lightning Bolt"); // Instant 3 damage - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ixidron"); - - waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.toString()); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerB, "Lightning Bolt", 1); - - assertGraveyardCount(playerA, "Midnight Reaper", 1); - assertGraveyardCount(playerA, "Ixidron", 1); - - assertHandCount(playerA, 0); - assertLife(playerA, 20); - - } + +package org.mage.test.cards.facedown; + +import mage.cards.Card; +import mage.constants.EmptyNames; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + + + +public class TriggerTest extends CardTestPlayerBase { + + /** + * Midnight Reaper triggers when dies face down #7063 + * Ixidron has turned Midnight Reaper and Balduvian Bears face down: + * + */ + + // test that cards imprinted using Summoner's Egg are face down + @Test + public void testReaperDoesNotTriggerDiesTriggerFaceDown() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + // As Ixidron enters the battlefield, turn all other nontoken creatures face down. + // Ixidron's power and toughness are each equal to the number of face-down creatures on the battlefield. + addCard(Zone.HAND, playerA, "Ixidron"); // Creature {3}{U}{U} (*/*) + // Whenever a nontoken creature you control dies, Midnight Reaper deals 1 damage to you and you draw a card. + addCard(Zone.BATTLEFIELD, playerA, "Midnight Reaper"); // Creature {2}{B} + + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + addCard(Zone.HAND, playerB, "Lightning Bolt"); // Instant 3 damage + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ixidron"); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.toString()); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerB, "Lightning Bolt", 1); + + assertGraveyardCount(playerA, "Midnight Reaper", 1); + assertGraveyardCount(playerA, "Ixidron", 1); + + assertHandCount(playerA, 0); + assertLife(playerA, 20); + + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/MultipleTimesUsableActivatedManaAbilitiesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/MultipleTimesUsableActivatedManaAbilitiesTest.java index f6fe75c5fe3..83a9f0f0df2 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/MultipleTimesUsableActivatedManaAbilitiesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/MultipleTimesUsableActivatedManaAbilitiesTest.java @@ -1,45 +1,45 @@ -package org.mage.test.cards.mana; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class MultipleTimesUsableActivatedManaAbilitiesTest extends CardTestPlayerBase { - - /** - * Seton, Krosan Protector - only seems to get counted as if it were one - * mana for determining if a spell can be cast, regardless of how many - * druids you have in playF - */ - @Test - public void testCanBeCastWithSetonKrosanProtector() { - // Tap an untapped Druid you control: Add {G}. - addCard(Zone.BATTLEFIELD, playerA, "Seton, Krosan Protector", 1); // Creature {G}{G}{G} - addCard(Zone.BATTLEFIELD, playerA, "Citanul Druid", 3); - - addCard(Zone.HAND, playerA, "Leatherback Baloth", 1); // Creature 4/5 - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Leatherback Baloth"); - - setChoice(playerA, "Citanul Druid"); - setChoice(playerA, "Citanul Druid"); - setChoice(playerA, "Citanul Druid"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - - - setStrictChooseMode(true); - execute(); - - assertAllCommandsUsed(); - - assertTappedCount("Citanul Druid", true, 3); - assertPermanentCount(playerA, "Leatherback Baloth", 1); - } - -} +package org.mage.test.cards.mana; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class MultipleTimesUsableActivatedManaAbilitiesTest extends CardTestPlayerBase { + + /** + * Seton, Krosan Protector - only seems to get counted as if it were one + * mana for determining if a spell can be cast, regardless of how many + * druids you have in playF + */ + @Test + public void testCanBeCastWithSetonKrosanProtector() { + // Tap an untapped Druid you control: Add {G}. + addCard(Zone.BATTLEFIELD, playerA, "Seton, Krosan Protector", 1); // Creature {G}{G}{G} + addCard(Zone.BATTLEFIELD, playerA, "Citanul Druid", 3); + + addCard(Zone.HAND, playerA, "Leatherback Baloth", 1); // Creature 4/5 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Leatherback Baloth"); + + setChoice(playerA, "Citanul Druid"); + setChoice(playerA, "Citanul Druid"); + setChoice(playerA, "Citanul Druid"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + + + setStrictChooseMode(true); + execute(); + + assertAllCommandsUsed(); + + assertTappedCount("Citanul Druid", true, 3); + assertPermanentCount(playerA, "Leatherback Baloth", 1); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/conditional/CrypticTrilobiteTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/conditional/CrypticTrilobiteTest.java index 20c0c9f449c..0cdf671f508 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/conditional/CrypticTrilobiteTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/conditional/CrypticTrilobiteTest.java @@ -1,112 +1,112 @@ -package org.mage.test.cards.mana.conditional; - -import mage.abilities.mana.ManaOptions; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.counters.CounterType; -import org.junit.Assert; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; -import static org.mage.test.utils.ManaOptionsTestUtils.assertManaOptions; - -/** - * - * @author LevelX2 - */ - -public class CrypticTrilobiteTest extends CardTestPlayerBase { - - @Test - public void testAvailableManaCalculation(){ - setStrictChooseMode(true); - - // Cryptic Trilobite enters the battlefield with X +1/+1 counters on it. - // Remove a +1/+1 counter from Cryptic Trilobite: Add {C}{C}. Spend this mana only to activate abilities. - // {1}, {T}: Put a +1/+1 counter on Cryptic Trilobite. - addCard(Zone.HAND, playerA, "Cryptic Trilobite"); // Creature {X}{X} - - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cryptic Trilobite"); - setChoice(playerA, "X=5"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Cryptic Trilobite", 1); - - ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); - Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}[{CrypticTrilobiteManaCondition}]", manaOptions); - } - - @Test - public void testUse(){ - setStrictChooseMode(true); - - // Cryptic Trilobite enters the battlefield with X +1/+1 counters on it. - // Remove a +1/+1 counter from Cryptic Trilobite: Add {C}{C}. Spend this mana only to activate abilities. - // {1}, {T}: Put a +1/+1 counter on Cryptic Trilobite. - addCard(Zone.HAND, playerA, "Cryptic Trilobite"); // Creature {X}{X} - // Flying - // {2}: Deathknell Kami gets +1/+1 until end of turn. Sacrifice it at the beginning of the next end step. - // Soulshift 1 (When this creature dies, you may return target Spirit card with converted mana cost 1 or less from your graveyard to your hand.) - addCard(Zone.BATTLEFIELD, playerA, "Deathknell Kami"); // Creature (0/1) - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); - - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cryptic Trilobite"); - setChoice(playerA, "X=5"); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}:"); - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}:"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Cryptic Trilobite", 1); - assertCounterCount(playerA, "Cryptic Trilobite", CounterType.P1P1, 3); - - assertPowerToughness(playerA, "Deathknell Kami", 2, 3); - - ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); - Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - assertManaOptions("{C}{C}{C}{C}{C}{C}[{CrypticTrilobiteManaCondition}]", manaOptions); - } - - @Test - public void testCantUse(){ - setStrictChooseMode(true); - - // Cryptic Trilobite enters the battlefield with X +1/+1 counters on it. - // Remove a +1/+1 counter from Cryptic Trilobite: Add {C}{C}. Spend this mana only to activate abilities. - // {1}, {T}: Put a +1/+1 counter on Cryptic Trilobite. - addCard(Zone.HAND, playerA, "Cryptic Trilobite"); // Creature {X}{X} - - // {4}{W}: Return another target creature you control to its owner's hand. - addCard(Zone.HAND, playerA, "Aegis Automaton"); // Creature {2} (0/2) - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); - - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cryptic Trilobite"); - setChoice(playerA, "X=5"); - - waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); - - checkPlayableAbility("can't play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Aegis Automaton", false); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Cryptic Trilobite", 1); - assertCounterCount(playerA, "Cryptic Trilobite", CounterType.P1P1, 5); - } - - +package org.mage.test.cards.mana.conditional; + +import mage.abilities.mana.ManaOptions; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.mage.test.utils.ManaOptionsTestUtils.assertManaOptions; + +/** + * + * @author LevelX2 + */ + +public class CrypticTrilobiteTest extends CardTestPlayerBase { + + @Test + public void testAvailableManaCalculation(){ + setStrictChooseMode(true); + + // Cryptic Trilobite enters the battlefield with X +1/+1 counters on it. + // Remove a +1/+1 counter from Cryptic Trilobite: Add {C}{C}. Spend this mana only to activate abilities. + // {1}, {T}: Put a +1/+1 counter on Cryptic Trilobite. + addCard(Zone.HAND, playerA, "Cryptic Trilobite"); // Creature {X}{X} + + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cryptic Trilobite"); + setChoice(playerA, "X=5"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Cryptic Trilobite", 1); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}[{CrypticTrilobiteManaCondition}]", manaOptions); + } + + @Test + public void testUse(){ + setStrictChooseMode(true); + + // Cryptic Trilobite enters the battlefield with X +1/+1 counters on it. + // Remove a +1/+1 counter from Cryptic Trilobite: Add {C}{C}. Spend this mana only to activate abilities. + // {1}, {T}: Put a +1/+1 counter on Cryptic Trilobite. + addCard(Zone.HAND, playerA, "Cryptic Trilobite"); // Creature {X}{X} + // Flying + // {2}: Deathknell Kami gets +1/+1 until end of turn. Sacrifice it at the beginning of the next end step. + // Soulshift 1 (When this creature dies, you may return target Spirit card with converted mana cost 1 or less from your graveyard to your hand.) + addCard(Zone.BATTLEFIELD, playerA, "Deathknell Kami"); // Creature (0/1) + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cryptic Trilobite"); + setChoice(playerA, "X=5"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}:"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}:"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Cryptic Trilobite", 1); + assertCounterCount(playerA, "Cryptic Trilobite", CounterType.P1P1, 3); + + assertPowerToughness(playerA, "Deathknell Kami", 2, 3); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + assertManaOptions("{C}{C}{C}{C}{C}{C}[{CrypticTrilobiteManaCondition}]", manaOptions); + } + + @Test + public void testCantUse(){ + setStrictChooseMode(true); + + // Cryptic Trilobite enters the battlefield with X +1/+1 counters on it. + // Remove a +1/+1 counter from Cryptic Trilobite: Add {C}{C}. Spend this mana only to activate abilities. + // {1}, {T}: Put a +1/+1 counter on Cryptic Trilobite. + addCard(Zone.HAND, playerA, "Cryptic Trilobite"); // Creature {X}{X} + + // {4}{W}: Return another target creature you control to its owner's hand. + addCard(Zone.HAND, playerA, "Aegis Automaton"); // Creature {2} (0/2) + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cryptic Trilobite"); + setChoice(playerA, "X=5"); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + checkPlayableAbility("can't play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Aegis Automaton", false); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Cryptic Trilobite", 1); + assertCounterCount(playerA, "Cryptic Trilobite", CounterType.P1P1, 5); + } + + } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/conditional/TitansNestTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/conditional/TitansNestTest.java index 89cfbed0095..a61f627ded6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/conditional/TitansNestTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/conditional/TitansNestTest.java @@ -1,44 +1,44 @@ - -package org.mage.test.cards.mana.conditional; - -import mage.abilities.mana.ManaOptions; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Assert; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; -import static org.mage.test.utils.ManaOptionsTestUtils.assertManaOptions; - -/** - * - * @author LevelX2 - */ -public class TitansNestTest extends CardTestPlayerBase { - - @Test - public void testTitansNest(){ - setStrictChooseMode(true); - - // At the beginning of your upkeep, look at the top card of your library. You may put that card into your graveyard. - // Exile a card from your graveyard: Add {C}. Spend this mana only to cast a colored spell without {X} in its mana cost. - addCard(Zone.HAND, playerA, "Titans' Nest"); // Enchantment {1}{B}{G}{U} - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); - addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); - addCard(Zone.BATTLEFIELD, playerA, "Island", 1); - - addCard(Zone.GRAVEYARD, playerA, "Grizzly Bears", 2); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Titans' Nest"); - - setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Titans' Nest", 1); - - ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); - Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - assertManaOptions("{C}{C}[{TitansNestManaCondition}]", manaOptions); - } + +package org.mage.test.cards.mana.conditional; + +import mage.abilities.mana.ManaOptions; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.mage.test.utils.ManaOptionsTestUtils.assertManaOptions; + +/** + * + * @author LevelX2 + */ +public class TitansNestTest extends CardTestPlayerBase { + + @Test + public void testTitansNest(){ + setStrictChooseMode(true); + + // At the beginning of your upkeep, look at the top card of your library. You may put that card into your graveyard. + // Exile a card from your graveyard: Add {C}. Spend this mana only to cast a colored spell without {X} in its mana cost. + addCard(Zone.HAND, playerA, "Titans' Nest"); // Enchantment {1}{B}{G}{U} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + + addCard(Zone.GRAVEYARD, playerA, "Grizzly Bears", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Titans' Nest"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Titans' Nest", 1); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + assertManaOptions("{C}{C}[{TitansNestManaCondition}]", manaOptions); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/prevention/PreventAllDamageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/prevention/PreventAllDamageTest.java index 28e27aa624f..c6fe1401b8a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/prevention/PreventAllDamageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/prevention/PreventAllDamageTest.java @@ -1,145 +1,145 @@ -package org.mage.test.cards.prevention; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class PreventAllDamageTest extends CardTestPlayerBase { - - @Test - public void test_SafePassage() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); - // Prevent all damage that would be dealt to you and creatures you control this turn. - addCard(Zone.HAND, playerA, "Safe Passage"); // Instant {2}{W} - - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // (2/2) - - addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 2); // (2/4) - addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); - - addCard(Zone.HAND, playerB, "Lightning Bolt", 2); // Instnat {R} - - castSpell(2, PhaseStep.UPKEEP, playerA, "Safe Passage"); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); - - attack(2, playerB, "Pillarfield Ox"); - attack(2, playerB, "Pillarfield Ox"); - - block(2, playerA, "Silvercoat Lion", "Pillarfield Ox"); - - setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Safe Passage", 1); - assertPermanentCount(playerA, "Silvercoat Lion", 1); - - assertGraveyardCount(playerB, "Lightning Bolt", 2); - - assertLife(playerA, 20); - assertLife(playerB, 20); - - } - - @Test - public void test_EtherealHaze() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); - // Prevent all damage that would be dealt by creatures this turn. - addCard(Zone.HAND, playerA, "Ethereal Haze"); // Instant {W} - - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // (2/2) - - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 2); // (2/4) - addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); - - addCard(Zone.HAND, playerB, "Lightning Bolt", 1); // Instant {R} - - castSpell(2, PhaseStep.UPKEEP, playerA, "Ethereal Haze"); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA); - - attack(2, playerB, "Silvercoat Lion"); - attack(2, playerB, "Silvercoat Lion"); - - block(2, playerA, "Silvercoat Lion", "Silvercoat Lion"); - - setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Ethereal Haze", 1); - assertPermanentCount(playerA, "Silvercoat Lion", 1); - - assertPermanentCount(playerB, "Silvercoat Lion", 2); - assertGraveyardCount(playerB, "Lightning Bolt", 1); - - assertLife(playerA, 17); - assertLife(playerB, 20); - - } - - @Test - public void test_EnergyStorm() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // Cumulative upkeep {1} - // Prevent all damage that would be dealt by instant and sorcery spells. - // Creatures with flying don't untap during their controllers' untap steps. - addCard(Zone.HAND, playerA, "Energy Storm"); // ENCHANTMENT {1}{W} - - // Flying, vigilance - addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 1); // (2/2) - - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); // (2/2) - - addCard(Zone.BATTLEFIELD, playerB, "Mountain", 4); - addCard(Zone.HAND, playerB, "Lightning Bolt", 2); // Instant {R} - // Fire Ambush deals 3 damage to any target. - addCard(Zone.HAND, playerB, "Fire Ambush", 2); // Sorcery {1}{R} - - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Energy Storm"); - - attack(1, playerA, "Abbey Griffin"); - - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Abbey Griffin"); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Fire Ambush", playerA); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Fire Ambush", "Abbey Griffin"); - - attack(2, playerB, "Silvercoat Lion"); - - setChoice(playerA, false); // Pay {1}? Energy Storm - CumulativeUpkeepAbility: Cumulative upkeep {1} - - setStopAt(3, PhaseStep.PRECOMBAT_MAIN); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Energy Storm", 1); - assertPermanentCount(playerA, "Abbey Griffin", 1); - - assertPermanentCount(playerB, "Silvercoat Lion", 1); - assertGraveyardCount(playerB, "Lightning Bolt", 2); - assertGraveyardCount(playerB, "Fire Ambush", 2); - - assertLife(playerA, 18); - assertLife(playerB, 18); - - } -} +package org.mage.test.cards.prevention; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class PreventAllDamageTest extends CardTestPlayerBase { + + @Test + public void test_SafePassage() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + // Prevent all damage that would be dealt to you and creatures you control this turn. + addCard(Zone.HAND, playerA, "Safe Passage"); // Instant {2}{W} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // (2/2) + + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 2); // (2/4) + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); + + addCard(Zone.HAND, playerB, "Lightning Bolt", 2); // Instnat {R} + + castSpell(2, PhaseStep.UPKEEP, playerA, "Safe Passage"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); + + attack(2, playerB, "Pillarfield Ox"); + attack(2, playerB, "Pillarfield Ox"); + + block(2, playerA, "Silvercoat Lion", "Pillarfield Ox"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Safe Passage", 1); + assertPermanentCount(playerA, "Silvercoat Lion", 1); + + assertGraveyardCount(playerB, "Lightning Bolt", 2); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + } + + @Test + public void test_EtherealHaze() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + // Prevent all damage that would be dealt by creatures this turn. + addCard(Zone.HAND, playerA, "Ethereal Haze"); // Instant {W} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // (2/2) + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 2); // (2/4) + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); + + addCard(Zone.HAND, playerB, "Lightning Bolt", 1); // Instant {R} + + castSpell(2, PhaseStep.UPKEEP, playerA, "Ethereal Haze"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + + attack(2, playerB, "Silvercoat Lion"); + attack(2, playerB, "Silvercoat Lion"); + + block(2, playerA, "Silvercoat Lion", "Silvercoat Lion"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Ethereal Haze", 1); + assertPermanentCount(playerA, "Silvercoat Lion", 1); + + assertPermanentCount(playerB, "Silvercoat Lion", 2); + assertGraveyardCount(playerB, "Lightning Bolt", 1); + + assertLife(playerA, 17); + assertLife(playerB, 20); + + } + + @Test + public void test_EnergyStorm() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // Cumulative upkeep {1} + // Prevent all damage that would be dealt by instant and sorcery spells. + // Creatures with flying don't untap during their controllers' untap steps. + addCard(Zone.HAND, playerA, "Energy Storm"); // ENCHANTMENT {1}{W} + + // Flying, vigilance + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 1); // (2/2) + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); // (2/2) + + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 4); + addCard(Zone.HAND, playerB, "Lightning Bolt", 2); // Instant {R} + // Fire Ambush deals 3 damage to any target. + addCard(Zone.HAND, playerB, "Fire Ambush", 2); // Sorcery {1}{R} + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Energy Storm"); + + attack(1, playerA, "Abbey Griffin"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Abbey Griffin"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Fire Ambush", playerA); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Fire Ambush", "Abbey Griffin"); + + attack(2, playerB, "Silvercoat Lion"); + + setChoice(playerA, false); // Pay {1}? Energy Storm - CumulativeUpkeepAbility: Cumulative upkeep {1} + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Energy Storm", 1); + assertPermanentCount(playerA, "Abbey Griffin", 1); + + assertPermanentCount(playerB, "Silvercoat Lion", 1); + assertGraveyardCount(playerB, "Lightning Bolt", 2); + assertGraveyardCount(playerB, "Fire Ambush", 2); + + assertLife(playerA, 18); + assertLife(playerB, 18); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/protection/EightAndAHalfTailsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/protection/EightAndAHalfTailsTest.java index 4fd955ec87f..69f900e8dbc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/protection/EightAndAHalfTailsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/protection/EightAndAHalfTailsTest.java @@ -1,48 +1,48 @@ -package org.mage.test.cards.protection; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.counters.CounterType; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - - -/** - * - * @author LevelX2 - */ -public class EightAndAHalfTailsTest extends CardTestPlayerBase { - - @Test - public void testProtectingPlaneswalker() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - - // Activated abilities of artifacts your opponents control can't be activated. - // +1: Until your next turn, up to one target noncreature artifact becomes an artifact creature with power and toughness equal to its converted mana cost. - // -2: You may choose an artifact card you own from outside the game or in exile, reveal that card, and put it into your hand. - addCard(Zone.BATTLEFIELD, playerA, "Karn, the Great Creator"); // Planeswalker (5) - - // {1}{W}: Target permanent you control gains protection from white until end of turn. - // {1}: Target spell or permanent becomes white until end of turn. - addCard(Zone.BATTLEFIELD, playerA, "Eight-and-a-Half-Tails"); // Creature - - // Flying, double strike - // Whenever a creature you control deals combat damage to a player, you and that player each gain that much life. - // At the beginning of your end step, if you have at least 15 life more than your starting life total, each player Angel of Destiny attacked this turn loses the game. - addCard(Zone.BATTLEFIELD, playerB, "Angel of Destiny"); // Creature - - attack(2, playerB, "Angel of Destiny", "Karn, the Great Creator"); - activateAbility(2, PhaseStep.DECLARE_ATTACKERS, playerA, "{1}{W}: Target permanent you control gains protection from white until end of turn."); - addTarget(playerA, "Karn, the Great Creator"); - - setStopAt(2, PhaseStep.END_COMBAT); - execute(); - - assertPermanentCount(playerA, "Karn, the Great Creator", 1); - assertCounterCount("Karn, the Great Creator", CounterType.LOYALTY, 5); - - } - +package org.mage.test.cards.protection; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +/** + * + * @author LevelX2 + */ +public class EightAndAHalfTailsTest extends CardTestPlayerBase { + + @Test + public void testProtectingPlaneswalker() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + // Activated abilities of artifacts your opponents control can't be activated. + // +1: Until your next turn, up to one target noncreature artifact becomes an artifact creature with power and toughness equal to its converted mana cost. + // -2: You may choose an artifact card you own from outside the game or in exile, reveal that card, and put it into your hand. + addCard(Zone.BATTLEFIELD, playerA, "Karn, the Great Creator"); // Planeswalker (5) + + // {1}{W}: Target permanent you control gains protection from white until end of turn. + // {1}: Target spell or permanent becomes white until end of turn. + addCard(Zone.BATTLEFIELD, playerA, "Eight-and-a-Half-Tails"); // Creature + + // Flying, double strike + // Whenever a creature you control deals combat damage to a player, you and that player each gain that much life. + // At the beginning of your end step, if you have at least 15 life more than your starting life total, each player Angel of Destiny attacked this turn loses the game. + addCard(Zone.BATTLEFIELD, playerB, "Angel of Destiny"); // Creature + + attack(2, playerB, "Angel of Destiny", "Karn, the Great Creator"); + activateAbility(2, PhaseStep.DECLARE_ATTACKERS, playerA, "{1}{W}: Target permanent you control gains protection from white until end of turn."); + addTarget(playerA, "Karn, the Great Creator"); + + setStopAt(2, PhaseStep.END_COMBAT); + execute(); + + assertPermanentCount(playerA, "Karn, the Great Creator", 1); + assertCounterCount("Karn, the Great Creator", CounterType.LOYALTY, 5); + + } + } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/BeltOfGiantStrengthTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/BeltOfGiantStrengthTest.java new file mode 100644 index 00000000000..38262ef5d75 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/BeltOfGiantStrengthTest.java @@ -0,0 +1,60 @@ +package org.mage.test.cards.single.afc; + +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class BeltOfGiantStrengthTest extends CardTestPlayerBase { + + private static final String belt = "Belt of Giant Strength"; + private static final String gigantosauras = "Gigantosaurus"; + + @Test + public void testWithManaAvailable() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 10); + addCard(Zone.BATTLEFIELD, playerA, belt); + addCard(Zone.BATTLEFIELD, playerA, gigantosauras); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", gigantosauras); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertIsAttachedTo(playerA, belt, gigantosauras); + Assert.assertTrue( + "All Forests should be untapped", + currentGame + .getBattlefield() + .getAllActivePermanents() + .stream() + .filter(permanent -> permanent.hasSubtype(SubType.FOREST, currentGame)) + .noneMatch(Permanent::isTapped) + ); + } + + @Ignore // currently failing, need to fix + @Test + public void testWithoutManaAvailable() { + addCard(Zone.BATTLEFIELD, playerA, belt); + addCard(Zone.BATTLEFIELD, playerA, gigantosauras); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", gigantosauras); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertIsAttachedTo(playerA, belt, gigantosauras); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/ConduitOfRuinTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/ConduitOfRuinTest.java index 727dda1f049..83eaf3783df 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/ConduitOfRuinTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/ConduitOfRuinTest.java @@ -1,51 +1,51 @@ -package org.mage.test.cards.single.bfz; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ - -public class ConduitOfRuinTest extends CardTestPlayerBase { - - @Test - public void testCast() { - setStrictChooseMode(true); - - // Emrakul, the Aeons Torn can't be countered. - // When you cast Emrakul, take an extra turn after this one. - // Flying, protection from colored spells, annihilator 6 - // When Emrakul is put into a graveyard from anywhere, its owner shuffles their graveyard into their library. - addCard(Zone.LIBRARY, playerA, "Emrakul, the Aeons Torn"); // Creature {15} 15/15 - - // When you cast Conduit of Ruin, you may search your library for a colorless creature card with converted mana cost 7 or greater, then shuffle your library and put that card on top of it. - // The first creature spell you cast each turn costs {2} less to cast. - addCard(Zone.HAND, playerA, "Conduit of Ruin"); // Creature {6} 5/5 - addCard(Zone.BATTLEFIELD, playerA, "Plains", 13); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Conduit of Ruin"); - setChoice(playerA, true); // When you cast this spell, you may search... - addTarget(playerA, "Emrakul, the Aeons Torn"); - - setStopAt(3, PhaseStep.DRAW); - - execute(); - - assertLibraryCount(playerA, "Emrakul, the Aeons Torn", 0); - assertHandCount(playerA, "Emrakul, the Aeons Torn", 1); - - castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Emrakul, the Aeons Torn"); - - setStopAt(3, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Conduit of Ruin", 1); - assertPermanentCount(playerA, "Emrakul, the Aeons Torn", 1); - } +package org.mage.test.cards.single.bfz; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class ConduitOfRuinTest extends CardTestPlayerBase { + + @Test + public void testCast() { + setStrictChooseMode(true); + + // Emrakul, the Aeons Torn can't be countered. + // When you cast Emrakul, take an extra turn after this one. + // Flying, protection from colored spells, annihilator 6 + // When Emrakul is put into a graveyard from anywhere, its owner shuffles their graveyard into their library. + addCard(Zone.LIBRARY, playerA, "Emrakul, the Aeons Torn"); // Creature {15} 15/15 + + // When you cast Conduit of Ruin, you may search your library for a colorless creature card with converted mana cost 7 or greater, then shuffle your library and put that card on top of it. + // The first creature spell you cast each turn costs {2} less to cast. + addCard(Zone.HAND, playerA, "Conduit of Ruin"); // Creature {6} 5/5 + addCard(Zone.BATTLEFIELD, playerA, "Plains", 13); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Conduit of Ruin"); + setChoice(playerA, true); // When you cast this spell, you may search... + addTarget(playerA, "Emrakul, the Aeons Torn"); + + setStopAt(3, PhaseStep.DRAW); + + execute(); + + assertLibraryCount(playerA, "Emrakul, the Aeons Torn", 0); + assertHandCount(playerA, "Emrakul, the Aeons Torn", 1); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Emrakul, the Aeons Torn"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Conduit of Ruin", 1); + assertPermanentCount(playerA, "Emrakul, the Aeons Torn", 1); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c17/TheUrDragonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c17/TheUrDragonTest.java index a1e26783b40..8d6b7560017 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c17/TheUrDragonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c17/TheUrDragonTest.java @@ -1,49 +1,49 @@ -package org.mage.test.cards.single.c17; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class TheUrDragonTest extends CardTestPlayerBase { - - - @Test - public void test_basic() { - setStrictChooseMode(true); - - addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1); - skipInitShuffling(); - // Eminence — As long as The Ur-Dragon is in the command zone or on the battlefield, other Dragon spells you cast cost 1 less to cast. - // Flying - // Whenever one or more Dragons you control attack, draw that many cards, then you may put a permanent card from your hand onto the battlefield. - addCard(Zone.BATTLEFIELD, playerA, "The Ur-Dragon", 1); // Creature (10/10) - // Flying - // {R}: Dragon Hatchling gets +1/+0 until end of turn. - addCard(Zone.HAND, playerA, "Dragon Hatchling", 2); // Creature Dragon {1}{R} (0/1) - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dragon Hatchling"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dragon Hatchling"); - - attack(3, playerA, "The Ur-Dragon"); - attack(3, playerA, "Dragon Hatchling"); - attack(3, playerA, "Dragon Hatchling"); - setChoice(playerA, true); // Put a permanent card from your hand onto the battlefield? - setChoice(playerA, "Silvercoat Lion"); - - setStopAt(3, PhaseStep.END_COMBAT); - - execute(); - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Dragon Hatchling", 2); - assertPermanentCount(playerA, "Silvercoat Lion", 1 ); - assertHandCount(playerA, 3); - - } +package org.mage.test.cards.single.c17; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class TheUrDragonTest extends CardTestPlayerBase { + + + @Test + public void test_basic() { + setStrictChooseMode(true); + + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1); + skipInitShuffling(); + // Eminence — As long as The Ur-Dragon is in the command zone or on the battlefield, other Dragon spells you cast cost 1 less to cast. + // Flying + // Whenever one or more Dragons you control attack, draw that many cards, then you may put a permanent card from your hand onto the battlefield. + addCard(Zone.BATTLEFIELD, playerA, "The Ur-Dragon", 1); // Creature (10/10) + // Flying + // {R}: Dragon Hatchling gets +1/+0 until end of turn. + addCard(Zone.HAND, playerA, "Dragon Hatchling", 2); // Creature Dragon {1}{R} (0/1) + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dragon Hatchling"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dragon Hatchling"); + + attack(3, playerA, "The Ur-Dragon"); + attack(3, playerA, "Dragon Hatchling"); + attack(3, playerA, "Dragon Hatchling"); + setChoice(playerA, true); // Put a permanent card from your hand onto the battlefield? + setChoice(playerA, "Silvercoat Lion"); + + setStopAt(3, PhaseStep.END_COMBAT); + + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Dragon Hatchling", 2); + assertPermanentCount(playerA, "Silvercoat Lion", 1 ); + assertHandCount(playerA, 3); + + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/AminatousAuguryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/AminatousAuguryTest.java index a8d36115ce2..167c09d5adf 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/AminatousAuguryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/AminatousAuguryTest.java @@ -1,75 +1,75 @@ - -package org.mage.test.cards.single.c18; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class AminatousAuguryTest extends CardTestPlayerBase { - - @Test - public void testCastMultiple() { - setStrictChooseMode(true); - - - addCard(Zone.LIBRARY, playerA, "Pillarfield Ox"); // Creature (2/4) - // As an additional cost to cast this spell, discard a card. - // Draw two cards. - addCard(Zone.LIBRARY, playerA, "Tormenting Voice"); // Sorcery - // {1}: Adarkar Sentinel gets +0/+1 until end of turn. - addCard(Zone.LIBRARY, playerA, "Adarkar Sentinel"); // Artifact Creature {5} (3/3) - addCard(Zone.LIBRARY, playerA, "Storm Crow"); - // You have hexproof. (You can't be the target of spells or abilities your opponents control.) - addCard(Zone.LIBRARY, playerA, "Aegis of the Gods"); // Enchantment Creature {1}{W} (2/1) - addCard(Zone.LIBRARY, playerA, "Lightning Bolt"); // Instant - addCard(Zone.LIBRARY, playerA, "Badlands"); - skipInitShuffling(); - // Exile the top eight cards of your library. You may put a land card from among them onto the battlefield. - // Until end of turn, for each nonland card type, you may cast a card of that type from among the exiled cards - // without paying its mana cost. - addCard(Zone.HAND, playerA, "Aminatou's Augury"); // SORCERY {6}{U}{U} - addCard(Zone.HAND, playerA, "Mountain"); - addCard(Zone.HAND, playerA, "Silvercoat Lion"); - addCard(Zone.BATTLEFIELD, playerA, "Island", 8); - - playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aminatou's Augury"); - setChoice(playerA, true); // Put a land from among the exiled cards into play? - setChoice(playerA, "Badlands"); // Select a land card - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Adarkar Sentinel"); - setChoice(playerA, "Artifact"); // Which card type do you want to consume? - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aegis of the Gods"); - setChoice(playerA, "Enchantment"); // Which card type do you want to consume? - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Storm Crow"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tormenting Voice"); - setChoice(playerA, "Silvercoat Lion"); // Select a card (discard cost) - - checkPlayableAbility("Cannot cast second creature from exile", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Pillarfield Ox", Boolean.FALSE); // Type Creature type is already consumed - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Aminatou's Augury", 1); - assertPermanentCount(playerA, "Mountain", 1); - assertPermanentCount(playerA, "Badlands", 1); - assertPermanentCount(playerA, "Adarkar Sentinel", 1); - assertPermanentCount(playerA, "Aegis of the Gods", 1); - assertPermanentCount(playerA, "Storm Crow", 1); - assertGraveyardCount(playerA, "Lightning Bolt", 1); - - assertLife(playerA, 20); - assertLife(playerB, 17); - - assertHandCount(playerA, 2); - assertGraveyardCount(playerA, "Silvercoat Lion",1); - assertExileCount(playerA, 2); - } - -} + +package org.mage.test.cards.single.c18; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class AminatousAuguryTest extends CardTestPlayerBase { + + @Test + public void testCastMultiple() { + setStrictChooseMode(true); + + + addCard(Zone.LIBRARY, playerA, "Pillarfield Ox"); // Creature (2/4) + // As an additional cost to cast this spell, discard a card. + // Draw two cards. + addCard(Zone.LIBRARY, playerA, "Tormenting Voice"); // Sorcery + // {1}: Adarkar Sentinel gets +0/+1 until end of turn. + addCard(Zone.LIBRARY, playerA, "Adarkar Sentinel"); // Artifact Creature {5} (3/3) + addCard(Zone.LIBRARY, playerA, "Storm Crow"); + // You have hexproof. (You can't be the target of spells or abilities your opponents control.) + addCard(Zone.LIBRARY, playerA, "Aegis of the Gods"); // Enchantment Creature {1}{W} (2/1) + addCard(Zone.LIBRARY, playerA, "Lightning Bolt"); // Instant + addCard(Zone.LIBRARY, playerA, "Badlands"); + skipInitShuffling(); + // Exile the top eight cards of your library. You may put a land card from among them onto the battlefield. + // Until end of turn, for each nonland card type, you may cast a card of that type from among the exiled cards + // without paying its mana cost. + addCard(Zone.HAND, playerA, "Aminatou's Augury"); // SORCERY {6}{U}{U} + addCard(Zone.HAND, playerA, "Mountain"); + addCard(Zone.HAND, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 8); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aminatou's Augury"); + setChoice(playerA, true); // Put a land from among the exiled cards into play? + setChoice(playerA, "Badlands"); // Select a land card + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Adarkar Sentinel"); + setChoice(playerA, "Artifact"); // Which card type do you want to consume? + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aegis of the Gods"); + setChoice(playerA, "Enchantment"); // Which card type do you want to consume? + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Storm Crow"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tormenting Voice"); + setChoice(playerA, "Silvercoat Lion"); // Select a card (discard cost) + + checkPlayableAbility("Cannot cast second creature from exile", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Pillarfield Ox", Boolean.FALSE); // Type Creature type is already consumed + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Aminatou's Augury", 1); + assertPermanentCount(playerA, "Mountain", 1); + assertPermanentCount(playerA, "Badlands", 1); + assertPermanentCount(playerA, "Adarkar Sentinel", 1); + assertPermanentCount(playerA, "Aegis of the Gods", 1); + assertPermanentCount(playerA, "Storm Crow", 1); + assertGraveyardCount(playerA, "Lightning Bolt", 1); + + assertLife(playerA, 20); + assertLife(playerB, 17); + + assertHandCount(playerA, 2); + assertGraveyardCount(playerA, "Silvercoat Lion",1); + assertExileCount(playerA, 2); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c20/PakoArcaneRetrieverTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c20/PakoArcaneRetrieverTest.java index 5f7ab83c382..849e3ede9b5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c20/PakoArcaneRetrieverTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c20/PakoArcaneRetrieverTest.java @@ -1,96 +1,96 @@ - -package org.mage.test.cards.single.c20; - -import mage.cards.Card; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.counters.CounterType; -import org.junit.Assert; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ - -public class PakoArcaneRetrieverTest extends CardTestPlayerBase { - - @Test - public void test_CheckExiled() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); - - addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1); - addCard(Zone.LIBRARY, playerB, "Pillarfield Ox", 1); - - skipInitShuffling(); - // Partner with Pako, Arcane Retriever - // You may play noncreature cards from exile with fetch counters on them if you - // exiled them, and you may spend mana as though it were mana of any color to cast those spells. - addCard(Zone.BATTLEFIELD, playerA, "Haldan, Avid Arcanist", 1); - // Partner with Haldan, Avid Arcanist - // Haste - // Whenever Pako, Arcane Retriever attacks, exile the top card of each player's library and put a fetch counter on each of them. Put a +1/+1 counter on Pako for each noncreature card exiled this way. - addCard(Zone.BATTLEFIELD, playerA, "Pako, Arcane Retriever", 1); // Creature {3}{R}{G} (3/3) - - attack(1, playerA, "Pako, Arcane Retriever"); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertLife(playerA, 20); - assertLife(playerB, 17); - - assertExileCount(playerA, "Silvercoat Lion", 1); - assertExileCount(playerB, "Pillarfield Ox", 1); - - for(Card card :currentGame.getExile().getAllCards(currentGame)) { - Assert.assertTrue(card.getName() + " has a fetch counter",card.getCounters(currentGame).getCount(CounterType.FETCH) == 1); - } - - } - - @Test - public void test_CastExiled() { - // setStrictChooseMode(true); - addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); - addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); - - addCard(Zone.LIBRARY, playerA, "Lightning Bolt", 1); // Instant 3 damge - // Create a 3/3 green Centaur creature token. - addCard(Zone.LIBRARY, playerB, "Call of the Conclave", 1); // Sorcery {W}{G} - - skipInitShuffling(); - // Partner with Pako, Arcane Retriever - // You may play noncreature cards from exile with fetch counters on them if you - // exiled them, and you may spend mana as though it were mana of any color to cast those spells. - addCard(Zone.BATTLEFIELD, playerA, "Haldan, Avid Arcanist", 1); - // Partner with Haldan, Avid Arcanist - // Haste - // Whenever Pako, Arcane Retriever attacks, exile the top card of each player's library and put a fetch counter on each of them. Put a +1/+1 counter on Pako for each noncreature card exiled this way. - addCard(Zone.BATTLEFIELD, playerA, "Pako, Arcane Retriever", 1); // Creature {3}{R}{G} (3/3) - - attack(1, playerA, "Pako, Arcane Retriever"); - - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Call of the Conclave"); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertLife(playerA, 20); - assertLife(playerB, 12); // 3+2 (attack) + 3 Lighning Bolt - - assertGraveyardCount(playerA, "Lightning Bolt", 1); - assertGraveyardCount(playerB, "Call of the Conclave", 1); - - - } -} + +package org.mage.test.cards.single.c20; + +import mage.cards.Card; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class PakoArcaneRetrieverTest extends CardTestPlayerBase { + + @Test + public void test_CheckExiled() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1); + addCard(Zone.LIBRARY, playerB, "Pillarfield Ox", 1); + + skipInitShuffling(); + // Partner with Pako, Arcane Retriever + // You may play noncreature cards from exile with fetch counters on them if you + // exiled them, and you may spend mana as though it were mana of any color to cast those spells. + addCard(Zone.BATTLEFIELD, playerA, "Haldan, Avid Arcanist", 1); + // Partner with Haldan, Avid Arcanist + // Haste + // Whenever Pako, Arcane Retriever attacks, exile the top card of each player's library and put a fetch counter on each of them. Put a +1/+1 counter on Pako for each noncreature card exiled this way. + addCard(Zone.BATTLEFIELD, playerA, "Pako, Arcane Retriever", 1); // Creature {3}{R}{G} (3/3) + + attack(1, playerA, "Pako, Arcane Retriever"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 17); + + assertExileCount(playerA, "Silvercoat Lion", 1); + assertExileCount(playerB, "Pillarfield Ox", 1); + + for(Card card :currentGame.getExile().getAllCards(currentGame)) { + Assert.assertTrue(card.getName() + " has a fetch counter",card.getCounters(currentGame).getCount(CounterType.FETCH) == 1); + } + + } + + @Test + public void test_CastExiled() { + // setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + + addCard(Zone.LIBRARY, playerA, "Lightning Bolt", 1); // Instant 3 damge + // Create a 3/3 green Centaur creature token. + addCard(Zone.LIBRARY, playerB, "Call of the Conclave", 1); // Sorcery {W}{G} + + skipInitShuffling(); + // Partner with Pako, Arcane Retriever + // You may play noncreature cards from exile with fetch counters on them if you + // exiled them, and you may spend mana as though it were mana of any color to cast those spells. + addCard(Zone.BATTLEFIELD, playerA, "Haldan, Avid Arcanist", 1); + // Partner with Haldan, Avid Arcanist + // Haste + // Whenever Pako, Arcane Retriever attacks, exile the top card of each player's library and put a fetch counter on each of them. Put a +1/+1 counter on Pako for each noncreature card exiled this way. + addCard(Zone.BATTLEFIELD, playerA, "Pako, Arcane Retriever", 1); // Creature {3}{R}{G} (3/3) + + attack(1, playerA, "Pako, Arcane Retriever"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Call of the Conclave"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 12); // 3+2 (attack) + 3 Lighning Bolt + + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertGraveyardCount(playerB, "Call of the Conclave", 1); + + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dst/DemonsHornTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dst/DemonsHornTest.java index 6f91f4cbded..c8c47a1beed 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dst/DemonsHornTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dst/DemonsHornTest.java @@ -1,72 +1,72 @@ -package org.mage.test.cards.single.dst; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class DemonsHornTest extends CardTestPlayerBase { - - - @Test - public void testWithBlackSpell() { - setStrictChooseMode(true); - - // When Abyssal Gatekeeper dies, each player sacrifices a creature. - addCard(Zone.HAND, playerA, "Abyssal Gatekeeper", 1); // Creature {2}{B} 1/1 - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); - - // Whenever a player casts a black spell, you may gain 1 life. - addCard(Zone.BATTLEFIELD, playerB, "Demon's Horn", 1); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Abyssal Gatekeeper"); - setChoice(playerB, true); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Abyssal Gatekeeper", 1); - - assertLife(playerA, 20); - assertLife(playerB, 21); - } - - /** - * https://github.com/magefree/mage/issues/6890 - * - * Color == Color Identity #6890 - * - * Alesha, Who Smiles at Death triggers Demon's Horn - */ - @Test - public void testSpellWithBlackManaOnlyInTriggeredOptionalCost() { - setStrictChooseMode(true); - - // First strike - // Whenever Alesha, Who Smiles at Death attacks, you may pay {W/B}{W/B}. If you do, return target creature card with power 2 or less from your graveyard to the battlefield tapped and attacking. - addCard(Zone.HAND, playerA, "Alesha, Who Smiles at Death", 1); // Creature {2}{R} 3/2 - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - - // Whenever a player casts a black spell, you may gain 1 life. - addCard(Zone.BATTLEFIELD, playerB, "Demon's Horn", 1); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alesha, Who Smiles at Death"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Alesha, Who Smiles at Death", 1); - - assertLife(playerA, 20); - assertLife(playerB, 20); - } - -} +package org.mage.test.cards.single.dst; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class DemonsHornTest extends CardTestPlayerBase { + + + @Test + public void testWithBlackSpell() { + setStrictChooseMode(true); + + // When Abyssal Gatekeeper dies, each player sacrifices a creature. + addCard(Zone.HAND, playerA, "Abyssal Gatekeeper", 1); // Creature {2}{B} 1/1 + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + // Whenever a player casts a black spell, you may gain 1 life. + addCard(Zone.BATTLEFIELD, playerB, "Demon's Horn", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Abyssal Gatekeeper"); + setChoice(playerB, true); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Abyssal Gatekeeper", 1); + + assertLife(playerA, 20); + assertLife(playerB, 21); + } + + /** + * https://github.com/magefree/mage/issues/6890 + * + * Color == Color Identity #6890 + * + * Alesha, Who Smiles at Death triggers Demon's Horn + */ + @Test + public void testSpellWithBlackManaOnlyInTriggeredOptionalCost() { + setStrictChooseMode(true); + + // First strike + // Whenever Alesha, Who Smiles at Death attacks, you may pay {W/B}{W/B}. If you do, return target creature card with power 2 or less from your graveyard to the battlefield tapped and attacking. + addCard(Zone.HAND, playerA, "Alesha, Who Smiles at Death", 1); // Creature {2}{R} 3/2 + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + // Whenever a player casts a black spell, you may gain 1 life. + addCard(Zone.BATTLEFIELD, playerB, "Demon's Horn", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alesha, Who Smiles at Death"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Alesha, Who Smiles at Death", 1); + + assertLife(playerA, 20); + assertLife(playerB, 20); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/BarteredCowTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/BarteredCowTest.java index 4e951974c1e..7be461ae765 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/BarteredCowTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/BarteredCowTest.java @@ -1,88 +1,88 @@ -package org.mage.test.cards.single.eld; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class BarteredCowTest extends CardTestPlayerBase { - - @Test - public void testDiesTrigger() { - setStrictChooseMode(true); - - // When Bartered Cow dies or when you discard it, create a Food token. - addCard(Zone.BATTLEFIELD, playerA, "Bartered Cow"); // Creature {3}{W} 3/3 - - addCard(Zone.HAND, playerB, "Lightning Bolt", 1); - addCard(Zone.BATTLEFIELD, playerB, "Mountain"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Bartered Cow"); - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerB, "Lightning Bolt", 1); - assertGraveyardCount(playerA, "Bartered Cow", 1); - assertPermanentCount(playerA, "Food", 1); - } - - @Test - public void testDiscardTrigger() { - setStrictChooseMode(true); - - // When Bartered Cow dies or when you discard it, create a Food token. - addCard(Zone.HAND, playerA, "Bartered Cow"); // Creature {3}{W} 3/3 - // Choose one — - // • Target player discards a card. - // • Target creature gets +2/-1 until end of turn. - // • Target creature gains swampwalk until end of turn. (It can't be blocked as long as defending player controls a Swamp.) - addCard(Zone.HAND, playerB, "Funeral Charm", 1); // Instant {B} - addCard(Zone.BATTLEFIELD, playerB, "Swamp"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Funeral Charm"); - setModeChoice(playerB, "1"); - addTarget(playerB, playerA); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerB, "Funeral Charm", 1); - assertGraveyardCount(playerA, "Bartered Cow", 1); - assertPermanentCount(playerA, "Food", 1); - } - - @Test - public void testDiscardTriggerWithTorturedExistence() { - setStrictChooseMode(true); - - // When Bartered Cow dies or when you discard it, create a Food token. - addCard(Zone.HAND, playerA, "Bartered Cow"); // Creature {3}{W} 3/3 - - // {B}, Discard a creature card: Return target creature card from your graveyard to your hand. - addCard(Zone.BATTLEFIELD, playerA, "Tortured Existence", 1); // Instant {B} - addCard(Zone.BATTLEFIELD, playerA, "Swamp"); - addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion"); - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B}, Discard a creature card"); - setChoice(playerA, "Bartered Cow"); - addTarget(playerA, "Silvercoat Lion"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Bartered Cow", 1); - assertHandCount(playerA, "Silvercoat Lion", 1); - assertPermanentCount(playerA, "Food", 1); - } - -} +package org.mage.test.cards.single.eld; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class BarteredCowTest extends CardTestPlayerBase { + + @Test + public void testDiesTrigger() { + setStrictChooseMode(true); + + // When Bartered Cow dies or when you discard it, create a Food token. + addCard(Zone.BATTLEFIELD, playerA, "Bartered Cow"); // Creature {3}{W} 3/3 + + addCard(Zone.HAND, playerB, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Bartered Cow"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerB, "Lightning Bolt", 1); + assertGraveyardCount(playerA, "Bartered Cow", 1); + assertPermanentCount(playerA, "Food", 1); + } + + @Test + public void testDiscardTrigger() { + setStrictChooseMode(true); + + // When Bartered Cow dies or when you discard it, create a Food token. + addCard(Zone.HAND, playerA, "Bartered Cow"); // Creature {3}{W} 3/3 + // Choose one — + // • Target player discards a card. + // • Target creature gets +2/-1 until end of turn. + // • Target creature gains swampwalk until end of turn. (It can't be blocked as long as defending player controls a Swamp.) + addCard(Zone.HAND, playerB, "Funeral Charm", 1); // Instant {B} + addCard(Zone.BATTLEFIELD, playerB, "Swamp"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Funeral Charm"); + setModeChoice(playerB, "1"); + addTarget(playerB, playerA); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerB, "Funeral Charm", 1); + assertGraveyardCount(playerA, "Bartered Cow", 1); + assertPermanentCount(playerA, "Food", 1); + } + + @Test + public void testDiscardTriggerWithTorturedExistence() { + setStrictChooseMode(true); + + // When Bartered Cow dies or when you discard it, create a Food token. + addCard(Zone.HAND, playerA, "Bartered Cow"); // Creature {3}{W} 3/3 + + // {B}, Discard a creature card: Return target creature card from your graveyard to your hand. + addCard(Zone.BATTLEFIELD, playerA, "Tortured Existence", 1); // Instant {B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B}, Discard a creature card"); + setChoice(playerA, "Bartered Cow"); + addTarget(playerA, "Silvercoat Lion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Bartered Cow", 1); + assertHandCount(playerA, "Silvercoat Lion", 1); + assertPermanentCount(playerA, "Food", 1); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/OnceUponATimeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/OnceUponATimeTest.java index ec736c19a57..2982fe655c5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/OnceUponATimeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/OnceUponATimeTest.java @@ -1,89 +1,89 @@ -package org.mage.test.cards.single.eld; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ - -public class OnceUponATimeTest extends CardTestPlayerBase { - - @Test - public void test_castRegularly() { - setStrictChooseMode(true); - - addCard(Zone.LIBRARY, playerA, "Silvercoat Lion"); - addCard(Zone.LIBRARY, playerA, "Plains", 4); - skipInitShuffling(); - - // If this spell is the first spell you've cast this game, you may cast it without paying its mana cost. - // Look at the top five cards of your library. - // You may reveal a creature or land card from among them and put it into your hand. - // Put the rest on the bottom of your library in a random order. - addCard(Zone.HAND, playerA, "Once Upon a Time"); // Instant {1}{G} - addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); - addCard(Zone.HAND, playerA, "Forest", 1); - - playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Forest"); - - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Once Upon a Time"); - - setChoice(playerA, false); // Cast without paying its mana cost? - - setChoice(playerA, true); // Do you wish to reveal a creature or land card and put into your hand? - setChoice(playerA, "Silvercoat Lion"); - - setStopAt(2, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Once Upon a Time", 1); - assertTappedCount("Forest", true, 2); - assertHandCount(playerA, "Silvercoat Lion", 1); - } - - @Test - public void test_castForFree() { - setStrictChooseMode(true); - - addCard(Zone.LIBRARY, playerA, "Silvercoat Lion"); - addCard(Zone.LIBRARY, playerA, "Plains", 4); - - addCard(Zone.LIBRARY, playerB, "Silvercoat Lion", 5); - - skipInitShuffling(); - - // If this spell is the first spell you've cast this game, you may cast it without paying its mana cost. - // Look at the top five cards of your library. - // You may reveal a creature or land card from among them and put it into your hand. - // Put the rest on the bottom of your library in a random order. - addCard(Zone.HAND, playerA, "Once Upon a Time"); // Instant {1}{G} - addCard(Zone.HAND, playerB, "Once Upon a Time"); // Instant {1}{G} - - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Once Upon a Time"); - setChoice(playerA, true); // Cast without paying its mana cost? - setChoice(playerA, true); // Do you wish to reveal a creature or land card and put into your hand? - setChoice(playerA, "Silvercoat Lion"); - - castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Once Upon a Time"); - setChoice(playerB, true); // Cast without paying its mana cost? - setChoice(playerB, true); // Do you wish to reveal a creature or land card and put into your hand? - setChoice(playerB, "Silvercoat Lion"); - - setStopAt(2, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Once Upon a Time", 1); - assertGraveyardCount(playerB, "Once Upon a Time", 1); - - assertHandCount(playerA, "Silvercoat Lion", 1); - assertHandCount(playerB, "Silvercoat Lion", 2); - } +package org.mage.test.cards.single.eld; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class OnceUponATimeTest extends CardTestPlayerBase { + + @Test + public void test_castRegularly() { + setStrictChooseMode(true); + + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion"); + addCard(Zone.LIBRARY, playerA, "Plains", 4); + skipInitShuffling(); + + // If this spell is the first spell you've cast this game, you may cast it without paying its mana cost. + // Look at the top five cards of your library. + // You may reveal a creature or land card from among them and put it into your hand. + // Put the rest on the bottom of your library in a random order. + addCard(Zone.HAND, playerA, "Once Upon a Time"); // Instant {1}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.HAND, playerA, "Forest", 1); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Forest"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Once Upon a Time"); + + setChoice(playerA, false); // Cast without paying its mana cost? + + setChoice(playerA, true); // Do you wish to reveal a creature or land card and put into your hand? + setChoice(playerA, "Silvercoat Lion"); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Once Upon a Time", 1); + assertTappedCount("Forest", true, 2); + assertHandCount(playerA, "Silvercoat Lion", 1); + } + + @Test + public void test_castForFree() { + setStrictChooseMode(true); + + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion"); + addCard(Zone.LIBRARY, playerA, "Plains", 4); + + addCard(Zone.LIBRARY, playerB, "Silvercoat Lion", 5); + + skipInitShuffling(); + + // If this spell is the first spell you've cast this game, you may cast it without paying its mana cost. + // Look at the top five cards of your library. + // You may reveal a creature or land card from among them and put it into your hand. + // Put the rest on the bottom of your library in a random order. + addCard(Zone.HAND, playerA, "Once Upon a Time"); // Instant {1}{G} + addCard(Zone.HAND, playerB, "Once Upon a Time"); // Instant {1}{G} + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Once Upon a Time"); + setChoice(playerA, true); // Cast without paying its mana cost? + setChoice(playerA, true); // Do you wish to reveal a creature or land card and put into your hand? + setChoice(playerA, "Silvercoat Lion"); + + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Once Upon a Time"); + setChoice(playerB, true); // Cast without paying its mana cost? + setChoice(playerB, true); // Do you wish to reveal a creature or land card and put into your hand? + setChoice(playerB, "Silvercoat Lion"); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Once Upon a Time", 1); + assertGraveyardCount(playerB, "Once Upon a Time", 1); + + assertHandCount(playerA, "Silvercoat Lion", 1); + assertHandCount(playerB, "Silvercoat Lion", 2); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/SyrGwynHeroOfAshvaleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/SyrGwynHeroOfAshvaleTest.java index d00136c897a..eab21ba8758 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/SyrGwynHeroOfAshvaleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eld/SyrGwynHeroOfAshvaleTest.java @@ -1,57 +1,57 @@ -package org.mage.test.cards.single.eld; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ - -public class SyrGwynHeroOfAshvaleTest extends CardTestPlayerBase { - - @Test - public void equipKnightTest() { - // Equipped creature gets +2/+2 and has trample and lifelink. - addCard(Zone.BATTLEFIELD, playerA, "Behemoth Sledge"); // Artifact - Equipment {1}{G}{W} - - // Vigilance, menace - // Whenever an equipped creature you control attacks, you draw a card and you lose 1 life. - // Equipment you control have equip Knight {0}. - addCard(Zone.BATTLEFIELD, playerA, "Syr Gwyn, Hero of Ashvale"); // Legendary Creature {3}{R}{W}{B} 5/5 Human Knight - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip Knight", "Syr Gwyn, Hero of Ashvale"); - - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertPowerToughness(playerA, "Syr Gwyn, Hero of Ashvale", 7, 7); - - } - - @Test - public void equipKnightTestInstantSpeed() { - // Equipped creature gets +2/+2 and has trample and lifelink. - addCard(Zone.BATTLEFIELD, playerA, "Behemoth Sledge"); // Artifact - Equipment {1}{G}{W} - - // You may activate equip abilities any time you could cast an instant. - addCard(Zone.BATTLEFIELD, playerA, "Leonin Shikari", 2); // Creature 2/2 - - // Vigilance, menace - // Whenever an equipped creature you control attacks, you draw a card and you lose 1 life. - // Equipment you control have equip Knight {0}. - addCard(Zone.BATTLEFIELD, playerA, "Syr Gwyn, Hero of Ashvale"); // Legendary Creature {3}{R}{W}{B} 5/5 Human Knight - - activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "Equip Knight", "Syr Gwyn, Hero of Ashvale"); - - - setStopAt(1, PhaseStep.END_COMBAT); - execute(); - - assertPowerToughness(playerA, "Syr Gwyn, Hero of Ashvale", 7, 7); - - } -} +package org.mage.test.cards.single.eld; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class SyrGwynHeroOfAshvaleTest extends CardTestPlayerBase { + + @Test + public void equipKnightTest() { + // Equipped creature gets +2/+2 and has trample and lifelink. + addCard(Zone.BATTLEFIELD, playerA, "Behemoth Sledge"); // Artifact - Equipment {1}{G}{W} + + // Vigilance, menace + // Whenever an equipped creature you control attacks, you draw a card and you lose 1 life. + // Equipment you control have equip Knight {0}. + addCard(Zone.BATTLEFIELD, playerA, "Syr Gwyn, Hero of Ashvale"); // Legendary Creature {3}{R}{W}{B} 5/5 Human Knight + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip Knight", "Syr Gwyn, Hero of Ashvale"); + + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Syr Gwyn, Hero of Ashvale", 7, 7); + + } + + @Test + public void equipKnightTestInstantSpeed() { + // Equipped creature gets +2/+2 and has trample and lifelink. + addCard(Zone.BATTLEFIELD, playerA, "Behemoth Sledge"); // Artifact - Equipment {1}{G}{W} + + // You may activate equip abilities any time you could cast an instant. + addCard(Zone.BATTLEFIELD, playerA, "Leonin Shikari", 2); // Creature 2/2 + + // Vigilance, menace + // Whenever an equipped creature you control attacks, you draw a card and you lose 1 life. + // Equipment you control have equip Knight {0}. + addCard(Zone.BATTLEFIELD, playerA, "Syr Gwyn, Hero of Ashvale"); // Legendary Creature {3}{R}{W}{B} 5/5 Human Knight + + activateAbility(1, PhaseStep.DECLARE_ATTACKERS, playerA, "Equip Knight", "Syr Gwyn, Hero of Ashvale"); + + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertPowerToughness(playerA, "Syr Gwyn, Hero of Ashvale", 7, 7); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/PeltCollectorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/PeltCollectorTest.java index 1833604d257..ea87437b731 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/PeltCollectorTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/PeltCollectorTest.java @@ -1,245 +1,245 @@ -package org.mage.test.cards.single.grn; - -import mage.abilities.keyword.TrampleAbility; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.counters.CounterType; -import mage.filter.Filter; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * @author LevelX2, TheElk801 - */ -public class PeltCollectorTest extends CardTestPlayerBase { - - private static final String collector = "Pelt Collector"; - private static final String lion = "Silvercoat Lion"; - private static final String trostani = "Trostani Discordant"; - private static final String bear = "Grizzly Bears"; - private static final String murder = "Murder"; - private static final String courser = "Centaur Courser"; - private static final String growth = "Giant Growth"; - private static final String karstoderm = "Karstoderm"; - - @Test - public void test_Simple() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // Whenever another creature you control enters the battlefield or dies, if that creature's power is greater than Pelt Collector's, put a +1/+1 counter on Pelt Collector. - // As long as Pelt Collector has three or more +1/+1 counters on it, it has trample. - addCard(Zone.HAND, playerA, collector, 1); // Creature {G} - addCard(Zone.HAND, playerA, lion, 1); // Creature {1}{W} - - addCard(Zone.BATTLEFIELD, playerB, collector, 1);// Creature {G} - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, collector); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lion); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerB, collector, 1, 1); - - assertPowerToughness(playerA, lion, 2, 2); - assertPowerToughness(playerA, collector, 2, 2); - assertAbility(playerA, collector, TrampleAbility.getInstance(), false); - assertAbility(playerB, collector, TrampleAbility.getInstance(), false); - } - - /** - * To determine if Pelt Collector’s first ability triggers when a creature - * enters the battlefield, use the creature’s power after applying any - * static abilities (such as that of Trostani Discordant) that modify its - * power. - */ - @Test - public void test_TrostaniDiscordant() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); - addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); - // Whenever another creature you control enters the battlefield or dies, if that creature's power is greater than Pelt Collector's, put a +1/+1 counter on Pelt Collector. - // As long as Pelt Collector has three or more +1/+1 counters on it, it has trample. - addCard(Zone.HAND, playerA, collector, 1); // Creature {G} - addCard(Zone.HAND, playerA, lion, 1); // Creature {1}{W} - // Other creatures you control get +1/+1. - // When Trostani Discordant enters the battlefield, create two 1/1 white Soldier creature tokens with lifelink. - // At the beginning of your end step, each player gains control of all creatures they own. - addCard(Zone.HAND, playerA, trostani, 1); // Creature {3}{G}{W} /1/4) - - addCard(Zone.BATTLEFIELD, playerB, collector, 1);// Creature {G} - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, trostani); - - castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, collector); - castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, lion); - - setStopAt(3, PhaseStep.BEGIN_COMBAT); - - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerB, collector, 1, 1); - - assertPowerToughness(playerA, "Soldier", 2, 2, Filter.ComparisonScope.All); - - assertPowerToughness(playerA, lion, 3, 3); - assertPowerToughness(playerA, collector, 3, 3); - assertAbility(playerA, collector, TrampleAbility.getInstance(), false); - assertAbility(playerB, collector, TrampleAbility.getInstance(), false); - - } - - - @Test - public void testEntersTrigger() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, collector); - addCard(Zone.HAND, playerA, bear); - addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerA, collector, 2, 2); - assertCounterCount(collector, CounterType.P1P1, 1); - } - - @Test - public void testEntersTrigger2() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, collector); - addCard(Zone.HAND, playerA, karstoderm); - addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, karstoderm); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerA, collector, 2, 2); - assertCounterCount(collector, CounterType.P1P1, 1); - } - - @Test - public void testDiesTrigger() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, collector); - addCard(Zone.HAND, playerA, bear); - addCard(Zone.HAND, playerA, murder); - addCard(Zone.BATTLEFIELD, playerA, "Bayou", 5); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, bear); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerA, collector, 2, 2); - assertCounterCount(collector, CounterType.P1P1, 1); - } - - @Test - public void testDiesTrigger2() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, collector); - addCard(Zone.HAND, playerA, courser); - addCard(Zone.HAND, playerA, murder); - addCard(Zone.BATTLEFIELD, playerA, "Bayou", 6); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, courser); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, courser); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerA, collector, 3, 3); - assertCounterCount(collector, CounterType.P1P1, 2); - } - - @Test - public void testDiesTrigger3() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, collector); - addCard(Zone.HAND, playerA, karstoderm); - addCard(Zone.HAND, playerA, murder); - addCard(Zone.BATTLEFIELD, playerA, "Bayou", 7); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, karstoderm); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, karstoderm); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerA, collector, 3, 3); - assertCounterCount(collector, CounterType.P1P1, 2); - } - - @Test - public void testInterveningIf() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, collector); - addCard(Zone.HAND, playerA, bear); - addCard(Zone.HAND, playerA, growth); - addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear); - waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, growth, collector); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerA, collector, 4, 4); - assertCounterCount(collector, CounterType.P1P1, 0); - } - - @Test - public void testInterveningIf2() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, collector); - addCard(Zone.HAND, playerA, bear); - addCard(Zone.HAND, playerA, "Scar"); - addCard(Zone.BATTLEFIELD, playerA, "Bayou", 3); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear); - waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scar", bear); - - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerA, collector, 1, 1); - assertCounterCount(collector, CounterType.P1P1, 0); - } -} +package org.mage.test.cards.single.grn; + +import mage.abilities.keyword.TrampleAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.Filter; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author LevelX2, TheElk801 + */ +public class PeltCollectorTest extends CardTestPlayerBase { + + private static final String collector = "Pelt Collector"; + private static final String lion = "Silvercoat Lion"; + private static final String trostani = "Trostani Discordant"; + private static final String bear = "Grizzly Bears"; + private static final String murder = "Murder"; + private static final String courser = "Centaur Courser"; + private static final String growth = "Giant Growth"; + private static final String karstoderm = "Karstoderm"; + + @Test + public void test_Simple() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // Whenever another creature you control enters the battlefield or dies, if that creature's power is greater than Pelt Collector's, put a +1/+1 counter on Pelt Collector. + // As long as Pelt Collector has three or more +1/+1 counters on it, it has trample. + addCard(Zone.HAND, playerA, collector, 1); // Creature {G} + addCard(Zone.HAND, playerA, lion, 1); // Creature {1}{W} + + addCard(Zone.BATTLEFIELD, playerB, collector, 1);// Creature {G} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, collector); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lion); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerB, collector, 1, 1); + + assertPowerToughness(playerA, lion, 2, 2); + assertPowerToughness(playerA, collector, 2, 2); + assertAbility(playerA, collector, TrampleAbility.getInstance(), false); + assertAbility(playerB, collector, TrampleAbility.getInstance(), false); + } + + /** + * To determine if Pelt Collector’s first ability triggers when a creature + * enters the battlefield, use the creature’s power after applying any + * static abilities (such as that of Trostani Discordant) that modify its + * power. + */ + @Test + public void test_TrostaniDiscordant() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + // Whenever another creature you control enters the battlefield or dies, if that creature's power is greater than Pelt Collector's, put a +1/+1 counter on Pelt Collector. + // As long as Pelt Collector has three or more +1/+1 counters on it, it has trample. + addCard(Zone.HAND, playerA, collector, 1); // Creature {G} + addCard(Zone.HAND, playerA, lion, 1); // Creature {1}{W} + // Other creatures you control get +1/+1. + // When Trostani Discordant enters the battlefield, create two 1/1 white Soldier creature tokens with lifelink. + // At the beginning of your end step, each player gains control of all creatures they own. + addCard(Zone.HAND, playerA, trostani, 1); // Creature {3}{G}{W} /1/4) + + addCard(Zone.BATTLEFIELD, playerB, collector, 1);// Creature {G} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, trostani); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, collector); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, lion); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerB, collector, 1, 1); + + assertPowerToughness(playerA, "Soldier", 2, 2, Filter.ComparisonScope.All); + + assertPowerToughness(playerA, lion, 3, 3); + assertPowerToughness(playerA, collector, 3, 3); + assertAbility(playerA, collector, TrampleAbility.getInstance(), false); + assertAbility(playerB, collector, TrampleAbility.getInstance(), false); + + } + + + @Test + public void testEntersTrigger() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, collector); + addCard(Zone.HAND, playerA, bear); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerA, collector, 2, 2); + assertCounterCount(collector, CounterType.P1P1, 1); + } + + @Test + public void testEntersTrigger2() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, collector); + addCard(Zone.HAND, playerA, karstoderm); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, karstoderm); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerA, collector, 2, 2); + assertCounterCount(collector, CounterType.P1P1, 1); + } + + @Test + public void testDiesTrigger() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, collector); + addCard(Zone.HAND, playerA, bear); + addCard(Zone.HAND, playerA, murder); + addCard(Zone.BATTLEFIELD, playerA, "Bayou", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, bear); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerA, collector, 2, 2); + assertCounterCount(collector, CounterType.P1P1, 1); + } + + @Test + public void testDiesTrigger2() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, collector); + addCard(Zone.HAND, playerA, courser); + addCard(Zone.HAND, playerA, murder); + addCard(Zone.BATTLEFIELD, playerA, "Bayou", 6); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, courser); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, courser); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerA, collector, 3, 3); + assertCounterCount(collector, CounterType.P1P1, 2); + } + + @Test + public void testDiesTrigger3() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, collector); + addCard(Zone.HAND, playerA, karstoderm); + addCard(Zone.HAND, playerA, murder); + addCard(Zone.BATTLEFIELD, playerA, "Bayou", 7); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, karstoderm); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, murder, karstoderm); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerA, collector, 3, 3); + assertCounterCount(collector, CounterType.P1P1, 2); + } + + @Test + public void testInterveningIf() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, collector); + addCard(Zone.HAND, playerA, bear); + addCard(Zone.HAND, playerA, growth); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, growth, collector); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerA, collector, 4, 4); + assertCounterCount(collector, CounterType.P1P1, 0); + } + + @Test + public void testInterveningIf2() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, collector); + addCard(Zone.HAND, playerA, bear); + addCard(Zone.HAND, playerA, "Scar"); + addCard(Zone.BATTLEFIELD, playerA, "Bayou", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scar", bear); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerA, collector, 1, 1); + assertCounterCount(collector, CounterType.P1P1, 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/TormentOfHailfireTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/TormentOfHailfireTest.java index 4d2d4d2aff6..7dc01ca0e75 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/TormentOfHailfireTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/TormentOfHailfireTest.java @@ -1,88 +1,88 @@ -package org.mage.test.cards.single.hou; - -import java.io.FileNotFoundException; - -import mage.constants.MultiplayerAttackOption; -import mage.constants.PhaseStep; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; -import mage.game.FreeForAll; -import mage.game.Game; -import mage.game.GameException; -import mage.game.mulligan.MulliganType; -import org.junit.Assert; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestMultiPlayerBase; - -/** - * - * @author LevelX2 - */ -public class TormentOfHailfireTest extends CardTestMultiPlayerBase { - - @Override - protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - // Start Life = 2 - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 20); - // Player order: A -> D -> C -> B - playerA = createPlayer(game, playerA, "PlayerA"); - playerB = createPlayer(game, playerB, "PlayerB"); - playerC = createPlayer(game, playerC, "PlayerC"); - playerD = createPlayer(game, playerD, "PlayerD"); - return game; - } - - @Test - public void test_Normal() { - setStrictChooseMode(true); - - // Repeat the following process X times. Each opponent loses 3 life unless they sacrifice a nonland permanent or discards a card. - addCard(Zone.HAND, playerA, "Torment of Hailfire", 1); // Sorcery {X}{B}{B} - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 12); - - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 2); - addCard(Zone.HAND, playerB, "Plains", 1); - - addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion", 3); - - addCard(Zone.BATTLEFIELD, playerD, "Silvercoat Lion", 3); - addCard(Zone.HAND, playerD, "Plains", 1); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Torment of Hailfire"); - setChoice(playerA, "X=10"); - - setChoice(playerD, true);// Sacrifices a nonland permanent? - setChoice(playerD, "Silvercoat Lion"); - - setChoice(playerB, true);// Sacrifices a nonland permanent? - setChoice(playerB, "Silvercoat Lion"); - - setChoice(playerD, true);// Sacrifices a nonland permanent? - setChoice(playerD, "Silvercoat Lion"); - - setChoice(playerB, true);// Sacrifices a nonland permanent? - setChoice(playerB, "Silvercoat Lion"); - - setChoice(playerD, false);// Sacrifices a nonland permanent? - setChoice(playerD, true);// Discard a card? - - setChoice(playerB, true);// Discard a card? - - setChoice(playerD, true);// Sacrifices a nonland permanent? - setChoice(playerD, "Silvercoat Lion"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Torment of Hailfire", 1); - - assertLife(playerA, 20); - assertLife(playerC, 20); - assertLife(playerD, 2); - assertLife(playerB, -1); - Assert.assertFalse("Player B is dead", playerB.isInGame()); - - } -} +package org.mage.test.cards.single.hou; + +import java.io.FileNotFoundException; + +import mage.constants.MultiplayerAttackOption; +import mage.constants.PhaseStep; +import mage.constants.RangeOfInfluence; +import mage.constants.Zone; +import mage.game.FreeForAll; +import mage.game.Game; +import mage.game.GameException; +import mage.game.mulligan.MulliganType; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestMultiPlayerBase; + +/** + * + * @author LevelX2 + */ +public class TormentOfHailfireTest extends CardTestMultiPlayerBase { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + // Start Life = 2 + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, MulliganType.GAME_DEFAULT.getMulligan(0), 20); + // Player order: A -> D -> C -> B + playerA = createPlayer(game, playerA, "PlayerA"); + playerB = createPlayer(game, playerB, "PlayerB"); + playerC = createPlayer(game, playerC, "PlayerC"); + playerD = createPlayer(game, playerD, "PlayerD"); + return game; + } + + @Test + public void test_Normal() { + setStrictChooseMode(true); + + // Repeat the following process X times. Each opponent loses 3 life unless they sacrifice a nonland permanent or discards a card. + addCard(Zone.HAND, playerA, "Torment of Hailfire", 1); // Sorcery {X}{B}{B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 12); + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 2); + addCard(Zone.HAND, playerB, "Plains", 1); + + addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion", 3); + + addCard(Zone.BATTLEFIELD, playerD, "Silvercoat Lion", 3); + addCard(Zone.HAND, playerD, "Plains", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Torment of Hailfire"); + setChoice(playerA, "X=10"); + + setChoice(playerD, true);// Sacrifices a nonland permanent? + setChoice(playerD, "Silvercoat Lion"); + + setChoice(playerB, true);// Sacrifices a nonland permanent? + setChoice(playerB, "Silvercoat Lion"); + + setChoice(playerD, true);// Sacrifices a nonland permanent? + setChoice(playerD, "Silvercoat Lion"); + + setChoice(playerB, true);// Sacrifices a nonland permanent? + setChoice(playerB, "Silvercoat Lion"); + + setChoice(playerD, false);// Sacrifices a nonland permanent? + setChoice(playerD, true);// Discard a card? + + setChoice(playerB, true);// Discard a card? + + setChoice(playerD, true);// Sacrifices a nonland permanent? + setChoice(playerD, "Silvercoat Lion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Torment of Hailfire", 1); + + assertLife(playerA, 20); + assertLife(playerC, 20); + assertLife(playerD, 2); + assertLife(playerB, -1); + Assert.assertFalse("Player B is dead", playerB.isInGame()); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/OboshThePreypiercerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/OboshThePreypiercerTest.java index c80bb289e79..0614756504c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/OboshThePreypiercerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/OboshThePreypiercerTest.java @@ -1,34 +1,34 @@ -package org.mage.test.cards.single.iko; - - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * @author LevelX2 - */ - -public class OboshThePreypiercerTest extends CardTestPlayerBase { - - @Test - public void testZeroCMSIsHandledAsOdd() { - setStrictChooseMode(true); - // At the beginning of your upkeep, flip a coin. If you lose the flip, Mana Crypt deals 3 damage to you. - // {T}: Add {C}{C}. - addCard(Zone.BATTLEFIELD, playerA, "Mana Crypt"); - // Companion — Your starting deck contains only cards with odd converted mana costs and land cards. - // If a source you control with an odd converted mana cost would deal damage to a permanent or player, it deals double that damage to that permanent or player instead. - addCard(Zone.BATTLEFIELD, playerA, "Obosh, the Preypiercer"); - - // lose the flip - setFlipCoinResult(playerA, false); - - setStopAt(1, PhaseStep.PRECOMBAT_MAIN); - execute(); - assertAllCommandsUsed(); - - assertLife(playerA, 20 - 3); - } +package org.mage.test.cards.single.iko; + + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author LevelX2 + */ + +public class OboshThePreypiercerTest extends CardTestPlayerBase { + + @Test + public void testZeroCMSIsHandledAsOdd() { + setStrictChooseMode(true); + // At the beginning of your upkeep, flip a coin. If you lose the flip, Mana Crypt deals 3 damage to you. + // {T}: Add {C}{C}. + addCard(Zone.BATTLEFIELD, playerA, "Mana Crypt"); + // Companion — Your starting deck contains only cards with odd converted mana costs and land cards. + // If a source you control with an odd converted mana cost would deal damage to a permanent or player, it deals double that damage to that permanent or player instead. + addCard(Zone.BATTLEFIELD, playerA, "Obosh, the Preypiercer"); + + // lose the flip + setFlipCoinResult(playerA, false); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20 - 3); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/SkycatSovereignTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/SkycatSovereignTest.java index ddd6ca85e2a..d94ae7d1b5c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/SkycatSovereignTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/iko/SkycatSovereignTest.java @@ -1,90 +1,90 @@ -package org.mage.test.cards.single.iko; - -import mage.ObjectColor; -import mage.abilities.keyword.FlyingAbility; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ - -public class SkycatSovereignTest extends CardTestPlayerBase { - - @Test - public void test_BoostFromFlyers() { - setStrictChooseMode(true); - - // Flying - // Skycat Sovereign gets +1/+1 for each other creature you control with flying. - // {2}{W}{U}: Create a 1/1 white Cat Bird creature token with flying. - addCard(Zone.BATTLEFIELD, playerA, "Skycat Sovereign"); // Creature {W}{U} (1/1) - - // Flying, vigilance - addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 2); // Creature — Griffin (2/2) - - setStopAt(1, PhaseStep.PRECOMBAT_MAIN); - execute(); - - assertAllCommandsUsed(); - assertPowerToughness(playerA, "Skycat Sovereign", 3, 3); - } - - /** - * Skycat Sovereign still gets +1/+1 for each creature that is supposed to have flying when there's an opposing Archetype of Imagination. - */ - @Test - public void test_NoBoostIfFlyingLost() { - setStrictChooseMode(true); - - // Flying - // Skycat Sovereign gets +1/+1 for each other creature you control with flying. - // {2}{W}{U}: Create a 1/1 white Cat Bird creature token with flying. - addCard(Zone.BATTLEFIELD, playerA, "Skycat Sovereign"); // Creature {W}{U} (1/1) - - // Flying, vigilance - addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 2); // Creature — Griffin (2/2) - - // Creatures you control have flying. - // Creatures your opponents control lose flying and can't have or gain flying. - addCard(Zone.BATTLEFIELD, playerB, "Archetype of Imagination"); // - - setStopAt(1, PhaseStep.PRECOMBAT_MAIN); - execute(); - - assertAllCommandsUsed(); - assertPowerToughness(playerA, "Skycat Sovereign", 1, 1); - } - - @Test - public void test_BoostFromToken() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Plains"); - addCard(Zone.BATTLEFIELD, playerA, "Island", 3); - // Flying - // Skycat Sovereign gets +1/+1 for each other creature you control with flying. - // {2}{W}{U}: Create a 1/1 white Cat Bird creature token with flying. - addCard(Zone.BATTLEFIELD, playerA, "Skycat Sovereign"); // Creature {W}{U} (1/1) - - // Flying, vigilance - addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 2); // Creature — Griffin (2/2) - - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{W}{U}: Create"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Cat Bird", 1); - assertColor(playerA, "Cat Bird", ObjectColor.WHITE, true); - assertColor(playerA, "Cat Bird", ObjectColor.BLUE, false); - assertAbility(playerA, "Cat Bird", FlyingAbility.getInstance(), true); - - assertPowerToughness(playerA, "Skycat Sovereign", 4, 4); - } +package org.mage.test.cards.single.iko; + +import mage.ObjectColor; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class SkycatSovereignTest extends CardTestPlayerBase { + + @Test + public void test_BoostFromFlyers() { + setStrictChooseMode(true); + + // Flying + // Skycat Sovereign gets +1/+1 for each other creature you control with flying. + // {2}{W}{U}: Create a 1/1 white Cat Bird creature token with flying. + addCard(Zone.BATTLEFIELD, playerA, "Skycat Sovereign"); // Creature {W}{U} (1/1) + + // Flying, vigilance + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 2); // Creature — Griffin (2/2) + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertAllCommandsUsed(); + assertPowerToughness(playerA, "Skycat Sovereign", 3, 3); + } + + /** + * Skycat Sovereign still gets +1/+1 for each creature that is supposed to have flying when there's an opposing Archetype of Imagination. + */ + @Test + public void test_NoBoostIfFlyingLost() { + setStrictChooseMode(true); + + // Flying + // Skycat Sovereign gets +1/+1 for each other creature you control with flying. + // {2}{W}{U}: Create a 1/1 white Cat Bird creature token with flying. + addCard(Zone.BATTLEFIELD, playerA, "Skycat Sovereign"); // Creature {W}{U} (1/1) + + // Flying, vigilance + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 2); // Creature — Griffin (2/2) + + // Creatures you control have flying. + // Creatures your opponents control lose flying and can't have or gain flying. + addCard(Zone.BATTLEFIELD, playerB, "Archetype of Imagination"); // + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertAllCommandsUsed(); + assertPowerToughness(playerA, "Skycat Sovereign", 1, 1); + } + + @Test + public void test_BoostFromToken() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + // Flying + // Skycat Sovereign gets +1/+1 for each other creature you control with flying. + // {2}{W}{U}: Create a 1/1 white Cat Bird creature token with flying. + addCard(Zone.BATTLEFIELD, playerA, "Skycat Sovereign"); // Creature {W}{U} (1/1) + + // Flying, vigilance + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin", 2); // Creature — Griffin (2/2) + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{W}{U}: Create"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Cat Bird", 1); + assertColor(playerA, "Cat Bird", ObjectColor.WHITE, true); + assertColor(playerA, "Cat Bird", ObjectColor.BLUE, false); + assertAbility(playerA, "Cat Bird", FlyingAbility.getInstance(), true); + + assertPowerToughness(playerA, "Skycat Sovereign", 4, 4); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh1/PlagueEngineerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh1/PlagueEngineerTest.java index 2ef638aea1d..8626a0bc76a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh1/PlagueEngineerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh1/PlagueEngineerTest.java @@ -1,39 +1,39 @@ -package org.mage.test.cards.single.mh1; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class PlagueEngineerTest extends CardTestPlayerBase { - - @Test - public void test_Standard() { - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); - // Deathtouch - // As Plague Engineer enters the battlefield, choose a creature type. - // Creatures of the chosen type your opponents control get -1/-1. - addCard(Zone.HAND, playerA, "Plague Engineer"); // Creature {2}{B} (2/2) - - addCard(Zone.BATTLEFIELD, playerA, "Defiant Elf", 2); //Creature - Elf (1/1) - addCard(Zone.BATTLEFIELD, playerB, "Defiant Elf", 2); //Creature - Elf (1/1) - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plague Engineer"); - setChoice(playerA, "Elf"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertPermanentCount(playerA, "Plague Engineer", 1); - - assertPermanentCount(playerA, "Defiant Elf", 2); - assertGraveyardCount(playerB, "Defiant Elf", 2); - - assertLife(playerA, 20); - assertLife(playerB, 20); - } +package org.mage.test.cards.single.mh1; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class PlagueEngineerTest extends CardTestPlayerBase { + + @Test + public void test_Standard() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + // Deathtouch + // As Plague Engineer enters the battlefield, choose a creature type. + // Creatures of the chosen type your opponents control get -1/-1. + addCard(Zone.HAND, playerA, "Plague Engineer"); // Creature {2}{B} (2/2) + + addCard(Zone.BATTLEFIELD, playerA, "Defiant Elf", 2); //Creature - Elf (1/1) + addCard(Zone.BATTLEFIELD, playerB, "Defiant Elf", 2); //Creature - Elf (1/1) + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plague Engineer"); + setChoice(playerA, "Elf"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Plague Engineer", 1); + + assertPermanentCount(playerA, "Defiant Elf", 2); + assertGraveyardCount(playerB, "Defiant Elf", 2); + + assertLife(playerA, 20); + assertLife(playerB, 20); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java new file mode 100644 index 00000000000..f126aa30453 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java @@ -0,0 +1,78 @@ +package org.mage.test.cards.single.mh2; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class DauthiVoidwalkerTest extends CardTestPlayerBase { + + @Test + public void test_FromBattlefield() { + // If a card would be put into an opponent's graveyard from anywhere, instead exile it with a void counter on it. + // {T}, Sacrifice Dauthi Voidwalker: Choose an exiled card an opponent owns with a void counter on it. You may play it this turn without paying its mana cost. + addCard(Zone.BATTLEFIELD, playerA, "Dauthi Voidwalker", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + // + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + // kill B's creature and exile with void counter + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkExileCount("after exile", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + + // can play it for free + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice"); + setChoice(playerA, "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_FromStack() { + // If a card would be put into an opponent's graveyard from anywhere, instead exile it with a void counter on it. + // {T}, Sacrifice Dauthi Voidwalker: Choose an exiled card an opponent owns with a void counter on it. You may play it this turn without paying its mana cost. + addCard(Zone.BATTLEFIELD, playerA, "Dauthi Voidwalker", 1); + // + addCard(Zone.HAND, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + // + // Counter target spell + addCard(Zone.HAND, playerA, "Cancel"); // {1}{U}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + + // B try to cast and get counter + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 3); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cancel", "Lightning Bolt", "Lightning Bolt"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + // countered bolt must be exiled and got void counter + checkExileCount("after exile", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", 1); + + // can play it for free + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}, Sacrifice"); + setChoice(playerA, "Lightning Bolt"); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 20 - 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/som/NimDeathmantleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/som/NimDeathmantleTest.java index 24990a66999..44104a5f8c1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/som/NimDeathmantleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/som/NimDeathmantleTest.java @@ -1,51 +1,51 @@ -package org.mage.test.cards.single.som; - -import mage.abilities.keyword.IntimidateAbility; -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 LevelX2 - */ -public class NimDeathmantleTest extends CardTestPlayerBase { - - @Test - public void test_Basic() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); - // Equipped creature gets +2/+2, has intimidate, and is a black Zombie. - // Whenever a nontoken creature is put into your graveyard from the battlefield, you may pay {4}. If you do, return that card to the battlefield and attach Nim Deathmantle to it. - addCard(Zone.HAND, playerA, "Nim Deathmantle"); // Artifact Equipment {2} - - addCard(Zone.BATTLEFIELD, playerB, "Mountain"); - addCard(Zone.HAND, playerB, "Lightning Bolt"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nim Deathmantle"); - - waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); - setChoice(playerA, true); // Message: Nim Deathmantle - Pay {4}? - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertGraveyardCount(playerB, "Lightning Bolt",1); - - assertPermanentCount(playerA, "Nim Deathmantle", 1); - - assertPowerToughness(playerA, "Silvercoat Lion", 4, 4); - assertAbility(playerA, "Silvercoat Lion", IntimidateAbility.getInstance(), true); - assertType("Silvercoat Lion", CardType.CREATURE, SubType.ZOMBIE); - - } +package org.mage.test.cards.single.som; + +import mage.abilities.keyword.IntimidateAbility; +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 LevelX2 + */ +public class NimDeathmantleTest extends CardTestPlayerBase { + + @Test + public void test_Basic() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + // Equipped creature gets +2/+2, has intimidate, and is a black Zombie. + // Whenever a nontoken creature is put into your graveyard from the battlefield, you may pay {4}. If you do, return that card to the battlefield and attach Nim Deathmantle to it. + addCard(Zone.HAND, playerA, "Nim Deathmantle"); // Artifact Equipment {2} + + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + addCard(Zone.HAND, playerB, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nim Deathmantle"); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); + setChoice(playerA, true); // Message: Nim Deathmantle - Pay {4}? + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerB, "Lightning Bolt",1); + + assertPermanentCount(playerA, "Nim Deathmantle", 1); + + assertPowerToughness(playerA, "Silvercoat Lion", 4, 4); + assertAbility(playerA, "Silvercoat Lion", IntimidateAbility.getInstance(), true); + assertType("Silvercoat Lion", CardType.CREATURE, SubType.ZOMBIE); + + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/targets/HexproofPlayerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/targets/HexproofPlayerTest.java new file mode 100644 index 00000000000..9ea99bd22f5 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/targets/HexproofPlayerTest.java @@ -0,0 +1,102 @@ +/* + * 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.targets; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author jeffwadsworth + */ +public class HexproofPlayerTest extends CardTestPlayerBase { + + /* + Test hexproof gained via both a static ability from a permanent and from an instant spell + */ + + @Test + public void leyLineOfSanctityOpponentCantTargetTest() { + https://github.com/magefree/mage/issues/5630 + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Leyline of Sanctity"); // controller has hexproof + addCard(Zone.BATTLEFIELD, playerB, "Liliana Vess"); // target player discards a card + addCard(Zone.HAND, playerA, "Memnite"); + + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, + "+1: Target player discards a card.", playerA); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, 0); + + } + + @Test + public void leyLineOfSanctityControllerCanTargetThemselfTest() { + https://github.com/magefree/mage/issues/5630 + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Leyline of Sanctity"); // controller has hexproof + addCard(Zone.BATTLEFIELD, playerA, "Liliana Vess"); // target player discards a card + addCard(Zone.HAND, playerA, "Memnite", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, + "+1: Target player discards a card.", playerA); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, 1); + + } + + @Test + public void veilOfSummerOpponentCantTargetTest() { + https://github.com/magefree/mage/issues/5630 + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, "Veil of Summer"); // Instant : controller has hexproof + addCard(Zone.BATTLEFIELD, playerB, "Liliana Vess"); // target player discards a card + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); // mana of Veil of Summer + addCard(Zone.HAND, playerA, "Memnite"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Veil of Summer"); // controller has hexproof + + activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, + "+1: Target player discards a card.", playerA); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, 1); // the Veil of Summer card only, no discarded card + } + + @Test + public void veilOfSummerControllerCanTargetThemselfTest() { + https://github.com/magefree/mage/issues/5630 + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, "Veil of Summer"); // Instant : controller has hexproof + addCard(Zone.BATTLEFIELD, playerA, "Liliana Vess"); // target player discards a card + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); // mana of Veil of Summer + addCard(Zone.HAND, playerA, "Memnite"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Veil of Summer"); // controller has hexproof + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, + "+1: Target player discards a card.", playerA); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/KardurDoomscourgeAndKithkinMourncallerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/KardurDoomscourgeAndKithkinMourncallerTest.java index 4895bca9749..206a3dea495 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/KardurDoomscourgeAndKithkinMourncallerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/KardurDoomscourgeAndKithkinMourncallerTest.java @@ -1,103 +1,103 @@ -/* - * 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.triggers; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author jeffwadsworth - */ -public class KardurDoomscourgeAndKithkinMourncallerTest extends CardTestPlayerBase { - - @Test - public void testKDRemovedFromCombatViaRegenerateAbility() { - setStrictChooseMode(true); - // Kardur, Doomscourge: if an attacking creature dies, each opponent loses 1 life and you gain 1 life - addCard(Zone.BATTLEFIELD, playerA, "Kardur, Doomscourge"); - addCard(Zone.BATTLEFIELD, playerA, "Elvish Archers"); - addCard(Zone.BATTLEFIELD, playerB, "Serra Angel"); - addCard(Zone.HAND, playerA, "Regenerate"); - addCard(Zone.HAND, playerA, "Terror"); - addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Regenerate", "Elvish Archers"); - - attack(1, playerA, "Elvish Archers"); - block(1, playerB, "Serra Angel", "Elvish Archers"); // regeneration shield used up and EA is removed from combat - - castSpell(1, PhaseStep.END_COMBAT, playerA, "Terror", "Elvish Archers"); // still within the combat phase, the EA is destroyed/dies - - setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - execute(); - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Elvish Archers", 1); - - // does not fire due to the Elvish Archers not in an attacking state - assertLife(playerA, 20); - assertLife(playerB, 20); - - } - - @Test - public void testSuccessfulKDTrigger() { - setStrictChooseMode(true); - // Kardur, Doomscourge: if an attacking creature dies, each opponent loses 1 life and you gain 1 life - addCard(Zone.BATTLEFIELD, playerA, "Kardur, Doomscourge"); - addCard(Zone.BATTLEFIELD, playerA, "Elvish Archers"); // 2/2 first strike - addCard(Zone.BATTLEFIELD, playerB, "Serra Angel"); // 4/4 vigilance - - attack(1, playerA, "Elvish Archers"); - block(1, playerB, "Serra Angel", "Elvish Archers"); // Elvish Archer dies causing KD to trigger - - setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - execute(); - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Elvish Archers", 1); - - // successful fire so playerA gains 1 life and playerB loses 1 life - assertLife(playerA, 21); - assertLife(playerB, 19); - - } - - @Test - public void testKMTrigger() { - setStrictChooseMode(true); - // Kithkin Mourncaller: if an elf or kithkin dies, you may draw a card - addCard(Zone.BATTLEFIELD, playerA, "Kithkin Mourncaller"); - addCard(Zone.BATTLEFIELD, playerA, "Elvish Archers"); // 2/1 first strike - addCard(Zone.BATTLEFIELD, playerA, "Pearled Unicorn"); // 2/2 - addCard(Zone.BATTLEFIELD, playerB, "Serra Angel"); // 4/4 vigilance - addCard(Zone.BATTLEFIELD, playerB, "Runeclaw Bear"); // 2/2 - addCard(Zone.LIBRARY, playerA, "Island", 2); // used for draw trigger - - attack(1, playerA, "Elvish Archers"); - attack(1, playerA, "Pearled Unicorn"); - block(1, playerB, "Serra Angel", "Elvish Archers"); // Elvish Achers will die and trigger KM - block(1, playerB, "Runeclaw Bear", "Pearled Unicorn"); // Pearled Unicorn will die but not trigger KM - - setChoice(playerA, "Yes"); // accept the drawing of a card from the single trigger (Elvish Archers "elf type") - - setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - execute(); - assertAllCommandsUsed(); - - assertGraveyardCount(playerA, "Elvish Archers", 1); - assertGraveyardCount(playerA, "Pearled Unicorn", 1); - - // successful fire due to dead Elvish Archers (elf) so playerA draws a card - assertHandCount(playerA, 1); - - } - -} +/* + * 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.triggers; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author jeffwadsworth + */ +public class KardurDoomscourgeAndKithkinMourncallerTest extends CardTestPlayerBase { + + @Test + public void testKDRemovedFromCombatViaRegenerateAbility() { + setStrictChooseMode(true); + // Kardur, Doomscourge: if an attacking creature dies, each opponent loses 1 life and you gain 1 life + addCard(Zone.BATTLEFIELD, playerA, "Kardur, Doomscourge"); + addCard(Zone.BATTLEFIELD, playerA, "Elvish Archers"); + addCard(Zone.BATTLEFIELD, playerB, "Serra Angel"); + addCard(Zone.HAND, playerA, "Regenerate"); + addCard(Zone.HAND, playerA, "Terror"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Regenerate", "Elvish Archers"); + + attack(1, playerA, "Elvish Archers"); + block(1, playerB, "Serra Angel", "Elvish Archers"); // regeneration shield used up and EA is removed from combat + + castSpell(1, PhaseStep.END_COMBAT, playerA, "Terror", "Elvish Archers"); // still within the combat phase, the EA is destroyed/dies + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Elvish Archers", 1); + + // does not fire due to the Elvish Archers not in an attacking state + assertLife(playerA, 20); + assertLife(playerB, 20); + + } + + @Test + public void testSuccessfulKDTrigger() { + setStrictChooseMode(true); + // Kardur, Doomscourge: if an attacking creature dies, each opponent loses 1 life and you gain 1 life + addCard(Zone.BATTLEFIELD, playerA, "Kardur, Doomscourge"); + addCard(Zone.BATTLEFIELD, playerA, "Elvish Archers"); // 2/2 first strike + addCard(Zone.BATTLEFIELD, playerB, "Serra Angel"); // 4/4 vigilance + + attack(1, playerA, "Elvish Archers"); + block(1, playerB, "Serra Angel", "Elvish Archers"); // Elvish Archer dies causing KD to trigger + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Elvish Archers", 1); + + // successful fire so playerA gains 1 life and playerB loses 1 life + assertLife(playerA, 21); + assertLife(playerB, 19); + + } + + @Test + public void testKMTrigger() { + setStrictChooseMode(true); + // Kithkin Mourncaller: if an elf or kithkin dies, you may draw a card + addCard(Zone.BATTLEFIELD, playerA, "Kithkin Mourncaller"); + addCard(Zone.BATTLEFIELD, playerA, "Elvish Archers"); // 2/1 first strike + addCard(Zone.BATTLEFIELD, playerA, "Pearled Unicorn"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Serra Angel"); // 4/4 vigilance + addCard(Zone.BATTLEFIELD, playerB, "Runeclaw Bear"); // 2/2 + addCard(Zone.LIBRARY, playerA, "Island", 2); // used for draw trigger + + attack(1, playerA, "Elvish Archers"); + attack(1, playerA, "Pearled Unicorn"); + block(1, playerB, "Serra Angel", "Elvish Archers"); // Elvish Achers will die and trigger KM + block(1, playerB, "Runeclaw Bear", "Pearled Unicorn"); // Pearled Unicorn will die but not trigger KM + + setChoice(playerA, "Yes"); // accept the drawing of a card from the single trigger (Elvish Archers "elf type") + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Elvish Archers", 1); + assertGraveyardCount(playerA, "Pearled Unicorn", 1); + + // successful fire due to dead Elvish Archers (elf) so playerA draws a card + assertHandCount(playerA, 1); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/SeraphAndSengirVampireTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/SeraphAndSengirVampireTest.java new file mode 100644 index 00000000000..b87d2c22605 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/SeraphAndSengirVampireTest.java @@ -0,0 +1,38 @@ +/* + * 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.triggers.damage; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author jeffwadsworth + */ +public class SeraphAndSengirVampireTest extends CardTestPlayerBase { + + @Test + public void testBothDieButTriggersStillFire() { + + // https://github.com/magefree/mage/issues/8293 + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Seraph", 1); // 4/4 flying : take control of creature that dies after taking damage + addCard(Zone.BATTLEFIELD, playerB, "Sengir Vampire", 1); // 4/4 flying : gains +1/+1 for any creature that takes damage and dies + + attack(3, playerA, "Seraph"); + block(3, playerB, "Sengir Vampire", "Seraph"); + + setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Seraph", 1); // Seraph dies + assertGraveyardCount(playerB, "Sengir Vampire", 0); + assertPermanentCount(playerA, "Sengir Vampire", 1); // playerA now controls the Sengir Vampire + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/AshenRiderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/AshenRiderTest.java index ed02d06706f..9de90e04f46 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/AshenRiderTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/AshenRiderTest.java @@ -1,68 +1,68 @@ -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; - -/** - * - * @author LevelX2 - */ - -public class AshenRiderTest extends CardTestPlayerBase { - - /* - * Volrath, the Shapestealer and Ashen Rider, Ashen Rider has a counter on it: - Turn Volrath into Ashen Rider: - Destroy the Volrath (who's the Ashen Rider) with Putrefy: - The death trigger for the Volrath copying Ashen Rider did not trigger. - */ - @Test - public void cartelAristrocraftInteractionOpponentDoesNotPayLife() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); - - addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); - addCard(Zone.BATTLEFIELD, playerA, "Island", 2); - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); - - // Flying - // When Ashen Rider enters the battlefield or dies, exile target permanent. - addCard(Zone.BATTLEFIELD, playerA, "Ashen Rider"); // Creature {4}{W}{W}{B}{B} - - // At the beginning of combat on your turn, put a -1/-1 counter on up to one target creature. - // {1}: Until your next turn, Volrath, the Shapestealer becomes a copy of target creature with a counter on it, except it's 7/5 and it has this ability. - addCard(Zone.HAND, playerA, "Volrath, the Shapestealer"); // Creature {2}{B}{G}{U} - addTarget(playerA, "Ashen Rider"); - - // Destroy target artifact or creature. It can't be regenerated. - addCard(Zone.HAND, playerA, "Putrefy"); // Instant {1}{B}{G} - - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Volrath, the Shapestealer"); - - activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}: Until your next turn"); - addTarget(playerA, "Ashen Rider"); - - waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); - - castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Putrefy", "Ashen Rider[only copy]"); - - addTarget(playerA, "Silvercoat Lion"); // Dies trigger of Volrath, the Shapestealer copied from Ashen Rider - - setStopAt(3, PhaseStep.BEGIN_COMBAT); - execute(); - - assertAllCommandsUsed(); - - assertPowerToughness(playerA, "Ashen Rider", 4,4); - - assertGraveyardCount(playerA, "Putrefy", 1); - assertGraveyardCount(playerA, "Volrath, the Shapestealer", 1); - - assertExileCount(playerB, "Silvercoat Lion", 1); - - } -} +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; + +/** + * + * @author LevelX2 + */ + +public class AshenRiderTest extends CardTestPlayerBase { + + /* + * Volrath, the Shapestealer and Ashen Rider, Ashen Rider has a counter on it: + Turn Volrath into Ashen Rider: + Destroy the Volrath (who's the Ashen Rider) with Putrefy: + The death trigger for the Volrath copying Ashen Rider did not trigger. + */ + @Test + public void cartelAristrocraftInteractionOpponentDoesNotPayLife() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + + // Flying + // When Ashen Rider enters the battlefield or dies, exile target permanent. + addCard(Zone.BATTLEFIELD, playerA, "Ashen Rider"); // Creature {4}{W}{W}{B}{B} + + // At the beginning of combat on your turn, put a -1/-1 counter on up to one target creature. + // {1}: Until your next turn, Volrath, the Shapestealer becomes a copy of target creature with a counter on it, except it's 7/5 and it has this ability. + addCard(Zone.HAND, playerA, "Volrath, the Shapestealer"); // Creature {2}{B}{G}{U} + addTarget(playerA, "Ashen Rider"); + + // Destroy target artifact or creature. It can't be regenerated. + addCard(Zone.HAND, playerA, "Putrefy"); // Instant {1}{B}{G} + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Volrath, the Shapestealer"); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}: Until your next turn"); + addTarget(playerA, "Ashen Rider"); + + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Putrefy", "Ashen Rider[only copy]"); + + addTarget(playerA, "Silvercoat Lion"); // Dies trigger of Volrath, the Shapestealer copied from Ashen Rider + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertPowerToughness(playerA, "Ashen Rider", 4,4); + + assertGraveyardCount(playerA, "Putrefy", 1); + assertGraveyardCount(playerA, "Volrath, the Shapestealer", 1); + + assertExileCount(playerB, "Silvercoat Lion", 1); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderColorChangeTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderColorChangeTest.java index e3cf24ae1ef..c4f23c1ebf7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderColorChangeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderColorChangeTest.java @@ -1,122 +1,122 @@ -package org.mage.test.commander.duel; - -import java.io.FileNotFoundException; -import mage.constants.PhaseStep; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.GameException; -import mage.game.permanent.Permanent; -import org.junit.Assert; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestCommanderDuelBase; - -/** - * - * @author LevelX2 - */ - -public class CommanderColorChangeTest extends CardTestCommanderDuelBase { - - @Override - protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - // When a player casts a spell or a creature attacks, exile Norin the Wary. Return it to the battlefield under its owner's control at the beginning of the next end step. - setDecknamePlayerA("CMDNorinTheWary.dck"); // Commander = Norin the Wary {R} - return super.createNewGameAndPlayers(); - } - - @Test - public void castCommanderWithAddedBlueColor() { - setStrictChooseMode(true); - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - - // As Painter's Servant enters the battlefield, choose a color. - // All cards that aren't on the battlefield, spells, and permanents are the chosen color in addition to their other colors. - addCard(Zone.HAND, playerA, "Painter's Servant", 1); // Artifact Creature {2} - - // Whenever a player casts a blue spell, you may gain 1 life. - addCard(Zone.BATTLEFIELD, playerA, "Kraken's Eye", 1); - - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Painter's Servant"); - setChoice(playerA, "Blue"); - - // When a player casts a spell or a creature attacks, exile Norin the Wary. - // Return it to the battlefield under its owner's control at the beginning of the next end step. - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Norin the Wary"); - setChoice(playerA, true); // Whenever a player casts a blue spell, you may gain 1 life. Choices: Yes - No - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - assertAllCommandsUsed(); - - assertPermanentCount(playerA, "Norin the Wary", 1); - - Permanent norin = getPermanent("Norin the Wary", playerA); - Assert.assertEquals(true, norin.getColor(currentGame).isBlue()); - Assert.assertEquals(true, norin.getColor(currentGame).isRed()); - - Permanent kraken = getPermanent("Kraken's Eye", playerA); - Assert.assertEquals(true, kraken.getColor(currentGame).isBlue()); - - assertLife(playerA, 41); - assertLife(playerB, 40); - - } - - - /** - * I played a Painter's Servant, named black, but the other commanders get a extra colors - * Later it got removed but the commanders and some cards still have the extra color - * I played it again later, named green, and the previously affected cards get the extra color - * so now they have 2 extra colors and the commander get and additional color on top of that - * And finally I got the empty hand error #6738 on my turn for what I assume is the Painter's Servant + Grindstone combo I have, - * but nonetheless manage to tie the game so it go into a second game and the issue carry over, - * all the commanders have all the extra colors they gain from the first game - */ - - @Test - public void castCommanderWithoutAddedBlueColor() { - setStrictChooseMode(true); - - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - - // As Painter's Servant enters the battlefield, choose a color. - // All cards that aren't on the battlefield, spells, and permanents are the chosen color in addition to their other colors. - addCard(Zone.HAND, playerA, "Painter's Servant", 1); // Artifact Creature {2} - - // Whenever a player casts a blue spell, you may gain 1 life. - addCard(Zone.BATTLEFIELD, playerA, "Kraken's Eye", 1); - - - // Exile target artifact or enchantment. - addCard(Zone.HAND, playerB, "Altar's Light", 1); // Instant {2}{W}{W} - addCard(Zone.BATTLEFIELD, playerB, "Plains", 4); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Painter's Servant"); - setChoice(playerA, "Blue"); - - // When a player casts a spell or a creature attacks, exile Norin the Wary. - // Return it to the battlefield under its owner's control at the beginning of the next end step. - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Norin the Wary"); - setChoice(playerA, true); // Whenever a player casts a blue spell, you may gain 1 life. Choices: Yes - No - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Altar's Light", "Painter's Servant", "Norin the Wary"); - setChoice(playerA, true); // Whenever a player casts a blue spell, you may gain 1 life. Choices: Yes - No - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertPermanentCount(playerA, "Norin the Wary", 1); - assertAllCommandsUsed(); - - Permanent norin = getPermanent("Norin the Wary", playerA); - Assert.assertEquals(false, norin.getColor(currentGame).isBlue()); - Assert.assertEquals(true, norin.getColor(currentGame).isRed()); - - Permanent kraken = getPermanent("Kraken's Eye", playerA); - Assert.assertEquals(false, kraken.getColor(currentGame).isBlue()); - - assertLife(playerA, 42); - assertLife(playerB, 40); - - } -} +package org.mage.test.commander.duel; + +import java.io.FileNotFoundException; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.GameException; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommanderDuelBase; + +/** + * + * @author LevelX2 + */ + +public class CommanderColorChangeTest extends CardTestCommanderDuelBase { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + // When a player casts a spell or a creature attacks, exile Norin the Wary. Return it to the battlefield under its owner's control at the beginning of the next end step. + setDecknamePlayerA("CMDNorinTheWary.dck"); // Commander = Norin the Wary {R} + return super.createNewGameAndPlayers(); + } + + @Test + public void castCommanderWithAddedBlueColor() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + // As Painter's Servant enters the battlefield, choose a color. + // All cards that aren't on the battlefield, spells, and permanents are the chosen color in addition to their other colors. + addCard(Zone.HAND, playerA, "Painter's Servant", 1); // Artifact Creature {2} + + // Whenever a player casts a blue spell, you may gain 1 life. + addCard(Zone.BATTLEFIELD, playerA, "Kraken's Eye", 1); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Painter's Servant"); + setChoice(playerA, "Blue"); + + // When a player casts a spell or a creature attacks, exile Norin the Wary. + // Return it to the battlefield under its owner's control at the beginning of the next end step. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Norin the Wary"); + setChoice(playerA, true); // Whenever a player casts a blue spell, you may gain 1 life. Choices: Yes - No + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Norin the Wary", 1); + + Permanent norin = getPermanent("Norin the Wary", playerA); + Assert.assertEquals(true, norin.getColor(currentGame).isBlue()); + Assert.assertEquals(true, norin.getColor(currentGame).isRed()); + + Permanent kraken = getPermanent("Kraken's Eye", playerA); + Assert.assertEquals(true, kraken.getColor(currentGame).isBlue()); + + assertLife(playerA, 41); + assertLife(playerB, 40); + + } + + + /** + * I played a Painter's Servant, named black, but the other commanders get a extra colors + * Later it got removed but the commanders and some cards still have the extra color + * I played it again later, named green, and the previously affected cards get the extra color + * so now they have 2 extra colors and the commander get and additional color on top of that + * And finally I got the empty hand error #6738 on my turn for what I assume is the Painter's Servant + Grindstone combo I have, + * but nonetheless manage to tie the game so it go into a second game and the issue carry over, + * all the commanders have all the extra colors they gain from the first game + */ + + @Test + public void castCommanderWithoutAddedBlueColor() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + // As Painter's Servant enters the battlefield, choose a color. + // All cards that aren't on the battlefield, spells, and permanents are the chosen color in addition to their other colors. + addCard(Zone.HAND, playerA, "Painter's Servant", 1); // Artifact Creature {2} + + // Whenever a player casts a blue spell, you may gain 1 life. + addCard(Zone.BATTLEFIELD, playerA, "Kraken's Eye", 1); + + + // Exile target artifact or enchantment. + addCard(Zone.HAND, playerB, "Altar's Light", 1); // Instant {2}{W}{W} + addCard(Zone.BATTLEFIELD, playerB, "Plains", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Painter's Servant"); + setChoice(playerA, "Blue"); + + // When a player casts a spell or a creature attacks, exile Norin the Wary. + // Return it to the battlefield under its owner's control at the beginning of the next end step. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Norin the Wary"); + setChoice(playerA, true); // Whenever a player casts a blue spell, you may gain 1 life. Choices: Yes - No + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Altar's Light", "Painter's Servant", "Norin the Wary"); + setChoice(playerA, true); // Whenever a player casts a blue spell, you may gain 1 life. Choices: Yes - No + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Norin the Wary", 1); + assertAllCommandsUsed(); + + Permanent norin = getPermanent("Norin the Wary", playerA); + Assert.assertEquals(false, norin.getColor(currentGame).isBlue()); + Assert.assertEquals(true, norin.getColor(currentGame).isRed()); + + Permanent kraken = getPermanent("Kraken's Eye", playerA); + Assert.assertEquals(false, kraken.getColor(currentGame).isBlue()); + + assertLife(playerA, 42); + assertLife(playerB, 40); + + } +} diff --git a/Mage/src/main/java/mage/cards/decks/DeckFormatsTest.java b/Mage.Tests/src/test/java/org/mage/test/decks/exporter/DeckFormatsTest.java similarity index 96% rename from Mage/src/main/java/mage/cards/decks/DeckFormatsTest.java rename to Mage.Tests/src/test/java/org/mage/test/decks/exporter/DeckFormatsTest.java index 6dfe1d612f7..47c85e69c9c 100644 --- a/Mage/src/main/java/mage/cards/decks/DeckFormatsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/decks/exporter/DeckFormatsTest.java @@ -1,5 +1,6 @@ -package mage.cards.decks; +package org.mage.test.decks.exporter; +import mage.cards.decks.DeckFormats; import org.junit.Assert; import org.junit.Test; diff --git a/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporterTest.java b/Mage.Tests/src/test/java/org/mage/test/decks/exporter/MtgArenaDeckExporterTest.java similarity index 92% rename from Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporterTest.java rename to Mage.Tests/src/test/java/org/mage/test/decks/exporter/MtgArenaDeckExporterTest.java index af7666a45d9..9a40ab631cf 100644 --- a/Mage/src/main/java/mage/cards/decks/exporter/MtgArenaDeckExporterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/decks/exporter/MtgArenaDeckExporterTest.java @@ -1,7 +1,9 @@ -package mage.cards.decks.exporter; +package org.mage.test.decks.exporter; import mage.cards.decks.DeckCardInfo; import mage.cards.decks.DeckCardLists; +import mage.cards.decks.exporter.DeckExporter; +import mage.cards.decks.exporter.MtgArenaDeckExporter; import org.junit.Test; import java.io.ByteArrayOutputStream; diff --git a/Mage.Tests/src/test/java/org/mage/test/decks/importer/TxtDeckImporterTest.java b/Mage.Tests/src/test/java/org/mage/test/decks/importer/TxtDeckImporterTest.java index 50180ee3d34..4043e013be3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/decks/importer/TxtDeckImporterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/decks/importer/TxtDeckImporterTest.java @@ -21,13 +21,13 @@ public class TxtDeckImporterTest { String[] sideboard = {"Swamp", "Mountain"}; for (String c : cards) { - card = CardRepository.instance.findPreferedCoreExpansionCard(c, true); + card = CardRepository.instance.findPreferredCoreExpansionCard(c, true); assert card != null; deck.getCards().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getSetCode())); } for (String s : sideboard) { - card = CardRepository.instance.findPreferedCoreExpansionCard(s, true); + card = CardRepository.instance.findPreferredCoreExpansionCard(s, true); assert card != null; deck.getSideboard().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getSetCode())); } 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 c211a2fa8bf..b700d8363f1 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 @@ -2400,7 +2400,7 @@ public class TestPlayer implements Player { } // card in hand (only own hand supports here) - // TODO: add not own hand too, example + // cards from non-own hand must be targeted through revealed cards if (target.getOriginalTarget() instanceof TargetCardInHand || target.getOriginalTarget() instanceof TargetDiscard || (target.getOriginalTarget() instanceof TargetCard && target.getOriginalTarget().getZone() == Zone.HAND)) { @@ -2569,6 +2569,13 @@ public class TestPlayer implements Player { } } + // library + if (target.getOriginalTarget() instanceof TargetCardInLibrary + || (target.getOriginalTarget() instanceof TargetCard && target.getOriginalTarget().getZone() == Zone.LIBRARY)) { + // user don't have access to library, so it must be targeted through list/revealed cards + Assert.fail("Library zone is private, you must target through cards list, e.g. revealed: " + target.getOriginalTarget().getClass().getCanonicalName()); + } + // uninplemented TargetCard's zone if (target.getOriginalTarget() instanceof TargetCard && !targetCardZonesChecked.contains(target.getOriginalTarget().getZone())) { Assert.fail("Found unimplemented TargetCard's zone or TargetCard's extented class: " @@ -3085,8 +3092,8 @@ public class TestPlayer implements Player { } @Override - public boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, ApprovingObject approvingObject) { - return computerPlayer.playCard(card, game, noMana, ignoreTiming, approvingObject); + public boolean playCard(Card card, Game game, boolean noMana, ApprovingObject approvingObject) { + return computerPlayer.playCard(card, game, noMana, approvingObject); } @Override @@ -3707,6 +3714,16 @@ public class TestPlayer implements Player { return computerPlayer.canPlayCardsFromGraveyard(); } + @Override + public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { + computerPlayer.setDrawsOnOpponentsTurn(drawsOnOpponentsTurn); + } + + @Override + public boolean isDrawsOnOpponentsTurn() { + return computerPlayer.isDrawsOnOpponentsTurn(); + } + @Override public void setPayManaMode(boolean payManaMode) { computerPlayer.setPayManaMode(payManaMode); @@ -4344,7 +4361,7 @@ public class TestPlayer implements Player { assertAliasSupportInChoices(false); MageObject object = game.getObject(card.getId()); // must be object to find real abilities (example: commander) - Map useable = PlayerImpl.getSpellAbilities(this.getId(), object, game.getState().getZone(object.getId()), game); + Map useable = PlayerImpl.getCastableSpellAbilities(game, this.getId(), object, game.getState().getZone(object.getId()), noMana); String allInfo = useable.values().stream().map(Object::toString).collect(Collectors.joining("\n")); if (useable.size() == 1) { return (SpellAbility) useable.values().iterator().next(); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/AbilityPickerTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/AbilityPickerTest.java index a064f280e43..100e71737d1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/AbilityPickerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/AbilityPickerTest.java @@ -7,6 +7,7 @@ import mage.cards.repository.CardRepository; import mage.game.permanent.PermanentCard; import mage.game.permanent.PermanentImpl; import mage.view.AbilityPickerView; +import mage.view.GameView; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -16,13 +17,17 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class AbilityPickerTest extends CardTestPlayerBase { + private GameView prepareGameView() { + return new GameView(currentGame.getState(), currentGame, playerA.getId(), null); + } + @Test public void test_PickerChoices_FusedSpells() { // must be 3 spells for choices Abilities abilities = getAbilitiesFromCard("Armed // Dangerous"); Assert.assertEquals(3, abilities.size()); - AbilityPickerView view = new AbilityPickerView("test name", abilities, "test message"); + AbilityPickerView view = new AbilityPickerView(prepareGameView(), "test name", abilities, "test message"); Assert.assertEquals(3, view.getChoices().size()); view.getChoices().values().forEach(c -> { Assert.assertTrue("Must start with Cast text, but found: " + c, c.contains("Cast ")); @@ -35,7 +40,7 @@ public class AbilityPickerTest extends CardTestPlayerBase { Abilities abilities = getAbilitiesFromCard("Foulmire Knight"); Assert.assertEquals(3, abilities.size()); - AbilityPickerView view = new AbilityPickerView("test name", abilities, "test message"); + AbilityPickerView view = new AbilityPickerView(prepareGameView(), "test name", abilities, "test message"); Assert.assertEquals(3, view.getChoices().size()); view.getChoices().values().forEach(c -> { if (c.contains("Deathtouch")) { @@ -51,7 +56,7 @@ public class AbilityPickerTest extends CardTestPlayerBase { Abilities abilities = getAbilitiesFromCard("Dimir Cluestone"); Assert.assertEquals(4, abilities.size()); - AbilityPickerView view = new AbilityPickerView("test name", abilities, "test message"); + AbilityPickerView view = new AbilityPickerView(prepareGameView(), "test name", abilities, "test message"); Assert.assertEquals(4, view.getChoices().size()); int castCount = 0; int abilsCount = 0; @@ -72,7 +77,7 @@ public class AbilityPickerTest extends CardTestPlayerBase { Abilities abilities = getAbilitiesFromCard("Cling to Dust"); Assert.assertEquals(2, abilities.size()); - AbilityPickerView view = new AbilityPickerView("test name", abilities, "test message"); + AbilityPickerView view = new AbilityPickerView(prepareGameView(), "test name", abilities, "test message"); Assert.assertEquals(2, view.getChoices().size()); view.getChoices().values().forEach(c -> { Assert.assertTrue("Must start with Cast text, but found: " + c, c.contains("Cast ")); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/DatabaseCompatibleTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/DatabaseCompatibleTest.java new file mode 100644 index 00000000000..4066f45ac77 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/DatabaseCompatibleTest.java @@ -0,0 +1,66 @@ +package org.mage.test.serverside; + +import mage.server.AuthorizedUser; +import mage.server.AuthorizedUserRepository; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * Testing database compatible on new libs or updates. + * + * @author JayDi85 + */ +public class DatabaseCompatibleTest { + + private final String JDBC_URL = "jdbc:h2:file:%s;AUTO_SERVER=TRUE"; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void test_AuthUsers() { + try { + // prepare test db + String dbDir = tempFolder.newFolder().getAbsolutePath(); + String dbName = "users-db-sample.h2"; + String dbFullName = Paths.get(dbDir, dbName).toAbsolutePath().toString(); + String dbFullFileName = dbFullName + ".mv.db"; + Files.copy( + Paths.get("src", "test", "data", dbName + ".mv.db"), + Paths.get(dbFullFileName) + ); + Assert.assertTrue(Files.exists(Paths.get(dbFullFileName))); + + AuthorizedUserRepository dbUsers = new AuthorizedUserRepository( + String.format(JDBC_URL, dbFullName) + ); + + // search + Assert.assertNotNull(dbUsers.getByName("user1")); + Assert.assertNotNull(dbUsers.getByEmail("user2@example.com")); + Assert.assertNull(dbUsers.getByName("userFAIL")); + + // login + AuthorizedUser user = dbUsers.getByName("user3"); + Assert.assertEquals("user name", user.getName(), "user3"); + Assert.assertTrue("user pas", user.doCredentialsMatch("user3", "pas3")); + Assert.assertFalse("user wrong pas", user.doCredentialsMatch("user3", "123")); + Assert.assertFalse("user empty pas", user.doCredentialsMatch("user3", "")); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(e.getMessage()); + } + } + + @Test + @Ignore // TODO: add records/stats db compatible test + public void test_Records() { + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java index 8fa74432277..996948230d4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java @@ -102,21 +102,25 @@ public abstract class MageTestBase { @BeforeClass public static void init() { Logger.getRootLogger().setLevel(Level.DEBUG); - deleteSavedGames(); - ConfigSettings config = new ConfigWrapper(ConfigFactory.loadFromFile("config/config.xml")); - config.getGameTypes().forEach((gameType) -> { - GameFactory.instance.addGameType(gameType.getName(), loadGameType(gameType), loadPlugin(gameType)); - }); - config.getTournamentTypes().forEach((tournamentType) -> { - TournamentFactory.instance.addTournamentType(tournamentType.getName(), loadTournamentType(tournamentType), loadPlugin(tournamentType)); - }); - config.getPlayerTypes().forEach((playerType) -> { - PlayerFactory.instance.addPlayerType(playerType.getName(), loadPlugin(playerType)); - }); + + // one time init for all tests + if (GameFactory.instance.getGameTypes().isEmpty()) { + deleteSavedGames(); + ConfigSettings config = new ConfigWrapper(ConfigFactory.loadFromFile("config/config.xml")); + config.getGameTypes().forEach((gameType) -> { + GameFactory.instance.addGameType(gameType.getName(), loadGameType(gameType), loadPlugin(gameType)); + }); + config.getTournamentTypes().forEach((tournamentType) -> { + TournamentFactory.instance.addTournamentType(tournamentType.getName(), loadTournamentType(tournamentType), loadPlugin(tournamentType)); + }); + config.getPlayerTypes().forEach((playerType) -> { + PlayerFactory.instance.addPlayerType(playerType.getName(), loadPlugin(playerType)); + }); // for (Plugin plugin : config.getDeckTypes()) { // DeckValidatorFactory.getInstance().addDeckType(plugin.getName(), loadPlugin(plugin)); // } - Copier.setLoader(classLoader); + Copier.setLoader(classLoader); + } } @SuppressWarnings("UseSpecificCatch") diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index 2db1d1b67fa..864103cd90b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -81,7 +81,8 @@ public abstract class MageTestPlayerBase { protected Map> commands = new HashMap<>(); - protected static Map loadedDeckCardLists = new HashMap<>(); // test decks buffer + protected static Map loadedDecks = new HashMap<>(); // deck's cache + protected static Map loadedCardInfo = new HashMap<>(); // db card's cache protected TestPlayer playerA; protected TestPlayer playerB; @@ -131,12 +132,15 @@ public abstract class MageTestPlayerBase { logger.debug("Logging level: " + logger.getLevel()); logger.debug("Default charset: " + Charset.defaultCharset()); - deleteSavedGames(); - ConfigSettings config = new ConfigWrapper(ConfigFactory.loadFromFile("config/config.xml")); - for (GamePlugin plugin : config.getGameTypes()) { - GameFactory.instance.addGameType(plugin.getName(), loadGameType(plugin), loadPlugin(plugin)); + // one time init for all tests + if (GameFactory.instance.getGameTypes().isEmpty()) { + deleteSavedGames(); + ConfigSettings config = new ConfigWrapper(ConfigFactory.loadFromFile("config/config.xml")); + for (GamePlugin plugin : config.getGameTypes()) { + GameFactory.instance.addGameType(plugin.getName(), loadGameType(plugin), loadPlugin(plugin)); + } + Copier.setLoader(classLoader); } - Copier.setLoader(classLoader); } private static Class loadPlugin(Plugin plugin) { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index b15a0320844..0dd3cdc2c81 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -55,6 +55,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement // DEBUG only, enable it to fast startup tests without database create (delete \db\ folder to force db recreate) private static final boolean FAST_SCAN_WITHOUT_DATABASE_CREATE = false; + private static final boolean SHOW_EXECUTE_TIME_PER_TEST = false; + public static final String ALIAS_PREFIX = "@"; // don't change -- it uses in user's tests public static final String CHECK_PARAM_DELIMETER = "#"; public static final String CHECK_PREFIX = "check:"; // prefix for all check commands @@ -259,13 +261,13 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement logger.debug("Loading deck..."); DeckCardLists list; - if (loadedDeckCardLists.containsKey(deckName)) { - list = loadedDeckCardLists.get(deckName); + if (loadedDecks.containsKey(deckName)) { + list = loadedDecks.get(deckName); } else { list = DeckImporter.importDeckFromFile(deckName, true); - loadedDeckCardLists.put(deckName, list); + loadedDecks.put(deckName, list); } - Deck deck = Deck.load(list, false, false); + Deck deck = Deck.load(list, false, false, loadedCardInfo); logger.debug("Done!"); if (deck.getCards().size() < 40) { throw new IllegalArgumentException("Couldn't load deck, deck size=" + deck.getCards().size()); @@ -332,7 +334,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement currentGame.setGameStopped(true); // used for rollback handling long t2 = System.nanoTime(); logger.debug("Winner: " + currentGame.getWinner()); - logger.info(Thread.currentThread().getStackTrace()[2].getMethodName() + " has been executed. Execution time: " + (t2 - t1) / 1000000 + " ms"); + if (SHOW_EXECUTE_TIME_PER_TEST) { + logger.info(Thread.currentThread().getStackTrace()[2].getMethodName() + " has been executed. Execution time: " + (t2 - t1) / 1000000 + " ms"); + } // TODO: 01.12.2018, JayDi85 - uncomment and fix MANY broken tests with wrong commands //assertAllCommandsUsed(); @@ -672,7 +676,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement Assert.fail("Can't add card " + cardName + " - alias " + aliasName + " already exists for " + player.getName()); } - CardInfo cardInfo = CardRepository.instance.findCard(cardName); + // game tests don't need cards from a specific set, so it can be from any set + CardInfo cardInfo = CardRepository.instance.findCard(cardName, true); if (cardInfo == null) { throw new IllegalArgumentException("[TEST] Couldn't find a card: " + cardName); } diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 8277b202bc8..91a4b116bd0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -236,6 +236,16 @@ public class PlayerStub implements Player { return false; } + @Override + public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { + + } + + @Override + public boolean isDrawsOnOpponentsTurn() { + return false; + } + @Override public List getAlternativeSourceCosts() { return null; @@ -601,7 +611,7 @@ public class PlayerStub implements Player { } @Override - public boolean playCard(Card card, Game game, boolean noMana, boolean checkTiming, ApprovingObject approvingObject) { + public boolean playCard(Card card, Game game, boolean noMana, ApprovingObject approvingObject) { return false; } diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/JsonGsonTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/JsonGsonTest.java new file mode 100644 index 00000000000..ec546ada70f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/utils/JsonGsonTest.java @@ -0,0 +1,45 @@ +package org.mage.test.utils; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import mage.util.JsonUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Paths; + +/** + * Gson (Google json) lib uses for scryfall and mtgjson data. + *

+ * Tests: + * - unknown data parser tests here (JsonParser.parseReader) + * - class parser tests in mtgjson verify tests (new Gson().fromJson) + */ +public class JsonGsonTest { + + @Test + public void test_ReadByStreamParser() { + String sampleFileName = Paths.get("src", "test", "data", "scryfall-card.json").toString(); + try { + // low level parser for unknown data structure + JsonObject json = JsonParser.parseReader(new FileReader(sampleFileName)).getAsJsonObject(); + + // data types + Assert.assertEquals("string", "card", JsonUtil.getAsString(json, "object")); + JsonArray jsonFaces = JsonUtil.getAsArray(json, "card_faces"); + Assert.assertEquals("int", 60370, JsonUtil.getAsInt(json, "mtgo_id")); + Assert.assertTrue("boolean", JsonUtil.getAsBoolean(json, "highres_image")); + Assert.assertEquals("double", 4.0, JsonUtil.getAsDouble(json, "cmc"), 0.0); + Assert.assertNotNull("array", jsonFaces); + + Assert.assertEquals("Card must have 2 faces", 2, jsonFaces.size()); + Assert.assertEquals("Unknown second side", "Infectious Curse", JsonUtil.getAsString(jsonFaces.get(1).getAsJsonObject(), "name")); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail("Can't load sample json file: " + sampleFileName); + } + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/ZipFilesReadWriteTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/ZipFilesReadWriteTest.java new file mode 100644 index 00000000000..f90aa7e5d9b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ZipFilesReadWriteTest.java @@ -0,0 +1,76 @@ +package org.mage.test.utils; + +import net.java.truevfs.access.TFile; +import net.java.truevfs.access.TFileReader; +import net.java.truevfs.access.TFileWriter; +import net.java.truevfs.access.TVFS; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Paths; +import java.util.Arrays; + +/** + * @author JayDi85 + */ +public class ZipFilesReadWriteTest { + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void test_Read() { + // exists + TFile fileZip = new TFile(Paths.get("src", "test", "data", "images.zip").toString()); + Assert.assertTrue(fileZip.exists()); + TFile fileZipDir = new TFile(Paths.get("src", "test", "data", "images.zip", "SET").toString()); + Assert.assertTrue(fileZipDir.exists()); + TFile fileZipFile = new TFile(Paths.get("src", "test", "data", "images.zip", "SET", "image1.png").toString()); + Assert.assertTrue(fileZipFile.exists()); + + // not exists + TFile fileNotZip = new TFile(Paths.get("src", "test", "data", "images-FAIL.zip").toString()); + Assert.assertFalse(fileNotZip.exists()); + TFile fileNotZipDir = new TFile(Paths.get("src", "test", "data", "images.zip", "SET-FAIL").toString()); + Assert.assertFalse(fileNotZipDir.exists()); + TFile fileNotZipFile = new TFile(Paths.get("src", "test", "data", "images.zip", "SET", "image1-FAIL.png").toString()); + Assert.assertFalse(fileNotZipFile.exists()); + + // reading + Assert.assertEquals(3, fileZipDir.list().length); + Assert.assertTrue(Arrays.asList(fileZipDir.list()).contains("image1.png")); + Assert.assertTrue(Arrays.asList(fileZipDir.list()).contains("image2.png")); + Assert.assertTrue(Arrays.asList(fileZipDir.list()).contains("image3.png")); + } + + @Test + public void test_write() { + try { + String zipPath = tempFolder.newFolder().getAbsolutePath(); + TFile fileWriteZip = new TFile(Paths.get(zipPath, "temp-images.zip", "DIR", "test.txt").toString()); + Assert.assertFalse(fileWriteZip.exists()); + + Writer writer = new TFileWriter(fileWriteZip); + try { + writer.write("test text"); + writer.close(); + Assert.assertTrue(fileWriteZip.exists()); + + TFileReader reader = new TFileReader(fileWriteZip); + BufferedReader br = new BufferedReader(reader); + Assert.assertEquals(br.readLine(), "test text"); + reader.close(); + } finally { + TVFS.umount(); + } + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(e.getMessage()); + } + } +} diff --git a/Mage.Verify/pom.xml b/Mage.Verify/pom.xml index df584fd29b9..9084b90ba03 100644 --- a/Mage.Verify/pom.xml +++ b/Mage.Verify/pom.xml @@ -34,23 +34,12 @@ mage-client ${mage-version} - - junit - junit - test - com.fasterxml.jackson.core jackson-databind [2.9.9.1,) - - log4j - log4j - jar - - org.reflections reflections diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index b6570834104..ed37b8723f1 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -9,6 +9,7 @@ import mage.abilities.common.WerewolfFrontTriggeredAbility; import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.keyword.MenaceAbility; import mage.abilities.keyword.MultikickerAbility; +import mage.abilities.keyword.TransformAbility; import mage.cards.*; import mage.cards.decks.DeckCardLists; import mage.cards.decks.importer.DeckImporter; @@ -57,7 +58,7 @@ public class VerifyCardDataTest { private static final Logger logger = Logger.getLogger(VerifyCardDataTest.class); - private static final String FULL_ABILITIES_CHECK_SET_CODE = "AFR"; // check all abilities and output cards with wrong abilities texts; + private static final String FULL_ABILITIES_CHECK_SET_CODE = "MID"; // check all abilities and output cards with wrong abilities texts; private static final boolean AUTO_FIX_SAMPLE_DECKS = false; // debug only: auto-fix sample decks by test_checkSampleDecks test run private static final boolean ONLY_TEXT = false; // use when checking text locally, suppresses unnecessary checks and output messages @@ -1381,6 +1382,14 @@ public class VerifyCardDataTest { fail(card, "abilities", "card is a front face werewolf with a back face ability"); } + if (card.getSecondCardFace() != null && !card.isNightCard() && !card.getAbilities().containsClass(TransformAbility.class)) { + fail(card, "abilities", "double-faced cards should have transform ability on the front"); + } + + if (card.getSecondCardFace() != null && card.isNightCard() && card.getAbilities().containsClass(TransformAbility.class)) { + fail(card, "abilities", "double-faced cards should not have transform ability on the back"); + } + // special check: missing or wrong ability/effect hints Map hints = new HashMap<>(); hints.put(MenaceAbility.class, "can't be blocked except by two or more"); @@ -1747,23 +1756,28 @@ public class VerifyCardDataTest { || checkName.equals("Forest") || checkName.equals("Swamp") || checkName.equals("Plains") - || checkName.equals("Mountain") - || checkName.equals("Wastes"); + || checkName.equals("Mountain"); } private void checkBasicLands(Card card, MtgJsonCard ref) { // basic lands must have Rarity.LAND and SuperType.BASIC // other cards can't have that stats - if (isBasicLandName(card.getName())) { + String name = card.getName(); + if (isBasicLandName(name)) { // lands - if (card.getRarity() != Rarity.LAND && card.getRarity() != Rarity.SPECIAL) { + if (card.getRarity() != Rarity.LAND) { fail(card, "rarity", "basic land must be Rarity.LAND"); } if (!card.getSuperType().contains(SuperType.BASIC)) { fail(card, "supertype", "basic land must be SuperType.BASIC"); } + } else if (name.equals("Wastes")) { + // Wastes are SuperType.BASIC but not necessarily Rarity.LAND + if (!card.getSuperType().contains(SuperType.BASIC)) { + fail(card, "supertype", "Wastes must be SuperType.BASIC"); + } } else { // non lands if (card.getRarity() == Rarity.LAND) { @@ -1849,7 +1863,7 @@ public class VerifyCardDataTest { if (!cardId.getExtension().isEmpty()) { cardInfo = CardRepository.instance.findCardWPreferredSet(cardId.getName(), cardId.getExtension(), false); } else { - cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(cardId.getName(), false); + cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardId.getName(), false); } if (cardInfo == null) { errorsList.add("Error: broken cube, can't find card: " + cube.getClass().getCanonicalName() + " - " + cardId.getName()); diff --git a/Mage/pom.xml b/Mage/pom.xml index 261fa2208f6..c704f8d6c88 100644 --- a/Mage/pom.xml +++ b/Mage/pom.xml @@ -14,35 +14,13 @@ Mage Framework - - com.googlecode.json-simple - json-simple - 1.1.1 - - - log4j - log4j - jar - - - - com.h2database - h2 - 1.4.197 - runtime - com.google.guava guava - com.j256.ormlite - ormlite-jdbc - 5.1 - - - junit - junit + com.google.code.gson + gson com.google.protobuf diff --git a/Mage/src/main/java/mage/ApprovingObject.java b/Mage/src/main/java/mage/ApprovingObject.java index 3701880d715..9468aad1577 100644 --- a/Mage/src/main/java/mage/ApprovingObject.java +++ b/Mage/src/main/java/mage/ApprovingObject.java @@ -1,28 +1,28 @@ -package mage; - -import mage.abilities.Ability; -import mage.game.Game; - -/** - * - * @author LevelX2 - */ -public class ApprovingObject { - - private final Ability approvingAbility; - private final MageObjectReference approvingMageObjectReference; - - public ApprovingObject(Ability source, Game game) { - this.approvingAbility = source; - this.approvingMageObjectReference = new MageObjectReference(source.getSourceId(), game); - } - - public Ability getApprovingAbility() { - return approvingAbility; - } - - public MageObjectReference getApprovingMageObjectReference() { - return approvingMageObjectReference; - } - -} +package mage; + +import mage.abilities.Ability; +import mage.game.Game; + +/** + * + * @author LevelX2 + */ +public class ApprovingObject { + + private final Ability approvingAbility; + private final MageObjectReference approvingMageObjectReference; + + public ApprovingObject(Ability source, Game game) { + this.approvingAbility = source; + this.approvingMageObjectReference = new MageObjectReference(source.getSourceId(), game); + } + + public Ability getApprovingAbility() { + return approvingAbility; + } + + public MageObjectReference getApprovingMageObjectReference() { + return approvingMageObjectReference; + } + +} diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index 1af6b5e8f81..63cb477b0c7 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -543,7 +543,7 @@ public interface Ability extends Controllable, Serializable { void adjustTargets(Game game); - void setCostAdjuster(CostAdjuster costAdjuster); + Ability setCostAdjuster(CostAdjuster costAdjuster); CostAdjuster getCostAdjuster(); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index dceaba8b20a..44306218224 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -26,7 +26,9 @@ import mage.game.stack.Spell; import mage.game.stack.StackAbility; import mage.players.Player; import mage.target.Target; +import mage.target.TargetCard; import mage.target.Targets; +import mage.target.common.TargetCardInLibrary; import mage.target.targetadjustment.TargetAdjuster; import mage.util.CardUtil; import mage.util.GameLog; @@ -881,6 +883,12 @@ public abstract class AbilityImpl implements Ability { @Override public void addTarget(Target target) { + // verify check + if (target instanceof TargetCardInLibrary + || (target instanceof TargetCard && target.getZone().equals(Zone.LIBRARY))) { + throw new IllegalArgumentException("Wrong usage of TargetCardInLibrary - you must use it with SearchLibrary only"); + } + if (target != null) { getTargets().add(target); } @@ -1323,8 +1331,9 @@ public abstract class AbilityImpl implements Ability { * @param costAdjuster */ @Override - public void setCostAdjuster(CostAdjuster costAdjuster) { + public AbilityImpl setCostAdjuster(CostAdjuster costAdjuster) { this.costAdjuster = costAdjuster; + return this; } @Override diff --git a/Mage/src/main/java/mage/abilities/common/AttackingCreaturePutIntoGraveyardTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttackingCreaturePutIntoGraveyardTriggeredAbility.java index 58f656fdd8f..8f83c2834cf 100644 --- a/Mage/src/main/java/mage/abilities/common/AttackingCreaturePutIntoGraveyardTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttackingCreaturePutIntoGraveyardTriggeredAbility.java @@ -1,124 +1,124 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package mage.abilities.common; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.constants.Zone; -import mage.filter.FilterPermanent; -import mage.game.Game; -import mage.game.events.GameEvent; -import static mage.game.events.GameEvent.EventType.END_COMBAT_STEP_POST; -import static mage.game.events.GameEvent.EventType.REMOVED_FROM_COMBAT; -import static mage.game.events.GameEvent.EventType.ZONE_CHANGE; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; - -/** - * - * @author weirddan455 and jeffwadsworth - */ -public class AttackingCreaturePutIntoGraveyardTriggeredAbility extends TriggeredAbilityImpl { - - protected FilterPermanent filterPermanent; - private final boolean onlyToControllerGraveyard; - private final boolean itDies; - - public AttackingCreaturePutIntoGraveyardTriggeredAbility(Effect effect, FilterPermanent filterPermanent, Boolean onlyToControllerGraveyard, Boolean itDies, Boolean optional) { - super(Zone.BATTLEFIELD, effect, optional); - this.filterPermanent = filterPermanent; - this.onlyToControllerGraveyard = onlyToControllerGraveyard; - this.itDies = itDies; - } - - private AttackingCreaturePutIntoGraveyardTriggeredAbility(final AttackingCreaturePutIntoGraveyardTriggeredAbility ability) { - super(ability); - this.filterPermanent = ability.filterPermanent; - this.onlyToControllerGraveyard = ability.onlyToControllerGraveyard; - this.itDies = ability.itDies; - } - - @Override - public AttackingCreaturePutIntoGraveyardTriggeredAbility copy() { - return new AttackingCreaturePutIntoGraveyardTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - switch (event.getType()) { - case ATTACKER_DECLARED: - case END_COMBAT_STEP_POST: - case ZONE_CHANGE: - case REMOVED_FROM_COMBAT: - return true; - default: - return false; - } - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - switch (event.getType()) { - case ATTACKER_DECLARED: - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null - && !filterPermanent.match(permanent, game)) { - return false; - } - List attackersList = new ArrayList<>(); - List attackersListCopy = (List) game.getState().getValue(this.getSourceId() + "Attackers"); - if (attackersListCopy == null) { - attackersListCopy = attackersList; - } - attackersListCopy.add(event.getSourceId()); // add the filtered creature to the list - game.getState().setValue(this.getSourceId() + "Attackers", attackersListCopy); - return false; - case END_COMBAT_STEP_POST: - game.getState().setValue(this.getSourceId() + "Attackers", null); - return false; - case ZONE_CHANGE: - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD - && zEvent.getToZone() == Zone.GRAVEYARD) { - if (onlyToControllerGraveyard - && !this.isControlledBy(game.getOwnerId(zEvent.getTargetId()))) { - return false; - } - if (itDies - && !zEvent.isDiesEvent()) { - return false; - } - List attackers = (List) game.getState().getValue(this.getSourceId() + "Attackers"); - return attackers != null - && attackers.contains(zEvent.getTargetId()); - } - case REMOVED_FROM_COMBAT: - // a card removed from combat is no longer an attacker or blocker so remove it from the list - List attackersListRFC = (List) game.getState().getValue(this.getSourceId() + "Attackers"); - if (attackersListRFC != null - && attackersListRFC.contains(event.getTargetId())) { - attackersListRFC.remove(event.getTargetId()); - game.getState().setValue(this.getSourceId() + "Attackers", attackersListRFC); - } - - default: - return false; - } - } - - @Override - public String getTriggerPhrase() { - if (itDies) { - return "Whenever " + filterPermanent.getMessage() + " dies, "; - } - return "Whenever " + filterPermanent.getMessage() + " is put into " + (onlyToControllerGraveyard ? "your" : "a") - + " graveyard from the battlefield, "; - } - -} +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.abilities.common; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import static mage.game.events.GameEvent.EventType.END_COMBAT_STEP_POST; +import static mage.game.events.GameEvent.EventType.REMOVED_FROM_COMBAT; +import static mage.game.events.GameEvent.EventType.ZONE_CHANGE; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; + +/** + * + * @author weirddan455 and jeffwadsworth + */ +public class AttackingCreaturePutIntoGraveyardTriggeredAbility extends TriggeredAbilityImpl { + + protected FilterPermanent filterPermanent; + private final boolean onlyToControllerGraveyard; + private final boolean itDies; + + public AttackingCreaturePutIntoGraveyardTriggeredAbility(Effect effect, FilterPermanent filterPermanent, Boolean onlyToControllerGraveyard, Boolean itDies, Boolean optional) { + super(Zone.BATTLEFIELD, effect, optional); + this.filterPermanent = filterPermanent; + this.onlyToControllerGraveyard = onlyToControllerGraveyard; + this.itDies = itDies; + } + + private AttackingCreaturePutIntoGraveyardTriggeredAbility(final AttackingCreaturePutIntoGraveyardTriggeredAbility ability) { + super(ability); + this.filterPermanent = ability.filterPermanent; + this.onlyToControllerGraveyard = ability.onlyToControllerGraveyard; + this.itDies = ability.itDies; + } + + @Override + public AttackingCreaturePutIntoGraveyardTriggeredAbility copy() { + return new AttackingCreaturePutIntoGraveyardTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + switch (event.getType()) { + case ATTACKER_DECLARED: + case END_COMBAT_STEP_POST: + case ZONE_CHANGE: + case REMOVED_FROM_COMBAT: + return true; + default: + return false; + } + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + switch (event.getType()) { + case ATTACKER_DECLARED: + Permanent permanent = game.getPermanent(event.getSourceId()); + if (permanent != null + && !filterPermanent.match(permanent, game)) { + return false; + } + List attackersList = new ArrayList<>(); + List attackersListCopy = (List) game.getState().getValue(this.getSourceId() + "Attackers"); + if (attackersListCopy == null) { + attackersListCopy = attackersList; + } + attackersListCopy.add(event.getSourceId()); // add the filtered creature to the list + game.getState().setValue(this.getSourceId() + "Attackers", attackersListCopy); + return false; + case END_COMBAT_STEP_POST: + game.getState().setValue(this.getSourceId() + "Attackers", null); + return false; + case ZONE_CHANGE: + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getFromZone() == Zone.BATTLEFIELD + && zEvent.getToZone() == Zone.GRAVEYARD) { + if (onlyToControllerGraveyard + && !this.isControlledBy(game.getOwnerId(zEvent.getTargetId()))) { + return false; + } + if (itDies + && !zEvent.isDiesEvent()) { + return false; + } + List attackers = (List) game.getState().getValue(this.getSourceId() + "Attackers"); + return attackers != null + && attackers.contains(zEvent.getTargetId()); + } + case REMOVED_FROM_COMBAT: + // a card removed from combat is no longer an attacker or blocker so remove it from the list + List attackersListRFC = (List) game.getState().getValue(this.getSourceId() + "Attackers"); + if (attackersListRFC != null + && attackersListRFC.contains(event.getTargetId())) { + attackersListRFC.remove(event.getTargetId()); + game.getState().setValue(this.getSourceId() + "Attackers", attackersListRFC); + } + + default: + return false; + } + } + + @Override + public String getTriggerPhrase() { + if (itDies) { + return "Whenever " + filterPermanent.getMessage() + " dies, "; + } + return "Whenever " + filterPermanent.getMessage() + " is put into " + (onlyToControllerGraveyard ? "your" : "a") + + " graveyard from the battlefield, "; + } + +} diff --git a/Mage/src/main/java/mage/abilities/common/BeginningOfDrawTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BeginningOfDrawTriggeredAbility.java index 710d3c62e64..e5cd4e62640 100644 --- a/Mage/src/main/java/mage/abilities/common/BeginningOfDrawTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BeginningOfDrawTriggeredAbility.java @@ -11,7 +11,7 @@ import mage.target.targetpointer.FixedTarget; public class BeginningOfDrawTriggeredAbility extends TriggeredAbilityImpl { - private TargetController targetController; + private final TargetController targetController; /** * The Ability sets if no target is defined the target pointer to the active @@ -50,10 +50,8 @@ public class BeginningOfDrawTriggeredAbility extends TriggeredAbilityImpl { switch (targetController) { case YOU: boolean yours = event.getPlayerId().equals(this.controllerId); - if (yours) { - if (getTargets().isEmpty()) { - this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); - } + if (yours && getTargets().isEmpty()) { + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } return yours; case OPPONENT: @@ -84,6 +82,15 @@ public class BeginningOfDrawTriggeredAbility extends TriggeredAbilityImpl { } } break; + case ENCHANTED: + Permanent permanent = getSourcePermanentIfItStillExists(game); + if (permanent == null || !game.isActivePlayer(permanent.getAttachedTo())) { + break; + } + if (getTargets().isEmpty()) { + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); + } + return true; case ANY: case ACTIVE: if (getTargets().isEmpty()) { @@ -108,6 +115,8 @@ public class BeginningOfDrawTriggeredAbility extends TriggeredAbilityImpl { return "At the beginning of each player's draw step, " + generateZoneString(); case CONTROLLER_ATTACHED_TO: return "At the beginning of the draw step of enchanted creature's controller, " + generateZoneString(); + case ENCHANTED: + return "At the beginning of enchanted player's draw step, " + generateZoneString(); } return ""; } diff --git a/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java index fa0b18c43df..f63bd155bf2 100644 --- a/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java @@ -46,20 +46,14 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl { switch (targetController) { case YOU: boolean yours = event.getPlayerId().equals(this.controllerId); - if (yours) { - if (getTargets().isEmpty()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } - } + if (yours && getTargets().isEmpty()) { + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } return yours; case OPPONENT: if (game.getPlayer(this.controllerId).hasOpponent(event.getPlayerId(), game)) { if (getTargets().isEmpty()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } return true; } @@ -68,24 +62,31 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl { case EACH_PLAYER: case NEXT: if (getTargets().isEmpty()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } return true; case CONTROLLER_ATTACHED_TO: Permanent attachment = game.getPermanent(sourceId); - if (attachment != null && attachment.getAttachedTo() != null) { - Permanent attachedTo = game.getPermanent(attachment.getAttachedTo()); - if (attachedTo != null && attachedTo.isControlledBy(event.getPlayerId())) { - if (getTargets().isEmpty()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } - } - return true; - } + if (attachment == null || attachment.getAttachedTo() == null) { + break; } + Permanent attachedTo = game.getPermanent(attachment.getAttachedTo()); + if (attachedTo == null || !attachedTo.isControlledBy(event.getPlayerId())) { + break; + } + if (getTargets().isEmpty()) { + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); + } + return true; + case ENCHANTED: + Permanent permanent = getSourcePermanentIfItStillExists(game); + if (permanent == null || !game.isActivePlayer(permanent.getAttachedTo())) { + break; + } + if (getTargets().isEmpty()) { + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); + } + return true; } return false; } @@ -113,6 +114,8 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl { return "At the beginning of each player's end step, " + generateConditionString(); case CONTROLLER_ATTACHED_TO: return "At the beginning of the end step of enchanted permanent's controller, " + generateConditionString(); + case ENCHANTED: + return "At the beginning of enchanted player's draw step, " + generateConditionString(); } return ""; } diff --git a/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepAttachedTriggeredAbility.java deleted file mode 100644 index ed24f7e994a..00000000000 --- a/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepAttachedTriggeredAbility.java +++ /dev/null @@ -1,63 +0,0 @@ -package mage.abilities.common; - -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; - -/** - * @author TheElk801 - */ -public class BeginningOfUpkeepAttachedTriggeredAbility extends TriggeredAbilityImpl { - - private final boolean setTargetPointer; - - public BeginningOfUpkeepAttachedTriggeredAbility(Effect effect) { - this(effect, false); - } - - public BeginningOfUpkeepAttachedTriggeredAbility(Effect effect, boolean optional) { - this(effect, optional, true); - } - - public BeginningOfUpkeepAttachedTriggeredAbility(Effect effect, boolean optional, boolean setTargetPointer) { - super(Zone.BATTLEFIELD, effect, optional); - this.setTargetPointer = setTargetPointer; - } - - private BeginningOfUpkeepAttachedTriggeredAbility(final BeginningOfUpkeepAttachedTriggeredAbility ability) { - super(ability); - this.setTargetPointer = ability.setTargetPointer; - } - - @Override - public BeginningOfUpkeepAttachedTriggeredAbility copy() { - return new BeginningOfUpkeepAttachedTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent enchantment = getSourcePermanentOrLKI(game); - if (enchantment == null || !game.isActivePlayer(enchantment.getAttachedTo())) { - return false; - } - if (setTargetPointer) { - this.getEffects().setTargetPointer(new FixedTarget(enchantment.getAttachedTo())); - } - this.getEffects().setValue("enchantedPlayer", enchantment.getAttachedTo()); - return true; - } - - @Override - public String getTriggerPhrase() { - return "At the beginning of enchanted player's upkeep, " ; - } -} diff --git a/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepTriggeredAbility.java index afd3a00a7ab..7f49922366e 100644 --- a/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BeginningOfUpkeepTriggeredAbility.java @@ -14,8 +14,8 @@ import mage.target.targetpointer.FixedTarget; */ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl { - private TargetController targetController; - private boolean setTargetPointer; + private final TargetController targetController; + private final boolean setTargetPointer; protected String ruleTrigger; public BeginningOfUpkeepTriggeredAbility(Effect effect, TargetController targetController, boolean isOptional) { @@ -59,30 +59,20 @@ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl { switch (targetController) { case YOU: boolean yours = event.getPlayerId().equals(this.controllerId); - if (yours && setTargetPointer) { - if (getTargets().isEmpty()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } - } + if (yours && setTargetPointer && getTargets().isEmpty()) { + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } return yours; case NOT_YOU: boolean notYours = !event.getPlayerId().equals(this.controllerId); - if (notYours && setTargetPointer) { - if (getTargets().isEmpty()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } - } + if (notYours && setTargetPointer && getTargets().isEmpty()) { + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } return notYours; case OPPONENT: if (game.getPlayer(this.controllerId).hasOpponent(event.getPlayerId(), game)) { if (setTargetPointer && getTargets().isEmpty()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } return true; } @@ -91,9 +81,7 @@ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl { case ACTIVE: case EACH_PLAYER: if (setTargetPointer && getTargets().isEmpty()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } return true; case CONTROLLER_ATTACHED_TO: @@ -102,16 +90,23 @@ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl { Permanent attachedTo = game.getPermanent(attachment.getAttachedTo()); if (attachedTo != null && attachedTo.isControlledBy(event.getPlayerId())) { if (setTargetPointer && getTargets().isEmpty()) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } return true; } } break; + case ENCHANTED: + Permanent permanent = getSourcePermanentIfItStillExists(game); + if (permanent == null || !game.isActivePlayer(permanent.getAttachedTo())) { + break; + } + if (setTargetPointer && getTargets().isEmpty()) { + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); + } + return true; default: - throw new UnsupportedOperationException("Value for targetController not supported: " + targetController.toString()); + throw new UnsupportedOperationException("Value for targetController not supported: " + targetController); } return false; } @@ -133,6 +128,8 @@ public class BeginningOfUpkeepTriggeredAbility extends TriggeredAbilityImpl { return "At the beginning of each upkeep, " + generateZoneString(); case CONTROLLER_ATTACHED_TO: return "At the beginning of the upkeep of enchanted creature's controller, " + generateZoneString(); + case ENCHANTED: + return "At the beginning of enchanted player's upkeep, " + generateZoneString(); } return ""; } diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java index b2a50fa6b6e..8ef3f6d6725 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java @@ -58,13 +58,14 @@ public class DealtDamageAndDiedTriggeredAbility extends TriggeredAbilityImpl { if (filter.match(zEvent.getTarget(), game)) { boolean damageDealt = false; for (MageObjectReference mor : zEvent.getTarget().getDealtDamageByThisTurn()) { - if (mor.refersTo(getSourceObject(game), game)) { + if (mor.refersTo(game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD), game) + || (mor.refersTo(getSourceObject(game), game))) { damageDealt = true; break; } } if (damageDealt) { - if(this.setTargetPointer == SetTargetPointer.PERMANENT) { + if (this.setTargetPointer == SetTargetPointer.PERMANENT) { for (Effect effect : getEffects()) { effect.setTargetPointer(new FixedTarget(event.getTargetId())); } @@ -78,6 +79,6 @@ public class DealtDamageAndDiedTriggeredAbility extends TriggeredAbilityImpl { @Override public String getTriggerPhrase() { - return "Whenever a " + filter.getMessage() + " dealt damage by {this} this turn dies, " ; + return "Whenever a " + filter.getMessage() + " dealt damage by {this} this turn dies, "; } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java index 1fc842ee8b7..bccded75fcf 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java @@ -4,7 +4,8 @@ import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; @@ -14,14 +15,14 @@ import mage.game.events.ZoneChangeEvent; */ public class DiesThisOrAnotherCreatureTriggeredAbility extends TriggeredAbilityImpl { - protected FilterCreaturePermanent filter; + protected FilterPermanent filter; private boolean applyFilterOnSource = false; public DiesThisOrAnotherCreatureTriggeredAbility(Effect effect, boolean optional) { - this(effect, optional, new FilterCreaturePermanent()); + this(effect, optional, StaticFilters.FILTER_PERMANENT_CREATURE); } - public DiesThisOrAnotherCreatureTriggeredAbility(Effect effect, boolean optional, FilterCreaturePermanent filter) { + public DiesThisOrAnotherCreatureTriggeredAbility(Effect effect, boolean optional, FilterPermanent filter) { super(Zone.ALL, effect, optional); // Needs "ALL" if the source itself should trigger or multiple (incl. source go to grave) this.filter = filter; } @@ -63,7 +64,7 @@ public class DiesThisOrAnotherCreatureTriggeredAbility extends TriggeredAbilityI } return false; } - + @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); @@ -71,6 +72,6 @@ public class DiesThisOrAnotherCreatureTriggeredAbility extends TriggeredAbilityI @Override public String getTriggerPhrase() { - return "Whenever {this} or another " + filter.getMessage() + " dies, " ; + return "Whenever {this} or another " + filter.getMessage() + " dies, "; } } diff --git a/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java index 1280f6d25f8..25494195784 100644 --- a/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java @@ -25,6 +25,8 @@ public class ExploitCreatureTriggeredAbility extends TriggeredAbilityImpl { public ExploitCreatureTriggeredAbility(Effect effect, boolean optional, SetTargetPointer setTargetPointer) { super(Zone.BATTLEFIELD, effect, optional); this.setTargetPointer = setTargetPointer; + // For example: if the creature with the Exploit ability is sacrificed, the trigger ability must use the controller of the LKI, not the owner + setLeavesTheBattlefieldTrigger(true); // https://github.com/magefree/mage/issues/8317 } public ExploitCreatureTriggeredAbility(final ExploitCreatureTriggeredAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/common/SacrificePermanentTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/SacrificePermanentTriggeredAbility.java index 3a3c5dc2855..8747f3c093d 100644 --- a/Mage/src/main/java/mage/abilities/common/SacrificePermanentTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/SacrificePermanentTriggeredAbility.java @@ -7,6 +7,7 @@ import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; /** @@ -50,14 +51,16 @@ public class SacrificePermanentTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (isControlledBy(event.getPlayerId()) - && filter.match(game.getPermanentOrLKIBattlefield(event.getTargetId()), getSourceId(), getControllerId(), game)) { - if (setTargetPointer) { - this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); - } - return true; + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (!isControlledBy(event.getPlayerId()) || permanent == null + || !filter.match(permanent, getSourceId(), getControllerId(), game)) { + return false; } - return false; + this.getEffects().setValue("sacrificedPermanent", permanent); + if (setTargetPointer) { + this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); + } + return true; } @Override diff --git a/Mage/src/main/java/mage/abilities/condition/common/DidNotAttackThisTurnEnchantedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/DidNotAttackThisTurnEnchantedCondition.java index 80fc43a4527..a45b5d5eaf7 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/DidNotAttackThisTurnEnchantedCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/DidNotAttackThisTurnEnchantedCondition.java @@ -1,31 +1,31 @@ -package mage.abilities.condition.common; - -import mage.MageObjectReference; -import mage.abilities.Ability; -import mage.abilities.condition.Condition; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.watchers.common.AttackedThisTurnWatcher; - -/** - * - * @author jeffwadsworth - */ -public enum DidNotAttackThisTurnEnchantedCondition implements Condition { - - instance; - - @Override - public boolean apply(Game game, Ability source) { - Permanent auraPermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); - if (auraPermanent != null) { - Permanent enchantedPermanent = game.getPermanent(auraPermanent.getAttachedTo()); - AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class); - return enchantedPermanent != null - && watcher != null - && !watcher.getAttackedThisTurnCreatures().contains( - new MageObjectReference(enchantedPermanent, game)); - } - return false; - } -} +package mage.abilities.condition.common; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.watchers.common.AttackedThisTurnWatcher; + +/** + * + * @author jeffwadsworth + */ +public enum DidNotAttackThisTurnEnchantedCondition implements Condition { + + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent auraPermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (auraPermanent != null) { + Permanent enchantedPermanent = game.getPermanent(auraPermanent.getAttachedTo()); + AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class); + return enchantedPermanent != null + && watcher != null + && !watcher.getAttackedThisTurnCreatures().contains( + new MageObjectReference(enchantedPermanent, game)); + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/NightCondition.java b/Mage/src/main/java/mage/abilities/condition/common/NightCondition.java new file mode 100644 index 00000000000..29e5c709d64 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/NightCondition.java @@ -0,0 +1,23 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; + +/** + * @author TheElk801 + * TODO: Implement this + */ +public enum NightCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public String toString() { + return "it's night"; + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/common/DiscardHandCost.java b/Mage/src/main/java/mage/abilities/costs/common/DiscardHandCost.java index ec4c6a7c9fe..74dbc145f3b 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/DiscardHandCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/DiscardHandCost.java @@ -44,6 +44,6 @@ public class DiscardHandCost extends CostImpl { @Override public String getText() { - return "Discard your hand"; + return "discard your hand"; } } \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/costs/common/RemoveVariableCountersTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/RemoveVariableCountersTargetCost.java index 0b017a680e6..2bf8e091e15 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RemoveVariableCountersTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RemoveVariableCountersTargetCost.java @@ -31,16 +31,25 @@ public class RemoveVariableCountersTargetCost extends VariableCostImpl { } public RemoveVariableCountersTargetCost(FilterPermanent filter, CounterType counterTypeToRemove, String xText, int minValue) { + this(filter, counterTypeToRemove, xText, minValue, null); + } + + public RemoveVariableCountersTargetCost(FilterPermanent filter, CounterType counterTypeToRemove, String xText, int minValue, String text) { super(VariableCostType.NORMAL, xText, new StringBuilder(counterTypeToRemove != null ? counterTypeToRemove.getName() + ' ' : "").append("counters to remove").toString()); this.filter = filter; this.counterTypeToRemove = counterTypeToRemove; - this.text = setText(); + if (text != null && !text.isEmpty()) { + this.text = text; + } else { + this.text = setText(); + } this.minValue = minValue; } public RemoveVariableCountersTargetCost(final RemoveVariableCountersTargetCost cost) { super(cost); this.filter = cost.filter; + this.counterTypeToRemove = cost.counterTypeToRemove; this.minValue = cost.minValue; } @@ -63,6 +72,16 @@ public class RemoveVariableCountersTargetCost extends VariableCostImpl { return new RemoveVariableCountersTargetCost(this); } + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + return getMaxValue(source, game) >= minValue; + } + + @Override + public int getMinValue(Ability source, Game game) { + return minValue; + } + @Override public int getMaxValue(Ability source, Game game) { int maxValue = 0; diff --git a/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java index 19a54ee2c90..08cbf858f45 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java @@ -1,4 +1,3 @@ - package mage.abilities.costs.common; import mage.abilities.Ability; @@ -7,6 +6,7 @@ import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; import mage.constants.AbilityType; import mage.constants.Outcome; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetControlledPermanent; @@ -23,6 +23,10 @@ public class SacrificeTargetCost extends CostImpl { private final List permanents = new ArrayList<>(); + public SacrificeTargetCost(FilterControlledPermanent filter) { + this(new TargetControlledPermanent(filter)); + } + public SacrificeTargetCost(TargetControlledPermanent target) { this.addTarget(target); target.setNotTarget(true); // sacrifice is never targeted @@ -88,7 +92,7 @@ public class SacrificeTargetCost extends CostImpl { } } // solves issue #8097, if a sacrifice cost is optional and you don't have valid targets, then the cost can be paid - if(validTargets == 0 && targets.get(0).getMinNumberOfTargets() == 0){ + if (validTargets == 0 && targets.get(0).getMinNumberOfTargets() == 0) { return true; } return false; diff --git a/Mage/src/main/java/mage/abilities/costs/common/SacrificeXTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/SacrificeXTargetCost.java index a2b043a3365..27d6a1b3f46 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/SacrificeXTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/SacrificeXTargetCost.java @@ -4,6 +4,7 @@ import mage.abilities.Ability; import mage.abilities.costs.Cost; import mage.abilities.costs.VariableCostImpl; import mage.abilities.costs.VariableCostType; +import mage.filter.Filter; import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.target.common.TargetControlledPermanent; @@ -46,5 +47,9 @@ public class SacrificeXTargetCost extends VariableCostImpl { TargetControlledPermanent target = new TargetControlledPermanent(xValue, xValue, filter, true); return new SacrificeTargetCost(target); } + + public Filter getFilter() { + return filter; + } } diff --git a/Mage/src/main/java/mage/abilities/costs/costadjusters/CommanderManaValueAdjuster.java b/Mage/src/main/java/mage/abilities/costs/costadjusters/CommanderManaValueAdjuster.java new file mode 100644 index 00000000000..df1c58ca0ad --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/costadjusters/CommanderManaValueAdjuster.java @@ -0,0 +1,35 @@ +package mage.abilities.costs.costadjusters; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.costs.CostAdjuster; +import mage.constants.CommanderCardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +/** + * @author TheElk801 + */ +public enum CommanderManaValueAdjuster implements CostAdjuster { + instance; + + @Override + public void adjustCosts(Ability ability, Game game) { + Player player = game.getPlayer(ability.getControllerId()); + if (player == null) { + return; + } + int maxValue = game + .getCommanderCardsFromAnyZones( + player, CommanderCardType.ANY, + Zone.BATTLEFIELD, Zone.COMMAND + ) + .stream() + .mapToInt(MageObject::getManaValue) + .max() + .orElse(0); + CardUtil.reduceCost(ability, maxValue); + } +} diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java index 2ad82b0b8be..fdaee170b57 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java @@ -65,6 +65,19 @@ public class ConditionalAsThoughEffect extends AsThoughEffectImpl { return false; } + @Override + public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + conditionState = condition.apply(game, source); + if (conditionState) { + effect.setTargetPointer(this.targetPointer); + return effect.applies(sourceId, affectedAbility, source, game, playerId); + } else if (otherwiseEffect != null) { + otherwiseEffect.setTargetPointer(this.targetPointer); + return otherwiseEffect.applies(sourceId, affectedAbility, source, game, playerId); + } + return false; + } + @Override public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { conditionState = condition.apply(game, source); diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java index 357d41588e1..d4904a82f60 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java @@ -9,7 +9,6 @@ import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; import mage.constants.*; import mage.game.Game; -import org.junit.Assert; import java.util.*; @@ -48,13 +47,13 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl { // checks for compatibility EffectType needType = EffectType.CONTINUOUS; if (effect.getEffectType() != needType) { - Assert.fail("ConditionalContinuousEffect supports only " + needType.toString() + " but found " + effect.getEffectType().toString()); + throw new IllegalArgumentException("ConditionalContinuousEffect supports only " + needType + " but found " + effect.getEffectType().toString()); } if (otherwiseEffect != null && otherwiseEffect.getEffectType() != needType) { - Assert.fail("ConditionalContinuousEffect supports only " + needType.toString() + " but found " + effect.getEffectType().toString()); + throw new IllegalArgumentException("ConditionalContinuousEffect supports only " + needType.toString() + " but found " + effect.getEffectType().toString()); } if (otherwiseEffect != null && effect.getEffectType() != otherwiseEffect.getEffectType()) { - Assert.fail("ConditionalContinuousEffect must be same but found " + effect.getEffectType().toString() + " and " + otherwiseEffect.getEffectType().toString()); + throw new IllegalArgumentException("ConditionalContinuousEffect must be same but found " + effect.getEffectType().toString() + " and " + otherwiseEffect.getEffectType().toString()); } } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ControllerLifeDividedValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ControllerLifeDividedValue.java index d6a3138440b..351dcea28bf 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ControllerLifeDividedValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ControllerLifeDividedValue.java @@ -1,48 +1,48 @@ -package mage.abilities.dynamicvalue.common; - -import mage.abilities.Ability; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; -import mage.game.Game; -import mage.players.Player; - -/** - * - * @author LevelX2 - */ -public class ControllerLifeDividedValue implements DynamicValue { - - private final Integer divider; - - public ControllerLifeDividedValue(Integer divider) { - this.divider = divider; - } - - public ControllerLifeDividedValue(final ControllerLifeDividedValue dynamicValue) { - this.divider = dynamicValue.divider; - } - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - Player p = game.getPlayer(sourceAbility.getControllerId()); - if (p != null) { - return p.getLife() / divider; - } - return 0; - } - - @Override - public ControllerLifeDividedValue copy() { - return new ControllerLifeDividedValue(this); - } - - @Override - public String toString() { - return "X"; - } - - @Override - public String getMessage() { - return ""; - } -} +package mage.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class ControllerLifeDividedValue implements DynamicValue { + + private final Integer divider; + + public ControllerLifeDividedValue(Integer divider) { + this.divider = divider; + } + + public ControllerLifeDividedValue(final ControllerLifeDividedValue dynamicValue) { + this.divider = dynamicValue.divider; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Player p = game.getPlayer(sourceAbility.getControllerId()); + if (p != null) { + return p.getLife() / divider; + } + return 0; + } + + @Override + public ControllerLifeDividedValue copy() { + return new ControllerLifeDividedValue(this); + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return ""; + } +} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/HighestCMCOfPermanentValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/HighestCMCOfPermanentValue.java index 338e4b9b51f..65bac591598 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/HighestCMCOfPermanentValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/HighestCMCOfPermanentValue.java @@ -1,62 +1,62 @@ -package mage.abilities.dynamicvalue.common; - -import mage.abilities.Ability; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; -import mage.filter.FilterPermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; - -/** - * - * @author LevelX2 - */ -public class HighestCMCOfPermanentValue implements DynamicValue { - - private final FilterPermanent filter; - private final boolean onlyIfCanBeSacrificed; - - public HighestCMCOfPermanentValue(FilterPermanent filter, boolean onlyIfCanBeSacrificed) { - super(); - this.filter = filter; - this.onlyIfCanBeSacrificed = onlyIfCanBeSacrificed; - } - - public HighestCMCOfPermanentValue(final HighestCMCOfPermanentValue dynamicValue) { - this.filter = dynamicValue.filter; - this.onlyIfCanBeSacrificed = dynamicValue.onlyIfCanBeSacrificed; - } - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - int value = 0; - Player controller = game.getPlayer(sourceAbility.getControllerId()); - if (controller != null) { - for (Permanent permanent : game.getBattlefield() - .getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility.getSourceId(), game)) { - if ((!onlyIfCanBeSacrificed || controller.canPaySacrificeCost(permanent, sourceAbility, sourceAbility.getControllerId(), game)) - && permanent.getManaValue() > value) { - value = permanent.getManaValue(); - } - - } - } - return value; - } - - @Override - public HighestCMCOfPermanentValue copy() { - return new HighestCMCOfPermanentValue(this); - } - - @Override - public String toString() { - return "X"; - } - - @Override - public String getMessage() { - return filter.getMessage(); - } -} +package mage.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class HighestCMCOfPermanentValue implements DynamicValue { + + private final FilterPermanent filter; + private final boolean onlyIfCanBeSacrificed; + + public HighestCMCOfPermanentValue(FilterPermanent filter, boolean onlyIfCanBeSacrificed) { + super(); + this.filter = filter; + this.onlyIfCanBeSacrificed = onlyIfCanBeSacrificed; + } + + public HighestCMCOfPermanentValue(final HighestCMCOfPermanentValue dynamicValue) { + this.filter = dynamicValue.filter; + this.onlyIfCanBeSacrificed = dynamicValue.onlyIfCanBeSacrificed; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + int value = 0; + Player controller = game.getPlayer(sourceAbility.getControllerId()); + if (controller != null) { + for (Permanent permanent : game.getBattlefield() + .getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility.getSourceId(), game)) { + if ((!onlyIfCanBeSacrificed || controller.canPaySacrificeCost(permanent, sourceAbility, sourceAbility.getControllerId(), game)) + && permanent.getManaValue() > value) { + value = permanent.getManaValue(); + } + + } + } + return value; + } + + @Override + public HighestCMCOfPermanentValue copy() { + return new HighestCMCOfPermanentValue(this); + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return filter.getMessage(); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java b/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java index bc4d6d4f6c2..e3e5beb5556 100644 --- a/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java @@ -15,6 +15,7 @@ public interface AsThoughEffect extends ContinuousEffect { * Apply to ONE affected ability from the object (sourceId) *

* Warning, if you don't need ability to check then ignore it (by default it calls full object check) + * Warning, if you use conditional effect then you must override both applies methods to support different types * * @param sourceId * @param affectedAbility ability to check (example: check if spell ability can be cast from non hand) diff --git a/Mage/src/main/java/mage/abilities/effects/common/CastCardFromOutsideTheGameEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CastCardFromOutsideTheGameEffect.java index a566afcc6ac..4b032cfc69c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CastCardFromOutsideTheGameEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CastCardFromOutsideTheGameEffect.java @@ -72,7 +72,9 @@ public class CastCardFromOutsideTheGameEffect extends OneShotEffect { if (player.choose(Outcome.Benefit, filteredCards, target, game)) { Card card = player.getSideboard().get(target.getFirstTarget(), game); if (card != null) { - player.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + player.cast(player.chooseAbilityForCast(card, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); } } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java index f090a430243..2e654033f6b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java @@ -33,7 +33,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { private final UUID playerId; private final CardType additionalCardType; private boolean hasHaste; - private final int number; + private int number; private final List addedTokenPermanents; private SubType additionalSubType; private SubType onlySubType; @@ -302,6 +302,10 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { this.startingLoyalty = startingLoyalty; } + public void setNumber(int number) { + this.number = number; + } + public void exileTokensCreatedAtNextEndStep(Game game, Ability source) { for (Permanent tokenPermanent : addedTokenPermanents) { ExileTargetEffect exileEffect = new ExileTargetEffect(null, "", Zone.BATTLEFIELD); diff --git a/Mage/src/main/java/mage/abilities/effects/common/DiscardCardControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/effects/common/DiscardCardControllerTriggeredAbility.java index 0a0914953c1..0d7a295b6cd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DiscardCardControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DiscardCardControllerTriggeredAbility.java @@ -11,7 +11,6 @@ import mage.game.events.GameEvent; /** * @author TheElk801 */ - public class DiscardCardControllerTriggeredAbility extends TriggeredAbilityImpl { private final FilterCard filter; @@ -48,6 +47,6 @@ public class DiscardCardControllerTriggeredAbility extends TriggeredAbilityImpl @Override public String getTriggerPhrase() { - return "Whenever you discard " + filter.getMessage() + ", " ; + return "Whenever you discard " + filter.getMessage() + ", "; } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/HideawayPlayEffect.java b/Mage/src/main/java/mage/abilities/effects/common/HideawayPlayEffect.java index 63772bab379..ec54ab19a98 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/HideawayPlayEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/HideawayPlayEffect.java @@ -67,7 +67,7 @@ public class HideawayPlayEffect extends OneShotEffect { } } - if (!controller.playCard(card, game, true, true, new ApprovingObject(source, game))) { + if (!controller.playCard(card, game, true, new ApprovingObject(source, game))) { if (card.getZoneChangeCounter(game) == zcc) { card.setFaceDown(true, game); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java index 860bfef7ed2..617f651e263 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java @@ -22,19 +22,26 @@ import java.util.UUID; public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect { private final boolean tapped; + private final boolean attacking; public ReturnFromGraveyardToBattlefieldTargetEffect() { this(false); } public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped) { + this(tapped, false); + } + + public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped, boolean attacking) { super(Outcome.PutCreatureInPlay); this.tapped = tapped; + this.attacking = attacking; } protected ReturnFromGraveyardToBattlefieldTargetEffect(final ReturnFromGraveyardToBattlefieldTargetEffect effect) { super(effect); this.tapped = effect.tapped; + this.attacking = effect.attacking; } @Override @@ -54,6 +61,11 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect } } controller.moveCards(cardsToMove, Zone.BATTLEFIELD, source, game, tapped, false, false, null); + if (attacking) { + for (Card card : cardsToMove) { + game.getCombat().addAttackingCreature(card.getId(), game); + } + } return true; } return false; @@ -82,8 +94,12 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect } sb.append(yourGrave ? " to" : " onto"); sb.append(" the battlefield"); - if (tapped) { + if (tapped && attacking) { + sb.append(" tapped and attacking"); + } else if (tapped) { sb.append(" tapped"); + } else if (attacking) { + sb.append(" attacking"); } if (!yourGrave) { sb.append(" under your control"); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandChosenControlledPermanentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandChosenControlledPermanentEffect.java index 0af04bc69fe..1f7e6ae523e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandChosenControlledPermanentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandChosenControlledPermanentEffect.java @@ -38,8 +38,9 @@ public class ReturnToHandChosenControlledPermanentEffect extends ReturnToHandCho @Override protected String getText() { - StringBuilder sb = new StringBuilder("return "); + StringBuilder sb = new StringBuilder("return"); if (!filter.getMessage().startsWith("another")) { + sb.append(' '); if(filter.getMessage().startsWith("a")){ sb.append("an"); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java index c9e7f2f5c4e..9386508f97b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java @@ -85,6 +85,8 @@ public class PlayFromNotOwnHandZoneTargetEffect extends AsThoughEffectImpl { if (affectedAbility == null) { // ContinuousEffects.asThough already checks affectedAbility, so that error must never be called here // PLAY_FROM_NOT_OWN_HAND_ZONE must applies to affectedAbility only + // If you see it then parent conditional effect must override both applies methods to support different + // AsThough effect types in one conditional effect throw new IllegalArgumentException("ERROR, can't call applies method on empty affectedAbility"); } diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java index 41f7b3100f8..8a6de130bf8 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java @@ -60,7 +60,7 @@ public class InvestigateEffect extends OneShotEffect { default: message = CardUtil.numberToText(amount) + " times. (To investigate, c"; } - return "investigate " + message + "reate a colorless Clue artifact token " + + return "investigate" + message + "reate a colorless Clue artifact token " + "with \"{2}, Sacrifice this artifact: Draw a card.\")"; } } diff --git a/Mage/src/main/java/mage/abilities/hint/common/NightHint.java b/Mage/src/main/java/mage/abilities/hint/common/NightHint.java new file mode 100644 index 00000000000..afc7f2631dd --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/common/NightHint.java @@ -0,0 +1,27 @@ +package mage.abilities.hint.common; + +import mage.abilities.Ability; +import mage.abilities.condition.common.NightCondition; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.game.Game; + +/** + * @author TheElk801 + */ +public enum NightHint implements Hint { + instance; + private static final Hint hint = new ConditionHint( + NightCondition.instance, "It's currently night" + ); + + @Override + public String getText(Game game, Ability ability) { + return hint.getText(game, ability); + } + + @Override + public Hint copy() { + return this; + } +} diff --git a/Mage/src/main/java/mage/abilities/icon/abilities/HexproofAbilityIcon.java b/Mage/src/main/java/mage/abilities/icon/abilities/HexproofAbilityIcon.java index 1872ad3d79e..5f6c29e013f 100644 --- a/Mage/src/main/java/mage/abilities/icon/abilities/HexproofAbilityIcon.java +++ b/Mage/src/main/java/mage/abilities/icon/abilities/HexproofAbilityIcon.java @@ -1,7 +1,9 @@ package mage.abilities.icon.abilities; import mage.abilities.icon.CardIcon; +import mage.abilities.icon.CardIconImpl; import mage.abilities.icon.CardIconType; +import mage.util.CardUtil; /** * @author JayDi85 @@ -28,4 +30,8 @@ public enum HexproofAbilityIcon implements CardIcon { public CardIcon copy() { return instance; } + + public static CardIconImpl createDynamicCardIcon(String hint) { + return new CardIconImpl(CardIconType.ABILITY_HEXPROOF, CardUtil.getTextWithFirstCharUpperCase(hint)); + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/CascadeAbility.java b/Mage/src/main/java/mage/abilities/keyword/CascadeAbility.java index ae1c543c08c..3e459f37658 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CascadeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CascadeAbility.java @@ -20,23 +20,27 @@ import java.util.List; import java.util.stream.Collectors; /** - * Cascade - * A keyword ability that may let a player cast a random extra spell for no cost. See rule 702.84, “Cascade.” + * Cascade A keyword ability that may let a player cast a random extra spell for + * no cost. See rule 702.84, “Cascade.” *

* 702.84. Cascade *

- * 702.84a Cascade is a triggered ability that functions only while the spell with cascade is on the stack. - * “Cascade” means “When you cast this spell, exile cards from the top of your library until you exile a - * nonland card whose converted mana cost is less than this spell’s converted mana cost. You may cast that - * card without paying its mana cost. Then put all cards exiled this way that weren’t cast on the bottom - * of your library in a random order.” + * 702.84a Cascade is a triggered ability that functions only while the spell + * with cascade is on the stack. “Cascade” means “When you cast this spell, + * exile cards from the top of your library until you exile a nonland card whose + * converted mana cost is less than this spell’s converted mana cost. You may + * cast that card without paying its mana cost. Then put all cards exiled this + * way that weren’t cast on the bottom of your library in a random order.” *

- * 702.84b If an effect allows a player to take an action with one or more of the exiled cards “as you cascade,” - * the player may take that action after they have finished exiling cards due to the cascade ability. This action - * is taken before choosing whether to cast the last exiled card or, if no appropriate card was exiled, before - * putting the exiled cards on the bottom of their library in a random order. + * 702.84b If an effect allows a player to take an action with one or more of + * the exiled cards “as you cascade,” the player may take that action after they + * have finished exiling cards due to the cascade ability. This action is taken + * before choosing whether to cast the last exiled card or, if no appropriate + * card was exiled, before putting the exiled cards on the bottom of their + * library in a random order. *

- * 702.84c If a spell has multiple instances of cascade, each triggers separately. + * 702.84c If a spell has multiple instances of cascade, each triggers + * separately. * * @author BetaSteward_at_googlemail.com */ @@ -46,7 +50,6 @@ public class CascadeAbility extends TriggeredAbilityImpl { // can't use singletone due rules: // 702.84c If a spell has multiple instances of cascade, each triggers separately. - private static final String REMINDERTEXT = " (When you cast this spell, " + "exile cards from the top of your library until you exile a " + "nonland card whose mana value is less than this spell's mana value. " @@ -124,12 +127,15 @@ class CascadeEffect extends OneShotEffect { Card cardToCast = null; for (Card card : controller.getLibrary().getCards(game)) { cardsToExile.add(card); - if (!card.isLand(game) && card.getManaValue() < sourceCost) { + // the card move is sequential, not all at once. + controller.moveCards(card, Zone.EXILED, source, game); + game.getState().processAction(game); // Laelia, the Blade Reforged + if (!card.isLand(game) + && card.getManaValue() < sourceCost) { cardToCast = card; break; } } - controller.moveCards(cardsToExile, Zone.EXILED, source, game); controller.getLibrary().reset(); // set back empty draw state if that caused an empty draw // additional replacement effect: As you cascade, you may put a land card from among the exiled cards onto the battlefield tapped diff --git a/Mage/src/main/java/mage/abilities/keyword/DayboundAbility.java b/Mage/src/main/java/mage/abilities/keyword/DayboundAbility.java index 968846cc88c..1e8761d6b4c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/DayboundAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/DayboundAbility.java @@ -1,34 +1,20 @@ package mage.abilities.keyword; -import mage.abilities.MageSingleton; import mage.abilities.StaticAbility; import mage.constants.Zone; -import java.io.ObjectStreamException; - /** * @author TheElk801 * TODO: Implement this */ -public class DayboundAbility extends StaticAbility implements MageSingleton { +public class DayboundAbility extends StaticAbility { - private static final DayboundAbility instance; - - static { - instance = new DayboundAbility(); - // instance.addIcon(DayboundAbilityIcon.instance); (needs to be added) + public DayboundAbility() { + super(Zone.BATTLEFIELD, null); } - private Object readResolve() throws ObjectStreamException { - return instance; - } - - public static DayboundAbility getInstance() { - return instance; - } - - private DayboundAbility() { - super(Zone.ALL, null); + private DayboundAbility(final DayboundAbility ability) { + super(ability); } @Override @@ -38,6 +24,6 @@ public class DayboundAbility extends StaticAbility implements MageSingleton { @Override public DayboundAbility copy() { - return instance; + return new DayboundAbility(this); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java index 968ffeee61d..22ca67bc198 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java @@ -38,12 +38,12 @@ public class FlashbackAbility extends SpellAbility { private String abilityName; private SpellAbility spellAbilityToResolve; - public FlashbackAbility(Cost cost, TimingRule timingRule) { + public FlashbackAbility(Card card, Cost cost) { super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.FLASHBACK); this.setAdditionalCostsRuleVisible(false); this.name = "Flashback " + cost.getText(); this.addCost(cost); - this.timing = timingRule; + this.timing = card.isSorcery() ? TimingRule.SORCERY : TimingRule.INSTANT; } public FlashbackAbility(final FlashbackAbility ability) { @@ -177,8 +177,9 @@ public class FlashbackAbility extends SpellAbility { * * @param abilityName */ - public void setAbilityName(String abilityName) { + public FlashbackAbility setAbilityName(String abilityName) { this.abilityName = abilityName; + return this; } } diff --git a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java index 60929049109..045cfd70856 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java @@ -24,6 +24,7 @@ import mage.util.CardUtil; import mage.watchers.common.ForetoldWatcher; import java.util.UUID; +import mage.game.events.GameEvent; /** * @author jeffwadsworth @@ -119,7 +120,10 @@ public class ForetellAbility extends SpecialAction { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && card != null) { + + // get main card id UUID mainCardId = card.getMainCard().getId(); + // retrieve the exileId of the foretold card UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); @@ -139,6 +143,7 @@ public class ForetellAbility extends SpecialAction { effect.apply(game, source); card.setFaceDown(true, game); game.addEffect(new ForetellAddCostEffect(new MageObjectReference(card, game)), source); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.FORETELL, card.getId(), null, source.getControllerId())); return true; } return false; diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofAbility.java index eb42f64c80f..c5b9b0ddb62 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HexproofAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofAbility.java @@ -45,4 +45,9 @@ public class HexproofAbility extends HexproofBaseAbility { public String getRule() { return "hexproof"; } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from all"; + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofBaseAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofBaseAbility.java index dea7eea8176..94da34ba717 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HexproofBaseAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofBaseAbility.java @@ -1,11 +1,18 @@ package mage.abilities.keyword; import mage.MageObject; +import mage.ObjectColor; import mage.abilities.MageSingleton; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.icon.CardIcon; +import mage.abilities.icon.CardIconImpl; +import mage.abilities.icon.CardIconType; import mage.abilities.icon.abilities.HexproofAbilityIcon; import mage.constants.Zone; import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.*; /** * an abstract base class for hexproof abilities @@ -16,8 +23,59 @@ public abstract class HexproofBaseAbility extends SimpleStaticAbility implements HexproofBaseAbility() { super(Zone.BATTLEFIELD, null); - this.addIcon(HexproofAbilityIcon.instance); } public abstract boolean checkObject(MageObject source, Game game); + + public static Set getFromColor(ObjectColor color) { + Set abilities = new HashSet<>(); + if (color.isWhite()) { + abilities.add(HexproofFromWhiteAbility.getInstance()); + } + if (color.isBlue()) { + abilities.add(HexproofFromBlueAbility.getInstance()); + } + if (color.isBlack()) { + abilities.add(HexproofFromBlackAbility.getInstance()); + } + if (color.isRed()) { + abilities.add(HexproofFromRedAbility.getInstance()); + } + if (color.isGreen()) { + abilities.add(HexproofFromGreenAbility.getInstance()); + } + return abilities; + } + + public static HexproofBaseAbility getFirstFromColor(ObjectColor color) { + if (color.isWhite()) { + return HexproofFromWhiteAbility.getInstance(); + } else if (color.isBlue()) { + return HexproofFromBlueAbility.getInstance(); + } else if (color.isBlack()) { + return HexproofFromBlackAbility.getInstance(); + } else if (color.isRed()) { + return HexproofFromRedAbility.getInstance(); + } else if (color.isGreen()) { + return HexproofFromGreenAbility.getInstance(); + } else { + return null; + } + } + + public abstract String getCardIconHint(Game game); + + @Override + public List getIcons(Game game) { + if (game == null) { + return new ArrayList<>(Collections.singletonList( + HexproofAbilityIcon.instance + )); + } + + // dynamic icon (example: colored hexproof) + return new ArrayList<>(Collections.singletonList( + HexproofAbilityIcon.createDynamicCardIcon(getCardIconHint(game)) + )); + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofFromArtifactsCreaturesAndEnchantments.java b/Mage/src/main/java/mage/abilities/keyword/HexproofFromArtifactsCreaturesAndEnchantments.java index 2ce0b0eeb5c..0c808248b65 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HexproofFromArtifactsCreaturesAndEnchantments.java +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofFromArtifactsCreaturesAndEnchantments.java @@ -44,4 +44,9 @@ public class HexproofFromArtifactsCreaturesAndEnchantments extends HexproofBaseA public String getRule() { return "hexproof from artifacts, creatures, and enchantments"; } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from artifacts, creatures, and enchantments"; + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofFromBlackAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofFromBlackAbility.java index 82f2275563d..c731cb15e90 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HexproofFromBlackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofFromBlackAbility.java @@ -45,4 +45,9 @@ public class HexproofFromBlackAbility extends HexproofBaseAbility { public String getRule() { return "hexproof from black (This creature can't be the target of black spells or abilities your opponents control.)"; } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from black"; + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofFromBlueAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofFromBlueAbility.java index ac7317f0253..dcf3f449748 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HexproofFromBlueAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofFromBlueAbility.java @@ -45,4 +45,9 @@ public class HexproofFromBlueAbility extends HexproofBaseAbility { public String getRule() { return "hexproof from blue (This creature can't be the target of blue spells or abilities your opponents control.)"; } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from blue"; + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofFromGreenAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofFromGreenAbility.java new file mode 100644 index 00000000000..12f8768cfd3 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofFromGreenAbility.java @@ -0,0 +1,53 @@ +package mage.abilities.keyword; + +import mage.MageObject; +import mage.game.Game; + +import java.io.ObjectStreamException; + +/** + * Hexproof from green (This creature or player can't be the target of green + * spells or abilities your opponents control.) + * + * @author igoudt + */ +public class HexproofFromGreenAbility extends HexproofBaseAbility { + + private static final HexproofFromGreenAbility instance; + + static { + instance = new HexproofFromGreenAbility(); + } + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static HexproofFromGreenAbility getInstance() { + return instance; + } + + private HexproofFromGreenAbility() { + super(); + } + + @Override + public boolean checkObject(MageObject source, Game game) { + return source.getColor(game).isGreen(); + } + + @Override + public HexproofFromGreenAbility copy() { + return instance; + } + + @Override + public String getRule() { + return "hexproof from green (This creature can't be the target of green spells or abilities your opponents control.)"; + } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from green"; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofFromMonocoloredAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofFromMonocoloredAbility.java index 27a711692a6..4d5a24b0d70 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HexproofFromMonocoloredAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofFromMonocoloredAbility.java @@ -45,4 +45,9 @@ public class HexproofFromMonocoloredAbility extends HexproofBaseAbility { public String getRule() { return "hexproof from monocolored (This creature can't be the target of monocolored spells or abilities your opponents control.)"; } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from monocolored"; + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofFromPlaneswalkersAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofFromPlaneswalkersAbility.java index 0e30e798d90..e7fa893ba13 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HexproofFromPlaneswalkersAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofFromPlaneswalkersAbility.java @@ -44,4 +44,9 @@ public class HexproofFromPlaneswalkersAbility extends HexproofBaseAbility { public String getRule() { return "hexproof from planeswalkers"; } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from planeswalkers"; + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofFromRedAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofFromRedAbility.java new file mode 100644 index 00000000000..ef41e617983 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofFromRedAbility.java @@ -0,0 +1,53 @@ +package mage.abilities.keyword; + +import mage.MageObject; +import mage.game.Game; + +import java.io.ObjectStreamException; + +/** + * Hexproof from red (This creature or player can't be the target of red + * spells or abilities your opponents control.) + * + * @author igoudt + */ +public class HexproofFromRedAbility extends HexproofBaseAbility { + + private static final HexproofFromRedAbility instance; + + static { + instance = new HexproofFromRedAbility(); + } + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static HexproofFromRedAbility getInstance() { + return instance; + } + + private HexproofFromRedAbility() { + super(); + } + + @Override + public boolean checkObject(MageObject source, Game game) { + return source.getColor(game).isRed(); + } + + @Override + public HexproofFromRedAbility copy() { + return instance; + } + + @Override + public String getRule() { + return "hexproof from red (This creature can't be the target of red spells or abilities your opponents control.)"; + } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from red"; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/HexproofFromWhiteAbility.java b/Mage/src/main/java/mage/abilities/keyword/HexproofFromWhiteAbility.java index ec1d60a0206..d3dc9924b95 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HexproofFromWhiteAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HexproofFromWhiteAbility.java @@ -45,4 +45,9 @@ public class HexproofFromWhiteAbility extends HexproofBaseAbility { public String getRule() { return "hexproof from white (This creature can't be the target of white spells or abilities your opponents control.)"; } + + @Override + public String getCardIconHint(Game game) { + return "hexproof from white"; + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/MentorAbility.java b/Mage/src/main/java/mage/abilities/keyword/MentorAbility.java index e7fbee0fb73..6cfe91e6e9e 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MentorAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MentorAbility.java @@ -45,7 +45,7 @@ public class MentorAbility extends AttacksTriggeredAbility { } -enum MentorAbilityPredicate implements ObjectSourcePlayerPredicate> { +enum MentorAbilityPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/NightboundAbility.java b/Mage/src/main/java/mage/abilities/keyword/NightboundAbility.java index 13f232d50d8..804b63c83cc 100644 --- a/Mage/src/main/java/mage/abilities/keyword/NightboundAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/NightboundAbility.java @@ -1,34 +1,20 @@ package mage.abilities.keyword; -import mage.abilities.MageSingleton; import mage.abilities.StaticAbility; import mage.constants.Zone; -import java.io.ObjectStreamException; - /** * @author TheElk801 * TODO: Implement this */ -public class NightboundAbility extends StaticAbility implements MageSingleton { +public class NightboundAbility extends StaticAbility { - private static final NightboundAbility instance; - - static { - instance = new NightboundAbility(); - // instance.addIcon(NightboundAbilityIcon.instance); (needs to be added) + public NightboundAbility() { + super(Zone.BATTLEFIELD, null); } - private Object readResolve() throws ObjectStreamException { - return instance; - } - - public static NightboundAbility getInstance() { - return instance; - } - - private NightboundAbility() { - super(Zone.ALL, null); + private NightboundAbility(final NightboundAbility ability) { + super(ability); } @Override @@ -38,6 +24,6 @@ public class NightboundAbility extends StaticAbility implements MageSingleton { @Override public NightboundAbility copy() { - return instance; + return new NightboundAbility(this); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/RippleAbility.java b/Mage/src/main/java/mage/abilities/keyword/RippleAbility.java index 35d959a2ee4..d1c9ef37cc5 100644 --- a/Mage/src/main/java/mage/abilities/keyword/RippleAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/RippleAbility.java @@ -88,7 +88,7 @@ class RippleEffect extends OneShotEffect { if (!player.chooseUse(Outcome.Neutral, "Reveal " + rippleNumber + " cards from the top of your library?", source, game)) { return true; //fizzle } - // reveal to/**/p cards from library + // reveal top cards from library Cards cards = new CardsImpl(); cards.addAll(player.getLibrary().getTopCards(game, rippleNumber)); player.revealCards(sourceObject.getIdName(), cards, game); @@ -104,7 +104,10 @@ class RippleEffect extends OneShotEffect { while (player.canRespond() && cards.count(sameNameFilter, game) > 0 && player.choose(Outcome.PlayForFree, cards, target1, game)) { Card card = cards.get(target1.getFirstTarget(), game); if (card != null) { - player.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + player.cast(player.chooseAbilityForCast(card, game, true), game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); + cards.remove(card); } target1.clearChosen(); diff --git a/Mage/src/main/java/mage/abilities/mana/AnyColorManaAbility.java b/Mage/src/main/java/mage/abilities/mana/AnyColorManaAbility.java index 1dc32fb3834..942fcded4b6 100644 --- a/Mage/src/main/java/mage/abilities/mana/AnyColorManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/AnyColorManaAbility.java @@ -1,17 +1,18 @@ package mage.abilities.mana; -import java.util.ArrayList; -import java.util.List; import mage.Mana; import mage.abilities.costs.Cost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; -import mage.abilities.effects.mana.ManaEffect; import mage.abilities.effects.mana.AddManaOfAnyColorEffect; +import mage.abilities.effects.mana.ManaEffect; import mage.constants.Zone; import mage.game.Game; +import java.util.ArrayList; +import java.util.List; + public class AnyColorManaAbility extends ActivatedManaAbilityImpl { public AnyColorManaAbility() { @@ -28,14 +29,17 @@ public class AnyColorManaAbility extends ActivatedManaAbilityImpl { } /** - * * @param cost * @param netAmount dynamic value used during available mana calculation to * set the max possible amount the source can produce * @param setFlag */ public AnyColorManaAbility(Cost cost, DynamicValue netAmount, boolean setFlag) { - super(Zone.BATTLEFIELD, new AddManaOfAnyColorEffect(1, netAmount, setFlag), cost); + this(Zone.BATTLEFIELD, cost, netAmount, setFlag); + } + + public AnyColorManaAbility(Zone zone, Cost cost, DynamicValue netAmount, boolean setFlag) { + super(zone, new AddManaOfAnyColorEffect(1, netAmount, setFlag), cost); this.netMana.add(new Mana(0, 0, 0, 0, 0, 0, 1, 0)); } diff --git a/Mage/src/main/java/mage/cards/Card.java b/Mage/src/main/java/mage/cards/Card.java index 5b99d66e858..c00bffaf700 100644 --- a/Mage/src/main/java/mage/cards/Card.java +++ b/Mage/src/main/java/mage/cards/Card.java @@ -144,6 +144,8 @@ public interface Card extends MageObject { void looseAllAbilities(Game game); + boolean addCounters(Counter counter, Ability source, Game game); + boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game); boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, boolean isEffect); @@ -152,6 +154,8 @@ public interface Card extends MageObject { boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List appliedEffects, boolean isEffect); + boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List appliedEffects, boolean isEffect, int maxCounters); + void removeCounters(String name, int amount, Ability source, Game game); void removeCounters(Counter counter, Ability source, Game game); diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index c3741f4a45f..0bd9d7464e3 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -271,7 +271,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { && mainCardState != null && !mainCardState.hasLostAllAbilities() && mainCardState.getAbilities().containsClass(FlashbackAbility.class)) { - FlashbackAbility flash = new FlashbackAbility(this.getManaCost(), this.isInstant(game) ? TimingRule.INSTANT : TimingRule.SORCERY); + FlashbackAbility flash = new FlashbackAbility(this, this.getManaCost()); flash.setSourceId(this.getId()); flash.setControllerId(this.getOwnerId()); flash.setSpellAbilityType(this.getSpellAbility().getSpellAbilityType()); @@ -687,6 +687,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return ownerId; } + @Override + public boolean addCounters(Counter counter, Ability source, Game game) { + return addCounters(counter, source.getControllerId(), source, game); + } + @Override public boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game) { return addCounters(counter, playerAddingCounters, source, game, null, true); @@ -704,12 +709,21 @@ public abstract class CardImpl extends MageObjectImpl implements Card { @Override public boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List appliedEffects, boolean isEffect) { + return addCounters(counter, playerAddingCounters, source, game, appliedEffects, isEffect, Integer.MAX_VALUE); + } + + public boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List appliedEffects, boolean isEffect, int maxCounters) { boolean returnCode = true; GameEvent addingAllEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, source, playerAddingCounters, counter.getName(), counter.getCount()); addingAllEvent.setAppliedEffects(appliedEffects); addingAllEvent.setFlag(isEffect); if (!game.replaceEvent(addingAllEvent)) { - int amount = addingAllEvent.getAmount(); + int amount; + if (maxCounters < Integer.MAX_VALUE) { + amount = Integer.min(addingAllEvent.getAmount(), maxCounters - this.getCounters(game).getCount(counter.getName())); + } else { + amount = addingAllEvent.getAmount(); + } boolean isEffectFlag = addingAllEvent.getFlag(); int finalAmount = amount; for (int i = 0; i < amount; i++) { diff --git a/Mage/src/main/java/mage/cards/CardsImpl.java b/Mage/src/main/java/mage/cards/CardsImpl.java index 4c7b2a725cb..b0f45e4ab9c 100644 --- a/Mage/src/main/java/mage/cards/CardsImpl.java +++ b/Mage/src/main/java/mage/cards/CardsImpl.java @@ -91,8 +91,7 @@ public class CardsImpl extends LinkedHashSet implements Cards, Serializabl if (this.isEmpty()) { return null; } - UUID[] cards = this.toArray(new UUID[this.size()]); - MageObject object = game.getObject(cards[RandomUtil.nextInt(cards.length)]); // neccessary if permanent tokens are in the collection + MageObject object = game.getObject(RandomUtil.randomFromCollection(this)); // neccessary if permanent tokens are in the collection if (object instanceof Card) { return (Card) object; } diff --git a/Mage/src/main/java/mage/cards/ExpansionSet.java b/Mage/src/main/java/mage/cards/ExpansionSet.java index 23b35ae9d5b..4afae0f6080 100644 --- a/Mage/src/main/java/mage/cards/ExpansionSet.java +++ b/Mage/src/main/java/mage/cards/ExpansionSet.java @@ -87,6 +87,19 @@ public abstract class ExpansionSet implements Serializable { } } + private static enum ExpansionSetComparator implements Comparator { + instance; + + @Override + public int compare(ExpansionSet lhs, ExpansionSet rhs) { + return lhs.getReleaseDate().after(rhs.getReleaseDate()) ? -1 : 1; + } + } + + public static ExpansionSetComparator getComparator() { + return ExpansionSetComparator.instance; + } + protected final List cards = new ArrayList<>(); protected String name; @@ -94,7 +107,6 @@ public abstract class ExpansionSet implements Serializable { protected Date releaseDate; protected ExpansionSet parentSet; protected SetType setType; - protected BoosterCollator boosterCollator; // TODO: 03.10.2018, hasBasicLands can be removed someday -- it's uses to optimize lands search in deck generation and lands adding (search all available lands from sets) protected boolean hasBasicLands = true; @@ -126,20 +138,12 @@ public abstract class ExpansionSet implements Serializable { protected final Map inBoosterMap = new HashMap<>(); public ExpansionSet(String name, String code, Date releaseDate, SetType setType) { - this(name, code, releaseDate, setType, null); - } - - public ExpansionSet(String name, String code, Date releaseDate, SetType setType, BoosterCollator boosterCollator) { this.name = name; this.code = code; this.releaseDate = releaseDate; this.setType = setType; this.maxCardNumberInBooster = Integer.MAX_VALUE; savedCards = new EnumMap<>(Rarity.class); - this.boosterCollator = boosterCollator; - if (this.boosterCollator != null) { - this.boosterCollator.shuffle(); - } } public String getName() { @@ -251,9 +255,14 @@ public abstract class ExpansionSet implements Serializable { } } + public BoosterCollator createCollator() { + return null; + } + public List createBooster() { - if (boosterCollator != null) { - return createBoosterUsingCollator(); + BoosterCollator collator = createCollator(); + if (collator != null) { + return createBoosterUsingCollator(collator); } for (int i = 0; i < 100; i++) {//don't want to somehow loop forever @@ -277,17 +286,13 @@ public abstract class ExpansionSet implements Serializable { return tryBooster(); } - public void shuffleCollator() { - if (boosterCollator != null) { - boosterCollator.shuffle(); + private List createBoosterUsingCollator(BoosterCollator collator) { + synchronized (inBoosterMap) { + if (inBoosterMap.isEmpty()) { + generateBoosterMap(); + } } - } - - private List createBoosterUsingCollator() { - if (inBoosterMap.isEmpty()) { - generateBoosterMap(); - } - return boosterCollator + return collator .makeBooster() .stream() .map(inBoosterMap::get) @@ -301,6 +306,15 @@ public abstract class ExpansionSet implements Serializable { .findCards(new CardCriteria().setCodes(code)) .stream() .forEach(cardInfo -> inBoosterMap.put(cardInfo.getCardNumber(), cardInfo)); + // get basic lands from parent set if this set doesn't have them + if (!hasBasicLands && parentSet != null) { + String parentCode = parentSet.code; + CardRepository + .instance + .findCards(new CardCriteria().setCodes(parentCode).rarities(Rarity.LAND)) + .stream() + .forEach(cardInfo -> inBoosterMap.put(parentCode + "_" + cardInfo.getCardNumber(), cardInfo)); + } } protected boolean boosterIsValid(List booster) { @@ -618,9 +632,9 @@ public abstract class ExpansionSet implements Serializable { List savedCardsInfos = savedCards.get(rarity); if (savedCardsInfos == null) { CardCriteria criteria = new CardCriteria(); - if (rarity == Rarity.LAND) { - // get basic lands from parent set if current haven't it - criteria.setCodes(!hasBasicLands && parentSet != null ? parentSet.code : this.code); + if (rarity == Rarity.LAND && !hasBasicLands && parentSet != null) { + // get basic lands from parent set if this set doesn't have them + criteria.setCodes(parentSet.code); } else { criteria.setCodes(this.code); } diff --git a/Mage/src/main/java/mage/cards/ModalDoubleFacesCard.java b/Mage/src/main/java/mage/cards/ModalDoubleFacesCard.java index 1680c05f062..db7062b9313 100644 --- a/Mage/src/main/java/mage/cards/ModalDoubleFacesCard.java +++ b/Mage/src/main/java/mage/cards/ModalDoubleFacesCard.java @@ -132,8 +132,8 @@ public abstract class ModalDoubleFacesCard extends CardImpl { } @Override - public boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List appliedEffects, boolean isEffect) { - return leftHalfCard.addCounters(counter, playerAddingCounters, source, game, appliedEffects, isEffect); + public boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List appliedEffects, boolean isEffect, int maxCounters) { + return leftHalfCard.addCounters(counter, playerAddingCounters, source, game, appliedEffects, isEffect, maxCounters); } @Override diff --git a/Mage/src/main/java/mage/cards/Sets.java b/Mage/src/main/java/mage/cards/Sets.java index 18a94102ece..1435da7c90a 100644 --- a/Mage/src/main/java/mage/cards/Sets.java +++ b/Mage/src/main/java/mage/cards/Sets.java @@ -199,10 +199,10 @@ public class Sets extends HashMap { return null; } - public static ExpansionSet.SetCardInfo findCardByClass(Class clazz, String preferedSetCode) { + public static ExpansionSet.SetCardInfo findCardByClass(Class clazz, String preferredSetCode) { ExpansionSet.SetCardInfo info = null; - if (instance.containsKey(preferedSetCode)) { - info = instance.get(preferedSetCode).findCardInfoByClass(clazz).stream().findFirst().orElse(null); + if (instance.containsKey(preferredSetCode)) { + info = instance.get(preferredSetCode).findCardInfoByClass(clazz).stream().findFirst().orElse(null); } if (info == null) { diff --git a/Mage/src/main/java/mage/cards/decks/Deck.java b/Mage/src/main/java/mage/cards/decks/Deck.java index 1a5dd01bb22..0869c68d390 100644 --- a/Mage/src/main/java/mage/cards/decks/Deck.java +++ b/Mage/src/main/java/mage/cards/decks/Deck.java @@ -4,13 +4,17 @@ import mage.cards.Card; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.game.GameException; +import mage.util.Copyable; import mage.util.DeckUtil; import org.apache.log4j.Logger; import java.io.Serializable; import java.util.*; +import java.util.stream.Collectors; -public class Deck implements Serializable { +public class Deck implements Serializable, Copyable { + + static final int MAX_CARDS_PER_DECK = 1000; private String name; private final Set cards = new LinkedHashSet<>(); @@ -20,6 +24,20 @@ public class Deck implements Serializable { private long deckHashCode = 0; private long deckCompleteHashCode = 0; + public Deck() { + super(); + } + + public Deck(final Deck deck) { + this.name = deck.name; + this.cards.addAll(deck.cards.stream().map(Card::copy).collect(Collectors.toList())); + this.sideboard.addAll(deck.sideboard.stream().map(Card::copy).collect(Collectors.toList())); + this.cardsLayout = deck.cardsLayout == null ? null : deck.cardsLayout.copy(); + this.sideboardLayout = deck.sideboardLayout == null ? null : deck.sideboardLayout.copy(); + this.deckHashCode = deck.deckHashCode; + this.deckCompleteHashCode = deck.deckCompleteHashCode; + } + public static Deck load(DeckCardLists deckCardLists) throws GameException { return Deck.load(deckCardLists, false); } @@ -51,6 +69,10 @@ public class Deck implements Serializable { return currentDeck; } + public static Deck load(DeckCardLists deckCardLists, boolean ignoreErrors, boolean mockCards) throws GameException { + return load(deckCardLists, ignoreErrors, mockCards, null); + } + /** * Warning, AI can't play Mock cards, so call it with extra params in real games or tests * @@ -60,17 +82,17 @@ public class Deck implements Serializable { * @return * @throws GameException */ - public static Deck load(DeckCardLists deckCardLists, boolean ignoreErrors, boolean mockCards) throws GameException { + public static Deck load(DeckCardLists deckCardLists, boolean ignoreErrors, boolean mockCards, Map cardInfoCache) throws GameException { Deck deck = new Deck(); deck.setName(deckCardLists.getName()); - deck.cardsLayout = deckCardLists.getCardLayout(); - deck.sideboardLayout = deckCardLists.getSideboardLayout(); + deck.cardsLayout = deckCardLists.getCardLayout() == null ? null : deckCardLists.getCardLayout().copy(); + deck.sideboardLayout = deckCardLists.getSideboardLayout() == null ? null : deckCardLists.getSideboardLayout().copy(); List deckCardNames = new ArrayList<>(); int totalCards = 0; for (DeckCardInfo deckCardInfo : deckCardLists.getCards()) { - Card card = createCard(deckCardInfo, mockCards); + Card card = createCard(deckCardInfo, mockCards, cardInfoCache); if (card != null) { - if (totalCards > 1000) { + if (totalCards > MAX_CARDS_PER_DECK) { break; } deck.cards.add(card); @@ -83,9 +105,9 @@ public class Deck implements Serializable { } List sbCardNames = new ArrayList<>(); for (DeckCardInfo deckCardInfo : deckCardLists.getSideboard()) { - Card card = createCard(deckCardInfo, mockCards); + Card card = createCard(deckCardInfo, mockCards, cardInfoCache); if (card != null) { - if (totalCards > 1000) { + if (totalCards > MAX_CARDS_PER_DECK) { break; } deck.sideboard.add(card); @@ -127,8 +149,21 @@ public class Deck implements Serializable { } - private static Card createCard(DeckCardInfo deckCardInfo, boolean mockCards) { - CardInfo cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNum()); + private static Card createCard(DeckCardInfo deckCardInfo, boolean mockCards, Map cardInfoCache) { + CardInfo cardInfo; + if (cardInfoCache != null) { + // from cache + String key = String.format("%s_%s", deckCardInfo.getSetCode(), deckCardInfo.getCardNum()); + cardInfo = cardInfoCache.getOrDefault(key, null); + if (cardInfo == null) { + cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNum()); + cardInfoCache.put(key, cardInfo); + } + } else { + // from db + cardInfo = CardRepository.instance.findCard(deckCardInfo.getSetCode(), deckCardInfo.getCardNum()); + } + if (cardInfo == null) { return null; } @@ -226,4 +261,8 @@ public class Deck implements Serializable { this.sideboardLayout = null; } + @Override + public Deck copy() { + return new Deck(this); + } } diff --git a/Mage/src/main/java/mage/cards/decks/DeckCardInfo.java b/Mage/src/main/java/mage/cards/decks/DeckCardInfo.java index 991af94fd02..6423805e317 100644 --- a/Mage/src/main/java/mage/cards/decks/DeckCardInfo.java +++ b/Mage/src/main/java/mage/cards/decks/DeckCardInfo.java @@ -2,19 +2,32 @@ package mage.cards.decks; +import mage.util.Copyable; + import java.io.Serializable; /** * * @author LevelX2 */ -public class DeckCardInfo implements Serializable { +public class DeckCardInfo implements Serializable, Copyable { private String cardName; private String setCode; private String cardNum; private int quantity; + public DeckCardInfo() { + super(); + } + + public DeckCardInfo(final DeckCardInfo info) { + this.cardName = info.cardName; + this.setCode = info.setCode; + this.cardNum = info.cardNum; + this.quantity = info.quantity; + } + public DeckCardInfo(String cardName, String cardNum, String setCode) { this(cardName, cardNum, setCode, 1); } @@ -51,4 +64,8 @@ public class DeckCardInfo implements Serializable { return setCode + cardNum; } + @Override + public DeckCardInfo copy() { + return new DeckCardInfo(this); + } } diff --git a/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java b/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java index eaf6dc4da95..9bf9645f199 100644 --- a/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java +++ b/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java @@ -1,15 +1,35 @@ package mage.cards.decks; +import mage.util.Copyable; + +import java.util.ArrayList; import java.util.List; /** * Created by stravant@gmail.com on 2016-10-03. */ -public class DeckCardLayout { +public class DeckCardLayout implements Copyable { private final List>> cards; private final String settings; + public DeckCardLayout(final DeckCardLayout layout) { + this.cards = new ArrayList<>(); + for (int i1 = 0; i1 < layout.cards.size(); i1++) { + List> list1 = new ArrayList<>(); + this.cards.add(list1); + for (int i2 = 0; i2 < layout.cards.get(i1).size(); i2++) { + List list2 = new ArrayList<>(); + list1.add(list2); + for (int i3 = 0; i3 < layout.cards.get(i1).get(i2).size(); i3++) { + DeckCardInfo info = layout.cards.get(i1).get(i2).get(i3); + list2.add(info.copy()); + } + } + } + this.settings = layout.settings; + } + public DeckCardLayout(List>> cards, String settings) { this.cards = cards; this.settings = settings; @@ -22,4 +42,9 @@ public class DeckCardLayout { public String getSettings() { return settings; } + + @Override + public DeckCardLayout copy() { + return new DeckCardLayout(this); + } } diff --git a/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java b/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java index 73d07878e9d..2998803e369 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java +++ b/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java @@ -12,7 +12,7 @@ public class CardLookup { public static final CardLookup instance = new CardLookup(); public Optional lookupCardInfo(String name) { - return Optional.ofNullable(CardRepository.instance.findPreferedCoreExpansionCard(name, true)); + return Optional.ofNullable(CardRepository.instance.findPreferredCoreExpansionCard(name, true)); } public List lookupCardInfo(CardCriteria criteria) { diff --git a/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java index e518e5ea79b..383f917ef2f 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java @@ -22,9 +22,9 @@ public class CodDeckImporter extends XmlDeckImporter { * @return */ @Override - public DeckCardLists importDeck(String filename, StringBuilder errorMessages, boolean saveAutoFixedFile) { + public DeckCardLists importDeck(String fileName, StringBuilder errorMessages, boolean saveAutoFixedFile) { try { - Document doc = getXmlDocument(filename); + Document doc = getXmlDocument(fileName); DeckCardLists decklist = new DeckCardLists(); List mainCards = getNodes(doc, "/cockatrice_deck/zone[@name='main']/card"); diff --git a/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java index 834aaf0ef8b..0b9119d5661 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java @@ -92,7 +92,7 @@ public class DckDeckImporter extends PlainTextDeckImporter { } if (!cardName.equals("")) { - foundedCard = CardRepository.instance.findPreferedCoreExpansionCard(cardName, false, setCode); + foundedCard = CardRepository.instance.findPreferredCoreExpansionCard(cardName, false, setCode); } if (foundedCard != null) { diff --git a/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java index 26785c1909b..57bc486956d 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java @@ -86,10 +86,10 @@ public abstract class DeckImporter { } } - public abstract DeckCardLists importDeck(String file, StringBuilder errorMessages, boolean saveAutoFixedFile); + public abstract DeckCardLists importDeck(String fileName, StringBuilder errorMessages, boolean saveAutoFixedFile); - public DeckCardLists importDeck(String file, boolean saveAutoFixedFile) { - return importDeck(file, new StringBuilder(), saveAutoFixedFile); + public DeckCardLists importDeck(String fileName, boolean saveAutoFixedFile) { + return importDeck(fileName, new StringBuilder(), saveAutoFixedFile); } public CardLookup getCardLookup() { diff --git a/Mage/src/main/java/mage/cards/decks/importer/DekDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DekDeckImporter.java index 2bb529ce896..af3c4841564 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DekDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DekDeckImporter.java @@ -21,7 +21,7 @@ public class DekDeckImporter extends PlainTextDeckImporter { Integer cardCount = Integer.parseInt(extractAttribute(line, "Quantity")); String cardName = extractAttribute(line, "Name"); boolean isSideboard = "true".equals(extractAttribute(line, "Sideboard")); - CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(cardName, true); + CardInfo cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardName, true); if (cardInfo == null) { sbMessage.append("Could not find card: '").append(cardName).append("' at line ").append(lineCount).append('\n'); } else { diff --git a/Mage/src/main/java/mage/cards/decks/importer/JsonDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/JsonDeckImporter.java index 9e5a7e52cc3..1502f291333 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/JsonDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/JsonDeckImporter.java @@ -1,9 +1,7 @@ package mage.cards.decks.importer; +import com.google.gson.*; import mage.cards.decks.DeckCardLists; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; import java.io.File; import java.io.FileReader; @@ -16,26 +14,25 @@ public abstract class JsonDeckImporter extends DeckImporter { protected StringBuilder sbMessage = new StringBuilder(); /** - * @param file file to import + * @param fileName file to import * @param errorMessages you can setup output messages to showup to user * @param saveAutoFixedFile do not supported for that format * @return decks list */ - public DeckCardLists importDeck(String file, StringBuilder errorMessages, boolean saveAutoFixedFile) { - File f = new File(file); + public DeckCardLists importDeck(String fileName, StringBuilder errorMessages, boolean saveAutoFixedFile) { + File f = new File(fileName); DeckCardLists deckList = new DeckCardLists(); if (!f.exists()) { - logger.warn("Deckfile " + file + " not found."); + logger.warn("Deckfile " + fileName + " not found."); return deckList; } sbMessage.setLength(0); try { try (FileReader reader = new FileReader(f)) { - try { // Json parsing - JSONParser parser = new JSONParser(); - JSONObject rootObj = (JSONObject) parser.parse(reader); - readJson(rootObj, deckList); + try { + JsonObject json = JsonParser.parseReader(reader).getAsJsonObject(); + readJson(json, deckList); if (sbMessage.length() > 0) { if (errorMessages != null) { @@ -46,8 +43,8 @@ public abstract class JsonDeckImporter extends DeckImporter { logger.fatal(sbMessage); } } - } catch (ParseException ex) { - logger.fatal(null, ex); + } catch (JsonParseException ex) { + logger.fatal("Can't parse json-deck: " + fileName, ex); } } catch (Exception ex) { logger.fatal(null, ex); @@ -59,9 +56,9 @@ public abstract class JsonDeckImporter extends DeckImporter { } @Override - public DeckCardLists importDeck(String file, boolean saveAutoFixedFile) { - return importDeck(file, null, saveAutoFixedFile); + public DeckCardLists importDeck(String fileName, boolean saveAutoFixedFile) { + return importDeck(fileName, null, saveAutoFixedFile); } - protected abstract void readJson(JSONObject line, DeckCardLists decklist); + protected abstract void readJson(JsonObject json, DeckCardLists decklist); } diff --git a/Mage/src/main/java/mage/cards/decks/importer/MtgjsonDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/MtgjsonDeckImporter.java index de0e800518d..5a0cc5371ea 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/MtgjsonDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/MtgjsonDeckImporter.java @@ -1,10 +1,11 @@ package mage.cards.decks.importer; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import mage.cards.decks.DeckCardInfo; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; +import mage.util.JsonUtil; import java.util.List; import java.util.Optional; @@ -16,39 +17,45 @@ import java.util.Optional; public class MtgjsonDeckImporter extends JsonDeckImporter { @Override - protected void readJson(JSONObject rootObj, DeckCardLists deckList) { - JSONObject data = (JSONObject) rootObj.get("data"); + protected void readJson(JsonObject json, DeckCardLists deckList) { + JsonObject data = JsonUtil.getAsObject(json, "data"); if (data == null) { sbMessage.append("Could not find data in json").append("'\n"); return; } // info - String deckSet = (String) data.get("code"); - String name = (String) data.get("name"); - if (name != null) { + String deckSet = JsonUtil.getAsString(data, "code"); + String name = JsonUtil.getAsString(data, "name"); + if (!name.isEmpty()) { deckList.setName(name); } + // mainboard - JSONArray mainBoard = (JSONArray) data.get("mainBoard"); + JsonArray mainBoard = JsonUtil.getAsArray(data, "mainBoard"); List mainDeckList = deckList.getCards(); addBoardToList(mainBoard, mainDeckList, deckSet); + // sideboard - JSONArray sideBoard = (JSONArray) data.get("sideBoard"); + JsonArray sideBoard = JsonUtil.getAsArray(data, "sideBoard"); List sideDeckList = deckList.getSideboard(); addBoardToList(sideBoard, sideDeckList, deckSet); } - private void addBoardToList(JSONArray board, List list, String deckSet) { + private void addBoardToList(JsonArray board, List list, String deckSet) { + if (board == null || board.isEmpty()) { + return; + } + board.forEach(arrayCard -> { - JSONObject card = (JSONObject) arrayCard; - String name = (String) card.get("name"); - String setCode = (String) card.get("setCode"); - if (setCode == null || setCode.isEmpty()) { + JsonObject card = (JsonObject) arrayCard; + String name = JsonUtil.getAsString(card, "name"); + String setCode = JsonUtil.getAsString(card, "setCode"); + if (setCode.isEmpty()) { setCode = deckSet; } - int num = ((Number) card.get("count")).intValue(); + int num = JsonUtil.getAsInt(card, "count"); Optional cardLookup = getCardLookup().lookupCardInfo(name, setCode); if (!cardLookup.isPresent()) { sbMessage.append("Could not find card: '").append(name).append("'\n"); diff --git a/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java index 3426f57c6f8..1a98f0454f6 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java @@ -22,9 +22,9 @@ public class O8dDeckImporter extends XmlDeckImporter { * @return */ @Override - public DeckCardLists importDeck(String filename, StringBuilder errorMessages, boolean saveAutoFixedFile) { + public DeckCardLists importDeck(String fileName, StringBuilder errorMessages, boolean saveAutoFixedFile) { try { - Document doc = getXmlDocument(filename); + Document doc = getXmlDocument(fileName); DeckCardLists decklist = new DeckCardLists(); List mainCards = getNodes(doc, "/deck/section[@name='Main']/card"); diff --git a/Mage/src/main/java/mage/cards/decks/importer/PlainTextDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/PlainTextDeckImporter.java index c6fd9a996fc..86c7965185c 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/PlainTextDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/PlainTextDeckImporter.java @@ -26,13 +26,13 @@ public abstract class PlainTextDeckImporter extends DeckImporter { * @param saveAutoFixedFile save fixed deck file (if any fixes applied) * @return decks list */ - public DeckCardLists importDeck(String file, StringBuilder errorMessages, boolean saveAutoFixedFile) { - File f = new File(file); + public DeckCardLists importDeck(String fileName, StringBuilder errorMessages, boolean saveAutoFixedFile) { + File f = new File(fileName); List originalFile = new ArrayList<>(); List fixedFile = new ArrayList<>(); DeckCardLists deckList = new DeckCardLists(); if (!f.exists()) { - logger.warn("Deckfile " + file + " not found."); + logger.warn("Deckfile " + fileName + " not found."); return deckList; } lineCount = 0; @@ -94,8 +94,8 @@ public abstract class PlainTextDeckImporter extends DeckImporter { @Override - public DeckCardLists importDeck(String file, boolean saveAutoFixedFile) { - return importDeck(file, null, saveAutoFixedFile); + public DeckCardLists importDeck(String fileName, boolean saveAutoFixedFile) { + return importDeck(fileName, null, saveAutoFixedFile); } /** diff --git a/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java index 002c7711a84..8b940b39ed3 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java @@ -126,7 +126,7 @@ public class TxtDeckImporter extends PlainTextDeckImporter { wasCardLines = true; - CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(lineName, true); + CardInfo cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(lineName, true); if (cardInfo == null) { sbMessage.append("Could not find card: '").append(lineName).append("' at line ").append(lineCount).append('\n'); } else { diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 1a83934c3bf..15de5a22743 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -369,38 +369,44 @@ public enum CardRepository { return Collections.emptyList(); } + public CardInfo findCard(String name) { + return findCard(name, false); + } + /** * @param name + * @param returnAnySet return card from first available set (WARNING, it's a performance optimization for tests, + * don't use it in real games - users must get random set) * @return random card with the provided name or null if none is found */ - public CardInfo findCard(String name) { - List cards = findCards(name); + public CardInfo findCard(String name, boolean returnAnySet) { + List cards = returnAnySet ? findCards(name, 1) : findCards(name); if (!cards.isEmpty()) { return cards.get(RandomUtil.nextInt(cards.size())); } return null; } - public CardInfo findPreferedCoreExpansionCard(String name, boolean caseInsensitive) { - return findPreferedCoreExpansionCard(name, caseInsensitive, null); + public CardInfo findPreferredCoreExpansionCard(String name, boolean caseInsensitive) { + return findPreferredCoreExpansionCard(name, caseInsensitive, null); } - public CardInfo findPreferedCoreExpansionCard(String name, boolean caseInsensitive, String preferedSetCode) { + public CardInfo findPreferredCoreExpansionCard(String name, boolean caseInsensitive, String preferredSetCode) { List cards; if (caseInsensitive) { cards = findCardsCaseInsensitive(name); } else { cards = findCards(name); } - return findPreferedOrLatestCard(cards, preferedSetCode); + return findPreferredOrLatestCard(cards, preferredSetCode); } - public CardInfo findPreferedCoreExpansionCardByClassName(String canonicalClassName, String preferedSetCode) { + public CardInfo findPreferredCoreExpansionCardByClassName(String canonicalClassName, String preferredSetCode) { List cards = findCardsByClass(canonicalClassName); - return findPreferedOrLatestCard(cards, preferedSetCode); + return findPreferredOrLatestCard(cards, preferredSetCode); } - private CardInfo findPreferedOrLatestCard(List cards, String preferedSetCode) { + private CardInfo findPreferredOrLatestCard(List cards, String preferredSetCode) { if (!cards.isEmpty()) { Date lastReleaseDate = null; Date lastExpansionDate = null; @@ -409,7 +415,7 @@ public enum CardRepository { ExpansionInfo set = ExpansionRepository.instance.getSetByCode(cardinfo.getSetCode()); if (set != null) { - if ((preferedSetCode != null) && (preferedSetCode.equals(set.getCode()))) { + if ((preferredSetCode != null) && (preferredSetCode.equals(set.getCode()))) { return cardinfo; } @@ -443,13 +449,27 @@ public enum CardRepository { } } } - return findPreferedCoreExpansionCard(name, true); + return findPreferredCoreExpansionCard(name, true); } public List findCards(String name) { + return findCards(name, 0); + } + + /** + * Find card's reprints from all sets + * + * @param name + * @param limitByMaxAmount return max amount of different cards (if 0 then return card from all sets) + * @return + */ + public List findCards(String name, long limitByMaxAmount) { try { QueryBuilder queryBuilder = cardDao.queryBuilder(); queryBuilder.where().eq("name", new SelectArg(name)); + if (limitByMaxAmount > 0) { + queryBuilder.limit(limitByMaxAmount); + } return cardDao.query(queryBuilder.prepare()); } catch (SQLException ex) { } diff --git a/Mage/src/main/java/mage/collation/BoosterCollator.java b/Mage/src/main/java/mage/collation/BoosterCollator.java index 350b4bcbb4f..01e68295ecf 100644 --- a/Mage/src/main/java/mage/collation/BoosterCollator.java +++ b/Mage/src/main/java/mage/collation/BoosterCollator.java @@ -6,8 +6,5 @@ import java.util.List; * @author TheElk801 */ public interface BoosterCollator { - - public void shuffle(); - public List makeBooster(); } diff --git a/Mage/src/main/java/mage/collation/BoosterStructure.java b/Mage/src/main/java/mage/collation/BoosterStructure.java index 23aa81e4a7f..e6d9bab8182 100644 --- a/Mage/src/main/java/mage/collation/BoosterStructure.java +++ b/Mage/src/main/java/mage/collation/BoosterStructure.java @@ -12,11 +12,11 @@ import java.util.List; * * @author TheElk801 */ -public abstract class BoosterStructure { +public class BoosterStructure { private final List slots; - protected BoosterStructure(CardRun... runs) { + public BoosterStructure(CardRun... runs) { this.slots = Arrays.asList(runs); } @@ -27,10 +27,4 @@ public abstract class BoosterStructure { } return cards; } - - public void shuffle() { - for (CardRun run : this.slots) { - run.shuffle(); - } - } } diff --git a/Mage/src/main/java/mage/collation/CardRun.java b/Mage/src/main/java/mage/collation/CardRun.java index 1889b59f0d3..c34876bf9d6 100644 --- a/Mage/src/main/java/mage/collation/CardRun.java +++ b/Mage/src/main/java/mage/collation/CardRun.java @@ -3,7 +3,7 @@ package mage.collation; /** * @author TheElk801 */ -public abstract class CardRun extends Rotater { +public class CardRun extends Rotater { public CardRun(boolean keepOrder, String... numbers) { super(keepOrder, numbers); diff --git a/Mage/src/main/java/mage/collation/RarityConfiguration.java b/Mage/src/main/java/mage/collation/RarityConfiguration.java index 3ba6807b860..9d71685db93 100644 --- a/Mage/src/main/java/mage/collation/RarityConfiguration.java +++ b/Mage/src/main/java/mage/collation/RarityConfiguration.java @@ -13,15 +13,8 @@ public class RarityConfiguration extends Rotater { super(item1, item2); } - public RarityConfiguration(boolean keepOrder, BoosterStructure... items) { - super(keepOrder, items); - } - - @Override - public void shuffle() { - for (BoosterStructure structure : this.items) { - structure.shuffle(); - } - super.shuffle(); + public RarityConfiguration(BoosterStructure... items) { + // change to false if we ever decide to generate sequential boosters + super(true, items); } } diff --git a/Mage/src/main/java/mage/collation/Rotater.java b/Mage/src/main/java/mage/collation/Rotater.java index 6558106e50e..0af5fa77497 100644 --- a/Mage/src/main/java/mage/collation/Rotater.java +++ b/Mage/src/main/java/mage/collation/Rotater.java @@ -2,6 +2,7 @@ package mage.collation; import mage.util.RandomUtil; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -13,9 +14,8 @@ import java.util.List; */ public class Rotater { - protected final List items; - private final boolean keepOrder; - private int position = 0; + private final List items; + private int position; public Rotater(T item) { this(true, item); @@ -26,8 +26,15 @@ public class Rotater { } public Rotater(boolean keepOrder, T... items) { - this.items = Arrays.asList(items); - this.keepOrder = keepOrder; + if (keepOrder) { + this.items = Arrays.asList(items); + this.position = RandomUtil.nextInt(this.items.size()); + } else { + this.items = new ArrayList(); + Collections.addAll(this.items, items); + Collections.shuffle(this.items, RandomUtil.getRandom()); + this.position = 0; + } } public int iterate() { @@ -40,11 +47,4 @@ public class Rotater { public T getNext() { return items.get(iterate()); } - - public void shuffle() { - position = RandomUtil.nextInt(items.size()); - if (!keepOrder) { - Collections.shuffle(items, RandomUtil.getRandom()); - } - } } diff --git a/Mage/src/main/java/mage/constants/ComparisonType.java b/Mage/src/main/java/mage/constants/ComparisonType.java index f47bd1839c4..cbbeb739114 100644 --- a/Mage/src/main/java/mage/constants/ComparisonType.java +++ b/Mage/src/main/java/mage/constants/ComparisonType.java @@ -4,7 +4,9 @@ package mage.constants; * Created by IGOUDT on 5-3-2017. */ public enum ComparisonType { - MORE_THAN(">", "more", "than"), FEWER_THAN("<", "fewer", "than"), EQUAL_TO("==", "equal", "to"); + FEWER_THAN("<", "fewer", "than"), + EQUAL_TO("==", "equal", "to"), + MORE_THAN(">", "more", "than"); String operator; String text1; diff --git a/Mage/src/main/java/mage/constants/TargetController.java b/Mage/src/main/java/mage/constants/TargetController.java index c827820bbdd..a81c3610c87 100644 --- a/Mage/src/main/java/mage/constants/TargetController.java +++ b/Mage/src/main/java/mage/constants/TargetController.java @@ -1,12 +1,11 @@ package mage.constants; import mage.cards.Card; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Controllable; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.players.Player; import java.util.UUID; @@ -26,6 +25,7 @@ public enum TargetController { CONTROLLER_ATTACHED_TO, NEXT, EACH_PLAYER, + ENCHANTED, SOURCE_TARGETS; private final OwnerPredicate ownerPredicate; @@ -50,7 +50,7 @@ public enum TargetController { return controllerPredicate; } - public static class OwnerPredicate implements ObjectPlayerPredicate> { + public static class OwnerPredicate implements ObjectSourcePlayerPredicate { private final TargetController targetOwner; @@ -59,7 +59,7 @@ public enum TargetController { } @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Card card = input.getObject(); UUID playerId = input.getPlayerId(); if (card == null || playerId == null) { @@ -83,6 +83,9 @@ public enum TargetController { return true; } break; + case ENCHANTED: + Permanent permanent = game.getPermanent(input.getSourceId()); + return permanent != null && input.getObject().isOwnedBy(permanent.getAttachedTo()); case ANY: return true; } @@ -96,7 +99,7 @@ public enum TargetController { } } - public static class PlayerPredicate implements ObjectSourcePlayerPredicate> { + public static class PlayerPredicate implements ObjectSourcePlayerPredicate { private final TargetController targetPlayer; @@ -140,7 +143,7 @@ public enum TargetController { } } - public static class ControllerPredicate implements ObjectPlayerPredicate> { + public static class ControllerPredicate implements ObjectSourcePlayerPredicate { private final TargetController controller; @@ -149,7 +152,7 @@ public enum TargetController { } @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Controllable object = input.getObject(); UUID playerId = input.getPlayerId(); @@ -180,6 +183,9 @@ public enum TargetController { return true; } break; + case ENCHANTED: + Permanent permanent = game.getPermanent(input.getSourceId()); + return permanent != null && input.getObject().isControlledBy(permanent.getAttachedTo()); case ANY: return true; } diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index 2c426b9a402..3a4612fb276 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -50,6 +50,7 @@ public enum CounterType { ECHO("echo"), EGG("egg"), ELIXIR("elixir"), + EMBER("ember"), ENERGY("energy"), ENLIGHTENED("enlightened"), EON("eon"), diff --git a/Mage/src/main/java/mage/filter/FilterCard.java b/Mage/src/main/java/mage/filter/FilterCard.java index cd1a0bb2cfe..bae5bcd3de9 100644 --- a/Mage/src/main/java/mage/filter/FilterCard.java +++ b/Mage/src/main/java/mage/filter/FilterCard.java @@ -2,7 +2,10 @@ package mage.filter; import mage.cards.Card; import mage.constants.TargetController; -import mage.filter.predicate.*; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; import mage.game.Game; import java.util.ArrayList; @@ -20,7 +23,7 @@ import java.util.stream.Collectors; public class FilterCard extends FilterObject { private static final long serialVersionUID = 1L; - protected List>> extraPredicates = new ArrayList<>(); + protected List> extraPredicates = new ArrayList<>(); public FilterCard() { super("card"); @@ -53,21 +56,17 @@ public class FilterCard extends FilterObject { } public boolean match(Card card, UUID playerId, Game game) { - if (!this.match(card, game)) { - return false; - } - - return Predicates.and(extraPredicates).apply(new ObjectPlayer(card, playerId), game); + return match(card, null, playerId, game); } public boolean match(Card card, UUID sourceId, UUID playerId, Game game) { if (!this.match(card, game)) { return false; } - return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(card, sourceId, playerId), game); + return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(card, sourceId, playerId), game); } - public final void add(ObjectPlayerPredicate predicate) { + public final void add(ObjectSourcePlayerPredicate predicate) { if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } diff --git a/Mage/src/main/java/mage/filter/FilterPermanent.java b/Mage/src/main/java/mage/filter/FilterPermanent.java index f7d4e9759a7..fa995d8a7dd 100644 --- a/Mage/src/main/java/mage/filter/FilterPermanent.java +++ b/Mage/src/main/java/mage/filter/FilterPermanent.java @@ -1,9 +1,8 @@ package mage.filter; import mage.constants.SubType; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; @@ -18,7 +17,7 @@ import java.util.UUID; */ public class FilterPermanent extends FilterObject implements FilterInPlay { - protected List>> extraPredicates = new ArrayList<>(); + protected List> extraPredicates = new ArrayList<>(); public FilterPermanent() { super("permanent"); @@ -56,10 +55,10 @@ public class FilterPermanent extends FilterObject implements FilterIn return false; } - return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(permanent, sourceId, playerId), game); + return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(permanent, sourceId, playerId), game); } - public final void add(ObjectPlayerPredicate predicate) { + public final void add(ObjectSourcePlayerPredicate predicate) { if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } diff --git a/Mage/src/main/java/mage/filter/FilterPlayer.java b/Mage/src/main/java/mage/filter/FilterPlayer.java index 85e26cff854..7a4e0c726e1 100644 --- a/Mage/src/main/java/mage/filter/FilterPlayer.java +++ b/Mage/src/main/java/mage/filter/FilterPlayer.java @@ -1,8 +1,7 @@ package mage.filter; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.players.Player; @@ -17,7 +16,7 @@ import java.util.UUID; */ public class FilterPlayer extends FilterImpl { - protected List>> extraPredicates = new ArrayList<>(); + protected List> extraPredicates = new ArrayList<>(); public FilterPlayer() { this("player"); @@ -32,7 +31,7 @@ public class FilterPlayer extends FilterImpl { this.extraPredicates = new ArrayList<>(filter.extraPredicates); } - public void add(ObjectPlayerPredicate predicate) { + public void add(ObjectSourcePlayerPredicate predicate) { if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } @@ -49,7 +48,7 @@ public class FilterPlayer extends FilterImpl { return false; } - return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(checkPlayer, sourceId, sourceControllerId), game); + return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(checkPlayer, sourceId, sourceControllerId), game); } @Override diff --git a/Mage/src/main/java/mage/filter/FilterStackObject.java b/Mage/src/main/java/mage/filter/FilterStackObject.java index 789c2d3267e..f6099773dd1 100644 --- a/Mage/src/main/java/mage/filter/FilterStackObject.java +++ b/Mage/src/main/java/mage/filter/FilterStackObject.java @@ -1,11 +1,9 @@ package mage.filter; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; 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 mage.game.stack.StackObject; import java.util.ArrayList; @@ -17,7 +15,7 @@ import java.util.UUID; */ public class FilterStackObject extends FilterObject { - protected List>> extraPredicates = new ArrayList<>(); + protected List> extraPredicates = new ArrayList<>(); public FilterStackObject() { this("spell or ability"); @@ -37,10 +35,10 @@ public class FilterStackObject extends FilterObject { return false; } - return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(stackObject, sourceId, playerId), game); + return Predicates.and(extraPredicates).apply(new ObjectSourcePlayer(stackObject, sourceId, playerId), game); } - public final void add(ObjectPlayerPredicate predicate) { + public final void add(ObjectSourcePlayerPredicate predicate) { if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 18abc034416..c0717cec1f2 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -10,6 +10,7 @@ import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.mageobject.KickedSpellPredicate; import mage.filter.predicate.mageobject.MulticoloredPredicate; import mage.filter.predicate.permanent.AttackingPredicate; +import mage.filter.predicate.permanent.TappedPredicate; import mage.filter.predicate.permanent.TokenPredicate; /** @@ -316,6 +317,12 @@ public final class StaticFilters { FILTER_CONTROLLED_A_PERMANENT.setLockedFilter(true); } + public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENTS = new FilterControlledPermanent("permanents you control"); + + static { + FILTER_CONTROLLED_PERMANENTS.setLockedFilter(true); + } + public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_SHORT_TEXT = new FilterControlledPermanent("permanent"); static { @@ -382,6 +389,13 @@ public final class StaticFilters { FILTER_OPPONENTS_PERMANENT_CREATURE.setLockedFilter(true); } + public static final FilterCreaturePermanent FILTER_OPPONENTS_PERMANENT_A_CREATURE = new FilterCreaturePermanent("a creature an opponent controls"); + + static { + FILTER_OPPONENTS_PERMANENT_A_CREATURE.add(TargetController.OPPONENT.getControllerPredicate()); + FILTER_OPPONENTS_PERMANENT_A_CREATURE.setLockedFilter(true); + } + public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT = new FilterPermanent("artifact an opponent controls"); static { @@ -440,6 +454,13 @@ public final class StaticFilters { FILTER_CONTROLLED_ANOTHER_CREATURE.setLockedFilter(true); } + public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_UNTAPPED_CREATURES = new FilterControlledCreaturePermanent("untapped creatures you control"); + + static { + FILTER_CONTROLLED_UNTAPPED_CREATURES.add(TappedPredicate.UNTAPPED); + FILTER_CONTROLLED_UNTAPPED_CREATURES.setLockedFilter(true); + } + public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_NON_LAND = new FilterControlledPermanent("nonland permanent"); static { @@ -613,6 +634,12 @@ public final class StaticFilters { FILTER_SPELL_NON_CREATURE.setLockedFilter(true); } + public static final FilterSpell FILTER_SPELLS_NON_CREATURE = (FilterSpell) new FilterSpell("noncreature spells").add(Predicates.not(CardType.CREATURE.getPredicate())); + + static { + FILTER_SPELLS_NON_CREATURE.setLockedFilter(true); + } + public static final FilterSpell FILTER_SPELL_A_NON_CREATURE = (FilterSpell) new FilterSpell("a noncreature spell").add(Predicates.not(CardType.CREATURE.getPredicate())); static { diff --git a/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java b/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java index c885b03522d..b6b6d18be11 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java +++ b/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java @@ -5,7 +5,7 @@ import mage.filter.FilterImpl; import mage.filter.FilterInPlay; import mage.filter.FilterPermanent; import mage.filter.FilterPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -46,7 +46,7 @@ public class FilterPermanentOrPlayer extends FilterImpl implements Fil return true; } - public void add(ObjectPlayerPredicate predicate) { + public void add(ObjectSourcePlayerPredicate predicate) { playerFilter.add((Predicate) predicate); permanentFilter.add((Predicate) predicate); } diff --git a/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java b/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java index 5a8c08ec3ae..e15fa68b20f 100644 --- a/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java +++ b/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java @@ -1,49 +1,17 @@ -/* - * - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - * - */ package mage.filter.common; -import java.util.UUID; - import mage.MageObject; import mage.filter.FilterImpl; import mage.filter.FilterInPlay; import mage.filter.FilterPermanent; import mage.filter.FilterSpell; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.Spell; +import java.util.UUID; + /** - * * @author LevelX */ public class FilterSpellOrPermanent extends FilterImpl implements FilterInPlay { @@ -92,12 +60,11 @@ public class FilterSpellOrPermanent extends FilterImpl implements Fi return false; } - public final void add(ObjectPlayerPredicate predicate) { - if (isLockedFilter()) { - throw new UnsupportedOperationException("You may not modify a locked filter"); - } - spellFilter.add(predicate); - permanentFilter.add(predicate); + @Override + public void setLockedFilter(boolean lockedFilter) { + super.setLockedFilter(lockedFilter); + spellFilter.setLockedFilter(lockedFilter); + permanentFilter.setLockedFilter(lockedFilter); } public FilterPermanent getPermanentFilter() { diff --git a/Mage/src/main/java/mage/filter/predicate/ObjectPlayer.java b/Mage/src/main/java/mage/filter/predicate/ObjectPlayer.java deleted file mode 100644 index d59c0fffb5e..00000000000 --- a/Mage/src/main/java/mage/filter/predicate/ObjectPlayer.java +++ /dev/null @@ -1,28 +0,0 @@ - -package mage.filter.predicate; - -import java.util.UUID; - -/** - * - * @author North - * @param - */ -public class ObjectPlayer { - - protected final T object; - protected final UUID playerId; - - public ObjectPlayer(T object, UUID playerId) { - this.object = object; - this.playerId = playerId; - } - - public T getObject() { - return object; - } - - public UUID getPlayerId() { - return playerId; - } -} diff --git a/Mage/src/main/java/mage/filter/predicate/ObjectPlayerPredicate.java b/Mage/src/main/java/mage/filter/predicate/ObjectPlayerPredicate.java deleted file mode 100644 index 6f664d00ba7..00000000000 --- a/Mage/src/main/java/mage/filter/predicate/ObjectPlayerPredicate.java +++ /dev/null @@ -1,11 +0,0 @@ - -package mage.filter.predicate; - -/** - * - * @author North - * @param - */ -@FunctionalInterface -public interface ObjectPlayerPredicate extends Predicate { -} diff --git a/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayer.java b/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayer.java index 671c8abf0ca..b5019768bdd 100644 --- a/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayer.java +++ b/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayer.java @@ -1,22 +1,31 @@ - package mage.filter.predicate; import java.util.UUID; /** - * - * @author North * @param + * @author North */ -public class ObjectSourcePlayer extends ObjectPlayer { +public class ObjectSourcePlayer { + protected final T object; + protected final UUID playerId; protected final UUID sourceId; public ObjectSourcePlayer(T object, UUID sourceId, UUID sourceControllerId) { - super(object, sourceControllerId); + this.object = object; + this.playerId = sourceControllerId; this.sourceId = sourceId; } + public T getObject() { + return object; + } + + public UUID getPlayerId() { + return playerId; + } + public UUID getSourceId() { return sourceId; } diff --git a/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayerPredicate.java b/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayerPredicate.java index 92bfbddee80..bd451e1112a 100644 --- a/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayerPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/ObjectSourcePlayerPredicate.java @@ -1,11 +1,9 @@ - package mage.filter.predicate; /** - * - * @author North * @param + * @author North */ @FunctionalInterface -public interface ObjectSourcePlayerPredicate extends ObjectPlayerPredicate { +public interface ObjectSourcePlayerPredicate extends Predicate> { } diff --git a/Mage/src/main/java/mage/filter/predicate/card/CardOnTopOfLibraryPredicate.java b/Mage/src/main/java/mage/filter/predicate/card/CardOnTopOfLibraryPredicate.java index f21aa550852..a39ce5110bd 100644 --- a/Mage/src/main/java/mage/filter/predicate/card/CardOnTopOfLibraryPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/card/CardOnTopOfLibraryPredicate.java @@ -1,8 +1,8 @@ package mage.filter.predicate.card; import mage.cards.Card; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.players.Player; @@ -10,12 +10,12 @@ import mage.players.Player; * @author JayDi85 */ -public enum CardOnTopOfLibraryPredicate implements ObjectPlayerPredicate> { +public enum CardOnTopOfLibraryPredicate implements ObjectSourcePlayerPredicate { YOUR, ANY; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Player player; switch (this) { diff --git a/Mage/src/main/java/mage/filter/predicate/card/DefendingPlayerOwnsCardPredicate.java b/Mage/src/main/java/mage/filter/predicate/card/DefendingPlayerOwnsCardPredicate.java index b4eeb609e5b..a863a65c2fd 100644 --- a/Mage/src/main/java/mage/filter/predicate/card/DefendingPlayerOwnsCardPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/card/DefendingPlayerOwnsCardPredicate.java @@ -1,18 +1,18 @@ package mage.filter.predicate.card; import mage.cards.Card; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; /** * @author TheElk801 */ -public enum DefendingPlayerOwnsCardPredicate implements ObjectPlayerPredicate> { +public enum DefendingPlayerOwnsCardPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { return game.getCombat().getPlayerDefenders(game, false).contains(input.getObject().getOwnerId()); } diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/AnotherCardPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/AnotherCardPredicate.java index 39f3d86867d..6bc62682bca 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/AnotherCardPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/AnotherCardPredicate.java @@ -10,7 +10,7 @@ import mage.game.Game; * * @author North */ -public class AnotherCardPredicate implements ObjectSourcePlayerPredicate> { +public class AnotherCardPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/AnotherPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/AnotherPredicate.java index 1c104eb326e..a7ce4548085 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/AnotherPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/AnotherPredicate.java @@ -9,7 +9,7 @@ import mage.game.Game; /** * @author North */ -public enum AnotherPredicate implements ObjectSourcePlayerPredicate> { +public enum AnotherPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenColorPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenColorPredicate.java index 77a920369f2..e0575ae97ce 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenColorPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenColorPredicate.java @@ -9,7 +9,7 @@ import mage.game.Game; /** * @author TheElk801 */ -public enum ChosenColorPredicate implements ObjectSourcePlayerPredicate> { +public enum ChosenColorPredicate implements ObjectSourcePlayerPredicate { TRUE(true), FALSE(false); private final boolean value; diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenSubtypePredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenSubtypePredicate.java index ed2cdb4cca0..1ac3570785d 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenSubtypePredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenSubtypePredicate.java @@ -13,7 +13,7 @@ import mage.game.Game; * * @author LoneFox */ -public enum ChosenSubtypePredicate implements ObjectSourcePlayerPredicate> { +public enum ChosenSubtypePredicate implements ObjectSourcePlayerPredicate { TRUE(true), FALSE(false); private final boolean value; diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/SharesColorWithSourcePredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/SharesColorWithSourcePredicate.java index 56f38b0d622..3beae46614e 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/SharesColorWithSourcePredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/SharesColorWithSourcePredicate.java @@ -10,7 +10,7 @@ import mage.game.Game; * @author LevelX2 */ -public class SharesColorWithSourcePredicate implements ObjectSourcePlayerPredicate> { +public class SharesColorWithSourcePredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsOnlyOnePlayerPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsOnlyOnePlayerPredicate.java index 5510e927ea2..98eea077fd6 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsOnlyOnePlayerPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsOnlyOnePlayerPredicate.java @@ -14,7 +14,7 @@ import mage.target.Target; * * @author jeffwadsworth */ -public class TargetsOnlyOnePlayerPredicate implements ObjectSourcePlayerPredicate> { +public class TargetsOnlyOnePlayerPredicate implements ObjectSourcePlayerPredicate { public TargetsOnlyOnePlayerPredicate() { } 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 fd16aea94be..ccb8adea32e 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPermanentPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPermanentPredicate.java @@ -15,7 +15,7 @@ import java.util.UUID; /** * @author LoneFox */ -public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate> { +public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate { private final FilterPermanent targetFilter; 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 e7bf4fe8a18..d5af2774f44 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPlayerPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPlayerPredicate.java @@ -14,7 +14,7 @@ import mage.target.Target; * * @author jeffwadsworth */ -public class TargetsPlayerPredicate implements ObjectSourcePlayerPredicate> { +public class TargetsPlayerPredicate implements ObjectSourcePlayerPredicate { public TargetsPlayerPredicate() { } diff --git a/Mage/src/main/java/mage/filter/predicate/other/AnotherTargetPredicate.java b/Mage/src/main/java/mage/filter/predicate/other/AnotherTargetPredicate.java index 5383ed5731e..5ab2b2f8c4c 100644 --- a/Mage/src/main/java/mage/filter/predicate/other/AnotherTargetPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/other/AnotherTargetPredicate.java @@ -18,7 +18,7 @@ import mage.target.Target; * * @author LevelX2 */ -public class AnotherTargetPredicate implements ObjectSourcePlayerPredicate> { +public class AnotherTargetPredicate implements ObjectSourcePlayerPredicate { private final int targetTag; private final boolean crossModalCheck; diff --git a/Mage/src/main/java/mage/filter/predicate/other/DamagedPlayerThisTurnPredicate.java b/Mage/src/main/java/mage/filter/predicate/other/DamagedPlayerThisTurnPredicate.java index c45dc5815c3..4f3392da66b 100644 --- a/Mage/src/main/java/mage/filter/predicate/other/DamagedPlayerThisTurnPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/other/DamagedPlayerThisTurnPredicate.java @@ -1,8 +1,8 @@ package mage.filter.predicate.other; import mage.constants.TargetController; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Controllable; import mage.game.Game; import mage.watchers.common.PlayerDamagedBySourceWatcher; @@ -12,7 +12,7 @@ import java.util.UUID; /** * @author LevelX2 */ -public class DamagedPlayerThisTurnPredicate implements ObjectPlayerPredicate> { +public class DamagedPlayerThisTurnPredicate implements ObjectSourcePlayerPredicate { private final TargetController controller; @@ -21,7 +21,7 @@ public class DamagedPlayerThisTurnPredicate implements ObjectPlayerPredicate input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Controllable object = input.getObject(); UUID playerId = input.getPlayerId(); diff --git a/Mage/src/main/java/mage/filter/predicate/other/PlayerCanGainLifePredicate.java b/Mage/src/main/java/mage/filter/predicate/other/PlayerCanGainLifePredicate.java index dcb75de1053..e8f48308e1a 100644 --- a/Mage/src/main/java/mage/filter/predicate/other/PlayerCanGainLifePredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/other/PlayerCanGainLifePredicate.java @@ -10,7 +10,7 @@ import mage.players.Player; * * @author LevelX2 */ -public class PlayerCanGainLifePredicate implements ObjectSourcePlayerPredicate> { +public class PlayerCanGainLifePredicate implements ObjectSourcePlayerPredicate { // public PlayerCanGainLifePredicate() { // } diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/AnotherEnchantedPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/AnotherEnchantedPredicate.java index 75d946dde9f..12bbdd5edbc 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/AnotherEnchantedPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/AnotherEnchantedPredicate.java @@ -10,7 +10,7 @@ import mage.game.permanent.Permanent; * * @author LevelX2 */ -public class AnotherEnchantedPredicate implements ObjectSourcePlayerPredicate> { +public class AnotherEnchantedPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToControlledPermanentPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToControlledPermanentPredicate.java index c8257e2c2dd..29f1b839a2f 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToControlledPermanentPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToControlledPermanentPredicate.java @@ -1,8 +1,8 @@ package mage.filter.predicate.permanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -10,10 +10,10 @@ import mage.game.permanent.Permanent; * * @author North & L_J */ -public class AttachedToControlledPermanentPredicate implements ObjectPlayerPredicate> { +public class AttachedToControlledPermanentPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectPlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { Permanent attachement = input.getObject(); if (attachement != null) { Permanent permanent = game.getPermanent(attachement.getAttachedTo()); diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/BlockingOrBlockedBySourcePredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/BlockingOrBlockedBySourcePredicate.java index 6b3048bbf78..4e5af9d9dd6 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/BlockingOrBlockedBySourcePredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/BlockingOrBlockedBySourcePredicate.java @@ -11,7 +11,7 @@ import java.util.UUID; /** * @author TheElk801 */ -public enum BlockingOrBlockedBySourcePredicate implements ObjectSourcePlayerPredicate> { +public enum BlockingOrBlockedBySourcePredicate implements ObjectSourcePlayerPredicate { BLOCKING, BLOCKED_BY, EITHER; diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/DefendingPlayerControlsPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/DefendingPlayerControlsPredicate.java index 89f43c2677f..d6cdf5e23ae 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/DefendingPlayerControlsPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/DefendingPlayerControlsPredicate.java @@ -8,7 +8,7 @@ import mage.game.permanent.Permanent; /** * @author TheElk801 */ -public enum DefendingPlayerControlsPredicate implements ObjectSourcePlayerPredicate> { +public enum DefendingPlayerControlsPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/GreatestPowerControlledPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/GreatestPowerControlledPredicate.java index 0b6e3d90e93..e2a83ef107c 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/GreatestPowerControlledPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/GreatestPowerControlledPredicate.java @@ -9,7 +9,7 @@ import mage.game.permanent.Permanent; /** * @author jeffwadsworth */ -public enum GreatestPowerControlledPredicate implements ObjectSourcePlayerPredicate> { +public enum GreatestPowerControlledPredicate implements ObjectSourcePlayerPredicate { instance; @Override diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index e6e9ea934b1..9cbdbf01d50 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -316,31 +316,17 @@ public interface Game extends MageItem, Serializable, Copyable { boolean replaceEvent(GameEvent event, Ability targetAbility); /** - * Creates and fires an damage prevention event + * Creates and fires a damage prevention event * * @param damageEvent damage event that will be replaced (instanceof * check will be done) * @param source ability that's the source of the prevention effect * @param game * @param amountToPrevent max preventable amount - * @return true prevention was successfull / false prevention was replaced + * @return true prevention was successful / false prevention was replaced */ PreventionEffectData preventDamage(GameEvent damageEvent, Ability source, Game game, int amountToPrevent); - /** - * Creates and fires an damage prevention event - * - * @param event damage event that will be replaced (instanceof - * check will be done) - * @param source ability that's the source of the prevention - * effect - * @param game - * @param preventAllDamage true if there is no limit to the damage that can - * be prevented - * @return true prevention was successfull / false prevention was replaced - */ - PreventionEffectData preventDamage(GameEvent event, Ability source, Game game, boolean preventAllDamage); - void start(UUID choosingPlayerId); void resume(); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index c07465a3335..71cb83352f3 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -3067,11 +3067,6 @@ public abstract class GameImpl implements Game { return state.replaceEvent(event, targetAbility, this); } - @Override - public PreventionEffectData preventDamage(GameEvent event, Ability source, Game game, boolean preventAllDamage) { - return preventDamage(event, source, game, Integer.MAX_VALUE); - } - @Override public PreventionEffectData preventDamage(GameEvent event, Ability source, Game game, int amountToPrevent) { PreventionEffectData result = new PreventionEffectData(amountToPrevent); diff --git a/Mage/src/main/java/mage/game/command/emblems/TeferiWhoSlowsTheSunsetEmblem.java b/Mage/src/main/java/mage/game/command/emblems/TeferiWhoSlowsTheSunsetEmblem.java new file mode 100644 index 00000000000..f2d3bd7981b --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/TeferiWhoSlowsTheSunsetEmblem.java @@ -0,0 +1,49 @@ +package mage.game.command.emblems; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.UntapAllDuringEachOtherPlayersUntapStepEffect; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.command.Emblem; +import mage.players.Player; + +public class TeferiWhoSlowsTheSunsetEmblem extends Emblem { + // You get an emblem with "Untap all permanents you control during each opponent's untap step" and "You draw a card during each opponent's draw step." + public TeferiWhoSlowsTheSunsetEmblem() { + this.setName("Emblem Teferi"); + this.getAbilities().add(new SimpleStaticAbility( + Zone.COMMAND, new UntapAllDuringEachOtherPlayersUntapStepEffect(StaticFilters.FILTER_CONTROLLED_PERMANENTS) + )); + this.getAbilities().add(new SimpleStaticAbility(new TeferiWhoSlowsTheSunsetEmblemEffect())); + } +} + +class TeferiWhoSlowsTheSunsetEmblemEffect extends ContinuousEffectImpl { + + TeferiWhoSlowsTheSunsetEmblemEffect() { + super(Duration.EndOfGame, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); + staticText = "you draw a card during each opponent's draw step"; + } + + private TeferiWhoSlowsTheSunsetEmblemEffect(final TeferiWhoSlowsTheSunsetEmblemEffect effect) { + super(effect); + } + + @Override + public TeferiWhoSlowsTheSunsetEmblemEffect copy() { + return new TeferiWhoSlowsTheSunsetEmblemEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.setDrawsOnOpponentsTurn(true); + return true; + } +} diff --git a/Mage/src/main/java/mage/game/draft/DraftCube.java b/Mage/src/main/java/mage/game/draft/DraftCube.java index 5c4e63cc99a..70b96132ec5 100644 --- a/Mage/src/main/java/mage/game/draft/DraftCube.java +++ b/Mage/src/main/java/mage/game/draft/DraftCube.java @@ -86,7 +86,7 @@ public abstract class DraftCube { if (!cardId.getExtension().isEmpty()) { cardInfo = CardRepository.instance.findCardWPreferredSet(cardId.getName(), cardId.getExtension(), false); } else { - cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(cardId.getName(), false); + cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardId.getName(), false); } if (cardInfo != null) { diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 704d001fa14..69c54154821 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -464,6 +464,8 @@ public class GameEvent implements Serializable { VENTURE, VENTURED, DUNGEON_COMPLETED, REMOVED_FROM_COMBAT, // targetId id of permanent removed from combat + FORETOLD, // targetId id of card foretold + FORETELL, // targetId id of card foretell playerId id of the controller //custom events CUSTOM_EVENT } diff --git a/Mage/src/main/java/mage/game/events/TargetEvent.java b/Mage/src/main/java/mage/game/events/TargetEvent.java index 063d16ac2ab..f2c90ebc87b 100644 --- a/Mage/src/main/java/mage/game/events/TargetEvent.java +++ b/Mage/src/main/java/mage/game/events/TargetEvent.java @@ -2,6 +2,7 @@ package mage.game.events; import mage.abilities.Ability; import mage.cards.Card; +import mage.players.Player; import java.util.UUID; @@ -20,6 +21,11 @@ public class TargetEvent extends GameEvent { this.setSourceId(sourceId); } + public TargetEvent(Player target, UUID sourceId, UUID sourceControllerId) { + super(GameEvent.EventType.TARGET, target.getId(), null, sourceControllerId); + this.setSourceId(sourceId); + } + /** * @param targetId * @param source diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index ded11c011ba..65b47042538 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -125,6 +125,8 @@ public interface Permanent extends Card, Controllable { int getDamage(); + int damage(int damage, Ability source, Game game); + int damage(int damage, UUID attackerId, Ability source, Game game); int damage(int damage, UUID attackerId, Ability source, Game game, boolean combat, boolean preventable); @@ -168,6 +170,8 @@ public interface Permanent extends Card, Controllable { MageObject getBasicMageObject(Game game); + boolean destroy(Ability source, Game game); + boolean destroy(Ability source, Game game, boolean noRegen); /** diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index b903450cc1a..78dcabfa8be 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -840,6 +840,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return this.damage; } + @Override + public int damage(int damage, Ability source, Game game) { + return damage(damage, source.getSourceId(), source, game); + } + @Override public int damage(int damage, UUID attackerId, Ability source, Game game) { return doDamage(damage, attackerId, source, game, true, false, false, null); @@ -1112,8 +1117,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return false; } } + if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game) - && game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null + && null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && abilities.stream() .filter(HexproofBaseAbility.class::isInstance) .map(HexproofBaseAbility.class::cast) @@ -1124,9 +1130,14 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { if (hasProtectionFrom(source, game)) { return false; } - // needed to get the correct possible targets if target rule modification effects are active - // e.g. Fiendslayer Paladin tried to target with Ultimate Price - return !game.getContinuousEffects().preventedByRuleModification(new TargetEvent(this, source.getId(), sourceControllerId), null, game, true); + + // example: Fiendslayer Paladin tried to target with Ultimate Price + return !game.getContinuousEffects().preventedByRuleModification( + new TargetEvent(this, source.getId(), sourceControllerId), + null, + game, + true + ); } return true; @@ -1165,6 +1176,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return true; } + @Override + public boolean destroy(Ability source, Game game) { + return destroy(source, game, false); + } + @Override public boolean destroy(Ability source, Game game, boolean noRegen) { // Only permanets on the battlefield can be destroyed diff --git a/Mage/src/main/java/mage/game/permanent/token/ConsumingBlobToken.java b/Mage/src/main/java/mage/game/permanent/token/ConsumingBlobToken.java new file mode 100644 index 00000000000..c64de225556 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/ConsumingBlobToken.java @@ -0,0 +1,71 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.hint.common.CardTypesInGraveyardHint; +import mage.constants.*; +import mage.game.Game; + +/** + * @author ciaccona007 + */ +public final class ConsumingBlobToken extends TokenImpl { + + public ConsumingBlobToken() { + super("Ooze", "green Ooze creature token with \"This creature's power is equal to the number of card types among cards in your graveyard and its toughness is equal to that number plus 1\"."); + setOriginalExpansionSetCode("MID"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.OOZE); + color.setGreen(true); + + power = new MageInt(0); + toughness = new MageInt(1); + + // This creature's power is equal to the number of card types among cards in your graveyard and its toughness is equal to that number plus 1. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConsumingBlobTokenEffect()).addHint(CardTypesInGraveyardHint.YOU)); + + } + + private ConsumingBlobToken(final ConsumingBlobToken token) { + super(token); + } + + @Override + public ConsumingBlobToken copy() { + return new ConsumingBlobToken(this); + } +} + + +class ConsumingBlobTokenEffect extends ContinuousEffectImpl { + + public ConsumingBlobTokenEffect() { + super(Duration.EndOfGame, Layer.PTChangingEffects_7, SubLayer.CharacteristicDefining_7a, Outcome.BoostCreature); + staticText = "{this}'s power is equal to the number of card types among cards in your graveyard and its toughness is equal to that number plus 1"; + } + + public ConsumingBlobTokenEffect(final ConsumingBlobTokenEffect effect) { + super(effect); + } + + @Override + public ConsumingBlobTokenEffect copy() { + return new ConsumingBlobTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject target = source.getSourceObject(game); + if (target == null) { + return false; + } + int number = CardTypesInGraveyardCount.YOU.calculate(game, source, this); + target.getPower().setValue(number); + target.getToughness().setValue(number + 1); + return true; + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/OminousRoostToken.java b/Mage/src/main/java/mage/game/permanent/token/OminousRoostToken.java new file mode 100644 index 00000000000..17e645c212b --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/OminousRoostToken.java @@ -0,0 +1,31 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.common.CanBlockOnlyFlyingAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +public class OminousRoostToken extends TokenImpl { + + public OminousRoostToken() { + super("Bird", "1/1 blue Bird creature token with flying and \"This creature can block only creatures with flying\""); + cardType.add(CardType.CREATURE); + color.setBlue(true); + subtype.add(SubType.BIRD); + power = new MageInt(1); + toughness = new MageInt(1); + + this.addAbility(FlyingAbility.getInstance()); + this.addAbility(new CanBlockOnlyFlyingAbility()); + } + + public OminousRoostToken(final OminousRoostToken token) { + super(token); + } + + @Override + public Token copy() { + return new OminousRoostToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/RiseOfTheAntsToken.java b/Mage/src/main/java/mage/game/permanent/token/RiseOfTheAntsToken.java new file mode 100644 index 00000000000..81167ac5669 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/RiseOfTheAntsToken.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 RiseOfTheAntsToken extends TokenImpl { + + public RiseOfTheAntsToken() { + super("Insect", "3/3 green Insect creature token"); + cardType.add(CardType.CREATURE); + color.setGreen(true); + subtype.add(SubType.INSECT); + power = new MageInt(3); + toughness = new MageInt(3); + } + + public RiseOfTheAntsToken(final RiseOfTheAntsToken token) { + super(token); + } + + public RiseOfTheAntsToken copy() { + return new RiseOfTheAntsToken(this); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/game/permanent/token/SeizeTheStormToken.java b/Mage/src/main/java/mage/game/permanent/token/SeizeTheStormToken.java new file mode 100644 index 00000000000..35004de4eef --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/SeizeTheStormToken.java @@ -0,0 +1,43 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.TrampleAbility; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class SeizeTheStormToken extends TokenImpl { + + public SeizeTheStormToken(DynamicValue xValue, Hint hint) { + super("Elemental", "red Elemental creature token with trample and " + + "\"This creature's power and toughness are each equal to the number of instant " + + "and sorcery cards in your graveyard, plus the number of cards with flashback you own in exile.\""); + cardType.add(CardType.CREATURE); + color.setRed(true); + subtype.add(SubType.ELEMENTAL); + power = new MageInt(0); + toughness = new MageInt(0); + this.addAbility(TrampleAbility.getInstance()); + this.addAbility(new SimpleStaticAbility(new SetPowerToughnessSourceEffect( + xValue, Duration.WhileOnBattlefield + ).setText("this creature's power and toughness are each equal to the number of " + + "instant and sorcery cards in your graveyard, plus the number of cards with flashback you own in exile") + ).addHint(hint)); + } + + private SeizeTheStormToken(final SeizeTheStormToken token) { + super(token); + } + + @Override + public SeizeTheStormToken copy() { + return new SeizeTheStormToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 82ea3710eae..6738fd8f1a3 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -989,6 +989,11 @@ public class Spell extends StackObjectImpl implements Card { return card.getCounters(state); } + @Override + public boolean addCounters(Counter counter, Ability source, Game game) { + return card.addCounters(counter, source, game); + } + @Override public boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game) { return card.addCounters(counter, playerAddingCounters, source, game); @@ -1009,6 +1014,11 @@ public class Spell extends StackObjectImpl implements Card { return card.addCounters(counter, playerAddingCounters, source, game, appliedEffects, isEffect); } + @Override + public boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List appliedEffects, boolean isEffect, int maxCounters) { + return card.addCounters(counter, playerAddingCounters, source, game, appliedEffects, isEffect, maxCounters); + } + @Override public void removeCounters(String name, int amount, Ability source, Game game) { card.removeCounters(name, amount, source, game); diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index 98df1330603..e9493bcb317 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -664,8 +664,9 @@ public class StackAbility extends StackObjectImpl implements Ability { } @Override - public void setCostAdjuster(CostAdjuster costAdjuster) { + public StackAbility setCostAdjuster(CostAdjuster costAdjuster) { this.costAdjuster = costAdjuster; + return this; } @Override diff --git a/Mage/src/main/java/mage/game/turn/DrawStep.java b/Mage/src/main/java/mage/game/turn/DrawStep.java index ffa7d26f3b7..df07bf4c85b 100644 --- a/Mage/src/main/java/mage/game/turn/DrawStep.java +++ b/Mage/src/main/java/mage/game/turn/DrawStep.java @@ -29,6 +29,14 @@ public class DrawStep extends Step { //20091005 - 504.1/703.4c activePlayer.drawCards(1, null, game); game.applyEffects(); + for (UUID playerId : game.getState().getPlayersInRange(activePlayerId, game)) { + Player player = game.getPlayer(playerId); + if (player != null + && player.isDrawsOnOpponentsTurn() + && player.hasOpponent(activePlayerId, game)) { + player.drawCards(1, null, game); + } + } super.beginStep(game, activePlayerId); } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 35adef2c317..1a1f9111da2 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -178,6 +178,10 @@ public interface Player extends MageItem, Copyable { boolean canPlayCardsFromGraveyard(); + void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn); + + boolean isDrawsOnOpponentsTurn(); + /** * Returns alternative casting costs a player can cast spells for * @@ -449,19 +453,15 @@ public interface Player extends MageItem, Copyable { boolean canPlayLand(); /** - * Plays a card if possible + * Plays a card (play land or cast spell). Works from any zones without timing restriction * * @param card the card that can be cast * @param game - * @param noMana if it's a spell i can be cast without paying mana - * @param ignoreTiming if it's cast during the resolution of another - * spell no sorcery or play land timing restriction - * are checked. For a land it has to be the turn of - * the player playing that card. + * @param noMana if it's a spell it can be cast without paying mana * @param approvingObject reference to the ability that allows to play the card * @return */ - boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, ApprovingObject approvingObject); + boolean playCard(Card card, Game game, boolean noMana, ApprovingObject approvingObject); /** * @param card the land card to play diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 6be8a2d3289..2feceac5e79 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -138,6 +138,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected boolean canPayLifeCost = true; protected boolean loseByZeroOrLessLife = true; protected boolean canPlayCardsFromGraveyard = true; + protected boolean drawsOnOpponentsTurn = false; protected FilterPermanent sacrificeCostFilter; @@ -239,6 +240,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.canLoseLife = player.canLoseLife; this.loseByZeroOrLessLife = player.loseByZeroOrLessLife; this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard; + this.drawsOnOpponentsTurn = player.drawsOnOpponentsTurn; this.attachments.addAll(player.attachments); @@ -347,6 +349,7 @@ public abstract class PlayerImpl implements Player, Serializable { ? player.getSacrificeCostFilter().copy() : null; this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife(); this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard(); + this.drawsOnOpponentsTurn = player.isDrawsOnOpponentsTurn(); this.alternativeSourceCosts.clear(); this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts()); @@ -470,6 +473,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.sacrificeCostFilter = null; this.loseByZeroOrLessLife = true; this.canPlayCardsFromGraveyard = false; + this.drawsOnOpponentsTurn = false; this.topCardRevealed = false; this.alternativeSourceCosts.clear(); this.clearCastSourceIdManaCosts(); @@ -630,22 +634,33 @@ public abstract class PlayerImpl implements Player, Serializable { return false; } if (source != null) { - if (abilities.containsKey(ShroudAbility.getInstance().getId())) { + if (abilities.containsKey(ShroudAbility.getInstance().getId()) + && null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.SHROUD, null, sourceControllerId, game)) { return false; } + if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) - && game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null + && null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && abilities.stream() - .filter(HexproofBaseAbility.class::isInstance) - .map(HexproofBaseAbility.class::cast) - .anyMatch(ability -> ability.checkObject(source, game))) { + .filter(HexproofBaseAbility.class::isInstance) + .map(HexproofBaseAbility.class::cast) + .anyMatch(ability -> ability.checkObject(source, game))) { return false; } - return !hasProtectionFrom(source, game); - } + if (hasProtectionFrom(source, game)) { + return false; + } + // example: Peace Talks + return !game.getContinuousEffects().preventedByRuleModification( + new TargetEvent(this, source.getId(), sourceControllerId), + null, + game, + true + ); + } return true; } @@ -679,7 +694,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(getLogName() + " discards down to " + this.maxHandSize + (this.maxHandSize == 1 - ? " hand card" : " hand cards")); + ? " hand card" : " hand cards")); } discard(hand.size() - this.maxHandSize, false, false, null, game); } @@ -1128,16 +1143,21 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, ApprovingObject approvingObject) { + public boolean playCard(Card card, Game game, boolean noMana, ApprovingObject approvingObject) { if (card == null) { return false; } + + // play without timing and from any zone boolean result; if (card.isLand(game)) { - result = playLand(card, game, ignoreTiming); + result = playLand(card, game, true); } else { - result = cast(card.getSpellAbility(), game, noMana, approvingObject); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + result = cast(this.chooseAbilityForCast(card, game, noMana), game, noMana, approvingObject); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); } + if (!result) { game.informPlayer(this, "You can't play " + card.getIdName() + '.'); } @@ -1147,7 +1167,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param originalAbility * @param game - * @param noMana cast it without paying mana costs + * @param noMana cast it without paying mana costs * @param approvingObject which object approved the cast * @return */ @@ -1544,13 +1564,14 @@ public abstract class PlayerImpl implements Player, Serializable { * for choosing from the card (example: effect allow to cast card and player * must choose the spell ability) * + * @param game * @param playerId * @param object * @param zone - * @param game + * @param noMana * @return */ - public static LinkedHashMap getSpellAbilities(UUID playerId, MageObject object, Zone zone, Game game) { + public static LinkedHashMap getCastableSpellAbilities(Game game, UUID playerId, MageObject object, Zone zone, boolean noMana) { // it uses simple check from spellCanBeActivatedRegularlyNow // reason: no approved info here (e.g. forced to choose spell ability from cast card) LinkedHashMap useable = new LinkedHashMap<>(); @@ -1563,16 +1584,29 @@ public abstract class PlayerImpl implements Player, Serializable { for (Ability ability : allAbilities) { if (ability instanceof SpellAbility) { - switch (((SpellAbility) ability).getSpellAbilityType()) { + SpellAbility spellAbility = (SpellAbility) ability; + + switch (spellAbility.getSpellAbilityType()) { case BASE_ALTERNATE: - if (((SpellAbility) ability).spellCanBeActivatedRegularlyNow(playerId, game)) { - useable.put(ability.getId(), (SpellAbility) ability); // example: Chandra, Torch of Defiance +1 loyal ability + // rules: + // If you cast a spell “without paying its mana cost,” you can’t choose to cast it for + // any alternative costs. You can, however, pay additional costs, such as kicker costs. + // If the card has any mandatory additional costs, those must be paid to cast the spell. + // (2021-02-05) + if (!noMana) { + if (spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)) { + useable.put(spellAbility.getId(), spellAbility); // example: Chandra, Torch of Defiance +1 loyal ability + } + return useable; } - return useable; + break; case SPLIT_FUSED: + // rules: + // If you cast a split card with fuse from your hand without paying its mana cost, + // you can choose to use its fuse ability and cast both halves without paying their mana costs. if (zone == Zone.HAND) { - if (ability.canChooseTarget(game, playerId)) { - useable.put(ability.getId(), (SpellAbility) ability); + if (spellAbility.canChooseTarget(game, playerId)) { + useable.put(spellAbility.getId(), spellAbility); } } case SPLIT: @@ -1599,8 +1633,8 @@ public abstract class PlayerImpl implements Player, Serializable { } return useable; default: - if (((SpellAbility) ability).spellCanBeActivatedRegularlyNow(playerId, game)) { - useable.put(ability.getId(), (SpellAbility) ability); + if (spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)) { + useable.put(spellAbility.getId(), spellAbility); } } } @@ -2895,7 +2929,7 @@ public abstract class PlayerImpl implements Player, Serializable { * @return */ private Object rollDieInner(Outcome outcome, Game game, Ability source, RollDieType rollDieType, - int sidesAmount, int chaosSidesAmount, int planarSidesAmount, int rollsAmount) { + int sidesAmount, int chaosSidesAmount, int planarSidesAmount, int rollsAmount) { if (rollsAmount == 1) { return rollDieInnerWithReplacement(game, source, rollDieType, sidesAmount, chaosSidesAmount, planarSidesAmount); } @@ -2991,8 +3025,8 @@ public abstract class PlayerImpl implements Player, Serializable { * @param outcome * @param source * @param game - * @param sidesAmount number of sides the dice has - * @param rollsAmount number of tries to roll the dice + * @param sidesAmount number of sides the dice has + * @param rollsAmount number of tries to roll the dice * @param ignoreLowestAmount remove the lowest rolls from the results * @return the number that the player rolled */ @@ -3010,18 +3044,18 @@ public abstract class PlayerImpl implements Player, Serializable { * @param outcome * @param source * @param game - * @param rollDieType die type to roll, e.g. planar or numerical - * @param sidesAmount sides per die - * @param chaosSidesAmount for planar die: chaos sides - * @param planarSidesAmount for planar die: planar sides - * @param rollsAmount rolls + * @param rollDieType die type to roll, e.g. planar or numerical + * @param sidesAmount sides per die + * @param chaosSidesAmount for planar die: chaos sides + * @param planarSidesAmount for planar die: planar sides + * @param rollsAmount rolls * @param ignoreLowestAmount for numerical die: ignore multiple rolls with - * the lowest values + * the lowest values * @return */ private List rollDiceInner(Outcome outcome, Ability source, Game game, RollDieType rollDieType, - int sidesAmount, int chaosSidesAmount, int planarSidesAmount, - int rollsAmount, int ignoreLowestAmount) { + int sidesAmount, int chaosSidesAmount, int planarSidesAmount, + int rollsAmount, int ignoreLowestAmount) { RollDiceEvent rollDiceEvent = new RollDiceEvent(source, rollDieType, sidesAmount, rollsAmount); if (ignoreLowestAmount > 0) { rollDiceEvent.incIgnoreLowestAmount(ignoreLowestAmount); @@ -3181,10 +3215,10 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param source * @param game - * @param chaosSidesAmount The number of chaos sides the planar die - * currently has (normally 1 but can be 5) + * @param chaosSidesAmount The number of chaos sides the planar die + * currently has (normally 1 but can be 5) * @param planarSidesAmount The number of chaos sides the planar die - * currently has (normally 1) + * currently has (normally 1) * @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll * or BlankRoll */ @@ -3242,7 +3276,7 @@ public abstract class PlayerImpl implements Player, Serializable { for (Card card : getHand().getCards(game)) { Abilities manaAbilities = card.getAbilities(game).getAvailableActivatedManaAbilities(Zone.HAND, playerId, game); - for (Iterator it = manaAbilities.iterator(); it.hasNext();) { + for (Iterator it = manaAbilities.iterator(); it.hasNext(); ) { ActivatedManaAbilityImpl ability = it.next(); Abilities noTapAbilities = new AbilitiesImpl<>(ability); if (ability.getManaCosts().isEmpty() && !ability.isPoolDependant()) { @@ -3259,7 +3293,7 @@ public abstract class PlayerImpl implements Player, Serializable { boolean useLater = false; // sources with mana costs or mana pool dependency Abilities manaAbilities = permanent.getAbilities(game).getAvailableActivatedManaAbilities(Zone.BATTLEFIELD, playerId, game); // returns ability only if canActivate is true - for (Iterator it = manaAbilities.iterator(); it.hasNext();) { + for (Iterator it = manaAbilities.iterator(); it.hasNext(); ) { ActivatedManaAbilityImpl ability = it.next(); if (canUse == null) { canUse = permanent.canUseActivatedAbilities(game); @@ -3301,7 +3335,7 @@ public abstract class PlayerImpl implements Player, Serializable { boolean usePoolDependantAbilities = false; // use such abilities later than other if possible because it can maximize mana production while (anAbilityWasUsed && !sourceWithCosts.isEmpty()) { anAbilityWasUsed = false; - for (Iterator> iterator = sourceWithCosts.iterator(); iterator.hasNext();) { + for (Iterator> iterator = sourceWithCosts.iterator(); iterator.hasNext(); ) { Abilities manaAbilities = iterator.next(); if (usePoolDependantAbilities || !manaAbilities.hasPoolDependantAbilities()) { boolean used; @@ -3337,7 +3371,7 @@ public abstract class PlayerImpl implements Player, Serializable { * and cleared thereafter * * @param netManaAvailable the net mana produced by the triggered mana - * abaility + * abaility */ @Override public void addAvailableTriggeredMana(List netManaAvailable @@ -3419,7 +3453,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param ability * @param availableMana if null, it won't be checked if enough mana is - * available + * available * @param sourceObject * @param game * @return @@ -3794,8 +3828,8 @@ public abstract class PlayerImpl implements Player, Serializable { } ApprovingObject approvingObject; - if (isPlaySpell || isPlayLand) { - // play hand from non hand zone + if ((isPlaySpell || isPlayLand) && (fromZone != Zone.BATTLEFIELD)) { + // play hand from non hand zone (except battlefield - you can't play already played permanents) approvingObject = game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, this.getId(), game); } else { @@ -3851,13 +3885,14 @@ public abstract class PlayerImpl implements Player, Serializable { /** * Returns a list of all available spells and abilities the player can - * currently cast/activate with his available resources + * currently cast/activate with his available resources. + * Without target validation. * * @param game - * @param hidden also from hidden objects (e.g. turned face down cards ?) - * @param fromZone of objects from which zone (ALL = from all zones) + * @param hidden also from hidden objects (e.g. turned face down cards ?) + * @param fromZone of objects from which zone (ALL = from all zones) * @param hideDuplicatedAbilities if equal abilities exist return only the - * first instance + * first instance * @return */ public List getPlayable(Game game, boolean hidden, Zone fromZone, boolean hideDuplicatedAbilities) { @@ -4316,6 +4351,16 @@ public abstract class PlayerImpl implements Player, Serializable { this.canPlayCardsFromGraveyard = playCardsFromGraveyard; } + @Override + public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { + this.drawsOnOpponentsTurn = drawsOnOpponentsTurn; + } + + @Override + public boolean isDrawsOnOpponentsTurn() { + return drawsOnOpponentsTurn; + } + @Override public boolean autoLoseGame() { return false; @@ -4443,7 +4488,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCards(Set cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return moveCards(cards, toZone, source, game, false, false, false, null); } @@ -4602,7 +4647,7 @@ public abstract class PlayerImpl implements Player, Serializable { // identify cards from one owner Cards cards = new CardsImpl(); UUID ownerId = null; - for (Iterator it = allCards.iterator(); it.hasNext();) { + for (Iterator it = allCards.iterator(); it.hasNext(); ) { Card card = it.next(); if (cards.isEmpty()) { ownerId = card.getOwnerId(); @@ -4780,7 +4825,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName() + (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' ' + (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) - + ' ' : "") + "to the exile zone" + CardUtil.getSourceLogName(game, source, card.getId())); + + ' ' : "") + "to the exile zone" + CardUtil.getSourceLogName(game, source, card.getId())); } } diff --git a/Mage/src/main/java/mage/target/TargetImpl.java b/Mage/src/main/java/mage/target/TargetImpl.java index 8d14e2f7031..0e1100e5b48 100644 --- a/Mage/src/main/java/mage/target/TargetImpl.java +++ b/Mage/src/main/java/mage/target/TargetImpl.java @@ -14,6 +14,7 @@ import mage.util.CardUtil; import mage.util.RandomUtil; import java.util.*; +import mage.game.permanent.Permanent; /** * @author BetaSteward_at_googlemail.com @@ -332,7 +333,16 @@ public abstract class TargetImpl implements Target { for (UUID targetId : targets.keySet()) { Card card = game.getCard(targetId); if (card != null) { - if (zoneChangeCounters.containsKey(targetId) && zoneChangeCounters.get(targetId) != card.getZoneChangeCounter(game)) { + // if a permanent, verify it is phased in, otherwise it is illegal + Permanent p = game.getPermanent(targetId); + if (p != null + && !p.isPhasedIn()) { + illegalTargets.add(targetId); + continue; // it's not legal so continue to have a look at other targeted objects + } + // check if the card moved to another zone + if (zoneChangeCounters.containsKey(targetId) + && zoneChangeCounters.get(targetId) != card.getZoneChangeCounter(game)) { illegalTargets.add(targetId); continue; // it's not legal so continue to have a look at other targeted objects } diff --git a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java index 5fa73b53a11..59b39f4ba89 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java @@ -19,6 +19,9 @@ import java.util.List; import java.util.UUID; /** + * + * Can be used with SearchLibrary only. User hasn't access to libs. + * * @author BetaSteward_at_googlemail.com */ public class TargetCardInLibrary extends TargetCard { diff --git a/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java b/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java new file mode 100644 index 00000000000..121b2e8ca52 --- /dev/null +++ b/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java @@ -0,0 +1,53 @@ +package mage.target.common; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class TargetCreaturesWithDifferentPowers extends TargetPermanent { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("creatures with different powers"); + + public TargetCreaturesWithDifferentPowers() { + super(0, Integer.MAX_VALUE, filter, false); + } + + private TargetCreaturesWithDifferentPowers(final TargetCreaturesWithDifferentPowers target) { + super(target); + } + + @Override + public TargetCreaturesWithDifferentPowers copy() { + return new TargetCreaturesWithDifferentPowers(this); + } + + @Override + public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + if (!super.canTarget(controllerId, id, source, game)) { + return false; + } + Permanent creature = game.getPermanent(id); + if (creature == null) { + return false; + } + return this.getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .noneMatch(p -> creature.getPower().getValue() == p); + } +} diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index c5a9cffbcae..402ecd44191 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -11,12 +11,15 @@ import mage.abilities.condition.Condition; import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.*; import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.asthought.CanPlayCardControllerEffect; import mage.abilities.effects.common.asthought.YouMaySpendManaAsAnyColorToCastTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.HintUtils; import mage.cards.*; import mage.constants.*; +import mage.counters.Counter; import mage.filter.Filter; import mage.filter.predicate.mageobject.NamePredicate; import mage.game.CardState; @@ -41,6 +44,7 @@ import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author nantuko @@ -1365,4 +1369,44 @@ public final class CardUtil { } return sb.toString(); } + + public static Stream castStream(Stream stream, Class clazz) { + return stream.filter(clazz::isInstance).map(clazz::cast); + } + + /** + * Move card or permanent to dest zone and add counter to it + * + * @param game + * @param source + * @param controller + * @param card can be card or permanent + * @param toZone + * @param counter + */ + public static boolean moveCardWithCounter(Game game, Ability source, Player controller, Card card, Zone toZone, Counter counter) { + if (toZone == Zone.BATTLEFIELD) { + throw new IllegalArgumentException("Wrong code usage - method doesn't support moving to battlefield zone"); + } + + // workaround: + // in ZONE_CHANGE replace events you must set new zone by event's setToZone, + // BUT for counter effect you need to complete zone change event first (so moveCards calls here) + // TODO: must be fixed someday by: + // * or by new event ZONE_CHANGED to apply counter effect on it + // * or by counter effects applier in ZONE_CHANGE event (see copy or token as example) + + // move to zone + if (!controller.moveCards(card, toZone, source, game)) { + return false; + } + + // add counter + // after move it's a new object (not a permanent), so must work with main card + Effect effect = new AddCountersTargetEffect(counter); + effect.setTargetPointer(new FixedTarget(card.getMainCard(), game)); + effect.apply(game, source); + return true; + } + } diff --git a/Mage/src/main/java/mage/util/JsonUtil.java b/Mage/src/main/java/mage/util/JsonUtil.java new file mode 100644 index 00000000000..6455d139a86 --- /dev/null +++ b/Mage/src/main/java/mage/util/JsonUtil.java @@ -0,0 +1,50 @@ +package mage.util; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +/** + * @author JayDi85 + */ +public class JsonUtil { + + public static JsonObject getAsObject(JsonObject json, String field) { + return json.has(field) ? json.get(field).getAsJsonObject() : null; + } + + public static JsonArray getAsArray(JsonObject json, String field) { + return json.has(field) ? json.get(field).getAsJsonArray() : null; + } + + public static String getAsString(JsonObject json, String field) { + return getAsString(json, field, ""); + } + + public static String getAsString(JsonObject json, String field, String nullValue) { + return json.has(field) ? json.get(field).getAsString() : nullValue; + } + + public static int getAsInt(JsonObject json, String field) { + return getAsInt(json, field, 0); + } + + public static int getAsInt(JsonObject json, String field, int nullValue) { + return json.has(field) ? json.get(field).getAsInt() : nullValue; + } + + public static double getAsDouble(JsonObject json, String field) { + return getAsDouble(json, field, 0.0); + } + + public static double getAsDouble(JsonObject json, String field, double nullValue) { + return json.has(field) ? json.get(field).getAsDouble() : nullValue; + } + + public static boolean getAsBoolean(JsonObject json, String field) { + return getAsBoolean(json, field, false); + } + + public static boolean getAsBoolean(JsonObject json, String field, boolean nullValue) { + return json.has(field) ? json.get(field).getAsBoolean() : nullValue; + } +} diff --git a/Mage/src/main/java/mage/watchers/common/AttackedOrBlockedThisCombatWatcher.java b/Mage/src/main/java/mage/watchers/common/AttackedOrBlockedThisCombatWatcher.java index 5f5410fca36..271c2c2d8ae 100644 --- a/Mage/src/main/java/mage/watchers/common/AttackedOrBlockedThisCombatWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/AttackedOrBlockedThisCombatWatcher.java @@ -1,15 +1,15 @@ package mage.watchers.common; -import java.util.HashSet; -import java.util.Set; import mage.MageObjectReference; import mage.constants.WatcherScope; import mage.game.Game; import mage.game.events.GameEvent; import mage.watchers.Watcher; +import java.util.HashSet; +import java.util.Set; + /** - * * @author LevelX2 */ public class AttackedOrBlockedThisCombatWatcher extends Watcher { @@ -23,14 +23,16 @@ public class AttackedOrBlockedThisCombatWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.BEGIN_COMBAT_STEP_PRE) { - this.getAttackedThisTurnCreatures().clear(); - } - if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) { - this.getAttackedThisTurnCreatures().add(new MageObjectReference(event.getSourceId(), game)); - } - if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED) { - this.getBlockedThisTurnCreatures().add(new MageObjectReference(event.getSourceId(), game)); + switch (event.getType()) { + case BEGIN_COMBAT_STEP_PRE: + this.attackedThisTurnCreatures.clear(); + this.blockedThisTurnCreatures.clear(); + return; + case ATTACKER_DECLARED: + this.attackedThisTurnCreatures.add(new MageObjectReference(event.getSourceId(), game)); + return; + case BLOCKER_DECLARED: + this.blockedThisTurnCreatures.add(new MageObjectReference(event.getSourceId(), game)); } } diff --git a/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java b/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java index 68aad21d073..3213e8b76de 100644 --- a/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java @@ -3,13 +3,10 @@ package mage.watchers.common; import java.util.HashSet; import java.util.Set; import java.util.UUID; -import mage.abilities.keyword.ForetellAbility; import mage.cards.Card; import mage.constants.WatcherScope; -import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.stack.Spell; import mage.util.CardUtil; import mage.watchers.Watcher; @@ -21,7 +18,7 @@ public class ForetoldWatcher extends Watcher { // If foretell was activated or a card was Foretold by the controller this turn, this list stores it. Cleared at the end of the turn. private final Set foretellCardsThisTurn = new HashSet<>(); - private final Set foretoldCardsThisTurn = new HashSet<>(); + private final Set foretoldCards = new HashSet<>(); public ForetoldWatcher() { super(WatcherScope.GAME); @@ -29,47 +26,35 @@ public class ForetoldWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.TAKEN_SPECIAL_ACTION) { - Card card = game.getCard(event.getSourceId()); + if (event.getType() == GameEvent.EventType.FORETELL) { + Card card = game.getCard(event.getTargetId()); if (card != null - && card.getAbilities(game).containsClass(ForetellAbility.class) && controllerId == event.getPlayerId()) { foretellCardsThisTurn.add(card.getId()); + foretoldCards.add(card.getId()); } } - if (event.getType() == GameEvent.EventType.SPELL_CAST - && event.getZone() == Zone.EXILED) { - Spell spell = (Spell) game.getObject(event.getTargetId()); - if (spell != null - && controllerId == event.getPlayerId()) { - UUID exileId = CardUtil.getExileZoneId(spell.getSourceId().toString() + "foretellAbility", game); - if (exileId != null) { - foretoldCardsThisTurn.add(spell.getSourceId()); - } + // Ethereal Valkyrie + if (event.getType() == GameEvent.EventType.FORETOLD) { + Card card = game.getCard(event.getTargetId()); + if (card != null) { + // Ethereal Valkyrie does not Foretell the card, it becomes Foretold, so don't add it to the Foretell list + foretoldCards.add(card.getId()); } } } - public boolean cardUsedForetell(UUID sourceId) { - return foretellCardsThisTurn.contains(sourceId); - } - public boolean cardWasForetold(UUID sourceId) { - return foretoldCardsThisTurn.contains(sourceId); + return foretoldCards.contains(sourceId); } public int countNumberForetellThisTurn() { return foretellCardsThisTurn.size(); } - public int countNumberForetoldThisTurn() { - return foretoldCardsThisTurn.size(); - } - @Override public void reset() { super.reset(); foretellCardsThisTurn.clear(); - foretoldCardsThisTurn.clear(); } } diff --git a/Mage/src/main/resources/jumpstart/jumpstart.txt b/Mage/src/main/resources/jumpstart/jumpstart.txt index 64aba0f50de..73df97d972a 100644 --- a/Mage/src/main/resources/jumpstart/jumpstart.txt +++ b/Mage/src/main/resources/jumpstart/jumpstart.txt @@ -1212,4 +1212,3 @@ 1 M21 71 Shipwreck Dowser 1 M21 62 Read the Tides 1 M21 295 Teferi's Protege - diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.cod b/Mage/src/test/data/importer/testdeck.cod similarity index 100% rename from Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.cod rename to Mage/src/test/data/importer/testdeck.cod diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.dec b/Mage/src/test/data/importer/testdeck.dec similarity index 100% rename from Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.dec rename to Mage/src/test/data/importer/testdeck.dec diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.draft b/Mage/src/test/data/importer/testdeck.draft similarity index 100% rename from Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.draft rename to Mage/src/test/data/importer/testdeck.draft diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.json b/Mage/src/test/data/importer/testdeck.json similarity index 100% rename from Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.json rename to Mage/src/test/data/importer/testdeck.json diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.mtga b/Mage/src/test/data/importer/testdeck.mtga similarity index 100% rename from Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.mtga rename to Mage/src/test/data/importer/testdeck.mtga diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.mwDeck b/Mage/src/test/data/importer/testdeck.mwDeck similarity index 100% rename from Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.mwDeck rename to Mage/src/test/data/importer/testdeck.mwDeck diff --git a/Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.o8d b/Mage/src/test/data/importer/testdeck.o8d similarity index 100% rename from Mage/src/test/java/mage/cards/decks/importer/samples/testdeck.o8d rename to Mage/src/test/data/importer/testdeck.o8d diff --git a/Mage/src/test/java/mage/cards/decks/importer/CodDeckImportTest.java b/Mage/src/test/java/mage/cards/decks/importer/CodDeckImportTest.java index c6ccb497143..c087b837ce8 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/CodDeckImportTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/CodDeckImportTest.java @@ -3,6 +3,8 @@ package mage.cards.decks.importer; import mage.cards.decks.DeckCardLists; import org.junit.Test; +import java.nio.file.Paths; + import static org.junit.Assert.assertEquals; public class CodDeckImportTest { @@ -23,7 +25,10 @@ public class CodDeckImportTest { }; StringBuilder errors = new StringBuilder(); DeckCardLists deck = importer.importDeck( - "src/test/java/mage/cards/decks/importer/samples/testdeck.cod", errors, false); + Paths.get("src", "test", "data", "importer", "testdeck.cod").toString(), + errors, + false + ); assertEquals("Deck Name", deck.getName()); TestDeckChecker.checker() diff --git a/Mage/src/test/java/mage/cards/decks/importer/DecDeckImportTest.java b/Mage/src/test/java/mage/cards/decks/importer/DecDeckImportTest.java index 1c7078d6ef0..8dd592af013 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/DecDeckImportTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/DecDeckImportTest.java @@ -3,6 +3,8 @@ package mage.cards.decks.importer; import mage.cards.decks.DeckCardLists; import org.junit.Test; +import java.nio.file.Paths; + import static org.junit.Assert.assertEquals; public class DecDeckImportTest { @@ -19,7 +21,10 @@ public class DecDeckImportTest { } }; DeckCardLists deck = importer.importDeck( - "src/test/java/mage/cards/decks/importer/samples/testdeck.dec", errors, false); + Paths.get("src", "test", "data", "importer", "testdeck.dec").toString(), + errors, + false + ); TestDeckChecker.checker() .addMain("Masticore", 4) diff --git a/Mage/src/test/java/mage/cards/decks/importer/DraftLogImporterTest.java b/Mage/src/test/java/mage/cards/decks/importer/DraftLogImporterTest.java index 80a2ea9309e..38a044e8150 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/DraftLogImporterTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/DraftLogImporterTest.java @@ -3,6 +3,8 @@ package mage.cards.decks.importer; import mage.cards.decks.DeckCardLists; import org.junit.Test; +import java.nio.file.Paths; + import static org.junit.Assert.assertEquals; public class DraftLogImporterTest { @@ -19,7 +21,10 @@ public class DraftLogImporterTest { } }; DeckCardLists deck = importer.importDeck( - "src/test/java/mage/cards/decks/importer/samples/testdeck.draft", errors, false); + Paths.get("src", "test", "data", "importer", "testdeck.draft").toString(), + errors, + false + ); TestDeckChecker.checker() .addMain("Raging Ravine", 1) diff --git a/Mage/src/test/java/mage/cards/decks/importer/MtgaImporterTest.java b/Mage/src/test/java/mage/cards/decks/importer/MtgaImporterTest.java index f4d186ac5c3..54632b4fd63 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/MtgaImporterTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/MtgaImporterTest.java @@ -3,6 +3,8 @@ package mage.cards.decks.importer; import mage.cards.decks.DeckCardLists; import org.junit.Test; +import java.nio.file.Paths; + import static org.junit.Assert.assertEquals; public class MtgaImporterTest { @@ -19,7 +21,10 @@ public class MtgaImporterTest { }; StringBuilder errors = new StringBuilder(); DeckCardLists deck = importer.importDeck( - "src/test/java/mage/cards/decks/importer/samples/testdeck.mtga", errors, false); + Paths.get("src", "test", "data", "importer", "testdeck.mtga").toString(), + errors, + false + ); TestDeckChecker.checker() .addMain("Niv-Mizzet Reborn", 1) diff --git a/Mage/src/test/java/mage/cards/decks/importer/MtgjsonDeckImportTest.java b/Mage/src/test/java/mage/cards/decks/importer/MtgjsonDeckImportTest.java index 84bb4cb1ade..00d28cae702 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/MtgjsonDeckImportTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/MtgjsonDeckImportTest.java @@ -3,6 +3,8 @@ package mage.cards.decks.importer; import mage.cards.decks.DeckCardLists; import org.junit.Test; +import java.nio.file.Paths; + import static org.junit.Assert.assertEquals; public class MtgjsonDeckImportTest { @@ -21,7 +23,10 @@ public class MtgjsonDeckImportTest { // offline deck from https://mtgjson.com/api/v5/decks/ArcaneTempo_GRN.json DeckCardLists deck = importer.importDeck( - "src/test/java/mage/cards/decks/importer/samples/testdeck.json", errors, false); + Paths.get("src", "test", "data", "importer", "testdeck.json").toString(), + errors, + false + ); assertEquals("Arcane Tempo", deck.getName()); TestDeckChecker.checker() .addMain("Goblin Electromancer", 4) diff --git a/Mage/src/test/java/mage/cards/decks/importer/MwsDeckImportTest.java b/Mage/src/test/java/mage/cards/decks/importer/MwsDeckImportTest.java index ef2b1a96bae..f449e1f7105 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/MwsDeckImportTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/MwsDeckImportTest.java @@ -3,6 +3,8 @@ package mage.cards.decks.importer; import mage.cards.decks.DeckCardLists; import org.junit.Test; +import java.nio.file.Paths; + import static org.junit.Assert.assertEquals; public class MwsDeckImportTest { @@ -19,7 +21,10 @@ public class MwsDeckImportTest { }; StringBuilder errors = new StringBuilder(); DeckCardLists deck = importer.importDeck( - "src/test/java/mage/cards/decks/importer/samples/testdeck.mwDeck", errors, false); + Paths.get("src", "test", "data", "importer", "testdeck.mwDeck").toString(), + errors, + false + ); TestDeckChecker.checker() .addMain("Mutavault", 4) diff --git a/Mage/src/test/java/mage/cards/decks/importer/O8dDeckImportTest.java b/Mage/src/test/java/mage/cards/decks/importer/O8dDeckImportTest.java index be230c36bac..c5a51c30d69 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/O8dDeckImportTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/O8dDeckImportTest.java @@ -3,6 +3,8 @@ package mage.cards.decks.importer; import mage.cards.decks.DeckCardLists; import org.junit.Test; +import java.nio.file.Paths; + import static org.junit.Assert.assertEquals; public class O8dDeckImportTest { @@ -19,7 +21,10 @@ public class O8dDeckImportTest { }; StringBuilder errors = new StringBuilder(); DeckCardLists deck = importer.importDeck( - "src/test/java/mage/cards/decks/importer/samples/testdeck.o8d", errors, false); + Paths.get("src", "test", "data", "importer", "testdeck.o8d").toString(), + errors, + false + ); TestDeckChecker.checker() .addMain("Forest", 1) diff --git a/Utils/keywords.txt b/Utils/keywords.txt index 6e62c8744db..617997e4f49 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -16,7 +16,7 @@ Crew|number| Cumulative upkeep|cost| Cycling|cost| Dash|card, manaString| -Daybound|instance| +Daybound|new| Deathtouch|instance| Demonstrate|new| Delve|new| @@ -43,7 +43,7 @@ Fear|instance| First strike|instance| Flanking|new| Flash|instance| -Flashback|cost| +Flashback|card, cost| Flying|instance| Forestcycling|cost| Forestwalk|new| @@ -73,7 +73,7 @@ Mountainwalk|new| Morph|card, cost| Mutate|card, manaString| Myriad|new| -Nightbound|instance| +Nightbound|new| Ninjutsu|cost| Outlast|cost| Partner|instance| diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index a86dcca9e88..a0457dc5f02 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -42176,7 +42176,7 @@ Ambitious Farmhand|Innistrad: Midnight Hunt|2|U|{1}{W}|Creature - Human Peasant| Seasoned Cathar|Innistrad: Midnight Hunt|2|U||Creature - Human Knight|3|3|Lifelink| Beloved Beggar|Innistrad: Midnight Hunt|3|U|{1}{W}|Creature - Human Peasant|0|4|Disturb {4}{W}{W}| Generous Soul|Innistrad: Midnight Hunt|3|U||Creature - Spirit|4|4|Flying, vigilance$If Generous Soul would be put into a graveyard from anywhere, exile it instead.| -Bereaved Survivor|Innistrad: Midnight Hunt|4|U|{2}{W}|Creature - Human Pesant|2|1|When another creature you control dies, transform Bereaved Survivor.| +Bereaved Survivor|Innistrad: Midnight Hunt|4|U|{2}{W}|Creature - Human Peasant|2|1|When another creature you control dies, transform Bereaved Survivor.| Dauntless Avenger|Innistrad: Midnight Hunt|4|U||Creature - Human Soldier|3|2|Whenever Dauntless Avenger attacks, return target creature card with mana value 2 or less from your graveyard to the battlefield tapped and attacking.| Blessed Defiance|Innistrad: Midnight Hunt|5|C|{W}|Instant|||Target creature you control gets +2/+0 and gains lifelink until end of turn. When that creature dies this turn, create a 1/1 white Spirit creature token with flying.| Borrowed Time|Innistrad: Midnight Hunt|6|U|{2}{W}|Enchantment|||When Borrowed Time enters the battlefield, exile target nonland permanent an opponent controls until Borrowed Time leaves the battlefield.| @@ -42234,7 +42234,7 @@ Dissipate|Innistrad: Midnight Hunt|49|U|{1}{U}{U}|Instant|||Counter target spell Drownyard Amalgam|Innistrad: Midnight Hunt|50|C|{4}{U}|Creature - Zombie Horror|3|6|When Drownyard Amalgam enters the battlefield, target player mills three cards.${2}{U}: Drownyard Amalgam can't be blocked this turn.| Fading Hope|Innistrad: Midnight Hunt|51|U|{U}|Instant|||Return target creature to its owner's hand. If its mana value was 3 or less, scry 1.| Falcon Abomination|Innistrad: Midnight Hunt|52|C|{2}{U}|Creature - Zombie Bird|2|2|Flying$When Falcon Abomination enters the battlefield, create a 2/2 black Zombie creature token with decayed.| -Firmament Sage|Innistrad: Midnight Hunt|53|U|{3}{U}|Creature - Human Wizard|2|3|If it's neither day or night, it becomes day as Firmament Sage enters the battlefield.$Whenever day becomes night or night becomes day, draw a card.| +Firmament Sage|Innistrad: Midnight Hunt|53|U|{3}{U}|Creature - Human Wizard|2|3|If it's neither day nor night, it becomes day as Firmament Sage enters the battlefield.$Whenever day becomes night or night becomes day, draw a card.| Flip the Switch|Innistrad: Midnight Hunt|54|C|{2}{U}|Instant|||Counter target spell unless its controller pays {4}. Create a 2/2 black Zombie creature token with decayed.| Galedrifter|Innistrad: Midnight Hunt|55|C|{3}{U}|Creature - Hippogriff|3|2|Flying$Disturb {4}{U}| Waildrifter|Innistrad: Midnight Hunt|55|C||Creature - Hippogriff Spirit|2|2|Flying$If Waildrifter would be put into a graveyard from anywhere, exile it instead.| @@ -42242,13 +42242,13 @@ Geistwave|Innistrad: Midnight Hunt|56|C|{1}{U}|Instant|||Return target nonland p Grafted Identity|Innistrad: Midnight Hunt|57|R|{2}{U}{U}|Enchantment - Aura|||As an additional cost to cast this spell, sacrifice a creature.$Enchant creature$You control enchanted creature.$Enchanted creature gets +1/+1.| Larder Zombie|Innistrad: Midnight Hunt|58|C|{U}|Creature - Zombie|1|3|Defender$Tap three untapped creatures you control: Look at the top card of your library. You may put it into your graveyard.| Lier, Disciple of the Drowned|Innistrad: Midnight Hunt|59|M|{3}{U}{U}|Legendary Creature - Human Wizard|3|4|Spells can't be countered.$Each instant and sorcery card in your graveyard has flashback. The flashback cost is equal to that card's mana cost.| -Locked in the Cemetary|Innistrad: Midnight Hunt|60|C|{1}{U}|Enchantment - Aura|||Enchant creature$When Locked in the Cemetary enters the battlefield, if there are five or more cards in your graveyard, tap enchanted creature.$Enchanted creature doesn't untap during its controller's untap step.| +Locked in the Cemetery|Innistrad: Midnight Hunt|60|C|{1}{U}|Enchantment - Aura|||Enchant creature$When Locked in the Cemetery enters the battlefield, if there are five or more cards in your graveyard, tap enchanted creature.$Enchanted creature doesn't untap during its controller's untap step.| Malevolent Hermit|Innistrad: Midnight Hunt|61|R|{1}{U}|Creature - Human Wizard|2|1|{U}, Sacrifice Malevolent Hermit: Counter target noncreature spell unless its controller pays {3}.$Disturb {2}{U}| Benevolent Geist|Innistrad: Midnight Hunt|61|R||Creature - Spirit Wizard|2|2|Flying$Noncreature spells you control can't be countered.$If Benevolent Geist would be put into a graveyard from anywhere, exile it instead.| Memory Deluge|Innistrad: Midnight Hunt|62|R|{2}{U}{U}|Instant|||Look at the top X cards of your library, where X is the amount of mana spent to cast this spell. Put two of them into your hand and the rest on the bottom of your library in a random order.$Flashback {5}{U}{U}| Mysterious Tome|Innistrad: Midnight Hunt|63|U|{2}{U}|Artifact|||{2}, {T}: Draw a card. Transform Mysterious Tome.| Chilling Chronicle|Innistrad: Midnight Hunt|63|U||Artifact|||{1}, {T}: Tap target nonland permanent. Transform Chilling Chronicle.| -Neblegast Intruder|Innistrad: Midnight Hunt|64|U|{2}{U}|Creature - Spirit|2|1|Flash$Flying$When Neblegast Intruder enters the battlefield, up to one target creature an opponent controls gets -2/-0 until end of turn.| +Nebelgast Intruder|Innistrad: Midnight Hunt|64|U|{2}{U}|Creature - Spirit|2|1|Flash$Flying$When Nebelgast Intruder enters the battlefield, up to one target creature an opponent controls gets -2/-0 until end of turn.| Ominous Roost|Innistrad: Midnight Hunt|65|U|{2}{U}|Enchantment|||When Ominous Roost enters the battlefield or whenever you cast a spell from your graveyard, create a 1/1 blue Bird creature token with flying and "This creature can block only creatures with flying."| Organ Hoarder|Innistrad: Midnight Hunt|66|C|{3}{U}|Creature - Zombie|3|2|When Organ Hoarder enters the battlefield, look at the top three cards of your library, then put one of them into your hand and the rest into you graveyard.| Otherworldly Gaze|Innistrad: Midnight Hunt|67|C|{U}|Instant|||Look at the top three cards of your library. Put any number of them into your graveyard and the rest back on top of your library in any order.$Flashback {1}{U}| @@ -42290,7 +42290,7 @@ Diregraf Horde|Innistrad: Midnight Hunt|96|C|{4}{B}|Creature - Zombie|3|4|When D Dreadhound|Innistrad: Midnight Hunt|97|U|{4}{B}{B}|Creature - Demon Dog|6|6|When Dreadhound enters the battlefield, mill three cards.$Whenever a creature dies or a creature card is put into a graveyard from a library, each opponent loses 1 life.| Duress|Innistrad: Midnight Hunt|98|C|{B}|Sorcery|||Target opponent reveals their hand. You choose a noncreature, nonland card from it. That player discards that card.| Eaten Alive|Innistrad: Midnight Hunt|99|C|{B}|Sorcery|||As an additional cost to cast this spell, sacrifice a creature or pay {3}{B}.$Exile target creature or planeswalker.| -Ecstatic Awakener|Innistrad: Midnight Hunt|100|C|{B}|Creature - Human Wizard|1|1|{2}{B}, Sacrifice another creature. Draw a card, then transform Ecstatic Awakener. Activate only once each turn.| +Ecstatic Awakener|Innistrad: Midnight Hunt|100|C|{B}|Creature - Human Wizard|1|1|{2}{B}, Sacrifice another creature: Draw a card, then transform Ecstatic Awakener. Activate only once each turn.| Awoken Demon|Innistrad: Midnight Hunt|100|C||Creature - Demon|4|4|| Foul Play|Innistrad: Midnight Hunt|101|U|{1}{B}|Sorcery|||Destroy target creature with power 2 or less. Investigate.| Ghoulish Procession|Innistrad: Midnight Hunt|102|U|{1}{B}|Enchantment|||Whenever one or more nontoken creatures die, create a 2/2 black Zombie creature token with decayed. This ability triggers only once each turn.| @@ -42336,7 +42336,7 @@ Falkenrath Perforator|Innistrad: Midnight Hunt|136|C|{1}{R}|Creature - Vampire|2 Falkenrath Pit Fighter|Innistrad: Midnight Hunt|137|R|{R}|Creature - Vampire Warrior|2|1|{1}{R}, Discard a card, Sacrifice a Vampire: Draw two cards. Activate only if an opponent lost life this turn.| Famished Foragers|Innistrad: Midnight Hunt|138|C|{3}{R}|Creature - Vampire|4|3|When Famished Foragers enters the battlefield, if an opponent lost life this turn, add {R}{R}{R}.${2}{R}, Discard a card: Draw a card.| Fangblade Brigand|Innistrad: Midnight Hunt|139|U|{3}{R}|Creature - Human Werewolf|3|4|{1}{R}: Fangblade Brigand gets +1/+0 and gains first strike until end of turn.$Daybound| -Fangblade Eviscerator|Innistrad: Midnight Hunt|139|U||Creature - Werewolf|4|5|{1}{R}: "Blade-Fang Eviscerator" gets +1/+0 and gains first strike until end of turn.${4}{R}: Creatures you control get +2/+0 until end of turn.$Nightbound| +Fangblade Eviscerator|Innistrad: Midnight Hunt|139|U||Creature - Werewolf|4|5|{1}{R}: Fangblade Eviscerator gets +1/+0 and gains first strike until end of turn.${4}{R}: Creatures you control get +2/+0 until end of turn.$Nightbound| Festival Crasher|Innistrad: Midnight Hunt|140|C|{1}{R}|Creature - Devil|1|3|Whenever you cast an instant or sorcery spell, Festival Crasher gets +2/+0 until end of turn.| Flame Channeler|Innistrad: Midnight Hunt|141|U|{1}{R}|Creature - Human Wizard|2|2|When a spell you control deals damage, transform Flame Channeler.| Embodiment of Flame|Innistrad: Midnight Hunt|141|U||Creature - Elemental Wizard|3|3|Whenever a spell you control deals damage, put a flame counter on Embodiment of Flame.${1}, Remove a flame counter from Embodiment of Flame: Exile the top card of your library. You may play that card this turn.| @@ -42347,12 +42347,12 @@ Immolation|Innistrad: Midnight Hunt|144|C|{R}|Enchantment - Aura|||Enchant creat Lambholt Harrier|Innistrad: Midnight Hunt|145|C|{1}{R}|Creature - Wolf|2|2|{3}{R}: Target creature can't block this turn.| Light Up the Night|Innistrad: Midnight Hunt|146|R|{X}{R}|Sorcery|||Light Up the Night deals X damage to any target. It deals X plus 1 damage instead if that target is a creature or planeswalker.$Flashback—{3}{R}, Remove X loyalty counters from among planeswalkers you control. If you cast this spell this way, X can't be 0.| Lunar Frenzy|Innistrad: Midnight Hunt|147|U|{X}{R}|Instant|||Target creature you control gets +X/+0 and gains first strike and trample until end of turn.| -Moonrager's Smash|Innistrad: Midnight Hunt|148|C|{2}{R}|Instant|||This spell costs {2} less to cast if it's night.$Moonrager's Smash deals 3 damage to any target.| +Moonrager's Slash|Innistrad: Midnight Hunt|148|C|{2}{R}|Instant|||This spell costs {2} less to cast if it's night.$Moonrager's Slash deals 3 damage to any target.| Moonveil Regent|Innistrad: Midnight Hunt|149|M|{3}{R}|Creature - Dragon|4|4|Flying$Whenever you cast a spell, you may discard your hand. If you do, draw a card for each of that spell's colors.$When Moonveil Regent dies, it deals X damage to any target, where X is the number of colors among permanents you control.| Mounted Dreadknight|Innistrad: Midnight Hunt|150|C|{4}{R}|Creature - Vampire Knight|5|4|Trample$Mounted Dreadknight enters the battlefield with a +1/+1 counter on it if an opponent lost life this turn.| Neonate's Rush|Innistrad: Midnight Hunt|151|C|{2}{R}|Instant|||This spell costs {1} less to cast if you control a Vampire.$Neonate's Rush deals 1 damage to target creature and 1 damage to its controller. Draw a card.| Obsessive Astronomer|Innistrad: Midnight Hunt|152|U|{1}{R}|Creature - Human Wizard|2|2|If it's neither day nor night, it becomes day as Obsessive Astronomer enters the battlefield.$Whenever day becomes night or night becomes day, discard up to two cards, then draw that many cards.| -Pack's Betrayal|Innistrad: Midnight Hunt|153|C|{2}{R}|Sorcery|||Gain control of target creature until end of turn. Untap that creature. It gains haste untl end of turn. If you control a Wolf or Werewolf, scry 2.| +Pack's Betrayal|Innistrad: Midnight Hunt|153|C|{2}{R}|Sorcery|||Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn. If you control a Wolf or Werewolf, scry 2.| Play with Fire|Innistrad: Midnight Hunt|154|U|{R}|Instant|||Play with Fire deals 2 damage to any target. If a player is dealt damage this way, scry 1.| Purifying Dragon|Innistrad: Midnight Hunt|155|U|{3}{R}{R}|Creature - Dragon|4|3|Flying$Whenever Purifying Dragon attacks, it deals 1 damage to target creature defending player controls. If that creature is a Zombie, Purifying Dragon deals 2 damage to that creature instead.| Raze the Effigy|Innistrad: Midnight Hunt|156|C|{R}|Instant|||Choose one—$• Destroy target artifact.$• Target attacking creature gets +2/+2 until end of turn.| @@ -42364,7 +42364,7 @@ Ashmouth Dragon|Innistrad: Midnight Hunt|159|R||Creature - Dragon|4|4|Flying$Whe Spellrune Painter|Innistrad: Midnight Hunt|160|U|{2}{R}|Creature - Human Shaman Werewolf|2|3|Whenever you cast an instant or sorcery spell, Spellrune Painter gets +1/+1 until end of turn.$Daybound| Spellrune Howler|Innistrad: Midnight Hunt|160|U||Creature - Werewolf|3|4|Whenever you cast an instant or sorcery spell, Spellrune Howler gets +2/+2 until end of turn.$Nightbound| Stolen Vitality|Innistrad: Midnight Hunt|161|C|{1}{R}|Instant|||Target creature gets +3/+1 until end of turn. If it's your turn, that creature gains trample until end of turn. Otherwise, it gains first strike until end of turn.| -Sunstreak Phoenix|Innistrad: Midnight Hunt|162|M|{2}{R}{R}|Creature - Phoenix|4|2|Flying$If it's nether day nor night, it becomes day when Sunstreak Phoenix enters the battlefield.$When day becomes night or night becomes day, you may pay {1}{R}. If you do, return Sunstreak Phoenix from your graveyard to the battlefield tapped.| +Sunstreak Phoenix|Innistrad: Midnight Hunt|162|M|{2}{R}{R}|Creature - Phoenix|4|2|Flying$If it's neither day nor night, it becomes day as Sunstreak Phoenix enters the battlefield.$When day becomes night or night becomes day, you may pay {1}{R}. If you do, return Sunstreak Phoenix from your graveyard to the battlefield tapped.| Tavern Ruffian|Innistrad: Midnight Hunt|163|C|{3}{R}|Creature - Human Warrior Werewolf|2|5|Daybound| Tavern Smasher|Innistrad: Midnight Hunt|163|C||Creature - Werewolf|6|5|Nightbound| Thermo-Alchemist|Innistrad: Midnight Hunt|164|U|{1}{R}|Creature - Human Shaman|0|3|Defender${T}: Thermo-Alchemist deals 1 damage to each opponent.$Whenever you cast an instant or sorcery spell, untap Thermo-Alchemist.| @@ -42393,13 +42393,13 @@ Defend the Celestus|Innistrad: Midnight Hunt|182|U|{2}{G}{G}|Instant|||Distribut Dryad's Revival|Innistrad: Midnight Hunt|183|U|{2}{G}|Sorcery|||Return target card from your graveyard to your hand.$Flashback {4}{G}| Duel for Dominance|Innistrad: Midnight Hunt|184|C|{1}{G}|Instant|||Coven — Choose target creature you control and target creature you don't control. If you control three or more creatures with different powers, put a +1/+1 counter on the chosen creature you control. Then the chosen creatures fight each other.| Eccentric Farmer|Innistrad: Midnight Hunt|185|C|{2}{G}|Creature - Human Peasant|2|3|When Eccentric Farmer enters the battlefield, mill three cards, then you may return a land card from your graveyard to your hand.| -Harvesttide Sentry|Innistrad: Midnight Hunt|186|C|{1}{G}|Creature - Human Warrior|3|1|Coven — At the beginning of combat on your turn, if. you control three or more creatures with different powers, Harvesttide Sentry can't be blocked by creatures with power 2 or less this turn.| +Harvesttide Sentry|Innistrad: Midnight Hunt|186|C|{1}{G}|Creature - Human Warrior|3|1|Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, Harvesttide Sentry can't be blocked by creatures with power 2 or less this turn.| Hound Tamer|Innistrad: Midnight Hunt|187|U|{2}{G}|Creature - Human Werewolf|3|3|Trample${3}{G}: Put a +1/+1 counter on target creature.$Daybound| Untamed Pup|Innistrad: Midnight Hunt|187|U||Creature - Werewolf|4|4|Trample$Other Wolves and Werewolves you control have trample.${3}{G}: Put a +1/+1 counter on target creature.$Nightbound| Howl of the Hunt|Innistrad: Midnight Hunt|188|C|{2}{G}|Enchantment - Aura|||Flash$Enchant creature$When Howl of the Hunt enters the battlefield, if enchanted creature is a Wolf or Werewolf, untap that creature.$Enchanted creature gets +2/+2 and has vigilance.| Might of the Old Ways|Innistrad: Midnight Hunt|189|C|{1}{G}|Instant|||Target creature gets +2/+2 until end of turn.$Coven — Then if you control three or more creatures with different powers, draw a card.| -Outland Liberator|Innistrad: Midnight Hunt|190|U|{1}{G}|Creature - Human Werwolf|2|2|{1}, Sacrifice Outland Liberator: Destroy target artifact or enchantment.$Daybound| -Frenzied Trapbreaker|Innistrad: Midnight Hunt|190|U||Creature - Werewolf|3|3|{1}, Sacrifice Frenzied Trapbreaker: Destroy target artifact or enchantment.$Whenenver "Frenzied Trapbreaker" attacks, destroy target artifact or enchantment defending player controls.$Nightbound| +Outland Liberator|Innistrad: Midnight Hunt|190|U|{1}{G}|Creature - Human Werewolf|2|2|{1}, Sacrifice Outland Liberator: Destroy target artifact or enchantment.$Daybound| +Frenzied Trapbreaker|Innistrad: Midnight Hunt|190|U||Creature - Werewolf|3|3|{1}, Sacrifice Frenzied Trapbreaker: Destroy target artifact or enchantment.$Whenever Frenzied Trapbreaker attacks, destroy target artifact or enchantment defending player controls.$Nightbound| Path to the Festival|Innistrad: Midnight Hunt|191|C|{2}{G}|Sorcery|||Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Then if there are three or more basic land types among lands you control, scry 1.$Flashback {4}{G}| Pestilent Wolf|Innistrad: Midnight Hunt|192|C|{1}{G}|Creature - Wolf|2|2|{2}{G}: Pestilent Wolf gains deathtouch until end of turn.| Plummet|Innistrad: Midnight Hunt|193|C|{1}{G}|Instant|||Destroy target creature with flying.| @@ -42865,12 +42865,152 @@ Universal Automaton|Jumpstart: Historic Horizons|759|C|{1}|Artifact Creature - S Zabaz, the Glimmerwasp|Jumpstart: Historic Horizons|762|R|{1}|Legendary Artifact Creature - Insect|0|0|Modular 1$If a modular triggered ability would put one or more +1/+1 counters on a creature you control, that many plus one +1/+1 counters are put on it instead.${R}: Destroy target artifact you control.${W}: Zabaz, the Glimmerwasp gains flying until end of turn.| Khalni Garden|Jumpstart: Historic Horizons|770|C||Land|||Khalni Garden enters the battlefield tapped.$When Khalni Garden enters the battlefield, create a 0/1 green Plant creature token.${T}: Add {G}.| Sliver Hive|Jumpstart: Historic Horizons|776|R||Land|||{T}: Add {C}.${T}: Add one mana of any color. Spend this mana only to cast a Sliver spell.${5}, {T}: Create a 1/1 colorless Sliver creature token. Activate only if you control a Sliver.| -Leinore, Autumn Sovereign|Midnight Hunt Commander|1|M|{2}{G}{W}|Legendary Creature - Human Noble|0|4|Coven — At the beginning of combat ono your turn, put a +1/+1 counter on up to one target creature you control. Then if you control three or more creatures with different powers, draw a card.| -Wilhelt, the Rotcleaver|Midnight Hunt Commander|2|R|{2}{U}{B}|Legendary Creature - Zombie Warrior|3|3|Whenever another zombie you control dies, if it didn't have decayed, create a 2/2 black Zombie creature token with decayed.$At the beginning of your end step, you may sacrifice a Zombie. If you do, draw a card.| +Leinore, Autumn Sovereign|Midnight Hunt Commander|1|M|{2}{G}{W}|Legendary Creature - Human Noble|0|4|Coven — At the beginning of combat on your turn, put a +1/+1 counter on up to one target creature you control. Then if you control three or more creatures with different powers, draw a card.| +Wilhelt, the Rotcleaver|Midnight Hunt Commander|2|M|{2}{U}{B}|Legendary Creature - Zombie Warrior|3|3|Whenever another Zombie you control dies, if it didn't have decayed, create a 2/2 black Zombie creature token with decayed.$At the beginning of your end step, you may sacrifice a Zombie. If you do, draw a card.| +Eloise, Nephalia Sleuth|Midnight Hunt Commander|3|M|{3}{U}{B}|Legendary Creature - Human Rogue|4|4|Whenever another creature you control dies, investigate.$Whenever you sacrifice a token, surveil 1.| +Kyler, Sigardian Emissary|Midnight Hunt Commander|4|M|{3}{G}{W}|Legendary Creature - Human Cleric|2|2|Whenever another Human enters the battlefield under your control, put a +1/+1 counter on Kyler, Sigardian Emissary.$Other Humans you control get +1/+1 for each counter on Kyler, Sigardian Emissary.| +Celestial Judgment|Midnight Hunt Commander|5|R|{4}{W}{W}|Sorcery|||For each different power among creatures on the battlefield, choose a creature with that power. Destroy each creature not chosen this way.| +Curse of Conformity|Midnight Hunt Commander|6|R|{4}{W}|Enchantment - Aura Curse|||Enchant player$Nonlegendary creatures enchanted player controls have base power and toughness 3/3 and lose all creature types.| +Moorland Rescuer|Midnight Hunt Commander|7|R|{5}{W}|Creature - Human Knight|4|4|When Moorland Rescuer dies, return any number of other creature cards with total power X or less from your graveyard to the battlefield, where X is Moorland Rescuer's power. Exile Moorland Rescuer.| +Sigarda's Vanguard|Midnight Hunt Commander|8|R|{4}{W}|Creature - Angel|3|3|Flash$Flying$Whenever Sigarda's Vanguard enters the battlefield or attacks, choose any number of creatures with different powers. Those creatures gain double strike until end of turn.| +Stalwart Pathlighter|Midnight Hunt Commander|9|R|{2}{W}|Creature - Human Soldier|3|1|Vigilance$Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, creatures you control gain indestructible until end of turn.| +Wall of Mourning|Midnight Hunt Commander|10|R|{1}{W}|Creature - Wall|0|4|Defender$When Wall of Mourning enters the battlefield, exile a card from the top of your library face down for each opponent you have.$Coven — At the beginning of your end step, if you control three or more creatures with different powers, put a card exiled with Wall of Mourning into its owner's hand.| +Cleaver Skaab|Midnight Hunt Commander|11|R|{3}{U}|Creature - Zombie Horror|2|4|{3}, {T}, Sacrifice another Zombie: Create two tokens that are copies of the sacrificed creature.| +Curse of Unbinding|Midnight Hunt Commander|12|R|{6}{U}|Enchantment - Aura Curse|||Enchant player$At the beginning of enchanted player's upkeep, that player reveals cards from the top of their library until they reveal a creature card. Put that card onto the battlefield under your control. That player puts the rest of the revealed cards into their graveyard.| +Drown in Dreams|Midnight Hunt Commander|13|R|{X}{2}{U}|Instant|||Choose one. If you control a commander as you cast this spell, you may choose both.$• Target player draws X cards.$• Target player mills twice X cards.| +Empty the Laboratory|Midnight Hunt Commander|14|R|{X}{U}{U}|Sorcery|||Sacrifice X Zombies, then reveal cards from the top of your library until you reveal a number of Zombie creature cards equal to the number of Zombies sacrificed this way. Put those cards onto the battlefield and the rest on the bottom of your library in a random order.| +Hordewing Skaab|Midnight Hunt Commander|15|R|{4}{U}|Creature - Zombie Horror|3|3|Flying$Other Zombies you control have flying.$Whenever one or more Zombies you control deal combat damage to one or more of your opponents, you may draw cards equal to the number of opponents dealt damage this way. If you do, discard that many cards.| +Shadow Kin|Midnight Hunt Commander|16|R|{3}{U}|Creature - Shapeshifter|2|2|Flash$At the beginning of your upkeep, each player mills three cards. You may exile a creature card from among the cards milled this way. If you do, Shadow Kin becomes a copy of that card, except it has this ability.| +Crowded Crypt|Midnight Hunt Commander|17|R|{2}{B}|Artifact|||{T}: Add {B}.$Whenever a creature you control dies, put a corpse counter on Crowded Crypt.${4}{B}{B}, {T}, Sacrifice Crowded Crypt: Create a 2/2 black Zombie creature token with decayed for each corpse counter on Crowded Crypt.| +Curse of the Restless Dead|Midnight Hunt Commander|18|R|{2}{B}|Enchantment - Aura Curse|||Enchant player$Whenever a land enters the battlefield under enchanted player's control, you create a 2/2 black Zombie creature token with decayed.| +Ghouls' Night Out|Midnight Hunt Commander|19|R|{3}{B}{B}|Sorcery|||For each player, choose a creature card in that player's graveyard. Put those cards onto the battlefield under your control. They're black Zombies in addition to their other colors and types and they gain decayed.| +Gorex, the Tombshell|Midnight Hunt Commander|20|R|{6}{B}{B}|Legendary Creature - Zombie Turtle|4|4|As an additional cost to cast this spell, you may exile a number of creature cards from your graveyard. This spell costs {2} less to cast for each card exiled this way.$Deathtouch$Whenever Gorex, the Tombshell attacks or dies, choose a card at random exiled with Gorex and put that card into its owner's hand.| +Prowling Geistcatcher|Midnight Hunt Commander|21|R|{3}{B}|Creature - Human Rogue|2|4|Whenever you sacrifice another creature, exile it. If that creature was a token, put a +1/+1 counter on Prowling Geistcatcher.$When Prowling Geistcatcher leaves the battlefield, return each card exiled with it to the battlefield under your control.| +Ravenous Rotbelly|Midnight Hunt Commander|22|R|{4}{B}|Creature - Zombie Horror|4|5|When Ravenous Rotbelly enters the battlefield, you may sacrifice up to three Zombies. When you sacrifice one or more Zombies this way, each opponent sacrifices that many creatures.| +Tomb Tyrant|Midnight Hunt Commander|23|R|{3}{B}|Creature - Zombie Noble|3|3|Other Zombies you control get +1/+1.${2}{B}, {T}, Sacrifice a creature: Return a Zombie creature card at random from your graveyard to the battlefield. Activate only during your turn and only if there are at least three Zombie creature cards in your graveyard.| +Celebrate the Harvest|Midnight Hunt Commander|24|R|{3}{G}|Sorcery|||Search your library for up to X basic land cards, where X is the number of different powers among creatures you control. Put those cards onto the battlefield tapped, then shuffle.| +Curse of Clinging Webs|Midnight Hunt Commander|25|R|{2}{G}|Enchantment - Aura Curse|||Enchant player$Whenever a nontoken creature enchanted player controls dies, exile it and you create a 1/2 green Spider creature token with reach.| +Heronblade Elite|Midnight Hunt Commander|26|R|{2}{G}|Creature - Human Warrior|1|1|Vigilance$Whenever another Human enters the battlefield under your control, put a +1/+1 counter on Heronblade Elite.${T}: Add X mana of any one color, where X is Heronblade Elite's power.| +Kurbis, Harvest Celebrant|Midnight Hunt Commander|27|R|{X}{G}{G}|Legendary Creature - Treefolk|0|0|Kurbis, Harvest Celebrant enters the battlefield with a number of +1/+1 counters on it equal to the amount of mana spent to cast it.$Remove a +1/+1 counter from Kurbis: Prevent all damage that would be dealt this turn to another target creature with a +1/+1 counter on it.| +Ruinous Intrusion|Midnight Hunt Commander|28|R|{3}{G}|Instant|||Exile target artifact or enchantment. Put X +1/+1 counters on target creature you control, where X is the mana value of the permanent exiled this way.| +Sigardian Zealot|Midnight Hunt Commander|29|R|{4}{G}|Creature - Human Cleric|3|3|At the beginning of combat on your turn, choose any number of creatures with different powers. Each of them gets +X/+X and gains vigilance until end of turn, where X is Sigardian Zealot's power.| +Somberwald Beastmaster|Midnight Hunt Commander|30|R|{6}{G}|Creature - Human Ranger|1|1|When Somberwald Beastmaster enters the battlefield, create a 2/2 green Wolf creature token, a 3/3 green Beast creature token, and a 4/4 green Beast creature token.$Creature tokens you control have deathtouch.| Avacyn's Memorial|Midnight Hunt Commander|31|M|{5}{W}{W}{W}|Legendary Artifact|||Indestructible$Other legendary permanents you control have indestructible.| Visions of Glory|Midnight Hunt Commander|32|R|{4}{W}|Sorcery|||Create a 1/1 white Human creature token for each creature you control.$Flashback {8}{W}{W}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone.| Visions of Duplicity|Midnight Hunt Commander|33|R|{2}{U}|Sorcery|||Exchange control of two target creatures you don't control.$Flashback {8}{U}{U}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone.| -Visions of Dread|Midnight Hunt Commander|34|R|{2}{B}|Sorcery|||Target opponent puts a creature card of choice from their graveyard onto the battlefield under your control.$Flashback {8}{B}{B}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone.| -Curse of Obsession|Midnight Hunt Commander|35|R|{4}{R}|Enchantment - Aura Curse|||Encahnt player$At the beginning of enchanted player's draw step, that player draws two additional cards.$At the beginning of enchanted player's end step, that player discards their hand.| +Visions of Dread|Midnight Hunt Commander|34|R|{2}{B}|Sorcery|||Target opponent puts a creature card of their choice from their graveyard onto the battlefield under your control.$Flashback {8}{B}{B}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone.| +Curse of Obsession|Midnight Hunt Commander|35|R|{4}{R}|Enchantment - Aura Curse|||Enchant player$At the beginning of enchanted player's draw step, that player draws two additional cards.$At the beginning of enchanted player's end step, that player discards their hand.| Visions of Ruin|Midnight Hunt Commander|36|R|{3}{R}|Sorcery|||Each opponent sacrifices an artifact. For each artifact sacrificed this way, you create a Treasure token.$Flashback {8}{R}{R}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone.| Visions of Dominance|Midnight Hunt Commander|37|R|{2}{G}|Sorcery|||Put a +1/+1 counter on target creature, then double the number of +1/+1 counters on it.$Flashback {8}{G}{G}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone.| +Lynde, Cheerful Tormentor|Midnight Hunt Commander|38|M|{1}{U}{B}{R}|Legendary Creature - Human Warlock|2|4|Deathtouch$Whenever a Curse is put into your graveyard from the battlefield, return it to the battlefield attached to you at the beginning of the next end step.$At the beginning of your upkeep, you may attach a Curse attached to you to one of your opponents. If you do, draw two cards.| +Abzan Falconer|Midnight Hunt Commander|77|U|{2}{W}|Creature - Human Soldier|2|3|Outlast {W}$Each creature you control with a +1/+1 counter on it has flying.| +Ainok Bond-Kin|Midnight Hunt Commander|78|C|{1}{W}|Creature - Dog Soldier|2|1|Outlast {1}{W}$Each creature you control with a +1/+1 counter on it has first strike.| +Angel of Glory's Rise|Midnight Hunt Commander|79|R|{5}{W}{W}|Creature - Angel|4|6|Flying$When Angel of Glory's Rise enters the battlefield, exile all Zombies, then return all Human creature cards from your graveyard to the battlefield.| +Bastion Protector|Midnight Hunt Commander|80|R|{2}{W}|Creature - Human Soldier|3|3|Commander creatures you control get +2/+2 and have indestructible.| +Citadel Siege|Midnight Hunt Commander|81|R|{2}{W}{W}|Enchantment|||As Citadel Siege enters the battlefield, choose Khans or Dragons.$• Khans — At the beginning of combat on your turn, put two +1/+1 counters on target creature you control.$• Dragons — At the beginning of combat on each opponent's turn, tap target creature that player controls.| +Cleansing Nova|Midnight Hunt Commander|82|R|{3}{W}{W}|Sorcery|||Choose one —$• Destroy all creatures.$• Destroy all artifacts and enchantments.| +Custodi Soulbinders|Midnight Hunt Commander|83|R|{3}{W}|Creature - Human Cleric|0|0|Custodi Soulbinders enters the battlefield with X +1/+1 counters on it, where X is the number of other creatures on the battlefield.${2}{W}, Remove a +1/+1 counter from Custodi Soulbinders: Create a 1/1 white Spirit creature token with flying.| +Dearly Departed|Midnight Hunt Commander|84|R|{4}{W}{W}|Creature - Spirit|5|5|Flying$As long as Dearly Departed is in your graveyard, each Human creature you control enters the battlefield with an additional +1/+1 counter on it.| +Elite Scaleguard|Midnight Hunt Commander|85|U|{4}{W}|Creature - Human Soldier|2|3|When Elite Scaleguard enters the battlefield, bolster 2.$Whenever a creature you control with a +1/+1 counter on it attacks, tap target creature defending player controls.| +Herald of War|Midnight Hunt Commander|86|R|{3}{W}{W}|Creature - Angel|3|3|Flying$Whenever Herald of War attacks, put a +1/+1 counter on it.$Angel spells and Human spells you cast cost {1} less to cast for each +1/+1 counter on Herald of War.| +Hour of Reckoning|Midnight Hunt Commander|87|R|{4}{W}{W}{W}|Sorcery|||Convoke$Destroy all nontoken creatures.| +Knight of the White Orchid|Midnight Hunt Commander|88|R|{W}{W}|Creature - Human Knight|2|2|First strike$When Knight of the White Orchid enters the battlefield, if an opponent controls more lands than you, you may search your library for a Plains card, put it onto the battlefield, then shuffle.| +Mikaeus, the Lunarch|Midnight Hunt Commander|89|M|{X}{W}|Legendary Creature - Human Cleric|0|0|Mikaeus, the Lunarch enters the battlefield with X +1/+1 counters on it.${T}: Put a +1/+1 counter on Mikaeus.${T}, Remove a +1/+1 counter from Mikaeus: Put a +1/+1 counter on each other creature you control.| +Odric, Master Tactician|Midnight Hunt Commander|90|R|{2}{W}{W}|Legendary Creature - Human Soldier|3|4|First strike$Whenever Odric, Master Tactician and at least three other creatures attack, you choose which creatures block this combat and how those creatures block.| +Orzhov Advokist|Midnight Hunt Commander|91|U|{2}{W}|Creature - Human Advisor|1|4|At the beginning of your upkeep, each player may put two +1/+1 counters on a creature they control. If a player does, creatures that player controls can't attack you or planeswalkers you control until your next turn.| +Return to Dust|Midnight Hunt Commander|92|U|{2}{W}{W}|Instant|||Exile target artifact or enchantment. If you cast this spell during your main phase, you may exile up to one other target artifact or enchantment.| +Riders of Gavony|Midnight Hunt Commander|93|R|{2}{W}{W}|Creature - Human Knight|3|3|Vigilance$As Riders of Gavony enters the battlefield, choose a creature type.$Human creatures you control have protection from creatures of the chosen type.| +Swords to Plowshares|Midnight Hunt Commander|94|U|{W}|Instant|||Exile target creature. Its controller gains life equal to its power.| +Unbreakable Formation|Midnight Hunt Commander|95|R|{2}{W}|Instant|||Creatures you control gain indestructible until end of turn.$Addendum — If you cast this spell during your main phase, put a +1/+1 counter on each of those creatures and they gain vigilance until end of turn.| +Victory's Envoy|Midnight Hunt Commander|96|R|{3}{W}{W}|Creature - Human Cleric|3|3|At the beginning of your upkeep, put a +1/+1 counter on each other creature you control.| +Aetherspouts|Midnight Hunt Commander|97|R|{3}{U}{U}|Instant|||For each attacking creature, its owner puts it on the top or bottom of their library.| +Distant Melody|Midnight Hunt Commander|98|C|{3}{U}|Sorcery|||Choose a creature type. Draw a card for each permanent you control of that type.| +Eternal Skylord|Midnight Hunt Commander|99|U|{4}{U}|Creature - Zombie Wizard|3|3|When Eternal Skylord enters the battlefield, amass 2.$Zombie tokens you control have flying.| +Forgotten Creation|Midnight Hunt Commander|100|R|{3}{U}|Creature - Zombie Horror|3|3|Skulk$At the beginning of your upkeep, you may discard all the cards in your hand. If you do, draw that many cards.| +Havengul Runebinder|Midnight Hunt Commander|101|R|{2}{U}{U}|Creature - Human Wizard|2|2|{2}{U}, {T}, Exile a creature card from your graveyard: Create a 2/2 black Zombie creature token, then put a +1/+1 counter on each Zombie creature you control.| +Hour of Eternity|Midnight Hunt Commander|102|R|{X}{X}{U}{U}{U}|Sorcery|||Exile X target creature cards from your graveyard. For each card exiled this way, create a token that's a copy of that card, except it's a 4/4 black Zombie.| +Rooftop Storm|Midnight Hunt Commander|103|R|{5}{U}|Enchantment|||You may pay {0} rather than pay the mana cost for Zombie creature spells you cast.| +Stitcher Geralf|Midnight Hunt Commander|104|M|{3}{U}{U}|Legendary Creature - Human Wizard|3|4|{2}{U}, {T}: Each player mills three cards. Exile up to two creature cards put into graveyards this way. Create an X/X blue Zombie creature token, where X is the total power of the cards exiled this way.| +Undead Alchemist|Midnight Hunt Commander|105|R|{3}{U}|Creature - Zombie|4|2|If a Zombie you control would deal combat damage to a player, instead that player mills that many cards.$Whenever a creature card is put into an opponent's graveyard from their library, exile that card and create a 2/2 black Zombie creature token.| +Army of the Damned|Midnight Hunt Commander|106|M|{5}{B}{B}{B}|Sorcery|||Create thirteen tapped 2/2 black Zombie creature tokens.$Flashback {7}{B}{B}{B}| +Butcher of Malakir|Midnight Hunt Commander|107|R|{5}{B}{B}|Creature - Vampire Warrior|5|4|Flying$Whenever Butcher of Malakir or another creature you control dies, each opponent sacrifices a creature.| +Cemetery Reaper|Midnight Hunt Commander|108|R|{1}{B}{B}|Creature - Zombie|2|2|Other Zombie creatures you control get +1/+1.${2}{B}, {T}: Exile target creature card from a graveyard. Create a 2/2 black Zombie creature token.| +Corpse Augur|Midnight Hunt Commander|109|U|{3}{B}|Creature - Zombie Wizard|4|2|When Corpse Augur dies, you draw X cards and you lose X life, where X is the number of creature cards in target player's graveyard.| +Dark Salvation|Midnight Hunt Commander|110|R|{X}{X}{B}|Sorcery|||Target player creates X 2/2 black Zombie creature tokens, then up to one target creature gets -1/-1 until end of turn for each Zombie that player controls.| +Death Baron|Midnight Hunt Commander|111|R|{1}{B}{B}|Creature - Zombie Wizard|2|2|Skeletons you control and other Zombies you control get +1/+1 and have deathtouch.| +Diregraf Colossus|Midnight Hunt Commander|112|R|{2}{B}|Creature - Zombie Giant|2|2|Diregraf Colossus enters the battlefield with a +1/+1 counter on it for each Zombie card in your graveyard.$Whenever you cast a Zombie spell, create a tapped 2/2 black Zombie creature token.| +Dread Summons|Midnight Hunt Commander|113|R|{X}{B}{B}|Sorcery|||Each player mills X cards. For each creature card put into a graveyard this way, you create a tapped 2/2 black Zombie creature token.| +Dreadhorde Invasion|Midnight Hunt Commander|114|R|{1}{B}|Enchantment|||At the beginning of your upkeep, you lose 1 life and amass 1.$Whenever a Zombie token you control with power 6 or greater attacks, it gains lifelink until end of turn.| +Eater of Hope|Midnight Hunt Commander|115|R|{5}{B}{B}|Creature - Demon|6|4|Flying${B}, Sacrifice another creature: Regenerate Eater of Hope.${2}{B}, Sacrifice two other creatures: Destroy target creature.| +Endless Ranks of the Dead|Midnight Hunt Commander|116|R|{2}{B}{B}|Enchantment|||At the beginning of your upkeep, create X 2/2 black Zombie creature tokens, where X is half the number of Zombies you control, rounded down.| +Feed the Swarm|Midnight Hunt Commander|117|C|{1}{B}|Sorcery|||Destroy target creature or enchantment an opponent controls. You lose life equal to that permanent's mana value.| +Fleshbag Marauder|Midnight Hunt Commander|118|U|{2}{B}|Creature - Zombie Warrior|3|1|When Fleshbag Marauder enters the battlefield, each player sacrifices a creature.| +Go for the Throat|Midnight Hunt Commander|119|U|{1}{B}|Instant|||Destroy target nonartifact creature.| +Gravespawn Sovereign|Midnight Hunt Commander|120|R|{4}{B}{B}|Creature - Zombie|3|3|Tap five untapped Zombies you control: Put target creature card from a graveyard onto the battlefield under your control.| +Liliana, Death's Majesty|Midnight Hunt Commander|121|M|{3}{B}{B}|Legendary Planeswalker - Liliana|5|+1: Create a 2/2 black Zombie creature token. Mill two cards.$−3: Return target creature card from your graveyard to the battlefield. That creature is a black Zombie in addition to its other colors and types.$−7: Destroy all non-Zombie creatures.| +Liliana's Devotee|Midnight Hunt Commander|122|U|{2}{B}|Creature - Human Warlock|2|3|Zombies you control get +1/+0.$At the beginning of your end step, if a creature died this turn, you may pay {1}{B}. If you do, create a 2/2 black Zombie creature token.| +Liliana's Mastery|Midnight Hunt Commander|123|R|{3}{B}{B}|Enchantment|||Zombies you control get +1/+1.$When Liliana's Mastery enters the battlefield, create two 2/2 black Zombie creature tokens.| +Lord of the Accursed|Midnight Hunt Commander|124|U|{2}{B}|Creature - Zombie|2|3|Other Zombies you control get +1/+1.${1}{B}, {T}: All Zombies gain menace until end of turn.| +Midnight Reaper|Midnight Hunt Commander|125|R|{2}{B}|Creature - Zombie Knight|3|2|Whenever a nontoken creature you control dies, Midnight Reaper deals 1 damage to you and you draw a card.| +Open the Graves|Midnight Hunt Commander|126|R|{3}{B}{B}|Enchantment|||Whenever a nontoken creature you control dies, create a 2/2 black Zombie creature token.| +Overseer of the Damned|Midnight Hunt Commander|127|R|{5}{B}{B}|Creature - Demon|5|5|Flying$When Overseer of the Damned enters the battlefield, you may destroy target creature.$Whenever a nontoken creature an opponent controls dies, create a tapped 2/2 black Zombie creature token.| +Spark Reaper|Midnight Hunt Commander|128|C|{2}{B}|Creature - Zombie|2|3|{3}, Sacrifice a creature or planeswalker: You gain 1 life and draw a card.| +Syphon Flesh|Midnight Hunt Commander|129|U|{4}{B}|Sorcery|||Each other player sacrifices a creature. You create a 2/2 black Zombie creature token for each creature sacrificed this way.| +Undead Augur|Midnight Hunt Commander|130|U|{B}{B}|Creature - Zombie Wizard|2|2|Whenever Undead Augur or another Zombie you control dies, you draw a card and you lose 1 life.| +Zombie Apocalypse|Midnight Hunt Commander|131|R|{3}{B}{B}{B}|Sorcery|||Return all Zombie creature cards from your graveyard to the battlefield tapped, then destroy all Humans.| +Avacyn's Pilgrim|Midnight Hunt Commander|132|C|{G}|Creature - Human Monk|1|1|{T}: Add {W}.| +Beast Within|Midnight Hunt Commander|133|U|{2}{G}|Instant|||Destroy target permanent. Its controller creates a 3/3 green Beast creature token.| +Bestial Menace|Midnight Hunt Commander|134|U|{3}{G}{G}|Sorcery|||Create a 1/1 green Snake creature token, a 2/2 green Wolf creature token, and a 3/3 green Elephant creature token.| +Biogenic Upgrade|Midnight Hunt Commander|135|U|{4}{G}{G}|Sorcery|||Distribute three +1/+1 counters among one, two, or three target creatures, then double the number of +1/+1 counters on each of those creatures.| +Champion of Lambholt|Midnight Hunt Commander|136|R|{1}{G}{G}|Creature - Human Warrior|1|1|Creatures with power less than Champion of Lambholt's power can't block creatures you control.$Whenever another creature enters the battlefield under your control, put a +1/+1 counter on Champion of Lambholt.| +Death's Presence|Midnight Hunt Commander|137|R|{5}{G}|Enchantment|||Whenever a creature you control dies, put X +1/+1 counters on target creature you control, where X is the power of the creature that died.| +Eternal Witness|Midnight Hunt Commander|138|U|{1}{G}{G}|Creature - Human Shaman|2|1|When Eternal Witness enters the battlefield, you may return target card from your graveyard to your hand.| +Growth Spasm|Midnight Hunt Commander|139|C|{2}{G}|Sorcery|||Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Create a 0/1 colorless Eldrazi Spawn creature token. It has "Sacrifice this creature: Add {C}."| +Gyre Sage|Midnight Hunt Commander|140|R|{1}{G}|Creature - Elf Druid|1|2|Evolve${T}: Add {G} for each +1/+1 counter on Gyre Sage.| +Inspiring Call|Midnight Hunt Commander|141|U|{2}{G}|Instant|||Draw a card for each creature you control with a +1/+1 counter on it. Those creatures gain indestructible until end of turn.| +Kessig Cagebreakers|Midnight Hunt Commander|142|R|{4}{G}|Creature - Human Rogue|3|4|Whenever Kessig Cagebreakers attacks, create a 2/2 green Wolf creature token that's tapped and attacking for each creature card in your graveyard.| +Shamanic Revelation|Midnight Hunt Commander|143|R|{3}{G}{G}|Sorcery|||Draw a card for each creature you control.$Ferocious — You gain 4 life for each creature you control with power 4 or greater.| +Somberwald Sage|Midnight Hunt Commander|144|R|{2}{G}|Creature - Human Druid|0|1|{T}: Add three mana of any one color. Spend this mana only to cast creature spells.| +Verdurous Gearhulk|Midnight Hunt Commander|145|M|{3}{G}{G}|Artifact Creature - Construct|4|4|Trample$When Verdurous Gearhulk enters the battlefield, distribute four +1/+1 counters among any number of target creatures you control.| +Wild Beastmaster|Midnight Hunt Commander|146|R|{2}{G}|Creature - Human Shaman|1|1|Whenever Wild Beastmaster attacks, each other creature you control gets +X/+X until end of turn, where X is Wild Beastmaster's power.| +Yavimaya Elder|Midnight Hunt Commander|147|C|{1}{G}{G}|Creature - Human Druid|2|1|When Yavimaya Elder dies, you may search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle.${2}, Sacrifice Yavimaya Elder: Draw a card.| +Diregraf Captain|Midnight Hunt Commander|148|U|{1}{U}{B}|Creature - Zombie Soldier|2|2|Deathtouch$Other Zombie creatures you control get +1/+1.$Whenever another Zombie you control dies, target opponent loses 1 life.| +Enduring Scalelord|Midnight Hunt Commander|149|U|{4}{G}{W}|Creature - Dragon|4|4|Flying$Whenever one or more +1/+1 counters are put on another creature you control, you may put a +1/+1 counter on Enduring Scalelord.| +Gisa and Geralf|Midnight Hunt Commander|150|M|{2}{U}{B}|Legendary Creature - Human Wizard|4|4|When Gisa and Geralf enters the battlefield, mill four cards.$During each of your turns, you may cast a Zombie creature spell from your graveyard.| +Gleaming Overseer|Midnight Hunt Commander|151|U|{1}{U}{B}|Creature - Zombie Wizard|1|4|When Gleaming Overseer enters the battlefield, amass 1.$Zombie tokens you control have hexproof and menace.| +Heron's Grace Champion|Midnight Hunt Commander|152|R|{2}{G}{W}|Creature - Human Knight|3|3|Flash$Lifelink$When Heron's Grace Champion enters the battlefield, other Humans you control get +1/+1 and gain lifelink until end of turn.| +Juniper Order Ranger|Midnight Hunt Commander|153|U|{3}{G}{W}|Creature - Human Knight Ranger|2|4|Whenever another creature enters the battlefield under your control, put a +1/+1 counter on that creature and a +1/+1 counter on Juniper Order Ranger.| +Ruthless Deathfang|Midnight Hunt Commander|154|U|{4}{U}{B}|Creature - Dragon|4|4|Flying$Whenever you sacrifice a creature, target opponent sacrifices a creature.| +Sigarda, Heron's Grace|Midnight Hunt Commander|155|M|{3}{G}{W}|Legendary Creature - Angel|4|5|Flying$You and Humans you control have hexproof.${2}, Exile a card from your graveyard: Create a 1/1 white Human Soldier creature token.| +Trostani's Summoner|Midnight Hunt Commander|156|U|{5}{G}{W}|Creature - Elf Shaman|1|1|When Trostani's Summoner enters the battlefield, create a 2/2 white Knight creature token with vigilance, a 3/3 green Centaur creature token, and a 4/4 green Rhino creature token with trample.| +Arcane Signet|Midnight Hunt Commander|157|C|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| +Charcoal Diamond|Midnight Hunt Commander|158|C|{2}|Artifact|||Charcoal Diamond enters the battlefield tapped.${T}: Add {B}.| +Commander's Sphere|Midnight Hunt Commander|159|C|{3}|Artifact|||{T}: Add one mana of any color in your commander's color identity.$Sacrifice Commander's Sphere: Draw a card.| +Lifecrafter's Bestiary|Midnight Hunt Commander|160|R|{3}|Artifact|||At the beginning of your upkeep, scry 1.$Whenever you cast a creature spell, you may pay {G}. If you do, draw a card.| +Sky Diamond|Midnight Hunt Commander|161|C|{2}|Artifact|||Sky Diamond enters the battlefield tapped.${T}: Add {U}.| +Sol Ring|Midnight Hunt Commander|162|U|{1}|Artifact|||{T}: Add {C}{C}.| +Swiftfoot Boots|Midnight Hunt Commander|163|U|{2}|Artifact - Equipment|||Equipped creature has hexproof and haste.$Equip {1}| +Talisman of Dominance|Midnight Hunt Commander|164|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {U} or {B}. Talisman of Dominance deals 1 damage to you.| +Talisman of Unity|Midnight Hunt Commander|165|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {G} or {W}. Talisman of Unity deals 1 damage to you.| +Blighted Woodland|Midnight Hunt Commander|166|U||Land|||{T}: Add {C}.${3}{G}, {T}, Sacrifice Blighted Woodland: Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.| +Bojuka Bog|Midnight Hunt Commander|167|C||Land|||Bojuka Bog enters the battlefield tapped.$When Bojuka Bog enters the battlefield, exile all cards from target player's graveyard.${T}: Add {B}.| +Canopy Vista|Midnight Hunt Commander|168|R||Land - Forest Plains|||({T}: Add {G} or {W}.)$Canopy Vista enters the battlefield tapped unless you control two or more basic lands.| +Choked Estuary|Midnight Hunt Commander|169|R||Land|||As Choked Estuary enters the battlefield, you may reveal an Island or Swamp card from your hand. If you don't, Choked Estuary enters the battlefield tapped.${T}: Add {U} or {B}.| +Command Tower|Midnight Hunt Commander|170|C||Land|||{T}: Add one mana of any color in your commander's color identity.| +Darkwater Catacombs|Midnight Hunt Commander|171|R||Land|||{1}, {T}: Add {U}{B}.| +Dimir Aqueduct|Midnight Hunt Commander|172|U||Land|||Dimir Aqueduct enters the battlefield tapped.$When Dimir Aqueduct enters the battlefield, return a land you control to its owner's hand.${T}: Add {U}{B}.| +Exotic Orchard|Midnight Hunt Commander|173|R||Land|||{T}: Add one mana of any color that a land an opponent controls could produce.| +Fortified Village|Midnight Hunt Commander|174|R||Land|||As Fortified Village enters the battlefield, you may reveal a Forest or Plains card from your hand. If you don't, Fortified Village enters the battlefield tapped.${T}: Add {G} or {W}.| +Krosan Verge|Midnight Hunt Commander|175|U||Land|||Krosan Verge enters the battlefield tapped.${T}: Add {C}.${2}, {T}, Sacrifice Krosan Verge: Search your library for a Forest card and a Plains card, put them onto the battlefield tapped, then shuffle.| +Mortuary Mire|Midnight Hunt Commander|176|C||Land|||Mortuary Mire enters the battlefield tapped.$When Mortuary Mire enters the battlefield, you may put target creature card from your graveyard on top of your library.${T}: Add {B}.| +Myriad Landscape|Midnight Hunt Commander|177|U||Land|||Myriad Landscape enters the battlefield tapped.${T}: Add {C}.${2}, {T}, Sacrifice Myriad Landscape: Search your library for up to two basic land cards that share a land type, put them onto the battlefield tapped, then shuffle.| +Path of Ancestry|Midnight Hunt Commander|178|C||Land|||Path of Ancestry enters the battlefield tapped.${T}: Add one mana of any color in your commander's color identity. When that mana is spent to cast a creature spell that shares a creature type with your commander, scry 1.| +Rogue's Passage|Midnight Hunt Commander|179|U||Land|||{T}: Add {C}.${4}, {T}: Target creature can't be blocked this turn.| +Selesnya Sanctuary|Midnight Hunt Commander|180|U||Land|||Selesnya Sanctuary enters the battlefield tapped.$When Selesnya Sanctuary enters the battlefield, return a land you control to its owner's hand.${T}: Add {G}{W}.| +Sungrass Prairie|Midnight Hunt Commander|181|R||Land|||{1}, {T}: Add {G}{W}.| +Sunken Hollow|Midnight Hunt Commander|182|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$Sunken Hollow enters the battlefield tapped unless you control two or more basic lands.| +Tainted Isle|Midnight Hunt Commander|183|U||Land|||{T}: Add {C}.${T}: Add {U} or {B}. Activate only if you control a Swamp.| +Temple of Deceit|Midnight Hunt Commander|184|R||Land|||Temple of Deceit enters the battlefield tapped.$When Temple of Deceit enters the battlefield, scry 1.${T}: Add {U} or {B}.| +Temple of Plenty|Midnight Hunt Commander|185|R||Land|||Temple of Plenty enters the battlefield tapped.$When Temple of Plenty enters the battlefield, scry 1.${T}: Add {G} or {W}.| +Temple of the False God|Midnight Hunt Commander|186|U||Land|||{T}: Add {C}{C}. Activate only if you control five or more lands.| +Unclaimed Territory|Midnight Hunt Commander|187|U||Land|||As Unclaimed Territory enters the battlefield, choose a creature type.${T}: Add {C}.${T}: Add one mana of any color. Spend this mana only to cast a creature spell of the chosen type.| diff --git a/pom.xml b/pom.xml index 8109ff51814..aae0becdc92 100644 --- a/pom.xml +++ b/pom.xml @@ -1,21 +1,20 @@ - 4.0.0 - org.mage mage-root 1.4.50 pom Mage Root Mage Root POM + org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.8.1 1.8 1.8 @@ -109,6 +108,7 @@ + Mage Mage.Common @@ -163,42 +163,88 @@ ${project.basedir}/../${aggregate.report.dir} + + + + + + + org.slf4j + slf4j-log4j12 + 1.7.32 + + + + + com.j256.ormlite + ormlite-jdbc + 5.6 + + + + + com.h2database + h2 + 1.4.197 + runtime + + + + + junit + junit + 4.13.2 + test + + + + org.junit.jupiter + junit-jupiter + 5.8.1 + test + + + + org.assertj + assertj-core + 3.21.0 + test + + + + + - junit - junit - 4.13.1 - - - log4j - log4j - 1.2.17 - - - org.slf4j - slf4j-log4j12 - 1.8.0-beta2 + + com.google.code.gson + gson + 2.8.8 + com.google.guava guava 30.1.1-jre - org.junit.jupiter - junit-jupiter - 5.7.0 + + org.unbescape + unbescape + 1.1.6.RELEASE - org.assertj - assertj-core - 3.18.0 + + net.java.truevfs + truevfs-profile-base + 0.14.0 + org.apache.commons commons-lang3 - 3.11 + 3.12.0 diff --git a/readme.md b/readme.md index ccb9539531a..c744b8fe5ac 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # XMage — Magic, Another Game Engine -[![Build Status](https://travis-ci.org/magefree/mage.svg?branch=master)](https://travis-ci.org/magefree/mage) [![latest release](https://img.shields.io/github/v/release/magefree/mage)](https://github.com/magefree/mage/releases/) [![commints since latest release](https://img.shields.io/github/commits-since/magefree/mage/latest)](https://github.com/magefree/mage/commits/) [![Join the chat at https://gitter.im/magefree/mage](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magefree/mage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Build Status](https://travis-ci.org/magefree/mage.svg?branch=master)](https://travis-ci.org/magefree/mage) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=JayDi85_mage&metric=coverage)](https://sonarcloud.io/dashboard?id=JayDi85_mage) [![latest release](https://img.shields.io/github/v/release/magefree/mage)](https://github.com/magefree/mage/releases/) [![commints since latest release](https://img.shields.io/github/commits-since/magefree/mage/latest)](https://github.com/magefree/mage/commits/) [![Join the chat at https://gitter.im/magefree/mage](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magefree/mage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **20 000** unique cards and more than 50.000 reprints from different editions.