diff --git a/.travis.yml b/.travis.yml
index b4d9040161f..4c4e861d971 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,15 +10,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 extends Ability> abilities, String message) {
+ public AbilityPickerView(GameView gameView, String objectName, List extends Ability> 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 extends Ability> 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 extends Card> pile1, final List extends Card> 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