diff --git a/Mage.Client/pom.xml b/Mage.Client/pom.xml index c573fb21f68..d27d1ff605e 100644 --- a/Mage.Client/pom.xml +++ b/Mage.Client/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-client 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 875c0c2033d..e427ef5b0a9 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form @@ -2626,19 +2626,19 @@ - + - + - + 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 7d5c5267e20..27c7b912bc0 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -2403,15 +2403,15 @@ public class PreferencesDialog extends javax.swing.JDialog { phases_stopSettings.add(cbStopAttack); cbStopBlockWithAny.setSelected(true); - cbStopBlockWithAny.setText("STOP skips on declare blockers if ANY blockers are available"); + cbStopBlockWithAny.setText("STOP skips when attacked and on declare blockers if ANY blockers are available"); cbStopBlockWithAny.setActionCommand(""); phases_stopSettings.add(cbStopBlockWithAny); - cbStopBlockWithZero.setText("STOP skips on declare blockers if ZERO blockers are available"); + cbStopBlockWithZero.setText("STOP skips when attacked if ZERO blockers are available"); cbStopBlockWithZero.setActionCommand(""); phases_stopSettings.add(cbStopBlockWithZero); - cbStopOnNewStackObjects.setText("Skip to STACK resolved (F10): stop on new objects added (on) or stop until empty (off)"); + cbStopOnNewStackObjects.setText("Skip to STACK resolved (F10): stop on new objects added (on) or stop when stack empty (off)"); cbStopOnNewStackObjects.setActionCommand(""); cbStopOnNewStackObjects.setPreferredSize(new java.awt.Dimension(300, 25)); phases_stopSettings.add(cbStopOnNewStackObjects); 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 eb2643061dc..f188a9fbd61 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 @@ -123,6 +123,7 @@ public class ModernCardRenderer extends CardRenderer { public static final Color ERROR_COLOR = new Color(255, 0, 255); static String SUB_TYPE_ADVENTURE = "Adventure"; + static String SUB_TYPE_OMEN = "Omen"; /////////////////////////////////////////////////////////////////////////// // Layout metrics for modern border cards @@ -168,8 +169,8 @@ public class ModernCardRenderer extends CardRenderer { // Processed mana cost string protected String manaCostString; - // Is an adventure - protected boolean isAdventure = false; + // Is an adventure or omen + protected boolean isCardWithSpellOption = false; public ModernCardRenderer(CardView card) { // Pass off to parent @@ -179,12 +180,13 @@ public class ModernCardRenderer extends CardRenderer { manaCostString = ManaSymbols.getClearManaCost(cardView.getManaCostStr()); if (cardView.isSplitCard()) { - isAdventure = cardView.getRightSplitTypeLine().contains(SUB_TYPE_ADVENTURE); + isCardWithSpellOption = cardView.getRightSplitTypeLine().contains(SUB_TYPE_ADVENTURE) + || cardView.getRightSplitTypeLine().contains(SUB_TYPE_OMEN); } } - protected boolean isAdventure() { - return isAdventure; + protected boolean isCardWithSpellOption() { + return isCardWithSpellOption; } @Override @@ -660,7 +662,7 @@ public class ModernCardRenderer extends CardRenderer { drawRulesText(g, textboxKeywords, textboxRules, contentWidth / 2 + totalContentInset + 4, totalContentInset + boxHeight + 2, contentWidth / 2 - 8, typeLineY - totalContentInset - boxHeight - 6, false); - } else if (isAdventure) { + } else if (isCardWithSpellOption) { drawRulesText(g, textboxKeywords, textboxRules, contentWidth / 2 + totalContentInset + 4, typeLineY + boxHeight + 2, contentWidth / 2 - 8, cardHeight - typeLineY - boxHeight - 4 - borderWidth * 3, false); 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 e43dfc10662..291dad2d4ec 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 @@ -56,7 +56,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { private boolean isAftermath = false; private static String trimAdventure(String rule) { - if (rule.startsWith("Adventure")) { + if (rule.startsWith("Adventure") || rule.startsWith("Omen")) { return rule.substring(rule.lastIndexOf("—") + 8); } return rule; @@ -71,7 +71,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { rightHalf.color = new ObjectColor(cardView.getRightSplitCostsStr()); leftHalf.color = new ObjectColor(cardView.getLeftSplitCostsStr()); - if (isAdventure()) { + if (isCardWithSpellOption()) { List trimmedRules = new ArrayList<>(); for (String rule : view.getRightSplitRules()) { trimmedRules.add(trimAdventure(rule)); @@ -95,7 +95,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { // they "rotate" in opposite directions making consquence and normal split cards // have the "right" vs "left" as the top half. // Adventures are treated differently and not rotated at all. - if (isAdventure()) { + if (isCardWithSpellOption()) { manaCostString = leftHalf.manaCostString; textboxKeywords = leftHalf.keywords; textboxRules = leftHalf.rules; @@ -159,7 +159,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { protected void drawBackground(Graphics2D g) { if (cardView.isFaceDown()) { drawCardBackTexture(g); - } if (isAdventure()) { + } if (isCardWithSpellOption()) { super.drawBackground(g); } else { { // Left half background (top of the card) @@ -204,7 +204,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { @Override protected void drawArt(Graphics2D g) { - if (isAdventure) { + if (isCardWithSpellOption) { super.drawArt(g); } else if (artImage != null) { if (isAftermath()) { @@ -318,7 +318,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { @Override protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) { - if (isAdventure()) { + if (isCardWithSpellOption()) { super.drawFrame(g, attribs, image, lessOpaqueRulesTextBox); CardPanelAttributes adventureAttribs = new CardPanelAttributes( diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java index b864f74a21d..4ea496665d0 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java @@ -54,7 +54,7 @@ public class ScryfallApiCard { this.card_faces.forEach(ScryfallApiCardFace::prepareCompatibleData); } - // workaround for adventure card name fix: + // workaround for adventure/omen card name fix: // - scryfall: Ondu Knotmaster // Throw a Line // - xmage: Ondu Knotmaster if (this.layout.equals("adventure")) { @@ -100,12 +100,12 @@ public class ScryfallApiCard { } this.name = this.card_faces.get(0).name; } else if (this.card_faces.get(0).layout.equals("adventure")) { - // adventure card + // adventure/omen card // Bloomvine Regent // Claim Territory // https://scryfall.com/card/tdm/381/bloomvine-regent-claim-territory-bloomvine-regent this.name = this.card_faces.get(0).name; if (this.card_faces.get(0).name.equals(this.card_faces.get(1).name)) { - throw new IllegalArgumentException("Scryfall: unsupported data type, adventure's reversible_card must have diff names in faces " + throw new IllegalArgumentException("Scryfall: unsupported data type, adventure/omen's reversible_card must have diff names in faces " + this.set + " - " + this.collector_number + " - " + this.name); } } else if (this.card_faces.get(0).layout.equals("token")) { diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java index 9441b6aa677..a6479a6c40a 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java @@ -2183,6 +2183,7 @@ public class ScryfallImageSupportTokens { // WHO put("WHO/Alien", "https://api.scryfall.com/cards/twho/2?format=image"); put("WHO/Alien Insect", "https://api.scryfall.com/cards/twho/19/en?format=image"); + put("WHO/Alien Rhino", "https://api.scryfall.com/cards/twho/3/en?format=image"); put("WHO/Alien Salamander", "https://api.scryfall.com/cards/twho/16?format=image"); put("WHO/Alien Warrior", "https://api.scryfall.com/cards/twho/14?format=image"); put("WHO/Beast", "https://api.scryfall.com/cards/twho/17?format=image"); @@ -2196,7 +2197,8 @@ public class ScryfallImageSupportTokens { put("WHO/Food/2", "https://api.scryfall.com/cards/twho/26?format=image"); put("WHO/Food/3", "https://api.scryfall.com/cards/twho/27?format=image"); put("WHO/Horse", "https://api.scryfall.com/cards/twho/4/en?format=image"); - put("WHO/Human", "https://api.scryfall.com/cards/twho/5?format=image"); + put("WHO/Human/1", "https://api.scryfall.com/cards/twho/6/en?format=image"); + put("WHO/Human/2", "https://api.scryfall.com/cards/twho/5/en?format=image"); put("WHO/Human Noble", "https://api.scryfall.com/cards/twho/7/en?format=image"); put("WHO/Mark of the Rani", "https://api.scryfall.com/cards/twho/15?format=image"); put("WHO/Soldier", "https://api.scryfall.com/cards/twho/8?format=image"); diff --git a/Mage.Common/pom.xml b/Mage.Common/pom.xml index 2a0c7e1bf6a..36fe0dc82ca 100644 --- a/Mage.Common/pom.xml +++ b/Mage.Common/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-common diff --git a/Mage.Common/src/main/java/mage/utils/MageVersion.java b/Mage.Common/src/main/java/mage/utils/MageVersion.java index 79dfcb49f43..07ba1547c1a 100644 --- a/Mage.Common/src/main/java/mage/utils/MageVersion.java +++ b/Mage.Common/src/main/java/mage/utils/MageVersion.java @@ -17,8 +17,8 @@ public class MageVersion implements Serializable, Comparable { // * launcher gives priority to 1.4.48 instead 1.4.48-any-text, so don't use empty release info public static final int MAGE_VERSION_MAJOR = 1; public static final int MAGE_VERSION_MINOR = 4; - public static final int MAGE_VERSION_RELEASE = 56; - public static final String MAGE_VERSION_RELEASE_INFO = "V3"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas + public static final int MAGE_VERSION_RELEASE = 57; + public static final String MAGE_VERSION_RELEASE_INFO = "V1"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas // strict mode // Each update requires a strict version diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index c13f283c768..84d55c232fb 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -432,21 +432,21 @@ public class CardView extends SimpleCardView { fullCardName = mainCard.getLeftHalfCard().getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + mainCard.getRightHalfCard().getName(); this.manaCostLeftStr = mainCard.getLeftHalfCard().getManaCostSymbols(); this.manaCostRightStr = mainCard.getRightHalfCard().getManaCostSymbols(); - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { this.isSplitCard = true; - AdventureCard adventureCard = ((AdventureCard) card); - leftSplitName = adventureCard.getName(); - leftSplitCostsStr = String.join("", adventureCard.getManaCostSymbols()); - leftSplitRules = adventureCard.getSharedRules(game); - leftSplitTypeLine = getCardTypeLine(game, adventureCard); - AdventureCardSpell adventureCardSpell = adventureCard.getSpellCard(); - rightSplitName = adventureCardSpell.getName(); - rightSplitCostsStr = String.join("", adventureCardSpell.getManaCostSymbols()); - rightSplitRules = adventureCardSpell.getRules(game); - rightSplitTypeLine = getCardTypeLine(game, adventureCardSpell); - fullCardName = adventureCard.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + adventureCardSpell.getName(); - this.manaCostLeftStr = adventureCard.getManaCostSymbols(); - this.manaCostRightStr = adventureCardSpell.getManaCostSymbols(); + CardWithSpellOption mainCard = ((CardWithSpellOption) card); + leftSplitName = mainCard.getName(); + leftSplitCostsStr = String.join("", mainCard.getManaCostSymbols()); + leftSplitRules = mainCard.getSharedRules(game); + leftSplitTypeLine = getCardTypeLine(game, mainCard); + SpellOptionCard splitCardSpell = mainCard.getSpellCard(); + rightSplitName = splitCardSpell.getName(); + rightSplitCostsStr = String.join("", splitCardSpell.getManaCostSymbols()); + rightSplitRules = splitCardSpell.getRules(game); + rightSplitTypeLine = getCardTypeLine(game, splitCardSpell); + fullCardName = mainCard.getName() + MockCard.CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + splitCardSpell.getName(); + this.manaCostLeftStr = mainCard.getManaCostSymbols(); + this.manaCostRightStr = splitCardSpell.getManaCostSymbols(); } else if (card instanceof MockCard) { // deck editor cards fullCardName = ((MockCard) card).getFullName(true); diff --git a/Mage.Plugins/Mage.Counter.Plugin/pom.xml b/Mage.Plugins/Mage.Counter.Plugin/pom.xml index 54eba397b58..01eb53581af 100644 --- a/Mage.Plugins/Mage.Counter.Plugin/pom.xml +++ b/Mage.Plugins/Mage.Counter.Plugin/pom.xml @@ -7,7 +7,7 @@ org.mage mage-plugins - 1.4.56 + 1.4.57 mage-counter-plugin diff --git a/Mage.Plugins/pom.xml b/Mage.Plugins/pom.xml index b3d08009be2..bb34aa35fd1 100644 --- a/Mage.Plugins/pom.xml +++ b/Mage.Plugins/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-plugins diff --git a/Mage.Reports/pom.xml b/Mage.Reports/pom.xml index 2e98574191d..ad07b817fe5 100644 --- a/Mage.Reports/pom.xml +++ b/Mage.Reports/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-reports diff --git a/Mage.Server.Console/pom.xml b/Mage.Server.Console/pom.xml index a2192534a39..83cc4306e94 100644 --- a/Mage.Server.Console/pom.xml +++ b/Mage.Server.Console/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-server-console diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml b/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml index 57d5a53b0f1..c7e540478b8 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-deck-constructed diff --git a/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml b/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml index 3d53e554370..d4dd4f092b5 100644 --- a/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml +++ b/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-deck-limited diff --git a/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml index b865a976eaa..a9b9a9dff43 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-brawlduel diff --git a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml index eb5c5913080..924b17f9daf 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-brawlfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml index d9defa0ebad..f269b29868d 100644 --- a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-canadianhighlanderduel diff --git a/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml index 55d69cf5c38..18459996ee6 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-commanderduel diff --git a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml index f5c7b42c0ca..90440eae253 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-commanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml index f2986e22ff7..61c4eee4421 100644 --- a/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-custompillaroftheparunsduel diff --git a/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml index 5a9acb210ec..33c2188d519 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-freeforall diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml index d68adb9f8a3..3b3b8d02a2c 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-freeformcommanderduel diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml index 1c0016007c7..a8fdeac89a2 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-freeformcommanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml index e0fc0dbc6ad..9db9b8620b5 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-freeformunlimitedcommander @@ -23,7 +23,7 @@ org.mage mage-game-freeformcommanderfreeforall - 1.4.56 + 1.4.57 compile diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml index dce83069cad..07d7171de27 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-momirduel diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml b/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml index 0451da80628..8a9d7ea5d46 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-momirfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml index 1c1fc8cf0cc..4cb658b9b42 100644 --- a/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-oathbreakerduel @@ -22,7 +22,7 @@ org.mage mage-game-oathbreakerfreeforall - 1.4.56 + 1.4.57 compile diff --git a/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml index a87f4e138ad..47cb4ce8bb9 100644 --- a/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-oathbreakerfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml index 9e1365bbe12..91a3176b885 100644 --- a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-pennydreadfulcommanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml index bf33a290fbc..0f53a19a16e 100644 --- a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-tinyleadersduel diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml index 8a0b37c4bdc..205565820ef 100644 --- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-game-twoplayerduel diff --git a/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml b/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml index 4599885dfe9..71a45bc1577 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-player-ai-draftbot diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml b/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml index dc71986f494..5dc6a5b7b7a 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-player-ai-ma diff --git a/Mage.Server.Plugins/Mage.Player.AI/pom.xml b/Mage.Server.Plugins/Mage.Player.AI/pom.xml index 04cf099d905..80eda4b4025 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-player-ai diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index d21ecf58811..71ec32b1c21 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 @@ -1863,8 +1863,11 @@ public class ComputerPlayer extends PlayerImpl { @Override public int announceXMana(int min, int max, String message, Game game, Ability ability) { - log.debug("announceXMana"); - //TODO: improve this + // current logic - use max possible mana + + // TODO: add good/bad effects support + // TODO: add simple game simulations like declare blocker? + int numAvailable = getAvailableManaProducers(game).size() - ability.getManaCosts().manaValue(); if (numAvailable < 0) { numAvailable = 0; @@ -1881,12 +1884,17 @@ public class ComputerPlayer extends PlayerImpl { @Override public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variablCost) { - log.debug("announceXCost"); + // current logic - use random non-zero value + + // TODO: add good/bad effects support + // TODO: remove random logic + int value = RandomUtil.nextInt(CardUtil.overflowInc(max, 1)); if (value < min) { value = min; } if (value < max) { + // do not use zero values value++; } return value; diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml index d488c1ed04f..51e7608305d 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-player-ai-mcts diff --git a/Mage.Server.Plugins/Mage.Player.Human/pom.xml b/Mage.Server.Plugins/Mage.Player.Human/pom.xml index 930c9c7787c..921c136dd8a 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.Human/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-player-human diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index f035a45fe6b..9f64a00117c 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 @@ -380,9 +380,13 @@ public class HumanPlayer extends PlayerImpl { } } + private boolean canCallFeedback(Game game) { + return !gameInCheckPlayableState(game) && !game.isSimulation(); + } + @Override public boolean chooseMulligan(Game game) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return true; } @@ -509,7 +513,7 @@ public class HumanPlayer extends PlayerImpl { @Override public int chooseReplacementEffect(Map effectsMap, Map objectsMap, Game game) { - if (gameInCheckPlayableState(game, true)) { // ignore warning logs until double call for TAPPED_FOR_MANA will be fix + if (gameInCheckPlayableState(game, true) || game.isSimulation()) { // TODO: ignore warning logs until double call for TAPPED_FOR_MANA will be fix return 0; } @@ -615,7 +619,7 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean choose(Outcome outcome, Choice choice, Game game) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return true; } @@ -678,7 +682,7 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map options) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return true; } @@ -782,7 +786,7 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return true; } @@ -862,7 +866,7 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean choose(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return true; } @@ -944,7 +948,7 @@ public class HumanPlayer extends PlayerImpl { // choose one or multiple target cards @Override public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return true; } @@ -1025,7 +1029,7 @@ public class HumanPlayer extends PlayerImpl { public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) { // choose amount // human can choose or un-choose MULTIPLE targets at once - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return true; } @@ -1486,8 +1490,8 @@ public class HumanPlayer extends PlayerImpl { @Override public TriggeredAbility chooseTriggeredAbility(java.util.List abilities, Game game) { // choose triggered abilitity from list - if (gameInCheckPlayableState(game)) { - return null; + if (!canCallFeedback(game)) { + return abilities.isEmpty() ? null : abilities.get(0); } // automatically order triggers with same ability, rules text, and targets @@ -1615,7 +1619,7 @@ public class HumanPlayer extends PlayerImpl { protected boolean playManaHandling(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) { // choose mana to pay (from permanents or from pool) - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return true; } @@ -1661,7 +1665,7 @@ public class HumanPlayer extends PlayerImpl { * @return */ public int announceRepetitions(Game game) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return 0; } @@ -1694,8 +1698,8 @@ public class HumanPlayer extends PlayerImpl { */ @Override public int announceXMana(int min, int max, String message, Game game, Ability ability) { - if (gameInCheckPlayableState(game)) { - return 0; + if (!canCallFeedback(game)) { + return min; } int xValue = 0; @@ -1709,6 +1713,8 @@ public class HumanPlayer extends PlayerImpl { if (response.getInteger() != null) { break; } + + // TODO: add response verify here } if (response.getInteger() != null) { @@ -1719,8 +1725,8 @@ public class HumanPlayer extends PlayerImpl { @Override public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) { - if (gameInCheckPlayableState(game)) { - return 0; + if (!canCallFeedback(game)) { + return min; } int xValue = 0; @@ -1792,7 +1798,7 @@ public class HumanPlayer extends PlayerImpl { @Override public void selectAttackers(Game game, UUID attackingPlayerId) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return; } @@ -2062,7 +2068,7 @@ public class HumanPlayer extends PlayerImpl { @Override public void selectBlockers(Ability source, Game game, UUID defendingPlayerId) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return; } @@ -2072,7 +2078,6 @@ public class HumanPlayer extends PlayerImpl { // stop skip on any/zero permanents available int possibleBlockersCount = game.getBattlefield().count(filter, playerId, source, game); boolean canStopOnAny = possibleBlockersCount != 0 && getControllingPlayersUserData(game).getUserSkipPrioritySteps().isStopOnDeclareBlockersWithAnyPermanents(); - boolean canStopOnZero = possibleBlockersCount == 0 && getControllingPlayersUserData(game).getUserSkipPrioritySteps().isStopOnDeclareBlockersWithZeroPermanents(); // skip declare blocker step // as opposed to declare attacker - it can be skipped by ANY skip button TODO: make same for declare attackers and rework skip buttons (normal and forced) @@ -2081,9 +2086,11 @@ public class HumanPlayer extends PlayerImpl { || passedTurn || passedUntilEndOfTurn || passedUntilNextMain; - if (skipButtonActivated && !canStopOnAny && !canStopOnZero) { + if (skipButtonActivated && !canStopOnAny) { return; } + // Skip prompt to select blockers if player has none + if (possibleBlockersCount == 0) return; while (canRespond()) { prepareForResponse(game); @@ -2123,7 +2130,7 @@ public class HumanPlayer extends PlayerImpl { } protected void selectCombatGroup(UUID defenderId, UUID blockerId, Game game) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return; } TargetAttackingCreature target = new TargetAttackingCreature(); @@ -2177,8 +2184,8 @@ public class HumanPlayer extends PlayerImpl { @Override public int getAmount(int min, int max, String message, Game game) { - if (gameInCheckPlayableState(game)) { - return 0; + if (!canCallFeedback(game)) { + return min; } while (canRespond()) { @@ -2217,7 +2224,7 @@ public class HumanPlayer extends PlayerImpl { return defaultList; } - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return defaultList; } @@ -2285,7 +2292,7 @@ public class HumanPlayer extends PlayerImpl { * @param unpaidForManaAction - set unpaid for mana actions like convoke */ protected void activateSpecialAction(Game game, ManaCost unpaidForManaAction) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return; } @@ -2316,7 +2323,7 @@ public class HumanPlayer extends PlayerImpl { } protected void activateAbility(Map abilities, MageObject object, Game game) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return; } @@ -2378,7 +2385,7 @@ public class HumanPlayer extends PlayerImpl { Card mainCard = game.getCard(CardUtil.getMainCardId(game, ability.getSourceId())); if (mainCard != null && !Zone.BATTLEFIELD.equals(game.getState().getZone(mainCard.getId()))) { if (mainCard instanceof SplitCard - || mainCard instanceof AdventureCard + || mainCard instanceof CardWithSpellOption || mainCard instanceof ModalDoubleFacedCard) { return false; } @@ -2401,7 +2408,7 @@ public class HumanPlayer extends PlayerImpl { @Override public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return null; } @@ -2441,7 +2448,7 @@ public class HumanPlayer extends PlayerImpl { @Override public ActivatedAbility chooseLandOrSpellAbility(Card card, Game game, boolean noMana) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return null; } @@ -2488,7 +2495,7 @@ public class HumanPlayer extends PlayerImpl { @Override public Mode chooseMode(Modes modes, Ability source, Game game) { // choose mode to activate - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return null; } @@ -2616,7 +2623,7 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean choosePile(Outcome outcome, String message, java.util.List pile1, java.util.List pile2, Game game) { - if (gameInCheckPlayableState(game)) { + if (!canCallFeedback(game)) { return true; } diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml index 520028b4293..8501c5e0d54 100644 --- a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-tournament-boosterdraft diff --git a/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml b/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml index 4ec19f4a47d..e14dff2efdb 100644 --- a/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-tournament-constructed diff --git a/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml b/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml index 3d00fdd278b..e2ad7473e1b 100644 --- a/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.56 + 1.4.57 mage-tournament-sealed diff --git a/Mage.Server.Plugins/pom.xml b/Mage.Server.Plugins/pom.xml index b22f00b7348..e216bbc0eea 100644 --- a/Mage.Server.Plugins/pom.xml +++ b/Mage.Server.Plugins/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-server-plugins diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index 9774edf32ca..ff104474d0e 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-server diff --git a/Mage.Server/release/config-example/init.example.txt b/Mage.Server/release/config-example/init.example.txt index d645b731d57..03cbfd7e7b8 100644 --- a/Mage.Server/release/config-example/init.example.txt +++ b/Mage.Server/release/config-example/init.example.txt @@ -62,9 +62,6 @@ hand:Human:Angelic Edict:3 battlefield:Computer:Grizzly Bears:2 battlefield:Human:Grizzly Bears:2 -// special command, see SystemUtil for more special commands list -[@activate opponent ability] - [diff set codes example] battlefield:Human:XLN-Island:1 battlefield:Human:UST-Island:1 diff --git a/Mage.Sets/pom.xml b/Mage.Sets/pom.xml index c55e8bb1d3b..b04a9bb367e 100644 --- a/Mage.Sets/pom.xml +++ b/Mage.Sets/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-sets diff --git a/Mage.Sets/src/mage/cards/a/AbandonHope.java b/Mage.Sets/src/mage/cards/a/AbandonHope.java index 16365813b9d..0aeae4b6229 100644 --- a/Mage.Sets/src/mage/cards/a/AbandonHope.java +++ b/Mage.Sets/src/mage/cards/a/AbandonHope.java @@ -1,43 +1,30 @@ package mage.cards.a; -import mage.abilities.Ability; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.costadjusters.DiscardXCardsCostAdjuster; import mage.abilities.dynamicvalue.common.GetXValue; -import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.discard.LookTargetHandChooseDiscardEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.target.common.TargetCardInHand; import mage.target.common.TargetOpponent; -import mage.util.CardUtil; import java.util.UUID; /** - * @author fireshoes + * @author fireshoes, JayDi85 */ public final class AbandonHope extends CardImpl { public AbandonHope(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{1}{B}"); - // As an additional cost to cast Abandon Hope, discard X cards. - Ability ability = new SimpleStaticAbility( - Zone.ALL, new InfoEffect("As an additional cost to cast this spell, discard X cards") - ); - ability.setRuleAtTheTop(true); - this.addAbility(ability); + // As an additional cost to cast this spell, discard X cards. + DiscardXCardsCostAdjuster.addAdjusterAndMessage(this, StaticFilters.FILTER_CARD_CARDS); // Look at target opponent's hand and choose X cards from it. That player discards those cards. this.getSpellAbility().addEffect(new LookTargetHandChooseDiscardEffect(false, GetXValue.instance, StaticFilters.FILTER_CARD_CARDS)); this.getSpellAbility().addTarget(new TargetOpponent()); - this.getSpellAbility().setCostAdjuster(AbandonHopeAdjuster.instance); } private AbandonHope(final AbandonHope card) { @@ -48,16 +35,4 @@ public final class AbandonHope extends CardImpl { public AbandonHope copy() { return new AbandonHope(this); } -} - -enum AbandonHopeAdjuster implements CostAdjuster { - instance; - - @Override - public void adjustCosts(Ability ability, Game game) { - int xValue = CardUtil.getSourceCostsTag(game, ability, "X", 0); - if (xValue > 0) { - ability.addCost(new DiscardTargetCost(new TargetCardInHand(xValue, xValue, StaticFilters.FILTER_CARD_CARDS))); - } - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/a/AbzanMonument.java b/Mage.Sets/src/mage/cards/a/AbzanMonument.java index fc1899a2030..b9c120e1fba 100644 --- a/Mage.Sets/src/mage/cards/a/AbzanMonument.java +++ b/Mage.Sets/src/mage/cards/a/AbzanMonument.java @@ -9,8 +9,6 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.GreatestToughnessAmongControlledCreaturesValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; -import mage.abilities.hint.Hint; -import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -41,11 +39,6 @@ public final class AbzanMonument extends CardImpl { )); } - private static final Hint hint = new ValueHint( - "Greatest toughness among creatures you control", - GreatestToughnessAmongControlledCreaturesValue.instance - ); - public AbzanMonument(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); @@ -60,7 +53,7 @@ public final class AbzanMonument extends CardImpl { ); ability.addCost(new TapSourceCost()); ability.addCost(new SacrificeSourceCost()); - this.addAbility(ability.addHint(hint)); + this.addAbility(ability.addHint(GreatestToughnessAmongControlledCreaturesValue.ALL.getHint())); } private AbzanMonument(final AbzanMonument card) { @@ -93,9 +86,7 @@ class AbzanMonumentEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { return new SpiritXXToken( - GreatestToughnessAmongControlledCreaturesValue - .instance - .calculate(game, source, this) + GreatestToughnessAmongControlledCreaturesValue.ALL.calculate(game, source, this) ).putOntoBattlefield(1, game, source); } } diff --git a/Mage.Sets/src/mage/cards/a/AdaptiveTrainingPost.java b/Mage.Sets/src/mage/cards/a/AdaptiveTrainingPost.java new file mode 100644 index 00000000000..332776d8e14 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AdaptiveTrainingPost.java @@ -0,0 +1,50 @@ +package mage.cards.a; + +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.common.delayed.CopyNextSpellDelayedTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AdaptiveTrainingPost extends CardImpl { + + private static final Condition condition = new SourceHasCounterCondition(CounterType.CHARGE, 0, 2); + + public AdaptiveTrainingPost(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + // Whenever you cast an instant or sorcery spell, if this artifact has fewer than three charge counters on it, put a charge counter on it. + this.addAbility(new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.CHARGE.createInstance()), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false + ).withInterveningIf(condition)); + + // Remove three charge counters from this artifact: When you next cast an instant or sorcery spell this turn, copy it and you may choose new targets for the copy. + this.addAbility(new SimpleActivatedAbility( + new CreateDelayedTriggeredAbilityEffect(new CopyNextSpellDelayedTriggeredAbility()), + new RemoveCountersSourceCost(CounterType.CHARGE.createInstance(3)) + )); + } + + private AdaptiveTrainingPost(final AdaptiveTrainingPost card) { + super(card); + } + + @Override + public AdaptiveTrainingPost copy() { + return new AdaptiveTrainingPost(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AeonChronicler.java b/Mage.Sets/src/mage/cards/a/AeonChronicler.java index b18938e90a4..291b1c7af64 100644 --- a/Mage.Sets/src/mage/cards/a/AeonChronicler.java +++ b/Mage.Sets/src/mage/cards/a/AeonChronicler.java @@ -31,7 +31,7 @@ public final class AeonChronicler extends CardImpl { this.toughness = new MageInt(0); // Aeon Chronicler's power and toughness are each equal to the number of cards in your hand. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.instance))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.ANY))); // Suspend X-{X}{3}{U}. X can't be 0. this.addAbility(new SuspendAbility(Integer.MAX_VALUE, new ManaCostsImpl<>("{3}{U}"), this, true)); diff --git a/Mage.Sets/src/mage/cards/a/AetherTide.java b/Mage.Sets/src/mage/cards/a/AetherTide.java index 885f74c69f0..eef2409f2b3 100644 --- a/Mage.Sets/src/mage/cards/a/AetherTide.java +++ b/Mage.Sets/src/mage/cards/a/AetherTide.java @@ -1,27 +1,18 @@ package mage.cards.a; -import mage.abilities.Ability; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.costadjusters.DiscardXCardsCostAdjuster; import mage.abilities.effects.Effect; -import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.target.common.TargetCardInHand; import mage.target.common.TargetCreaturePermanent; import mage.target.targetadjustment.XTargetsCountAdjuster; -import mage.util.CardUtil; import java.util.UUID; /** - * * @author jeffwadsworth */ public final class AetherTide extends CardImpl { @@ -29,10 +20,8 @@ public final class AetherTide extends CardImpl { public AetherTide(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{U}"); - // As an additional cost to cast Aether Tide, discard X creature cards. - Ability ability = new SimpleStaticAbility(Zone.ALL, new InfoEffect("As an additional cost to cast this spell, discard X creature cards")); - ability.setRuleAtTheTop(true); - this.addAbility(ability); + // As an additional cost to cast this spell, discard X creature cards. + DiscardXCardsCostAdjuster.addAdjusterAndMessage(this, StaticFilters.FILTER_CARD_CREATURES); // Return X target creatures to their owners' hands. Effect effect = new ReturnToHandTargetEffect(); @@ -40,8 +29,6 @@ public final class AetherTide extends CardImpl { this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().setTargetAdjuster(new XTargetsCountAdjuster()); - this.getSpellAbility().setCostAdjuster(AetherTideCostAdjuster.instance); - } private AetherTide(final AetherTide card) { @@ -53,15 +40,3 @@ public final class AetherTide extends CardImpl { return new AetherTide(this); } } - -enum AetherTideCostAdjuster implements CostAdjuster { - instance; - - @Override - public void adjustCosts(Ability ability, Game game) { - int xValue = CardUtil.getSourceCostsTag(game, ability, "X", 0); - if (xValue > 0) { - ability.addCost(new DiscardTargetCost(new TargetCardInHand(xValue, xValue, StaticFilters.FILTER_CARD_CREATURES))); - } - } -} diff --git a/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java b/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java new file mode 100644 index 00000000000..d4563bdb910 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java @@ -0,0 +1,47 @@ +package mage.cards.a; + +import java.util.UUID; + +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ExileCardYouChooseTargetOpponentEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +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.target.TargetPlayer; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +/** + * Aggressive Negotiations implementation + * Author: @mikejcunn + */ +public final class AggressiveNegotiations extends CardImpl { + + public AggressiveNegotiations(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // Target opponent reveals their hand. You choose a nonland card from it. That player exiles that card. + Effect effect1 = new ExileCardYouChooseTargetOpponentEffect(StaticFilters.FILTER_CARD_A_NON_LAND); + this.getSpellAbility().addEffect(effect1); + this.getSpellAbility().addTarget(new TargetPlayer()); + + // Put a +1/+1 counter on target creature you control. + this.getSpellAbility().addEffect(new AddCountersTargetEffect( + CounterType.P1P1.createInstance() + ).setTargetPointer(new SecondTargetPointer())); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(0, 1)); + } + + private AggressiveNegotiations(final AggressiveNegotiations card) { + super(card); + } + + @Override + public AggressiveNegotiations copy() { + return new AggressiveNegotiations(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java index 3789ce55f66..7253374c3df 100644 --- a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java +++ b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java @@ -90,7 +90,7 @@ class AgrusKosEternalSoldierTriggeredAbility extends TriggeredAbilityImpl { if (!event.getTargetId().equals(getSourceId())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || targetingObject instanceof Spell) { return false; } diff --git a/Mage.Sets/src/mage/cards/a/AladdinsLamp.java b/Mage.Sets/src/mage/cards/a/AladdinsLamp.java index 44614e7ec56..d2f261f0ca9 100644 --- a/Mage.Sets/src/mage/cards/a/AladdinsLamp.java +++ b/Mage.Sets/src/mage/cards/a/AladdinsLamp.java @@ -3,9 +3,9 @@ package mage.cards.a; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.CostAdjuster; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.costs.mana.VariableManaCost; import mage.abilities.effects.ReplacementEffectImpl; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -36,12 +36,8 @@ public final class AladdinsLamp extends CardImpl { // {X}, {T}: The next time you would draw a card this turn, instead look at the top X cards of your library, put all but one of them on the bottom of your library in a random order, then draw a card. X can't be 0. Ability ability = new SimpleActivatedAbility(new AladdinsLampEffect(), new ManaCostsImpl<>("{X}")); ability.addCost(new TapSourceCost()); - for (Object cost : ability.getManaCosts()) { - if (cost instanceof VariableManaCost) { - ((VariableManaCost) cost).setMinX(1); - break; - } - } + ability.setCostAdjuster(AladdinsLampCostAdjuster.instance); + this.addAbility(ability); } @@ -101,3 +97,17 @@ class AladdinsLampEffect extends ReplacementEffectImpl { return source.isControlledBy(event.getPlayerId()); } } + +enum AladdinsLampCostAdjuster implements CostAdjuster { + instance; + + @Override + public void prepareX(Ability ability, Game game) { + Player controller = game.getPlayer(ability.getControllerId()); + if (controller == null) { + return; + } + + ability.setVariableCostsMinMax(1, Math.max(1, controller.getLibrary().size())); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AlandraSkyDreamer.java b/Mage.Sets/src/mage/cards/a/AlandraSkyDreamer.java index 520be3501a2..a78555c9360 100644 --- a/Mage.Sets/src/mage/cards/a/AlandraSkyDreamer.java +++ b/Mage.Sets/src/mage/cards/a/AlandraSkyDreamer.java @@ -47,8 +47,8 @@ public final class AlandraSkyDreamer extends CardImpl { // Whenever you draw your fifth card each turn, Alandra, Sky Dreamer and Drakes you control each get +X/+X until end of turn, where X is the number of cards in your hand. DrawNthCardTriggeredAbility drawNthCardTriggeredAbility = new DrawNthCardTriggeredAbility( new BoostSourceEffect( - CardsInControllerHandCount.instance, - CardsInControllerHandCount.instance, + CardsInControllerHandCount.ANY, + CardsInControllerHandCount.ANY, Duration.EndOfTurn ).setText("{this}"), false, @@ -56,8 +56,8 @@ public final class AlandraSkyDreamer extends CardImpl { ); drawNthCardTriggeredAbility.addEffect( new BoostControlledEffect( - CardsInControllerHandCount.instance, - CardsInControllerHandCount.instance, + CardsInControllerHandCount.ANY, + CardsInControllerHandCount.ANY, Duration.EndOfTurn, filter, false diff --git a/Mage.Sets/src/mage/cards/a/AlignedHeart.java b/Mage.Sets/src/mage/cards/a/AlignedHeart.java new file mode 100644 index 00000000000..f031fd1ead0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AlignedHeart.java @@ -0,0 +1,42 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.FlurryAbility; +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.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.game.permanent.token.MonasteryMentorToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AlignedHeart extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(CounterType.RALLY); + + public AlignedHeart(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + // Flurry -- Whenever you cast your second spell each turn, put a rally counter on this enchantment. Then create a 1/1 white Monk creature token with prowess for each rally counter on it. + Ability ability = new FlurryAbility(new AddCountersSourceEffect(CounterType.RALLY.createInstance())); + ability.addEffect(new CreateTokenEffect(new MonasteryMentorToken(), xValue) + .setText("then create a 1/1 white Monk creature token with prowess for each rally counter on it")); + this.addAbility(ability); + } + + private AlignedHeart(final AlignedHeart card) { + super(card); + } + + @Override + public AlignedHeart copy() { + return new AlignedHeart(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AncientExcavation.java b/Mage.Sets/src/mage/cards/a/AncientExcavation.java index 9feb0e5b44a..b8f990713ab 100644 --- a/Mage.Sets/src/mage/cards/a/AncientExcavation.java +++ b/Mage.Sets/src/mage/cards/a/AncientExcavation.java @@ -61,7 +61,7 @@ class AncientExcavationEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - DynamicValue numCards = CardsInControllerHandCount.instance; + DynamicValue numCards = CardsInControllerHandCount.ANY; int amount = numCards.calculate(game, source, this); int cardsDrawn = player.drawCards(amount, source, game); player.discard(cardsDrawn, false, false, source, game); diff --git a/Mage.Sets/src/mage/cards/a/ArborAdherent.java b/Mage.Sets/src/mage/cards/a/ArborAdherent.java new file mode 100644 index 00000000000..686eb403b1b --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArborAdherent.java @@ -0,0 +1,48 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.common.GreatestToughnessAmongControlledCreaturesValue; +import mage.abilities.mana.AnyColorManaAbility; +import mage.abilities.mana.DynamicManaAbility; +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 ArborAdherent extends CardImpl { + + public ArborAdherent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.DOG); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + + // {T}: Add X mana of any one color, where X is the greatest toughness among other creatures you control. + this.addAbility(new DynamicManaAbility( + Mana.AnyMana(1), GreatestToughnessAmongControlledCreaturesValue.OTHER, + new TapSourceCost(), "add X mana of any one color, where X is the " + + "greatest toughness among other creatures you control", true + ).addHint(GreatestToughnessAmongControlledCreaturesValue.OTHER.getHint())); + } + + private ArborAdherent(final ArborAdherent card) { + super(card); + } + + @Override + public ArborAdherent copy() { + return new ArborAdherent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/Archivist.java b/Mage.Sets/src/mage/cards/a/Archivist.java index b0cde1b880f..688acbb500c 100644 --- a/Mage.Sets/src/mage/cards/a/Archivist.java +++ b/Mage.Sets/src/mage/cards/a/Archivist.java @@ -1,4 +1,3 @@ - package mage.cards.a; import java.util.UUID; @@ -26,7 +25,7 @@ public final class Archivist extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(1); - //{T}: Draw a card. + // {T}: Draw a card. this.addAbility(new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new TapSourceCost())); } diff --git a/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java b/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java index 1ee3a83718c..ca6455ad195 100644 --- a/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java +++ b/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java @@ -71,7 +71,7 @@ enum ArmMountedAnchorAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { // checking state if (HeckbentCondition.instance.apply(game, ability)) { CardUtil.reduceCost(ability, 2); diff --git a/Mage.Sets/src/mage/cards/a/AshlingsPrerogative.java b/Mage.Sets/src/mage/cards/a/AshlingsPrerogative.java index 84d3a3c59a1..56e862bef7e 100644 --- a/Mage.Sets/src/mage/cards/a/AshlingsPrerogative.java +++ b/Mage.Sets/src/mage/cards/a/AshlingsPrerogative.java @@ -1,11 +1,8 @@ package mage.cards.a; -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.PermanentsEnterBattlefieldTappedEffect; import mage.abilities.effects.common.continuous.GainAbilityAllEffect; @@ -14,31 +11,44 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Zone; +import mage.constants.ModeChoice; +import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; -import mage.game.events.EntersTheBattlefieldEvent; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author Eirkei */ public final class AshlingsPrerogative extends CardImpl { + private static final FilterPermanent filterMatch + = new FilterCreaturePermanent("each creature with mana value of the chosen quality"); + private static final FilterPermanent filterNotMatch + = new FilterCreaturePermanent("each creature without mana value of the chosen quality"); + + static { + filterMatch.add(AshlingsPrerogativePredicate.WITH); + filterNotMatch.add(AshlingsPrerogativePredicate.WITHOUT); + } + public AshlingsPrerogative(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); // As Ashling's Prerogative enters the battlefield, choose odd or even. - this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Odd or even?", "Odd", "Even"), null, "As {this} enters, choose odd or even. (Zero is even.)", "")); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.ODD, ModeChoice.EVEN))); // Each creature with converted mana cost of the chosen value has haste. - this.addAbility(new SimpleStaticAbility(new AshlingsPrerogativeCorrectOddityEffect())); + this.addAbility(new SimpleStaticAbility(new GainAbilityAllEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, filterMatch + ))); // Each creature without converted mana cost of the chosen value enters the battlefield tapped. - this.addAbility(new SimpleStaticAbility(new AshlingsPrerogativeIncorrectOddityEffect())); - + this.addAbility(new SimpleStaticAbility(new PermanentsEnterBattlefieldTappedEffect(filterNotMatch))); } private AshlingsPrerogative(final AshlingsPrerogative card) { @@ -51,67 +61,23 @@ public final class AshlingsPrerogative extends CardImpl { } } -class AshlingsPrerogativeIncorrectOddityEffect extends PermanentsEnterBattlefieldTappedEffect { +enum AshlingsPrerogativePredicate implements ObjectSourcePlayerPredicate { + WITH(true), + WITHOUT(false); + private final boolean match; - private static final FilterCreaturePermanent creaturefilter = new FilterCreaturePermanent("Each creature without mana value of the chosen quality"); - private static final ModeChoiceSourceCondition oddCondition = new ModeChoiceSourceCondition("Odd"); - - public AshlingsPrerogativeIncorrectOddityEffect() { - super(creaturefilter); - staticText = "Each creature without mana value of the chosen quality enters the battlefield tapped."; - } - - private AshlingsPrerogativeIncorrectOddityEffect(final AshlingsPrerogativeIncorrectOddityEffect effect) { - super(effect); + AshlingsPrerogativePredicate(boolean match) { + this.match = match; } @Override - public boolean applies(GameEvent event, Ability source, Game game) { - int incorrectModResult; - - if (oddCondition.apply(game, source)) { - incorrectModResult = 0; + public boolean apply(ObjectSourcePlayer input, Game game) { + if (ModeChoice.ODD.checkMode(game, input.getSource())) { + return (input.getObject().getManaValue() % 2 == 1) == match; + } else if (ModeChoice.EVEN.checkMode(game, input.getSource())) { + return (input.getObject().getManaValue() % 2 == 0) == match; } else { - incorrectModResult = 1; + return false; } - - Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget(); - - return permanent != null && creaturefilter.match(permanent, game) && permanent.getManaValue() % 2 == incorrectModResult; - } - - @Override - public AshlingsPrerogativeIncorrectOddityEffect copy() { - return new AshlingsPrerogativeIncorrectOddityEffect(this); - } -} - -class AshlingsPrerogativeCorrectOddityEffect extends GainAbilityAllEffect { - - private static final FilterCreaturePermanent creaturefilter = new FilterCreaturePermanent("Each creature with mana value of the chosen quality"); - private static final ModeChoiceSourceCondition oddCondition = new ModeChoiceSourceCondition("Odd"); - - public AshlingsPrerogativeCorrectOddityEffect() { - super(HasteAbility.getInstance(), Duration.WhileOnBattlefield, creaturefilter); - staticText = "Each creature with mana value of the chosen quality has haste."; - } - private AshlingsPrerogativeCorrectOddityEffect(final AshlingsPrerogativeCorrectOddityEffect effect) { - super(effect); - } - - @Override - protected boolean selectedByRuntimeData(Permanent permanent, Ability source, Game game) { - int correctModResult; - if (oddCondition.apply(game, source)) { - correctModResult = 1; - } else { - correctModResult = 0; - } - return permanent != null && creaturefilter.match(permanent, game) && permanent.getManaValue() % 2 == correctModResult; - } - - @Override - public AshlingsPrerogativeCorrectOddityEffect copy() { - return new AshlingsPrerogativeCorrectOddityEffect(this); } } diff --git a/Mage.Sets/src/mage/cards/b/BaldinCenturyHerdmaster.java b/Mage.Sets/src/mage/cards/b/BaldinCenturyHerdmaster.java index 0a545ef0130..544aac236fa 100644 --- a/Mage.Sets/src/mage/cards/b/BaldinCenturyHerdmaster.java +++ b/Mage.Sets/src/mage/cards/b/BaldinCenturyHerdmaster.java @@ -43,7 +43,7 @@ public final class BaldinCenturyHerdmaster extends CardImpl { // Whenever Baldin, Century Herdmaster attacks, up to one hundred target creatures each get +0/+X until end of turn, where X is the number of cards in your hand. Ability ability = new AttacksTriggeredAbility(new BoostTargetEffect( - StaticValue.get(0), CardsInControllerHandCount.instance, Duration.EndOfTurn + StaticValue.get(0), CardsInControllerHandCount.ANY, Duration.EndOfTurn ).setText("up to one hundred target creatures each get +0/+X until end of turn, where X is the number of cards in your hand")); ability.addTarget(new TargetCreaturePermanent(0, 100)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/b/BanditsTalent.java b/Mage.Sets/src/mage/cards/b/BanditsTalent.java index 49d95f90602..f9e840fbc0c 100644 --- a/Mage.Sets/src/mage/cards/b/BanditsTalent.java +++ b/Mage.Sets/src/mage/cards/b/BanditsTalent.java @@ -144,10 +144,9 @@ class BanditsTalentDiscardEffect extends OneShotEffect { } } - enum BanditsTalentValue implements DynamicValue { instance; - private static final Hint hint = new ValueHint("opponents who have one or fewer cards in hand", instance); + private static final Hint hint = new ValueHint("Opponents who have one or fewer cards in hand", instance); public static Hint getHint() { return hint; diff --git a/Mage.Sets/src/mage/cards/b/BargainingTable.java b/Mage.Sets/src/mage/cards/b/BargainingTable.java index 3f280d7f552..62a928c6e58 100644 --- a/Mage.Sets/src/mage/cards/b/BargainingTable.java +++ b/Mage.Sets/src/mage/cards/b/BargainingTable.java @@ -1,25 +1,28 @@ package mage.cards.b; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.EarlyTargetCost; +import mage.abilities.costs.VariableCostType; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.costs.mana.VariableManaCost; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.InfoEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; +import mage.target.Target; import mage.target.common.TargetOpponent; -import mage.util.CardUtil; + +import java.util.Objects; +import java.util.UUID; /** - * - * @author awjackson + * @author awjackson, JayDi85 */ public final class BargainingTable extends CardImpl { @@ -27,13 +30,10 @@ public final class BargainingTable extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); // {X}, {T}: Draw a card. X is the number of cards in an opponent's hand. - Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{X}")); + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new BargainingTableXCost()); ability.addCost(new TapSourceCost()); ability.addEffect(new InfoEffect("X is the number of cards in an opponent's hand")); - // You choose an opponent on announcement. This is not targeted, but a choice is still made. - // This choice is made before determining the value for X that is used in the cost. (2004-10-04) - ability.addTarget(new TargetOpponent(true)); - ability.setCostAdjuster(BargainingTableAdjuster.instance); + ability.setCostAdjuster(BargainingTableCostAdjuster.instance); this.addAbility(ability); } @@ -47,26 +47,70 @@ public final class BargainingTable extends CardImpl { } } -enum BargainingTableAdjuster implements CostAdjuster { +class BargainingTableXCost extends VariableManaCost implements EarlyTargetCost { + + // You choose an opponent on announcement. This is not targeted, but a choice is still made. + // This choice is made before determining the value for X that is used in the cost. + // (2004-10-04) + + public BargainingTableXCost() { + super(VariableCostType.NORMAL, 1); + } + + public BargainingTableXCost(final BargainingTableXCost cost) { + super(cost); + } + + @Override + public void chooseTarget(Game game, Ability source, Player controller) { + Target targetOpponent = new TargetOpponent(true); + controller.choose(Outcome.Benefit, targetOpponent, source, game); + addTarget(targetOpponent); + } + + @Override + public BargainingTableXCost copy() { + return new BargainingTableXCost(this); + } +} + +enum BargainingTableCostAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { - int handSize = Integer.MAX_VALUE; - if (game.inCheckPlayableState()) { - for (UUID playerId : CardUtil.getAllPossibleTargets(ability, game)) { - Player player = game.getPlayer(playerId); - if (player != null) { - handSize = Math.min(handSize, player.getHand().size()); - } - } - } else { - Player player = game.getPlayer(ability.getFirstTarget()); - if (player != null) { - handSize = player.getHand().size(); - } + public void prepareX(Ability ability, Game game) { + // make sure early target used + BargainingTableXCost cost = ability.getManaCostsToPay().getVariableCosts().stream() + .filter(c -> c instanceof BargainingTableXCost) + .map(c -> (BargainingTableXCost) c) + .findFirst() + .orElse(null); + if (cost == null) { + throw new IllegalArgumentException("Wrong code usage: cost item lost"); + } + + if (game.inCheckPlayableState()) { + // possible X + int minHandSize = game.getOpponents(ability.getControllerId(), true).stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .mapToInt(p -> p.getHand().size()) + .min() + .orElse(0); + int maxHandSize = game.getOpponents(ability.getControllerId(), true).stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .mapToInt(p -> p.getHand().size()) + .max() + .orElse(Integer.MAX_VALUE); + ability.setVariableCostsMinMax(minHandSize, maxHandSize); + } else { + // real X + Player opponent = game.getPlayer(cost.getTargets().getFirstTarget()); + if (opponent == null) { + throw new IllegalStateException("Wrong code usage: cost target lost"); + } + ability.setVariableCostsValue(opponent.getHand().size()); } - ability.clearManaCostsToPay(); - ability.addManaCostsToPay(new GenericManaCost(handSize)); } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BarrensteppeSiege.java b/Mage.Sets/src/mage/cards/b/BarrensteppeSiege.java index 5bac6c137fd..b4370e0ab64 100644 --- a/Mage.Sets/src/mage/cards/b/BarrensteppeSiege.java +++ b/Mage.Sets/src/mage/cards/b/BarrensteppeSiege.java @@ -1,17 +1,17 @@ package mage.cards.b; -import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.condition.Condition; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.CreatureDiedControlledCondition; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.SacrificeOpponentsEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; import mage.abilities.effects.common.counter.AddCountersAllEffect; import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ModeChoice; import mage.counters.CounterType; import mage.filter.StaticFilters; @@ -22,35 +22,25 @@ import java.util.UUID; */ public final class BarrensteppeSiege extends CardImpl { - private static final Condition condition1 = new ModeChoiceSourceCondition("Abzan"); - private static final String rule1 = "&bull Abzan — At the beginning of your end step, " + - "put a +1/+1 counter on each creature you control."; - private static final Condition condition2 = new ModeChoiceSourceCondition("Mardu"); - private static final String rule2 = "&bull Mardu — At the beginning of your end step, " + - "if a creature died under your control this turn, each opponent sacrifices a creature of their choice."; - public BarrensteppeSiege(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}{B}"); // As this enchantment enters, choose Abzan or Mardu. - this.addAbility(new EntersBattlefieldAbility( - new ChooseModeEffect("Abzan or Mardu?", "Abzan", "Mardu"), - null, "As {this} enters, choose Abzan or Mardu.", "" - )); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.ABZAN, ModeChoice.MARDU))); // * Abzan -- At the beginning of your end step, put a +1/+1 counter on each creature you control. - this.addAbility(new ConditionalTriggeredAbility( + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( new BeginningOfEndStepTriggeredAbility(new AddCountersAllEffect( CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE - )), condition1, rule1 - )); + )), ModeChoice.ABZAN + ))); // * Mardu -- At the beginning of your end step, if a creature died under your control this turn, each opponent sacrifices a creature of their choice. - this.addAbility(new ConditionalTriggeredAbility( + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( new BeginningOfEndStepTriggeredAbility( new SacrificeOpponentsEffect(StaticFilters.FILTER_PERMANENT_CREATURE) - ).withInterveningIf(CreatureDiedControlledCondition.instance), condition2, rule2 - )); + ).withInterveningIf(CreatureDiedControlledCondition.instance), ModeChoice.MARDU + ))); } private BarrensteppeSiege(final BarrensteppeSiege card) { diff --git a/Mage.Sets/src/mage/cards/b/BaruWurmspeaker.java b/Mage.Sets/src/mage/cards/b/BaruWurmspeaker.java index d87af6d364b..c76fb6d540b 100644 --- a/Mage.Sets/src/mage/cards/b/BaruWurmspeaker.java +++ b/Mage.Sets/src/mage/cards/b/BaruWurmspeaker.java @@ -19,10 +19,7 @@ import mage.abilities.hint.ValueHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.SubType; -import mage.constants.SuperType; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; @@ -116,7 +113,7 @@ enum BaruWurmspeakerAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { int value = BaruWurmspeakerValue.instance.calculate(game, ability, null); if (value > 0) { CardUtil.reduceCost(ability, value); diff --git a/Mage.Sets/src/mage/cards/b/BattleOfHooverDam.java b/Mage.Sets/src/mage/cards/b/BattleOfHooverDam.java index fa2a607a372..b8e39e72ee1 100644 --- a/Mage.Sets/src/mage/cards/b/BattleOfHooverDam.java +++ b/Mage.Sets/src/mage/cards/b/BattleOfHooverDam.java @@ -1,33 +1,35 @@ package mage.cards.b; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.common.AsEntersBattlefieldAbility; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.DiesCreatureTriggeredAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ComparisonType; +import mage.constants.ModeChoice; import mage.counters.CounterType; +import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetControlledCreaturePermanent; +import java.util.UUID; + /** * @author Cguy7777 */ public final class BattleOfHooverDam extends CardImpl { - private static final FilterCreatureCard filter = new FilterCreatureCard(); + private static final FilterCard filter = new FilterCreatureCard(); static { filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); @@ -37,32 +39,23 @@ public final class BattleOfHooverDam extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); // As Battle of Hoover Dam enters the battlefield, choose NCR or Legion. - this.addAbility(new AsEntersBattlefieldAbility( - new ChooseModeEffect("NCR or Legion?", "NCR", "Legion"))); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.NCR, ModeChoice.LEGION))); // * NCR -- At the beginning of your end step, return target creature card with mana value 3 or less // from your graveyard to the battlefield with a finality counter on it. - Ability ncrAbility = new ConditionalTriggeredAbility( - new BeginningOfEndStepTriggeredAbility( - new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.FINALITY.createInstance()) - ), - new ModeChoiceSourceCondition("NCR"), - "&bull NCR — At the beginning of your end step, return target creature card with " + - "mana value 3 or less from your graveyard to the battlefield with a finality counter on it."); - ncrAbility.addTarget(new TargetCardInYourGraveyard(filter)); - this.addAbility(ncrAbility); + Ability ability = new BeginningOfEndStepTriggeredAbility( + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.FINALITY.createInstance()) + ); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability, ModeChoice.NCR))); // * Legion -- Whenever a creature you control dies, put two +1/+1 counters on target creature you control. - Ability legionAbility = new ConditionalTriggeredAbility( - new DiesCreatureTriggeredAbility( - new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)), - false, - StaticFilters.FILTER_CONTROLLED_A_CREATURE), - new ModeChoiceSourceCondition("Legion"), - "&bull Legion — Whenever a creature you control dies, " + - "put two +1/+1 counters on target creature you control."); - legionAbility.addTarget(new TargetControlledCreaturePermanent()); - this.addAbility(legionAbility); + ability = new DiesCreatureTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)), + false, StaticFilters.FILTER_CONTROLLED_A_CREATURE + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability, ModeChoice.LEGION))); } private BattleOfHooverDam(final BattleOfHooverDam card) { diff --git a/Mage.Sets/src/mage/cards/b/BattlefieldButcher.java b/Mage.Sets/src/mage/cards/b/BattlefieldButcher.java index ab752a8e2cb..6251116133f 100644 --- a/Mage.Sets/src/mage/cards/b/BattlefieldButcher.java +++ b/Mage.Sets/src/mage/cards/b/BattlefieldButcher.java @@ -58,7 +58,7 @@ enum BattlefieldButcherAdjuster implements CostAdjuster { private static final Hint hint = new ValueHint("Creature cards in your graveyard", xValue); @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { CardUtil.reduceCost(ability, xValue.calculate(game, ability, null)); } diff --git a/Mage.Sets/src/mage/cards/b/BecomeTheAvalanche.java b/Mage.Sets/src/mage/cards/b/BecomeTheAvalanche.java new file mode 100644 index 00000000000..b8c2d218264 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BecomeTheAvalanche.java @@ -0,0 +1,55 @@ +package mage.cards.b; + +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Duration; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BecomeTheAvalanche extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("creature you control with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private static final Hint hint = new ValueHint("Creatures you control with power 4 or greater", xValue); + + public BecomeTheAvalanche(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}{G}"); + + // Draw a card for each creature you control with power 4 or greater. Then creatures you control get +X/+X until end of turn, where X is the number of cards in your hand. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(xValue)); + this.getSpellAbility().addEffect(new BoostControlledEffect( + CardsInControllerHandCount.ANY, CardsInControllerHandCount.ANY, Duration.EndOfTurn + )); + this.getSpellAbility().addHint(hint); + } + + private BecomeTheAvalanche(final BecomeTheAvalanche card) { + super(card); + } + + @Override + public BecomeTheAvalanche copy() { + return new BecomeTheAvalanche(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BeltOfGiantStrength.java b/Mage.Sets/src/mage/cards/b/BeltOfGiantStrength.java index cd9693c66f6..40acc7e97f6 100644 --- a/Mage.Sets/src/mage/cards/b/BeltOfGiantStrength.java +++ b/Mage.Sets/src/mage/cards/b/BeltOfGiantStrength.java @@ -3,20 +3,21 @@ package mage.cards.b; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; import mage.abilities.keyword.EquipAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Outcome; import mage.constants.SubType; import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; import mage.util.CardUtil; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; -import mage.abilities.costs.mana.GenericManaCost; -import mage.constants.Outcome; -import mage.target.common.TargetControlledCreaturePermanent; /** * @author TheElk801 @@ -35,7 +36,7 @@ public final class BeltOfGiantStrength extends CardImpl { // Equip {10}. This ability costs {X} less to activate where X is the power of the creature it targets. EquipAbility ability = new EquipAbility(Outcome.BoostCreature, new GenericManaCost(10), new TargetControlledCreaturePermanent(), false); ability.setCostReduceText("This ability costs {X} less to activate, where X is the power of the creature it targets."); - ability.setCostAdjuster(BeltOfGiantStrengthAdjuster.instance); + ability.setCostAdjuster(BeltOfGiantStrengthCostAdjuster.instance); this.addAbility(ability); } @@ -49,33 +50,23 @@ public final class BeltOfGiantStrength extends CardImpl { } } -enum BeltOfGiantStrengthAdjuster implements CostAdjuster { +enum BeltOfGiantStrengthCostAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { + int power; if (game.inCheckPlayableState()) { - int maxPower = 0; - for (UUID permId : CardUtil.getAllPossibleTargets(ability, game)) { - Permanent permanent = game.getPermanent(permId); - if (permanent != null) { - int power = permanent.getPower().getValue(); - if (power > maxPower) { - maxPower = power; - } - } - } - if (maxPower > 0) { - CardUtil.reduceCost(ability, maxPower); - } + power = CardUtil.getAllPossibleTargets(ability, game).stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .mapToInt(p -> p.getPower().getValue()) + .max().orElse(0); } else { - Permanent permanent = game.getPermanent(ability.getFirstTarget()); - if (permanent != null) { - int power = permanent.getPower().getValue(); - if (power > 0) { - CardUtil.reduceCost(ability, power); - } - } + power = Optional.ofNullable(game.getPermanent(ability.getFirstTarget())) + .map(p -> p.getPower().getValue()) + .orElse(0); } + CardUtil.reduceCost(ability, power); } } diff --git a/Mage.Sets/src/mage/cards/b/BetorKinToAll.java b/Mage.Sets/src/mage/cards/b/BetorKinToAll.java new file mode 100644 index 00000000000..923f3db7a30 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BetorKinToAll.java @@ -0,0 +1,165 @@ +package mage.cards.b; + +import java.util.UUID; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author androosss + */ +public final class BetorKinToAll extends CardImpl { + + private static final Hint hint = new ValueHint( + "Total toughness of creatures you control", ControlledCreaturesToughnessValue.instance); + + public BetorKinToAll(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.CREATURE }, "{2}{W}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(5); + this.toughness = new MageInt(7); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // At the beginning of your end step, if creatures you control have total + // toughness 10 or greater, draw a card. Then if creatures you control have + // total toughness 20 or greater, untap each creature you control. Then if + // creatures you control have total toughness 40 or greater, each opponent loses + // half their life, rounded up. + Ability betorAbility = new BeginningOfEndStepTriggeredAbility(new DrawCardSourceControllerEffect(1)) + .withInterveningIf(BetorKinToAllCondition.instance).addHint(hint); + betorAbility.addEffect(new BetorKinToAllEffect()); + this.addAbility(betorAbility); + } + + private BetorKinToAll(final BetorKinToAll card) { + super(card); + } + + @Override + public BetorKinToAll copy() { + return new BetorKinToAll(this); + } +} + +enum BetorKinToAllCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return ControlledCreaturesToughnessValue.instance.calculate(game, source, null) >= 10; + } + +} + +class BetorKinToAllEffect extends OneShotEffect { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + BetorKinToAllEffect() { + super(Outcome.Benefit); + this.staticText = "Then if creatures you control have total toughness 20 or greater, untap each creature you control. Then if creatures you control have total toughness 40 or greater, each opponent loses half their life, rounded up."; + } + + private BetorKinToAllEffect(final BetorKinToAllEffect effect) { + super(effect); + } + + @Override + public BetorKinToAllEffect copy() { + return new BetorKinToAllEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + int sumToughness = ControlledCreaturesToughnessValue.instance.calculate(game, source, null); + + if (sumToughness < 20) { + return true; + } + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), + game)) { + permanent.untap(game); + } + + if (sumToughness < 40) { + return true; + } + + for (UUID playerId : game.getOpponents(controller.getId())) { + Player opponent = game.getPlayer(playerId); + if (opponent == null) { + continue; + } + int amount = (int) Math.ceil(opponent.getLife() / 2f); + if (amount > 0) { + opponent.loseLife(amount, game, source, false); + } + } + + return true; + } +} + +enum ControlledCreaturesToughnessValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + sourceAbility.getControllerId(), sourceAbility, game) + .stream() + .map(MageObject::getToughness) + .mapToInt(MageInt::getValue) + .sum(); + } + + @Override + public ControlledCreaturesToughnessValue copy() { + return this; + } + + @Override + public String getMessage() { + return "total toughness of creatures you control"; + } + + @Override + public String toString() { + return "X"; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BighornerRancher.java b/Mage.Sets/src/mage/cards/b/BighornerRancher.java index 7532b135ed4..e5967f1599f 100644 --- a/Mage.Sets/src/mage/cards/b/BighornerRancher.java +++ b/Mage.Sets/src/mage/cards/b/BighornerRancher.java @@ -14,7 +14,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import java.util.UUID; @@ -42,8 +41,10 @@ public final class BighornerRancher extends CardImpl { // Sacrifice Bighorner Rancher: You gain life equal to the greatest toughness among other creatures you control. this.addAbility(new SimpleActivatedAbility( - new GainLifeEffect(GreatestToughnessAmongControlledCreaturesValue.instance).setText("You gain life equal to the greatest toughness among other creatures you control."), - new SacrificeSourceCost())); + new GainLifeEffect(GreatestToughnessAmongControlledCreaturesValue.OTHER) + .setText("You gain life equal to the greatest toughness among other creatures you control."), + new SacrificeSourceCost() + ).addHint(GreatestToughnessAmongControlledCreaturesValue.OTHER.getHint())); } private BighornerRancher(final BighornerRancher card) { diff --git a/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java b/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java index 98e460eadb2..b705cba0318 100644 --- a/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java +++ b/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java @@ -60,7 +60,7 @@ enum BiteDownOnCrimeAdjuster implements CostAdjuster { private static final OptionalAdditionalCost collectEvidenceCost = CollectEvidenceAbility.makeCost(6); @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (CollectedEvidenceCondition.instance.apply(game, ability) || (game.inCheckPlayableState() && collectEvidenceCost.canPay(ability, null, ability.getControllerId(), game))) { CardUtil.reduceCost(ability, 2); diff --git a/Mage.Sets/src/mage/cards/b/BloomvineRegent.java b/Mage.Sets/src/mage/cards/b/BloomvineRegent.java new file mode 100644 index 00000000000..88f7e8cb72d --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BloomvineRegent.java @@ -0,0 +1,68 @@ +package mage.cards.b; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.search.SearchLibraryPutOntoBattlefieldTappedRestInHandEffect; +import mage.cards.OmenCard; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.Target; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author Jmlundeen + */ +public final class BloomvineRegent extends OmenCard { + + private static final FilterCard filter = new FilterCard("basic Forest cards"); + + static { + filter.add(SubType.FOREST.getPredicate()); + filter.add(SuperType.BASIC.getPredicate()); + } + + public BloomvineRegent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{3}{G}{G}", "Claim Territory", "{2}{G}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever this creature or another Dragon you control enters, you gain 3 life. + this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility( + new GainLifeEffect(3), + new FilterCreaturePermanent(SubType.DRAGON, "Dragon"), + false, + true + )); + + // Claim Territory + // Search your library for up to two basic Forest cards, reveal them, put one onto the battlefield tapped and the other into your hand, then shuffle. (Also shuffle this card.) + TargetCardInLibrary target = new TargetCardInLibrary(0, 2, filter); + this.getSpellCard().getSpellAbility().addEffect(new SearchLibraryPutOntoBattlefieldTappedRestInHandEffect(target, 1)); + this.finalizeOmen(); + } + + private BloomvineRegent(final BloomvineRegent card) { + super(card); + } + + @Override + public BloomvineRegent copy() { + return new BloomvineRegent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BodyOfKnowledge.java b/Mage.Sets/src/mage/cards/b/BodyOfKnowledge.java index 675a461e032..34e37cfb6b8 100644 --- a/Mage.Sets/src/mage/cards/b/BodyOfKnowledge.java +++ b/Mage.Sets/src/mage/cards/b/BodyOfKnowledge.java @@ -33,7 +33,7 @@ public final class BodyOfKnowledge extends CardImpl { this.addAbility(new SimpleStaticAbility( Zone.ALL, new SetBasePowerToughnessSourceEffect( - CardsInControllerHandCount.instance + CardsInControllerHandCount.ANY ) )); diff --git a/Mage.Sets/src/mage/cards/b/BoneDevourer.java b/Mage.Sets/src/mage/cards/b/BoneDevourer.java new file mode 100644 index 00000000000..79921a60ede --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BoneDevourer.java @@ -0,0 +1,63 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.dynamicvalue.common.CreaturesDiedThisTurnCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.CreaturesDiedThisTurnHint; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BoneDevourer extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(null); + + public BoneDevourer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // This creature enters with a number of +1/+1 counters on it equal to the number of creatures that died this turn. + this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect( + CounterType.P1P1.createInstance(), CreaturesDiedThisTurnCount.instance, false + ), "with a number of +1/+1 counters on it equal to the number of creatures that died this turn").addHint(CreaturesDiedThisTurnHint.instance)); + + // When this creature dies, you draw X cards and you lose X life, where X is the number of +1/+1 counters on it. + Ability ability = new DiesSourceTriggeredAbility(new DrawCardSourceControllerEffect(xValue).setText("you draw X cards")); + ability.addEffect(new LoseLifeSourceControllerEffect(xValue)); + this.addAbility(ability); + } + + private BoneDevourer(final BoneDevourer card) { + super(card); + } + + @Override + public BoneDevourer copy() { + return new BoneDevourer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BrightcapBadger.java b/Mage.Sets/src/mage/cards/b/BrightcapBadger.java index 34e8561d800..14d1b89aee0 100644 --- a/Mage.Sets/src/mage/cards/b/BrightcapBadger.java +++ b/Mage.Sets/src/mage/cards/b/BrightcapBadger.java @@ -52,7 +52,7 @@ public final class BrightcapBadger extends AdventureCard { // Fungus Frolic // Create two 1/1 green Saproling creature tokens. this.getSpellCard().getSpellAbility().addEffect(new CreateTokenEffect(new SaprolingToken(), 2)); - this.getSpellCard().finalizeAdventure(); + this.finalizeAdventure(); } private BrightcapBadger(final BrightcapBadger card) { diff --git a/Mage.Sets/src/mage/cards/b/BroodcallerScourge.java b/Mage.Sets/src/mage/cards/b/BroodcallerScourge.java new file mode 100644 index 00000000000..4a8145bbece --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BroodcallerScourge.java @@ -0,0 +1,76 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.OneOrMoreDamagePlayerTriggeredAbility; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BroodcallerScourge extends CardImpl { + + private static final FilterCard filter + = new FilterPermanentCard("a permanent card with mana value less than or equal to that damage"); + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent(SubType.DRAGON, "Dragons"); + + static { + filter.add(BroodcallerScourgePredicate.instance); + } + + public BroodcallerScourge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(5); + this.toughness = new MageInt(7); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever one or more Dragons you control deal combat damage to a player, you may put a permanent card with mana value less than or equal to that damage from your hand onto the battlefield. + this.addAbility(new OneOrMoreDamagePlayerTriggeredAbility( + new PutCardFromHandOntoBattlefieldEffect(filter), + filter2, true, true + )); + } + + private BroodcallerScourge(final BroodcallerScourge card) { + super(card); + } + + @Override + public BroodcallerScourge copy() { + return new BroodcallerScourge(this); + } +} + +enum BroodcallerScourgePredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input + .getObject() + .getManaValue() + <= CardUtil + .getEffectValueFromAbility( + input.getSource(), "damage", + Integer.class, 0 + ); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java b/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java index a486ea2e0b7..6b2043d66e7 100644 --- a/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java +++ b/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java @@ -59,7 +59,7 @@ enum CallerOfTheHuntAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void prepareCost(Ability ability, Game game) { if (game.inCheckPlayableState()) { return; } @@ -103,6 +103,7 @@ enum CallerOfTheHuntAdjuster implements CostAdjuster { game.getState().setValue(sourceObject.getId() + "_type", maxSubType); } else { // human choose + // TODO: need early target cost instead dialog here Effect effect = new ChooseCreatureTypeEffect(Outcome.Benefit); effect.apply(game, ability); } diff --git a/Mage.Sets/src/mage/cards/c/CanopyGargantuan.java b/Mage.Sets/src/mage/cards/c/CanopyGargantuan.java new file mode 100644 index 00000000000..c7763975ded --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CanopyGargantuan.java @@ -0,0 +1,83 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WardAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CanopyGargantuan extends CardImpl { + + public CanopyGargantuan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // At the beginning of your upkeep, put a number of +1/+1 counters on each other creature you control equal to that creature's toughness. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new CanopyGargantuanEffect())); + } + + private CanopyGargantuan(final CanopyGargantuan card) { + super(card); + } + + @Override + public CanopyGargantuan copy() { + return new CanopyGargantuan(this); + } +} + +class CanopyGargantuanEffect extends OneShotEffect { + + CanopyGargantuanEffect() { + super(Outcome.Benefit); + staticText = "put a number of +1/+1 counters on each other creature you control equal to that creature's toughness"; + } + + private CanopyGargantuanEffect(final CanopyGargantuanEffect effect) { + super(effect); + } + + @Override + public CanopyGargantuanEffect copy() { + return new CanopyGargantuanEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_OTHER_CONTROLLED_CREATURE, + source.getControllerId(), source, game + )) { + int toughness = permanent.getToughness().getValue(); + if (toughness > 0) { + permanent.addCounters(CounterType.P1P1.createInstance(toughness), source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaptainAmericaFirstAvenger.java b/Mage.Sets/src/mage/cards/c/CaptainAmericaFirstAvenger.java index 34ecb776194..807a333291b 100644 --- a/Mage.Sets/src/mage/cards/c/CaptainAmericaFirstAvenger.java +++ b/Mage.Sets/src/mage/cards/c/CaptainAmericaFirstAvenger.java @@ -4,6 +4,7 @@ import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; +import mage.abilities.costs.CostImpl; import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.Cost; @@ -130,7 +131,7 @@ enum CaptainAmericaFirstAvengerValue implements DynamicValue { } } -class CaptainAmericaFirstAvengerUnattachCost extends EarlyTargetCost { +class CaptainAmericaFirstAvengerUnattachCost extends CostImpl implements EarlyTargetCost { private static final FilterPermanent filter = new FilterEquipmentPermanent("equipment attached to this creature"); private static final FilterPermanent subfilter = new FilterControlledPermanent("{this}"); diff --git a/Mage.Sets/src/mage/cards/c/CastleLocthwain.java b/Mage.Sets/src/mage/cards/c/CastleLocthwain.java index 4f11e611bba..7fa016e86e8 100644 --- a/Mage.Sets/src/mage/cards/c/CastleLocthwain.java +++ b/Mage.Sets/src/mage/cards/c/CastleLocthwain.java @@ -40,7 +40,7 @@ public final class CastleLocthwain extends CardImpl { Ability ability = new SimpleActivatedAbility( new DrawCardSourceControllerEffect(1).setText("draw a card,"), new ManaCostsImpl<>("{1}{B}{B}") ); - ability.addEffect(new LoseLifeSourceControllerEffect(CardsInControllerHandCount.instance) + ability.addEffect(new LoseLifeSourceControllerEffect(CardsInControllerHandCount.ANY) .setText("then you lose life equal to the number of cards in your hand")); ability.addCost(new TapSourceCost()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/ChanneledForce.java b/Mage.Sets/src/mage/cards/c/ChanneledForce.java index 74917f9627a..39a2d151ab1 100644 --- a/Mage.Sets/src/mage/cards/c/ChanneledForce.java +++ b/Mage.Sets/src/mage/cards/c/ChanneledForce.java @@ -25,7 +25,7 @@ public final class ChanneledForce extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}{R}"); // As an additional cost to cast this spell, discard X cards. - this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS)); + this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true)); // Target player draws X cards. Channeled Force deals X damage to up to one target creature or planeswalker. this.getSpellAbility().addEffect(new ChanneledForceEffect()); diff --git a/Mage.Sets/src/mage/cards/c/ChitinGravestalker.java b/Mage.Sets/src/mage/cards/c/ChitinGravestalker.java index 1499612e08e..f33c4dd99fa 100644 --- a/Mage.Sets/src/mage/cards/c/ChitinGravestalker.java +++ b/Mage.Sets/src/mage/cards/c/ChitinGravestalker.java @@ -34,7 +34,7 @@ public final class ChitinGravestalker extends CardImpl { } private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter); - private static final Hint hint = new ValueHint("Instant and sorcery card in your graveyard", xValue); + private static final Hint hint = new ValueHint("Artifact and/or creature cards in your graveyard", xValue); public ChitinGravestalker(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}"); diff --git a/Mage.Sets/src/mage/cards/c/CitadelSiege.java b/Mage.Sets/src/mage/cards/c/CitadelSiege.java index 793ee47f43d..edbafe2556e 100644 --- a/Mage.Sets/src/mage/cards/c/CitadelSiege.java +++ b/Mage.Sets/src/mage/cards/c/CitadelSiege.java @@ -1,23 +1,20 @@ - package mage.cards.c; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; -import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ModeChoice; import mage.constants.TargetController; import mage.counters.CounterType; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.Predicate; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; @@ -28,36 +25,29 @@ import java.util.UUID; */ public final class CitadelSiege extends CardImpl { - private static final String ruleTrigger1 = "&bull Khans — At the beginning of combat on your turn, put two +1/+1 counters on target creature you control."; - private static final String ruleTrigger2 = "&bull Dragons — At the beginning of combat on each opponent's turn, tap target creature that player controls."; private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature controlled by the active player"); static { - filter.add(CitadelSiegePredicate.instance); + filter.add(TargetController.ACTIVE.getControllerPredicate()); } public CitadelSiege(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}{W}"); // As Citadel Siege enters the battlefield, choose Khans or Dragons. - this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Khans or Dragons?", "Khans", "Dragons"), null, - "As {this} enters, choose Khans or Dragons.", "")); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.KHANS, ModeChoice.DRAGONS))); // * Khans - At the beginning of combat on your turn, put two +1/+1 counters on target creature you control. - Ability ability = new ConditionalTriggeredAbility( - new BeginningOfCombatTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2))), - new ModeChoiceSourceCondition("Khans"), - ruleTrigger1); + Ability ability = new BeginningOfCombatTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2))); ability.addTarget(new TargetControlledCreaturePermanent()); - this.addAbility(ability); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability, ModeChoice.KHANS))); // * Dragons - At the beginning of combat on each opponent's turn, tap target creature that player controls. - ability = new ConditionalTriggeredAbility( - new BeginningOfCombatTriggeredAbility(TargetController.OPPONENT, new TapTargetEffect(), false), - new ModeChoiceSourceCondition("Dragons"), - ruleTrigger2); + ability = new BeginningOfCombatTriggeredAbility( + TargetController.OPPONENT, new TapTargetEffect("tap target creature that player controls"), false + ); ability.addTarget(new TargetCreaturePermanent(filter)); - this.addAbility(ability); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability, ModeChoice.DRAGONS))); } private CitadelSiege(final CitadelSiege card) { @@ -69,12 +59,3 @@ public final class CitadelSiege extends CardImpl { return new CitadelSiege(this); } } - -enum CitadelSiegePredicate implements Predicate { - instance; - - @Override - public boolean apply(Permanent input, Game game) { - return input.getControllerId().equals(game.getActivePlayerId()); - } -} diff --git a/Mage.Sets/src/mage/cards/c/ClarionConqueror.java b/Mage.Sets/src/mage/cards/c/ClarionConqueror.java new file mode 100644 index 00000000000..efaf4e73ab0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ClarionConqueror.java @@ -0,0 +1,72 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.RestrictionEffect; +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.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ClarionConqueror extends CardImpl { + + public ClarionConqueror(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Activated abilities of artifacts, creatures, and planeswalkers can't be activated. + this.addAbility(new SimpleStaticAbility(new ClarionConquerorEffect())); + } + + private ClarionConqueror(final ClarionConqueror card) { + super(card); + } + + @Override + public ClarionConqueror copy() { + return new ClarionConqueror(this); + } +} + +class ClarionConquerorEffect extends RestrictionEffect { + + ClarionConquerorEffect() { + super(Duration.WhileOnBattlefield); + staticText = "activated abilities of artifacts, creatures, and planeswalkers can't be activated"; + } + + private ClarionConquerorEffect(final ClarionConquerorEffect effect) { + super(effect); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return permanent.isArtifact(game) || permanent.isCreature(game) || permanent.isPlaneswalker(game); + } + + @Override + public boolean canUseActivatedAbilities(Permanent permanent, Ability source, Game game, boolean canUseChooseDialogs) { + return false; + } + + @Override + public ClarionConquerorEffect copy() { + return new ClarionConquerorEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ColossalGraveReaver.java b/Mage.Sets/src/mage/cards/c/ColossalGraveReaver.java new file mode 100644 index 00000000000..3b130b45c27 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ColossalGraveReaver.java @@ -0,0 +1,164 @@ +package mage.cards.c; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.cards.*; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.targetpointer.FixedTargets; + +/** + * + * @author Grath + */ +public final class ColossalGraveReaver extends CardImpl { + + public ColossalGraveReaver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{B}{G}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(7); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever this creature enters or attacks, mill three cards. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new MillCardsControllerEffect(3))); + + // Whenever one or more creature cards are put into your graveyard from your library, put one of them onto the battlefield. + this.addAbility(new ColossalGraveReaverTriggeredAbility()); + } + + private ColossalGraveReaver(final ColossalGraveReaver card) { + super(card); + } + + @Override + public ColossalGraveReaver copy() { + return new ColossalGraveReaver(this); + } +} + +class ColossalGraveReaverEffect extends OneShotEffect { + + private static final FilterCard defaultFilter = new FilterCard("creature to return to battlefield"); + + public ColossalGraveReaverEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "put one of them onto the battlefield"; + } + + protected ColossalGraveReaverEffect(final ColossalGraveReaverEffect effect) { + super(effect); + } + + @Override + public ColossalGraveReaverEffect copy() { + return new ColossalGraveReaverEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Cards cardsToChooseFrom = new CardsImpl(); + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + Card card = game.getCard(targetId); + if (card != null && game.getState().getZone(card.getId()) == Zone.GRAVEYARD) { + cardsToChooseFrom.add(card); + } + } + Set cardsToMove; + switch (cardsToChooseFrom.size()) { + case 0: + return false; + case 1: + cardsToMove = new HashSet<>(cardsToChooseFrom.getCards(game)); + break; + default: + cardsToMove = new HashSet<>(); + TargetCard target = new TargetCard(1, 1, Zone.ALL, defaultFilter); + target.withNotTarget(true); + controller.choose(Outcome.PlayForFree, cardsToChooseFrom, target, source, game); + cardsToMove.add(cardsToChooseFrom.get(target.getFirstTarget(), game)); + + } + controller.moveCards(cardsToMove, Zone.BATTLEFIELD, source, game, false, false, false, null); + return true; + } + return false; + } +} + +class ColossalGraveReaverTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + ColossalGraveReaverTriggeredAbility() { + super(Zone.BATTLEFIELD, new ColossalGraveReaverEffect()); + } + + private ColossalGraveReaverTriggeredAbility(final ColossalGraveReaverTriggeredAbility ability) { + super(ability); + } + + @Override + public ColossalGraveReaverTriggeredAbility copy() { + return new ColossalGraveReaverTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; + } + + @Override + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getFromZone() != Zone.LIBRARY || event.getToZone() != Zone.GRAVEYARD) { + return false; + } + Card card = game.getCard(event.getTargetId()); + return card != null && card.isCreature(game) && card.isOwnedBy(getControllerId()); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Set set = getFilteredEvents((ZoneChangeBatchEvent) event, game) + .stream() + .map(ZoneChangeEvent::getTargetId) + .map(game::getCard) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (set.isEmpty()) { + return false; + } + this.getEffects().setTargetPointer(new FixedTargets(set, game)); + return true; + } + + @Override + public String getRule() { + return "Whenever one or more creature cards are put into your graveyard " + + "from your library, put one of them onto the battlefield tapped."; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/CrownOfGondor.java b/Mage.Sets/src/mage/cards/c/CrownOfGondor.java index 27329de82bf..d9c7c0cb3f9 100644 --- a/Mage.Sets/src/mage/cards/c/CrownOfGondor.java +++ b/Mage.Sets/src/mage/cards/c/CrownOfGondor.java @@ -72,7 +72,7 @@ enum CrownOfGondorAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (MonarchIsSourceControllerCondition.instance.apply(game, ability)) { CardUtil.reduceCost(ability, 3); } diff --git a/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java b/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java index e689e8989a8..effdfb78226 100644 --- a/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java +++ b/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java @@ -17,7 +17,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.TargetController; -import mage.constants.Zone; import mage.game.Game; import mage.players.Player; @@ -59,7 +58,7 @@ public final class DamiaSageOfStone extends CardImpl { class DamiaSageOfStoneTriggeredAbility extends BeginningOfUpkeepTriggeredAbility { DamiaSageOfStoneTriggeredAbility() { - super(TargetController.YOU, new DrawCardSourceControllerEffect(new IntPlusDynamicValue(7, new MultipliedValue(CardsInControllerHandCount.instance, -1))), false); + super(TargetController.YOU, new DrawCardSourceControllerEffect(new IntPlusDynamicValue(7, new MultipliedValue(CardsInControllerHandCount.ANY, -1))), false); } private DamiaSageOfStoneTriggeredAbility(final DamiaSageOfStoneTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/d/DarksteelMonolith.java b/Mage.Sets/src/mage/cards/d/DarksteelMonolith.java index 3ae3a10afc3..158d6a8747b 100644 --- a/Mage.Sets/src/mage/cards/d/DarksteelMonolith.java +++ b/Mage.Sets/src/mage/cards/d/DarksteelMonolith.java @@ -54,7 +54,7 @@ enum IsBeingCastFromHandCondition implements Condition { @Override public boolean apply(Game game, Ability source) { MageObject object = game.getObject(source); - if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) { + if (object instanceof SplitCardHalf || object instanceof SpellOptionCard || object instanceof ModalDoubleFacedCardHalf) { UUID mainCardId = ((Card) object).getMainCard().getId(); object = game.getObject(mainCardId); } diff --git a/Mage.Sets/src/mage/cards/d/DeathBegetsLife.java b/Mage.Sets/src/mage/cards/d/DeathBegetsLife.java new file mode 100644 index 00000000000..8c3efdd56d6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeathBegetsLife.java @@ -0,0 +1,83 @@ +package mage.cards.d; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author androosss + */ +public final class DeathBegetsLife extends CardImpl { + + public DeathBegetsLife(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.SORCERY }, "{5}{B}{G}{U}"); + + // Destroy all creatures and enchantments. Draw a card for each permanent + // destroyed this way. + this.getSpellAbility().addEffect(new DeathBegetsLifeEffect()); + } + + private DeathBegetsLife(final DeathBegetsLife card) { + super(card); + } + + @Override + public DeathBegetsLife copy() { + return new DeathBegetsLife(this); + } +} + +class DeathBegetsLifeEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent("creature or enchantment"); + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + CardType.ENCHANTMENT.getPredicate())); + } + + DeathBegetsLifeEffect() { + super(Outcome.DestroyPermanent); + this.staticText = "Destroy all creatures and enchantments. Draw a card for each permanent destroyed this way"; + } + + private DeathBegetsLifeEffect(final DeathBegetsLifeEffect effect) { + super(effect); + } + + @Override + public DeathBegetsLifeEffect copy() { + return new DeathBegetsLifeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int destroyedPermanent = 0; + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, + controller.getId(), game)) { + if (permanent.destroy(source, game)) { + destroyedPermanent++; + } + } + if (destroyedPermanent > 0) { + game.processAction(); + controller.drawCards(destroyedPermanent, source, game); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DeceptiveFrostkite.java b/Mage.Sets/src/mage/cards/d/DeceptiveFrostkite.java new file mode 100644 index 00000000000..c3090a452b3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeceptiveFrostkite.java @@ -0,0 +1,74 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.EntersBattlefieldEffect; +import mage.abilities.effects.common.CopyPermanentEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.util.functions.CopyApplier; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DeceptiveFrostkite extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("a creature you control with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + private static final CopyApplier applier = new CopyApplier() { + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID copyToObjectId) { + blueprint.addSubType(SubType.DRAGON); + blueprint.getAbilities().add(FlyingAbility.getInstance()); + return true; + } + }; + + public DeceptiveFrostkite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{U}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // You may have this creature enter as a copy of a creature you control with power 4 or greater, except it's a Dragon in addition to its other types and it has flying. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, + new EntersBattlefieldEffect( + new CopyPermanentEffect(filter, applier), "you may have this creature enter " + + "as a copy of a creature you control with power 4 or greater, " + + "except it's a Dragon in addition to its other types and it has flying", true + ) + )); + } + + private DeceptiveFrostkite(final DeceptiveFrostkite card) { + super(card); + } + + @Override + public DeceptiveFrostkite copy() { + return new DeceptiveFrostkite(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DeepwoodDenizen.java b/Mage.Sets/src/mage/cards/d/DeepwoodDenizen.java index 5733ba08da2..a3097459e2f 100644 --- a/Mage.Sets/src/mage/cards/d/DeepwoodDenizen.java +++ b/Mage.Sets/src/mage/cards/d/DeepwoodDenizen.java @@ -62,7 +62,7 @@ enum DeepwoodDenizenAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { CardUtil.reduceCost(ability, DeepwoodDenizenValue.instance.calculate(game, ability, null)); } } diff --git a/Mage.Sets/src/mage/cards/d/DeflectingPalm.java b/Mage.Sets/src/mage/cards/d/DeflectingPalm.java index 7189cbe5329..a9f8bdccb89 100644 --- a/Mage.Sets/src/mage/cards/d/DeflectingPalm.java +++ b/Mage.Sets/src/mage/cards/d/DeflectingPalm.java @@ -1,6 +1,5 @@ package mage.cards.d; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.PreventionEffectData; import mage.abilities.effects.PreventionEffectImpl; @@ -11,8 +10,6 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.game.stack.Spell; import mage.players.Player; import mage.target.TargetSource; @@ -44,9 +41,11 @@ class DeflectingPalmEffect extends PreventionEffectImpl { private final TargetSource target; - public DeflectingPalmEffect() { + DeflectingPalmEffect() { super(Duration.EndOfTurn, Integer.MAX_VALUE, false, false); - this.staticText = "The next time a source of your choice would deal damage to you this turn, prevent that damage. If damage is prevented this way, {this} deals that much damage to that source's controller"; + this.staticText = "the next time a source of your choice would deal damage to you this turn, " + + "prevent that damage. If damage is prevented this way, " + + "{this} deals that much damage to that source's controller"; this.target = new TargetSource(); } @@ -71,31 +70,23 @@ class DeflectingPalmEffect extends PreventionEffectImpl { PreventionEffectData preventionData = preventDamageAction(event, source, game); this.used = true; this.discard(); // only one use - if (preventionData.getPreventedDamage() > 0) { - MageObject damageDealingObject = game.getObject(target.getFirstTarget()); - UUID objectControllerId = null; - if (damageDealingObject instanceof Permanent) { - objectControllerId = ((Permanent) damageDealingObject).getControllerId(); - } else if (damageDealingObject instanceof Ability) { - objectControllerId = ((Ability) damageDealingObject).getControllerId(); - } else if (damageDealingObject instanceof Spell) { - objectControllerId = ((Spell) damageDealingObject).getControllerId(); - } - if (objectControllerId != null) { - Player objectController = game.getPlayer(objectControllerId); - if (objectController != null) { - objectController.damage(preventionData.getPreventedDamage(), source.getSourceId(), source, game); - } - } + if (preventionData.getPreventedDamage() < 1) { + return true; } + UUID objectControllerId = game.getControllerId(target.getFirstTarget()); + Player objectController = game.getPlayer(objectControllerId); + if (objectController == null) { + return true; + } + objectController.damage(preventionData.getPreventedDamage(), source.getSourceId(), source, game); return true; } @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!this.used && super.applies(event, source, game)) { - return event.getTargetId().equals(source.getControllerId()) && event.getSourceId().equals(target.getFirstTarget()); - } - return false; + return !this.used + && super.applies(event, source, game) + && event.getTargetId().equals(source.getControllerId()) + && event.getSourceId().equals(target.getFirstTarget()); } } diff --git a/Mage.Sets/src/mage/cards/d/Delay.java b/Mage.Sets/src/mage/cards/d/Delay.java index 0e43d711f0f..ee21e510be2 100644 --- a/Mage.Sets/src/mage/cards/d/Delay.java +++ b/Mage.Sets/src/mage/cards/d/Delay.java @@ -68,7 +68,7 @@ class DelayEffect extends OneShotEffect { if (controller != null && spell != null) { Effect effect = new CounterTargetWithReplacementEffect(PutCards.EXILED); effect.setTargetPointer(this.getTargetPointer().copy()); - Card card = game.getCard(spell.getSourceId()); + Card card = spell.getMainCard(); if (card != null && effect.apply(game, source) && game.getState().getZone(card.getId()) == Zone.EXILED) { boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class); UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); diff --git a/Mage.Sets/src/mage/cards/d/DemonicLore.java b/Mage.Sets/src/mage/cards/d/DemonicLore.java index be9f94ed3da..3cdc30eb088 100644 --- a/Mage.Sets/src/mage/cards/d/DemonicLore.java +++ b/Mage.Sets/src/mage/cards/d/DemonicLore.java @@ -18,7 +18,7 @@ import java.util.UUID; */ public final class DemonicLore extends CardImpl { - private static final DynamicValue xValue = new MultipliedValue(CardsInControllerHandCount.instance, 2); + private static final DynamicValue xValue = new MultipliedValue(CardsInControllerHandCount.ANY, 2); public DemonicLore(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); diff --git a/Mage.Sets/src/mage/cards/d/DescendantOfSoramaro.java b/Mage.Sets/src/mage/cards/d/DescendantOfSoramaro.java index 41a9c725d78..7dd093feff4 100644 --- a/Mage.Sets/src/mage/cards/d/DescendantOfSoramaro.java +++ b/Mage.Sets/src/mage/cards/d/DescendantOfSoramaro.java @@ -12,7 +12,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; /** * @@ -28,7 +27,7 @@ public final class DescendantOfSoramaro extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(3); // {1}{U}: Look at the top X cards of your library, where X is the number of cards in your hand, then put them back in any order. - Effect effect = new LookLibraryControllerEffect(CardsInControllerHandCount.instance); + Effect effect = new LookLibraryControllerEffect(CardsInControllerHandCount.ANY); effect.setText("Look at the top X cards of your library, where X is the number of cards in your hand, then put them back in any order"); this.addAbility(new SimpleActivatedAbility( effect, new ManaCostsImpl<>("{1}{U}"))); diff --git a/Mage.Sets/src/mage/cards/d/DevastatingDreams.java b/Mage.Sets/src/mage/cards/d/DevastatingDreams.java index 6847e84dfe6..7c42ed2a34f 100644 --- a/Mage.Sets/src/mage/cards/d/DevastatingDreams.java +++ b/Mage.Sets/src/mage/cards/d/DevastatingDreams.java @@ -5,6 +5,8 @@ import mage.abilities.costs.Cost; import mage.abilities.costs.VariableCostImpl; import mage.abilities.costs.VariableCostType; import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.common.DiscardXTargetCost; +import mage.abilities.costs.costadjusters.DiscardXCardsCostAdjuster; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.SacrificeAllEffect; @@ -12,6 +14,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledLandPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; @@ -28,8 +31,8 @@ public final class DevastatingDreams extends CardImpl { public DevastatingDreams(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}{R}"); - // As an additional cost to cast Devastating Dreams, discard X cards at random. - this.getSpellAbility().addCost(new DevastatingDreamsAdditionalCost()); + // As an additional cost to cast this spell, discard X cards at random. + this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true).withRandom()); // Each player sacrifices X lands. this.getSpellAbility().addEffect(new SacrificeAllEffect(GetXValue.instance, new FilterControlledLandPermanent("lands"))); diff --git a/Mage.Sets/src/mage/cards/d/DirgurIslandDragon.java b/Mage.Sets/src/mage/cards/d/DirgurIslandDragon.java new file mode 100644 index 00000000000..bd4685d7d1b --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DirgurIslandDragon.java @@ -0,0 +1,52 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardSetInfo; +import mage.cards.OmenCard; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class DirgurIslandDragon extends OmenCard { + + public DirgurIslandDragon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.INSTANT}, "{5}{U}", "Skimming Strike", "{1}{U}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // Skimming Strike + // Tap up to one target creature. Draw a card. + this.getSpellCard().getSpellAbility().addEffect(new TapTargetEffect()); + this.getSpellCard().getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); + this.getSpellCard().getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1)); + this.finalizeOmen(); + } + + private DirgurIslandDragon(final DirgurIslandDragon card) { + super(card); + } + + @Override + public DirgurIslandDragon copy() { + return new DirgurIslandDragon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DisruptiveStormbrood.java b/Mage.Sets/src/mage/cards/d/DisruptiveStormbrood.java new file mode 100644 index 00000000000..382d66191e4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DisruptiveStormbrood.java @@ -0,0 +1,64 @@ +package mage.cards.d; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.OmenCard; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Jmlundeen + */ +public final class DisruptiveStormbrood extends OmenCard { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power 3 or less"); + + static { + filter.add(new PowerPredicate(ComparisonType.OR_LESS, 3)); + } + + public DisruptiveStormbrood(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{4}{G}", "Petty Revenge", "{1}{B}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, destroy up to one target artifact or enchantment. + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); + this.addAbility(ability); + + // Petty Revenge + // Destroy target creature with power 3 or less. + Effect spellEffect = new DestroyTargetEffect(); + this.getSpellCard().getSpellAbility().addEffect(spellEffect); + this.getSpellCard().getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + this.finalizeOmen(); + } + + private DisruptiveStormbrood(final DisruptiveStormbrood card) { + super(card); + } + + @Override + public DisruptiveStormbrood copy() { + return new DisruptiveStormbrood(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DivinersPortent.java b/Mage.Sets/src/mage/cards/d/DivinersPortent.java index 0795197206e..4efb58a46f3 100644 --- a/Mage.Sets/src/mage/cards/d/DivinersPortent.java +++ b/Mage.Sets/src/mage/cards/d/DivinersPortent.java @@ -27,7 +27,7 @@ public final class DivinersPortent extends CardImpl { // Roll a d20 and add the number of cards in your hand. RollDieWithResultTableEffect effect = new RollDieWithResultTableEffect( 20, "roll a d20 and add the number " + - "of cards in your hand", CardsInControllerHandCount.instance, 0 + "of cards in your hand", CardsInControllerHandCount.ANY, 0 ); this.getSpellAbility().addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/d/DragonclawStrike.java b/Mage.Sets/src/mage/cards/d/DragonclawStrike.java new file mode 100644 index 00000000000..c8318674d70 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DragonclawStrike.java @@ -0,0 +1,73 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.FightTargetsEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DragonclawStrike extends CardImpl { + + public DragonclawStrike(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2/U}{2/R}{2/G}"); + + // Double the power and toughness of target creature you control until end of turn. Then it fights up to one target creature an opponent controls. + this.getSpellAbility().addEffect(new DragonclawStrikeEffect()); + this.getSpellAbility().addEffect(new FightTargetsEffect() + .setText("Then it fights up to one target creature an opponent controls")); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetOpponentsCreaturePermanent(0, 1)); + } + + private DragonclawStrike(final DragonclawStrike card) { + super(card); + } + + @Override + public DragonclawStrike copy() { + return new DragonclawStrike(this); + } +} + +class DragonclawStrikeEffect extends OneShotEffect { + + DragonclawStrikeEffect() { + super(Outcome.Benefit); + staticText = "double the power and toughness of target creature you control until end of turn"; + } + + private DragonclawStrikeEffect(final DragonclawStrikeEffect effect) { + super(effect); + } + + @Override + public DragonclawStrikeEffect copy() { + return new DragonclawStrikeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + game.addEffect(new BoostTargetEffect( + permanent.getPower().getValue(), + permanent.getToughness().getValue() + ).setTargetPointer(new FixedTarget(permanent, game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DragonfireBlade.java b/Mage.Sets/src/mage/cards/d/DragonfireBlade.java new file mode 100644 index 00000000000..c417986a982 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DragonfireBlade.java @@ -0,0 +1,83 @@ +package mage.cards.d; + +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.CostAdjuster; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.HexproofFromMonocoloredAbility; +import mage.abilities.mana.ManaOptions; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.util.CardUtil; + +/** + * + * @author Jmlundeen + */ +public final class DragonfireBlade extends CardImpl { + + public DragonfireBlade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +2/+2 and has hexproof from monocolored. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2)); + ability.addEffect(new GainAbilityAttachedEffect(HexproofFromMonocoloredAbility.getInstance(), AttachmentType.EQUIPMENT) + .setText("and has hexproof from monocolored")); + this.addAbility(ability); + + // Equip {4}. This ability costs {1} less to activate for each color of the creature it targets. + EquipAbility equipAbility = new EquipAbility(4); + equipAbility.setCostAdjuster(DragonfireBladeCostAdjuster.instance); + equipAbility.setCostReduceText("This ability costs {1} less to activate for each color of the creature it targets."); + this.addAbility(equipAbility); + } + + private DragonfireBlade(final DragonfireBlade card) { + super(card); + } + + @Override + public DragonfireBlade copy() { + return new DragonfireBlade(this); + } +} + +enum DragonfireBladeCostAdjuster implements CostAdjuster { + instance; + + @Override + public void reduceCost(Ability ability, Game game) { + int reduceCount = 0; + if (game.inCheckPlayableState()) { + reduceCount = game.getBattlefield().getAllActivePermanents(ability.getControllerId()) + .stream() + .filter(Permanent::isCreature) + .mapToInt(permanent -> permanent.getColor(game).getColorCount()) + .max() + .orElse(reduceCount); + } + else { + Permanent target = game.getPermanent(ability.getFirstTarget()); + if (target != null) { + reduceCount = target.getColor(game).getColorCount(); + } + + } + CardUtil.reduceCost(ability, reduceCount); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java b/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java index 4b48dddf5e1..59db469c383 100644 --- a/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java +++ b/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java @@ -180,7 +180,7 @@ class DraugrNecromancerSpendAnyManaEffect extends AsThoughEffectImpl implements CardState cardState; if (card instanceof SplitCard) { cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); } else if (card instanceof ModalDoubleFacedCard) { cardState = game.getLastKnownInformationCard(((ModalDoubleFacedCard) card).getLeftHalfCard().getId(), Zone.EXILED); diff --git a/Mage.Sets/src/mage/cards/d/DreadSlag.java b/Mage.Sets/src/mage/cards/d/DreadSlag.java index ac89d84771f..90704bab302 100644 --- a/Mage.Sets/src/mage/cards/d/DreadSlag.java +++ b/Mage.Sets/src/mage/cards/d/DreadSlag.java @@ -15,7 +15,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; /** * @@ -32,7 +31,7 @@ public final class DreadSlag extends CardImpl { // Trample this.addAbility(TrampleAbility.getInstance()); // Dread Slag gets -4/-4 for each card in your hand. - DynamicValue amount = new MultipliedValue(CardsInControllerHandCount.instance, -4); + DynamicValue amount = new MultipliedValue(CardsInControllerHandCount.ANY, -4); Effect effect = new BoostSourceEffect(amount, amount, Duration.WhileOnBattlefield); effect.setText("{this} gets -4/-4 for each card in your hand"); this.addAbility(new SimpleStaticAbility(effect)); diff --git a/Mage.Sets/src/mage/cards/d/DreamDevourer.java b/Mage.Sets/src/mage/cards/d/DreamDevourer.java index e9a00346504..0ed63bc1e30 100644 --- a/Mage.Sets/src/mage/cards/d/DreamDevourer.java +++ b/Mage.Sets/src/mage/cards/d/DreamDevourer.java @@ -103,9 +103,9 @@ class DreamDevourerAddAbilityEffect extends ContinuousEffectImpl { foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); } } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { String creatureCost = CardUtil.reduceCost(card.getMainCard().getManaCost(), 2).getText(); - String spellCost = CardUtil.reduceCost(((AdventureCard) card).getSpellCard().getManaCost(), 2).getText(); + String spellCost = CardUtil.reduceCost(((CardWithSpellOption) card).getSpellCard().getManaCost(), 2).getText(); foretellAbility = new ForetellAbility(card, creatureCost, spellCost); } else { String costText = CardUtil.reduceCost(card.getManaCost(), 2).getText(); diff --git a/Mage.Sets/src/mage/cards/d/DugganPrivateDetective.java b/Mage.Sets/src/mage/cards/d/DugganPrivateDetective.java index 0bcd954c207..f2f19b21ccf 100644 --- a/Mage.Sets/src/mage/cards/d/DugganPrivateDetective.java +++ b/Mage.Sets/src/mage/cards/d/DugganPrivateDetective.java @@ -36,7 +36,7 @@ public final class DugganPrivateDetective extends CardImpl { this.toughness = new MageInt(0); // Duggan's power and toughness are each equal to the number of cards in your hand. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.instance))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.ANY))); // Whenever Duggan enters the battlefield or attacks, investigate. this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new InvestigateEffect().setText("investigate"))); diff --git a/Mage.Sets/src/mage/cards/e/EdginLarcenousLutenist.java b/Mage.Sets/src/mage/cards/e/EdginLarcenousLutenist.java index 20a122cf63f..ada220bef82 100644 --- a/Mage.Sets/src/mage/cards/e/EdginLarcenousLutenist.java +++ b/Mage.Sets/src/mage/cards/e/EdginLarcenousLutenist.java @@ -100,9 +100,9 @@ class EdginLarcenousLutenistEffect extends ContinuousEffectImpl { foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); } } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { String creatureCost = CardUtil.reduceCost(card.getMainCard().getManaCost(), 2).getText(); - String spellCost = CardUtil.reduceCost(((AdventureCard) card).getSpellCard().getManaCost(), 2).getText(); + String spellCost = CardUtil.reduceCost(((CardWithSpellOption) card).getSpellCard().getManaCost(), 2).getText(); foretellAbility = new ForetellAbility(card, creatureCost, spellCost); } else { String costText = CardUtil.reduceCost(card.getManaCost(), 2).getText(); diff --git a/Mage.Sets/src/mage/cards/e/EffortlessMaster.java b/Mage.Sets/src/mage/cards/e/EffortlessMaster.java new file mode 100644 index 00000000000..6ddf6b305f3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EffortlessMaster.java @@ -0,0 +1,68 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EffortlessMaster extends CardImpl { + + public EffortlessMaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{R}"); + + this.subtype.add(SubType.ORC); + this.subtype.add(SubType.MONK); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Menace + this.addAbility(new MenaceAbility()); + + // This creature enters with two +1/+1 counters on it if you've cast two or more spells this turn. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + EffortlessMasterCondition.instance, null, + "with two +1/+1 counters on it if you've cast two or more spells this turn" + )); + } + + private EffortlessMaster(final EffortlessMaster card) { + super(card); + } + + @Override + public EffortlessMaster copy() { + return new EffortlessMaster(this); + } +} + +enum EffortlessMasterCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getState() + .getWatcher(SpellsCastWatcher.class) + .getSpellsCastThisTurn(source.getControllerId()) + .size() >= 2; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EliteArcanist.java b/Mage.Sets/src/mage/cards/e/EliteArcanist.java index ff2ad860b1e..d69a5fdc689 100644 --- a/Mage.Sets/src/mage/cards/e/EliteArcanist.java +++ b/Mage.Sets/src/mage/cards/e/EliteArcanist.java @@ -1,12 +1,13 @@ package mage.cards.e; +import mage.ApprovingObject; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.CostAdjuster; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.costadjusters.ImprintedManaValueXCostAdjuster; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; @@ -23,7 +24,6 @@ import mage.players.Player; import mage.target.TargetCard; import java.util.UUID; -import mage.ApprovingObject; /** * @author LevelX2 @@ -44,7 +44,7 @@ public final class EliteArcanist extends CardImpl { // {X}, {T}: Copy the exiled card. You may cast the copy without paying its mana cost. X is the converted mana cost of the exiled card. Ability ability = new SimpleActivatedAbility(new EliteArcanistCopyEffect(), new ManaCostsImpl<>("{X}")); ability.addCost(new TapSourceCost()); - ability.setCostAdjuster(EliteArcanistAdjuster.instance); + ability.setCostAdjuster(ImprintedManaValueXCostAdjuster.instance); this.addAbility(ability); } @@ -58,29 +58,6 @@ public final class EliteArcanist extends CardImpl { } } -enum EliteArcanistAdjuster implements CostAdjuster { - instance; - - @Override - public void adjustCosts(Ability ability, Game game) { - Permanent sourcePermanent = game.getPermanent(ability.getSourceId()); - if (sourcePermanent == null - || sourcePermanent.getImprinted() == null - || sourcePermanent.getImprinted().isEmpty()) { - return; - } - Card imprintedInstant = game.getCard(sourcePermanent.getImprinted().get(0)); - if (imprintedInstant == null) { - return; - } - int cmc = imprintedInstant.getManaValue(); - if (cmc > 0) { - ability.clearManaCostsToPay(); - ability.addManaCostsToPay(new GenericManaCost(cmc)); - } - } -} - class EliteArcanistImprintEffect extends OneShotEffect { private static final FilterCard filter = new FilterCard("instant card from your hand"); diff --git a/Mage.Sets/src/mage/cards/e/ElshaThreefoldMaster.java b/Mage.Sets/src/mage/cards/e/ElshaThreefoldMaster.java new file mode 100644 index 00000000000..c6e328b9144 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElshaThreefoldMaster.java @@ -0,0 +1,51 @@ +package mage.cards.e; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.ProwessAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.MonasteryMentorToken; + +/** + * + * @author Grath + */ +public final class ElshaThreefoldMaster extends CardImpl { + + public ElshaThreefoldMaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DJINN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Prowess + this.addAbility(new ProwessAbility()); + + // Whenever Elsha deals combat damage to a player, create that many 1/1 white Monk creature tokens with prowess. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new CreateTokenEffect(new MonasteryMentorToken(), SavedDamageValue.MANY), false, true)); + } + + private ElshaThreefoldMaster(final ElshaThreefoldMaster card) { + super(card); + } + + @Override + public ElshaThreefoldMaster copy() { + return new ElshaThreefoldMaster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EmpyrialArmor.java b/Mage.Sets/src/mage/cards/e/EmpyrialArmor.java index 8ddd1e03472..333e3294023 100644 --- a/Mage.Sets/src/mage/cards/e/EmpyrialArmor.java +++ b/Mage.Sets/src/mage/cards/e/EmpyrialArmor.java @@ -15,7 +15,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -38,7 +37,7 @@ public final class EmpyrialArmor extends CardImpl { this.addAbility(ability); // Enchanted creature gets +1/+1 for each card in your hand. - DynamicValue xValue = CardsInControllerHandCount.instance; + DynamicValue xValue = CardsInControllerHandCount.ANY; this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(xValue, xValue, Duration.WhileOnBattlefield))); } diff --git a/Mage.Sets/src/mage/cards/e/EmpyrialPlate.java b/Mage.Sets/src/mage/cards/e/EmpyrialPlate.java index 8437ac9dbed..0010967bbc3 100644 --- a/Mage.Sets/src/mage/cards/e/EmpyrialPlate.java +++ b/Mage.Sets/src/mage/cards/e/EmpyrialPlate.java @@ -12,7 +12,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; /** * @@ -25,7 +24,7 @@ public final class EmpyrialPlate extends CardImpl { this.subtype.add(SubType.EQUIPMENT); // Equipped creature gets +1/+1 for each card in your hand. - this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(CardsInControllerHandCount.instance, CardsInControllerHandCount.instance))); + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(CardsInControllerHandCount.ANY, CardsInControllerHandCount.ANY))); // Equip {2} this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(2), false)); diff --git a/Mage.Sets/src/mage/cards/e/EndlessSwarm.java b/Mage.Sets/src/mage/cards/e/EndlessSwarm.java index 230b1379a56..acb59f93f31 100644 --- a/Mage.Sets/src/mage/cards/e/EndlessSwarm.java +++ b/Mage.Sets/src/mage/cards/e/EndlessSwarm.java @@ -22,7 +22,7 @@ public final class EndlessSwarm extends CardImpl { // Create a 1/1 green Snake creature token for each card in your hand. - this.getSpellAbility().addEffect(new CreateTokenEffect(new SnakeToken(), CardsInControllerHandCount.instance).setText("create a 1/1 green Snake creature token for each card in your hand")); + this.getSpellAbility().addEffect(new CreateTokenEffect(new SnakeToken(), CardsInControllerHandCount.ANY).setText("create a 1/1 green Snake creature token for each card in your hand")); // Epic this.getSpellAbility().addEffect(new EpicEffect()); diff --git a/Mage.Sets/src/mage/cards/e/EshkiDragonclaw.java b/Mage.Sets/src/mage/cards/e/EshkiDragonclaw.java new file mode 100644 index 00000000000..695026c5fa7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EshkiDragonclaw.java @@ -0,0 +1,156 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.keyword.WardAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.WatcherScope; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EshkiDragonclaw extends CardImpl { + + public EshkiDragonclaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Ward {1} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{1}"))); + + // At the beginning of combat on your turn, if you've cast both a creature spell and a noncreature spell this turn, draw a card and put two +1/+1 counters on Eshki Dragonclaw. + Ability ability = new BeginningOfCombatTriggeredAbility(new DrawCardSourceControllerEffect(1)) + .withInterveningIf(EshkiDragonclawCondition.instance); + ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)).concatBy("and")); + this.addAbility(ability.addHint(EshkiDragonclawHint.instance), new EshkiDragonclawWatcher()); + } + + private EshkiDragonclaw(final EshkiDragonclaw card) { + super(card); + } + + @Override + public EshkiDragonclaw copy() { + return new EshkiDragonclaw(this); + } +} + +enum EshkiDragonclawCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return EshkiDragonclawWatcher.checkPlayer(source.getControllerId(), game) == 3; + } + + @Override + public String toString() { + return "you've cast both a creature spell and a noncreature spell this turn"; + } +} + +enum EshkiDragonclawHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + switch (EshkiDragonclawWatcher.checkPlayer(ability.getControllerId(), game)) { + case 0: + return null; + case 1: + return "You've cast a creature spell this turn"; + case 2: + return "You've cast a noncreature spell this turn"; + case 3: + return "You've cast a creature spell and a noncreature spell this turn"; + } + return null; + } + + @Override + public Hint copy() { + return this; + } +} + +class EshkiDragonclawWatcher extends Watcher { + + private final Map creatureCount = new HashMap<>(); + private final Map nonCreatureCount = new HashMap<>(); + + EshkiDragonclawWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST) { + return; + } + Spell spell = game.getSpell(event.getTargetId()); + if (spell == null) { + return; + } + if (spell.isCreature(game)) { + creatureCount.compute(spell.getControllerId(), CardUtil::setOrIncrementValue); + } else { + nonCreatureCount.compute(spell.getControllerId(), CardUtil::setOrIncrementValue); + } + } + + @Override + public void reset() { + super.reset(); + creatureCount.clear(); + nonCreatureCount.clear(); + } + + private int checkCreature(UUID playerId) { + return creatureCount.getOrDefault(playerId, 0) > 0 ? 1 : 0; + } + + private int checkNonCreature(UUID playerId) { + return nonCreatureCount.getOrDefault(playerId, 0) > 0 ? 2 : 0; + } + + private int check(UUID playerId) { + return checkCreature(playerId) + checkNonCreature(playerId); + } + + static int checkPlayer(UUID playerId, Game game) { + return game.getState().getWatcher(EshkiDragonclawWatcher.class).check(playerId); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EshkiTemursRoar.java b/Mage.Sets/src/mage/cards/e/EshkiTemursRoar.java new file mode 100644 index 00000000000..2e8c907d1ef --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EshkiTemursRoar.java @@ -0,0 +1,89 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EshkiTemursRoar extends CardImpl { + + public EshkiTemursRoar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you cast a creature spell, put a +1/+1 counter on Eshki. If that spell's power is 4 or greater, draw a card. If that spell's power is 6 or greater, Eshki deals damage equal to Eshki's power to each opponent. + Ability ability = new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_SPELL_A_CREATURE, false + ); + ability.addEffect(new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(1), EshkiTemursRoarCondition.FOUR, + "If that spell's power is 4 or greater, draw a card" + )); + ability.addEffect(new ConditionalOneShotEffect( + new DamagePlayersEffect(SourcePermanentPowerValue.NOT_NEGATIVE, TargetController.OPPONENT), + EshkiTemursRoarCondition.SIX, "If that spell's power is 6 or greater, " + + "{this} deals damage equal to {this}'s power to each opponent" + )); + this.addAbility(ability); + } + + private EshkiTemursRoar(final EshkiTemursRoar card) { + super(card); + } + + @Override + public EshkiTemursRoar copy() { + return new EshkiTemursRoar(this); + } +} + +enum EshkiTemursRoarCondition implements Condition { + FOUR(4), + SIX(6); + private final int amount; + + EshkiTemursRoarCondition(int amount) { + this.amount = amount; + } + + @Override + public boolean apply(Game game, Ability source) { + return CardUtil.castStream( + source.getEffects() + .stream() + .map(effect -> effect.getValue("spellCast")), + Spell.class + ) + .findFirst() + .map(Spell::getPower) + .map(MageInt::getValue) + .orElse(0) >= amount; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EsquireOfTheKing.java b/Mage.Sets/src/mage/cards/e/EsquireOfTheKing.java index 6f1ecf66002..86d81519f8d 100644 --- a/Mage.Sets/src/mage/cards/e/EsquireOfTheKing.java +++ b/Mage.Sets/src/mage/cards/e/EsquireOfTheKing.java @@ -64,7 +64,7 @@ enum EsquireOfTheKingAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (game.getBattlefield().contains(StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY, ability, game, 1)) { CardUtil.reduceCost(ability, 2); } diff --git a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java index 1cc5b9d3b91..b9f8d113128 100644 --- a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java +++ b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java @@ -110,9 +110,9 @@ class EtherealValkyrieEffect extends OneShotEffect { foretellAbility = new ForetellAbility(exileCard, leftHalfCost, rightHalfCost); } } - } else if (exileCard instanceof AdventureCard) { + } else if (exileCard instanceof CardWithSpellOption) { String creatureCost = CardUtil.reduceCost(exileCard.getMainCard().getManaCost(), 2).getText(); - String spellCost = CardUtil.reduceCost(((AdventureCard) exileCard).getSpellCard().getManaCost(), 2).getText(); + String spellCost = CardUtil.reduceCost(((CardWithSpellOption) exileCard).getSpellCard().getManaCost(), 2).getText(); game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Cost", creatureCost); game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Split Cost", spellCost); foretellAbility = new ForetellAbility(exileCard, creatureCost, spellCost); diff --git a/Mage.Sets/src/mage/cards/e/EtheriumPteramander.java b/Mage.Sets/src/mage/cards/e/EtheriumPteramander.java index 2b29f88b3bd..681cdb1cb70 100644 --- a/Mage.Sets/src/mage/cards/e/EtheriumPteramander.java +++ b/Mage.Sets/src/mage/cards/e/EtheriumPteramander.java @@ -80,8 +80,7 @@ enum EtheriumPteramanderAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { - int count = artifactCount.calculate(game, ability, null); - CardUtil.reduceCost(ability, count); + public void reduceCost(Ability ability, Game game) { + CardUtil.reduceCost(ability, artifactCount.calculate(game, ability, null)); } } diff --git a/Mage.Sets/src/mage/cards/f/FatefulShowdown.java b/Mage.Sets/src/mage/cards/f/FatefulShowdown.java index f6c43b5a40f..f2166040b7f 100644 --- a/Mage.Sets/src/mage/cards/f/FatefulShowdown.java +++ b/Mage.Sets/src/mage/cards/f/FatefulShowdown.java @@ -21,7 +21,7 @@ public final class FatefulShowdown extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{R}{R}"); // Fateful Showdown deals damage to any target equal to the number of cards in your hand. Discard all the cards in your hand, then draw that many cards. - Effect effect = new DamageTargetEffect(CardsInControllerHandCount.instance); + Effect effect = new DamageTargetEffect(CardsInControllerHandCount.ANY); effect.setText("{this} deals damage to any target equal to the number of cards in your hand"); this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetAnyTarget()); diff --git a/Mage.Sets/src/mage/cards/f/FelotharTheSteadfast.java b/Mage.Sets/src/mage/cards/f/FelotharTheSteadfast.java new file mode 100644 index 00000000000..449c92ac516 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FelotharTheSteadfast.java @@ -0,0 +1,99 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +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.combat.CanAttackAsThoughItDidntHaveDefenderAllEffect; +import mage.abilities.effects.common.ruleModifying.CombatDamageByToughnessControlledEffect; +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.util.CardUtil; + +import java.util.Collection; +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FelotharTheSteadfast extends CardImpl { + + public FelotharTheSteadfast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(0); + this.toughness = new MageInt(5); + + // Each creature you control assigns combat damage equal to its toughness rather than its power. + this.addAbility(new SimpleStaticAbility(new CombatDamageByToughnessControlledEffect())); + + // Creatures you control can attack as though they didn't have defender. + this.addAbility(new SimpleStaticAbility(new CanAttackAsThoughItDidntHaveDefenderAllEffect( + Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURES + ))); + + // {3}, {T}, Sacrifice another creature: Draw cards equal to the sacrificed creature's toughness, then discard cards equal to its power. + Ability ability = new SimpleActivatedAbility(new FelotharTheSteadfastEffect(), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_ANOTHER_CREATURE)); + this.addAbility(ability); + } + + private FelotharTheSteadfast(final FelotharTheSteadfast card) { + super(card); + } + + @Override + public FelotharTheSteadfast copy() { + return new FelotharTheSteadfast(this); + } +} + +class FelotharTheSteadfastEffect extends OneShotEffect { + + FelotharTheSteadfastEffect() { + super(Outcome.Benefit); + staticText = "draw cards equal to the sacrificed creature's toughness, then discard cards equal to its power"; + } + + private FelotharTheSteadfastEffect(final FelotharTheSteadfastEffect effect) { + super(effect); + } + + @Override + public FelotharTheSteadfastEffect copy() { + return new FelotharTheSteadfastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = CardUtil + .castStream(source.getCosts(), SacrificeTargetCost.class) + .filter(Objects::nonNull) + .map(SacrificeTargetCost::getPermanents) + .flatMap(Collection::stream) + .findFirst() + .orElse(null); + if (player == null || permanent == null) { + return false; + } + player.drawCards(permanent.getToughness().getValue(), source, game); + game.processAction(); + player.discard(permanent.getPower().getValue(), false, false, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FeralDeathgorger.java b/Mage.Sets/src/mage/cards/f/FeralDeathgorger.java new file mode 100644 index 00000000000..9c44eaf81bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FeralDeathgorger.java @@ -0,0 +1,64 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.OmenCard; +import mage.cards.o.Omen; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.target.common.TargetCardInASingleGraveyard; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Jmlundeen + */ +public final class FeralDeathgorger extends OmenCard { + + public FeralDeathgorger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{5}{B}", "Dusk Sight", "{1}{B}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // When this creature enters, exile up to two target cards from a single graveyard. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect()); + ability.addTarget(new TargetCardInASingleGraveyard(0, 2, new FilterCard("cards"))); + this.addAbility(ability); + + // Dusk Sight + // Put a +1/+1 counter on up to one target creature. Draw a card. + this.getSpellCard().getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(), StaticValue.get(1))); + this.getSpellCard().getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); + this.getSpellCard().getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1)); + this.finalizeOmen(); + } + + private FeralDeathgorger(final FeralDeathgorger card) { + super(card); + } + + @Override + public FeralDeathgorger copy() { + return new FeralDeathgorger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/Fireball.java b/Mage.Sets/src/mage/cards/f/Fireball.java index acb030d570e..b55b90de1b9 100644 --- a/Mage.Sets/src/mage/cards/f/Fireball.java +++ b/Mage.Sets/src/mage/cards/f/Fireball.java @@ -2,11 +2,11 @@ package mage.cards.f; import mage.abilities.Ability; import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CostModificationType; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.Permanent; @@ -24,8 +24,8 @@ public final class Fireball extends CardImpl { public Fireball(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}"); - // Fireball deals X damage divided evenly, rounded down, among any number of target creatures and/or players. - // Fireball costs 1 more to cast for each target beyond the first. + // This spell costs {1} more to cast for each target beyond the first. + // Fireball deals X damage divided evenly, rounded down, among any number of targets. this.getSpellAbility().addTarget(new FireballTargetCreatureOrPlayer(0, Integer.MAX_VALUE)); this.getSpellAbility().addEffect(new FireballEffect()); this.getSpellAbility().setCostAdjuster(FireballAdjuster.instance); @@ -45,10 +45,10 @@ enum FireballAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void increaseCost(Ability ability, Game game) { int numTargets = ability.getTargets().isEmpty() ? 0 : ability.getTargets().get(0).getTargets().size(); if (numTargets > 1) { - ability.addManaCostsToPay(new GenericManaCost(numTargets - 1)); + CardUtil.increaseCost(ability, numTargets - 1); } } } diff --git a/Mage.Sets/src/mage/cards/f/FiresOfVictory.java b/Mage.Sets/src/mage/cards/f/FiresOfVictory.java index 7b7fe0c75a7..d2c5b87c2af 100644 --- a/Mage.Sets/src/mage/cards/f/FiresOfVictory.java +++ b/Mage.Sets/src/mage/cards/f/FiresOfVictory.java @@ -31,7 +31,7 @@ public final class FiresOfVictory extends CardImpl { KickedCondition.ONCE, "If this spell was kicked, draw a card." )); - this.getSpellAbility().addEffect(new DamageTargetEffect(CardsInControllerHandCount.instance) + this.getSpellAbility().addEffect(new DamageTargetEffect(CardsInControllerHandCount.ANY) .setText("{this} deals damage to target creature or planeswalker equal to the number of cards in your hand.")); this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); } diff --git a/Mage.Sets/src/mage/cards/f/Firestorm.java b/Mage.Sets/src/mage/cards/f/Firestorm.java index a2f7f19f98f..2a01e592ac8 100644 --- a/Mage.Sets/src/mage/cards/f/Firestorm.java +++ b/Mage.Sets/src/mage/cards/f/Firestorm.java @@ -9,6 +9,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -25,8 +26,8 @@ public final class Firestorm extends CardImpl { public Firestorm(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); - // As an additional cost to cast Firestorm, discard X cards. - this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true)); + // As an additional cost to cast this spell, discard X cards. + this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true)); // Firestorm deals X damage to each of X target creatures and/or players. this.getSpellAbility().addEffect(new FirestormEffect()); diff --git a/Mage.Sets/src/mage/cards/f/FlameholdGrappler.java b/Mage.Sets/src/mage/cards/f/FlameholdGrappler.java new file mode 100644 index 00000000000..2d96ae46614 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlameholdGrappler.java @@ -0,0 +1,46 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.CopyNextSpellDelayedTriggeredAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.keyword.FirstStrikeAbility; +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 FlameholdGrappler extends CardImpl { + + public FlameholdGrappler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // When this creature enters, copy the next spell you cast this turn when you cast it. You may choose new targets for the copy. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateDelayedTriggeredAbilityEffect( + new CopyNextSpellDelayedTriggeredAbility(StaticFilters.FILTER_SPELL) + ))); + } + + private FlameholdGrappler(final FlameholdGrappler card) { + super(card); + } + + @Override + public FlameholdGrappler copy() { + return new FlameholdGrappler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FloralEvoker.java b/Mage.Sets/src/mage/cards/f/FloralEvoker.java new file mode 100644 index 00000000000..29a9d87389b --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FloralEvoker.java @@ -0,0 +1,55 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LandfallAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInHand; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FloralEvoker extends CardImpl { + + public FloralEvoker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Landfall -- Whenever a land you control enters, put a +1/+1 counter on this creature. + this.addAbility(new LandfallAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); + + // {G}, Discard a creature card: Return target land card from your graveyard to the battlefield tapped. + Ability ability = new SimpleActivatedAbility( + new ReturnFromGraveyardToBattlefieldTargetEffect(true), new ManaCostsImpl<>("{G}") + ); + ability.addCost(new DiscardTargetCost(new TargetCardInHand(StaticFilters.FILTER_CARD_CREATURE_A))); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_LAND)); + this.addAbility(ability); + } + + private FloralEvoker(final FloralEvoker card) { + super(card); + } + + @Override + public FloralEvoker copy() { + return new FloralEvoker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FlourishingHunter.java b/Mage.Sets/src/mage/cards/f/FlourishingHunter.java index 0f4979e105a..89fc37c1059 100644 --- a/Mage.Sets/src/mage/cards/f/FlourishingHunter.java +++ b/Mage.Sets/src/mage/cards/f/FlourishingHunter.java @@ -1,21 +1,17 @@ package mage.cards.f; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.OneShotEffect; -import mage.constants.Outcome; -import mage.constants.SubType; +import mage.abilities.dynamicvalue.common.GreatestToughnessAmongControlledCreaturesValue; +import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; +import mage.constants.SubType; + +import java.util.UUID; /** - * * @author weirddan455 */ public final class FlourishingHunter extends CardImpl { @@ -29,7 +25,10 @@ public final class FlourishingHunter extends CardImpl { this.toughness = new MageInt(6); // When Flourishing Hunter enters the battlefield, you gain life equal to the greatest toughness among other creatures you control. - this.addAbility(new EntersBattlefieldTriggeredAbility(new FlourishingHunterEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new GainLifeEffect(GreatestToughnessAmongControlledCreaturesValue.OTHER) + .setText("you gain life equal to the greatest toughness among other creatures you control") + ).addHint(GreatestToughnessAmongControlledCreaturesValue.OTHER.getHint())); } private FlourishingHunter(final FlourishingHunter card) { @@ -41,39 +40,3 @@ public final class FlourishingHunter extends CardImpl { return new FlourishingHunter(this); } } - -class FlourishingHunterEffect extends OneShotEffect { - - FlourishingHunterEffect() { - super(Outcome.GainLife); - staticText = "you gain life equal to the greatest toughness among other creatures you control"; - } - - private FlourishingHunterEffect(final FlourishingHunterEffect effect) { - super(effect); - } - - @Override - public FlourishingHunterEffect copy() { - return new FlourishingHunterEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - int greatestToughness = 0; - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(controller.getId())) { - if (permanent.isCreature(game) && !permanent.getId().equals(source.getSourceId())) { - int toughness = permanent.getToughness().getValue(); - if (toughness > greatestToughness) { - greatestToughness = toughness; - } - } - } - controller.gainLife(greatestToughness, game, source); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/f/FocusTheMind.java b/Mage.Sets/src/mage/cards/f/FocusTheMind.java new file mode 100644 index 00000000000..cdce75924b6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FocusTheMind.java @@ -0,0 +1,44 @@ +package mage.cards.f; + +import java.util.UUID; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CastAnotherSpellThisTurnCondition; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; + +/** + * + * @author Jmlundeen + */ +public final class FocusTheMind extends CardImpl { + + public FocusTheMind(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{U}"); + + + // This spell costs {2} less to cast if you've cast another spell this turn. + Effect effect = new SpellCostReductionSourceEffect(2, CastAnotherSpellThisTurnCondition.instance) + .setCanWorksOnStackOnly(true); + this.addAbility(new SimpleStaticAbility(Zone.ALL, effect) + .setRuleAtTheTop(true) + .addHint(CastAnotherSpellThisTurnCondition.instance.getHint())); + + // Draw three cards, then discard a card. + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(3, 1)); + } + + private FocusTheMind(final FocusTheMind card) { + super(card); + } + + @Override + public FocusTheMind copy() { + return new FocusTheMind(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FormationBreaker.java b/Mage.Sets/src/mage/cards/f/FormationBreaker.java new file mode 100644 index 00000000000..492a6fcc11e --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FormationBreaker.java @@ -0,0 +1,86 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.permanent.CounterAnyPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FormationBreaker extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + private static final FilterPermanent filter2 = new FilterControlledCreaturePermanent(); + + static { + filter.add(FormationBreakerPredicate.instance); + filter2.add(CounterAnyPredicate.instance); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter2); + private static final Hint hint = new ConditionHint(condition, "You control a creature with a counter on it"); + + public FormationBreaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Creatures with power less than this creature's power can't block it. + this.addAbility(new SimpleStaticAbility(new CantBeBlockedByCreaturesSourceEffect(filter, Duration.WhileOnBattlefield) + .setText("creatures with power less than this creature's power can't block it"))); + + // As long as you control a creature with a counter on it, this creature gets +1/+2. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostSourceEffect(1, 2, Duration.WhileOnBattlefield), + condition, "as long as you control a creature with a counter on it, this creature gets +1/+2" + )).addHint(hint)); + } + + private FormationBreaker(final FormationBreaker card) { + super(card); + } + + @Override + public FormationBreaker copy() { + return new FormationBreaker(this); + } +} + +enum FormationBreakerPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return Optional + .ofNullable(input.getSource().getSourcePermanentIfItStillExists(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .map(x -> input.getObject().getPower().getValue() < x) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FrontierSiege.java b/Mage.Sets/src/mage/cards/f/FrontierSiege.java index 87b2e5bfcfa..c18360d64e2 100644 --- a/Mage.Sets/src/mage/cards/f/FrontierSiege.java +++ b/Mage.Sets/src/mage/cards/f/FrontierSiege.java @@ -3,17 +3,18 @@ package mage.cards.f; import mage.Mana; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseModeEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; import mage.abilities.effects.mana.AddManaToManaPoolSourceControllerEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; @@ -29,37 +30,28 @@ import java.util.UUID; */ public final class FrontierSiege extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature with flying"); + private static final FilterPermanent filter = new FilterCreaturePermanent("a creature with flying you control"); static { filter.add(TargetController.YOU.getControllerPredicate()); filter.add(new AbilityPredicate(FlyingAbility.class)); } - private static final String ruleTrigger1 = "&bull Khans — At the beginning of each of your main phases, add {G}{G}."; - private static final String ruleTrigger2 = "&bull Dragons — Whenever a creature with flying you control enters, you may have it fight target creature you don't control."; - public FrontierSiege(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); // As Frontier Siege enters the battlefield, choose Khans or Dragons. - this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Khans or Dragons?", "Khans", "Dragons"), null, - "As {this} enters, choose Khans or Dragons.", "")); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.KHANS, ModeChoice.DRAGONS))); // * Khans - At the beginning of each of your main phases, add {G}{G}. - this.addAbility(new ConditionalTriggeredAbility( - new FrontierSiegeKhansTriggeredAbility(), - new ModeChoiceSourceCondition("Khans"), - ruleTrigger1)); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(new FrontierSiegeKhansTriggeredAbility(), ModeChoice.KHANS))); // * Dragons - Whenever a creature with flying you control enters, you may have it fight target creature you don't control. - Ability ability2 = new ConditionalTriggeredAbility( - new EntersBattlefieldControlledTriggeredAbility(Zone.BATTLEFIELD, new FrontierSiegeFightEffect(), filter, true, SetTargetPointer.PERMANENT), - new ModeChoiceSourceCondition("Dragons"), - ruleTrigger2); - ability2.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); - this.addAbility(ability2); - + Ability ability = new EntersBattlefieldAllTriggeredAbility( + Zone.BATTLEFIELD, new FrontierSiegeFightEffect(), filter, true, SetTargetPointer.PERMANENT + ); + ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability, ModeChoice.DRAGONS))); } private FrontierSiege(final FrontierSiege card) { diff --git a/Mage.Sets/src/mage/cards/f/FrostcliffSiege.java b/Mage.Sets/src/mage/cards/f/FrostcliffSiege.java new file mode 100644 index 00000000000..1f4199d77e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrostcliffSiege.java @@ -0,0 +1,63 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.ChooseModeEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.ModeChoice; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author androosss + */ +public final class FrostcliffSiege extends CardImpl { + + public FrostcliffSiege(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}{R}"); + + // As this enchantment enters, choose Jeskai or Temur. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.JESKAI, ModeChoice.TEMUR))); + + // * Jeskai -- Whenever one or more creatures you control deal combat damage to a player, draw a card. + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new OneOrMoreCombatDamagePlayerTriggeredAbility(new DrawCardSourceControllerEffect(1)), ModeChoice.JESKAI + ))); + + // * Temur -- Creatures you control get +1/+0 and have trample and haste. + Ability ability = new SimpleStaticAbility( + new BoostControlledEffect(1, 0, Duration.WhileOnBattlefield) + ); + ability.addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("and have trample")); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("and haste")); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability, ModeChoice.TEMUR))); + } + + private FrostcliffSiege(final FrostcliffSiege card) { + super(card); + } + + @Override + public FrostcliffSiege copy() { + return new FrostcliffSiege(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/f/FugitiveCodebreaker.java b/Mage.Sets/src/mage/cards/f/FugitiveCodebreaker.java index e21d8c6fc0d..d38e97e54aa 100644 --- a/Mage.Sets/src/mage/cards/f/FugitiveCodebreaker.java +++ b/Mage.Sets/src/mage/cards/f/FugitiveCodebreaker.java @@ -92,7 +92,7 @@ enum FugitiveCodebreakerAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { CardUtil.reduceCost(ability, FugitiveCodebreakerDisguiseAbility.xValue.calculate(game, ability, null)); } } diff --git a/Mage.Sets/src/mage/cards/f/FugitiveOfTheJudoon.java b/Mage.Sets/src/mage/cards/f/FugitiveOfTheJudoon.java new file mode 100644 index 00000000000..05e2b69c45a --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FugitiveOfTheJudoon.java @@ -0,0 +1,82 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.abilities.common.SagaAbility; +import mage.abilities.costs.CompositeCost; +import mage.abilities.costs.common.ExileTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SagaChapter; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.AlienRhinoToken; +import mage.game.permanent.token.Human11WithWard2Token; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetControlledPermanent; + + +/** + * + * @author padfoothelix + */ +public final class FugitiveOfTheJudoon extends CardImpl { + + private static final FilterCard filter = new FilterCard("a Doctor card"); + private static final FilterControlledPermanent filterHuman = new FilterControlledPermanent(SubType.HUMAN,"a Human you control"); + private static final FilterControlledArtifactPermanent filterArtifact = new FilterControlledArtifactPermanent("an artifact you control"); + + static { + filter.add(SubType.DOCTOR.getPredicate()); + } + + public FugitiveOfTheJudoon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Create a 1/1 white Human creature token with ward {2} and a 4/4 white Alien Rhino creature token. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new CreateTokenEffect(new Human11WithWard2Token()).withAdditionalTokens(new AlienRhinoToken()) + ); + + // II -- Investigate. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new InvestigateEffect()); + + // III -- You may exile a Human you control and an artifact you control. If you do, search your library for a Doctor card, put it onto the battlefield, then shuffle. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_III, + new DoIfCostPaid( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter)), + new CompositeCost( + new ExileTargetCost(new TargetControlledPermanent(1, 1, filterHuman, true)), + new ExileTargetCost(new TargetControlledPermanent(1, 1, filterArtifact, true)), + "exile a Human you control and an artifact you control" + ), + "Exile a Human and an artifact ?" + ) + ); + + this.addAbility(sagaAbility); + + } + + private FugitiveOfTheJudoon(final FugitiveOfTheJudoon card) { + super(card); + } + + @Override + public FugitiveOfTheJudoon copy() { + return new FugitiveOfTheJudoon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FungalPlots.java b/Mage.Sets/src/mage/cards/f/FungalPlots.java index 4f9dc2b38d2..d833aba5152 100644 --- a/Mage.Sets/src/mage/cards/f/FungalPlots.java +++ b/Mage.Sets/src/mage/cards/f/FungalPlots.java @@ -1,4 +1,3 @@ - package mage.cards.f; import java.util.UUID; @@ -13,12 +12,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreatureCard; import mage.game.permanent.token.SaprolingToken; import mage.target.common.TargetCardInYourGraveyard; -import mage.target.common.TargetControlledPermanent; /** * @@ -40,7 +37,7 @@ public final class FungalPlots extends CardImpl { SimpleActivatedAbility ability = new SimpleActivatedAbility( new CreateTokenEffect(new SaprolingToken()), new ManaCostsImpl<>("{1}{G}")); - ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(filter))); + ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(filter)).withSourceExileZone(false)); this.addAbility(ability); // Sacrifice two Saprolings: You gain 2 life and draw a card. diff --git a/Mage.Sets/src/mage/cards/f/FuriousForebear.java b/Mage.Sets/src/mage/cards/f/FuriousForebear.java new file mode 100644 index 00000000000..852f999adf1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FuriousForebear.java @@ -0,0 +1,54 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; + +/** + * + * @author Jmlundeen + */ +public final class FuriousForebear extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public FuriousForebear(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Whenever a creature you control dies while this card is in your graveyard, you may pay {1}{W}. If you do, return this card from your graveyard to your hand. + Effect effect = new DoIfCostPaid(new ReturnSourceFromGraveyardToHandEffect(), new ManaCostsImpl<>("{1}{W}")); + Ability ability = new DiesCreatureTriggeredAbility(Zone.GRAVEYARD, effect,false, filter, false) + .setTriggerPhrase("Whenever a creature you control dies while this card is in your graveyard, "); + this.addAbility(ability); + } + + private FuriousForebear(final FuriousForebear card) { + super(card); + } + + @Override + public FuriousForebear copy() { + return new FuriousForebear(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FurnacePunisher.java b/Mage.Sets/src/mage/cards/f/FurnacePunisher.java index a086a692657..88d6854e5db 100644 --- a/Mage.Sets/src/mage/cards/f/FurnacePunisher.java +++ b/Mage.Sets/src/mage/cards/f/FurnacePunisher.java @@ -2,9 +2,9 @@ package mage.cards.f; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.MenaceAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -23,11 +23,10 @@ public class FurnacePunisher extends CardImpl { this.power = new MageInt(3); this.toughness = new MageInt(3); - //Menace + // Menace this.addAbility(new MenaceAbility(false)); - //At the beginning of each player’s upkeep, Furnace Punisher deals 2 damage to that player unless they control - //two or more basic lands. + // At the beginning of each player’s upkeep, Furnace Punisher deals 2 damage to that player unless they control two or more basic lands. this.addAbility(new BeginningOfUpkeepTriggeredAbility(TargetController.EACH_PLAYER, new FurnacePunisherEffect(), false ).setTriggerPhrase("At the beginning of each player's upkeep, ")); diff --git a/Mage.Sets/src/mage/cards/g/GandalfOfTheSecretFire.java b/Mage.Sets/src/mage/cards/g/GandalfOfTheSecretFire.java index 165793c9f6a..b7b93aa887f 100644 --- a/Mage.Sets/src/mage/cards/g/GandalfOfTheSecretFire.java +++ b/Mage.Sets/src/mage/cards/g/GandalfOfTheSecretFire.java @@ -119,7 +119,7 @@ class GandalfOfTheSecretFireEffect extends ReplacementEffectImpl { game.informPlayers(controller.getLogName() + " exiles " + sourceSpell.getLogName() + " with 3 time counters on it"); } if (!sourceSpell.getAbilities(game).containsClass(SuspendAbility.class)) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(sourceSpell.getCard(), game)), source); + game.addEffect(new GainSuspendEffect(new MageObjectReference(sourceSpell.getMainCard(), game)), source); } return true; } diff --git a/Mage.Sets/src/mage/cards/g/GeralfsMasterpiece.java b/Mage.Sets/src/mage/cards/g/GeralfsMasterpiece.java index 8a9f2d34e33..3cb638e14df 100644 --- a/Mage.Sets/src/mage/cards/g/GeralfsMasterpiece.java +++ b/Mage.Sets/src/mage/cards/g/GeralfsMasterpiece.java @@ -21,7 +21,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Zone; -import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.target.common.TargetCardInHand; @@ -42,7 +41,7 @@ public final class GeralfsMasterpiece extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Geralf's Masterpiece gets -1/-1 for each card in your hand. - DynamicValue count = new SignInversionDynamicValue(CardsInControllerHandCount.instance); + DynamicValue count = new SignInversionDynamicValue(CardsInControllerHandCount.ANY); Effect effect = new BoostSourceEffect(count, count, Duration.WhileOnBattlefield); effect.setText("{this} gets -1/-1 for each card in your hand"); this.addAbility(new SimpleStaticAbility(effect)); diff --git a/Mage.Sets/src/mage/cards/g/GerrardsWisdom.java b/Mage.Sets/src/mage/cards/g/GerrardsWisdom.java index f474ecfb227..aa4008901c2 100644 --- a/Mage.Sets/src/mage/cards/g/GerrardsWisdom.java +++ b/Mage.Sets/src/mage/cards/g/GerrardsWisdom.java @@ -20,7 +20,7 @@ public final class GerrardsWisdom extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{W}{W}"); // You gain 2 life for each card in your hand. - this.getSpellAbility().addEffect(new GainLifeEffect(new MultipliedValue(CardsInControllerHandCount.instance, 2), + this.getSpellAbility().addEffect(new GainLifeEffect(new MultipliedValue(CardsInControllerHandCount.ANY, 2), "You gain 2 life for each card in your hand")); } diff --git a/Mage.Sets/src/mage/cards/g/GhostfireBlade.java b/Mage.Sets/src/mage/cards/g/GhostfireBlade.java index ad2537bc363..57b42d9ba57 100644 --- a/Mage.Sets/src/mage/cards/g/GhostfireBlade.java +++ b/Mage.Sets/src/mage/cards/g/GhostfireBlade.java @@ -55,9 +55,9 @@ enum GhostfireBladeAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { - // checking state + public void reduceCost(Ability ability, Game game) { if (game.inCheckPlayableState()) { + // possible if (CardUtil .getAllPossibleTargets(ability, game) .stream() @@ -67,6 +67,7 @@ enum GhostfireBladeAdjuster implements CostAdjuster { return; } } else { + // real Permanent permanent = game.getPermanent(ability.getFirstTarget()); if (permanent == null || !permanent.getColor(game).isColorless()) { return; diff --git a/Mage.Sets/src/mage/cards/g/GlacierwoodSiege.java b/Mage.Sets/src/mage/cards/g/GlacierwoodSiege.java new file mode 100644 index 00000000000..6452cc88eb6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GlacierwoodSiege.java @@ -0,0 +1,53 @@ +package mage.cards.g; + +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.ChooseModeEffect; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; +import mage.abilities.effects.common.ruleModifying.PlayFromGraveyardControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ModeChoice; +import mage.filter.StaticFilters; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author androosss + */ +public final class GlacierwoodSiege extends CardImpl { + + public GlacierwoodSiege(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}{U}"); + + // As this enchantment enters, choose Temur or Sultai. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.TEMUR, ModeChoice.SULTAI))); + + // * Temur -- Whenever you cast an instant or sorcery spell, target player mills four cards. + Ability ability = new SpellCastControllerTriggeredAbility( + new MillCardsTargetEffect(4), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false + ); + ability.addTarget(new TargetPlayer()); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability, ModeChoice.TEMUR))); + + // * Sultai -- You may play lands from your graveyard. + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + PlayFromGraveyardControllerEffect.playLands(), ModeChoice.SULTAI + ))); + } + + private GlacierwoodSiege(final GlacierwoodSiege card) { + super(card); + } + + @Override + public GlacierwoodSiege copy() { + return new GlacierwoodSiege(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GlintWeaver.java b/Mage.Sets/src/mage/cards/g/GlintWeaver.java index f99e13ef97a..626cd6f719c 100644 --- a/Mage.Sets/src/mage/cards/g/GlintWeaver.java +++ b/Mage.Sets/src/mage/cards/g/GlintWeaver.java @@ -32,10 +32,10 @@ public final class GlintWeaver extends CardImpl { // When Glint Weaver enters the battlefield, distribute three +1/+1 counters among one, two, or three target creatures, then you gain life equal to the greatest toughness among creatures you control. Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect()); - ability.addEffect(new GainLifeEffect(GreatestToughnessAmongControlledCreaturesValue.instance) + ability.addEffect(new GainLifeEffect(GreatestToughnessAmongControlledCreaturesValue.ALL) .setText(", then you gain life equal to the greatest toughness among creatures you control")); ability.addTarget(new TargetCreaturePermanentAmount(3)); - this.addAbility(ability); + this.addAbility(ability.addHint(GreatestToughnessAmongControlledCreaturesValue.ALL.getHint())); } private GlintWeaver(final GlintWeaver card) { diff --git a/Mage.Sets/src/mage/cards/g/GoldlustTriad.java b/Mage.Sets/src/mage/cards/g/GoldlustTriad.java new file mode 100644 index 00000000000..68e84e4f8cf --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GoldlustTriad.java @@ -0,0 +1,46 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.MyriadAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GoldlustTriad extends CardImpl { + + public GoldlustTriad(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Myriad + this.addAbility(new MyriadAbility()); + + // Whenever this creature deals combat damage to a player, create a Treasure token. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new CreateTokenEffect(new TreasureToken()))); + } + + private GoldlustTriad(final GoldlustTriad card) { + super(card); + } + + @Override + public GoldlustTriad copy() { + return new GoldlustTriad(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GracefulRestoration.java b/Mage.Sets/src/mage/cards/g/GracefulRestoration.java index fed8e25db2f..3421be44ed7 100644 --- a/Mage.Sets/src/mage/cards/g/GracefulRestoration.java +++ b/Mage.Sets/src/mage/cards/g/GracefulRestoration.java @@ -32,7 +32,7 @@ public final class GracefulRestoration extends CardImpl { // Choose one — // • Return target creature card from your graveyard to the battlefield with an additional +1/+1 counter on it. - this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.P1P1.createInstance(), true)); + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(true, CounterType.P1P1.createInstance())); this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); // • Return up to two target creature cards with power 2 or less from your graveyard to the battlefield. diff --git a/Mage.Sets/src/mage/cards/g/GrimGiganotosaurus.java b/Mage.Sets/src/mage/cards/g/GrimGiganotosaurus.java index f67309915c5..8d4cb8ba558 100644 --- a/Mage.Sets/src/mage/cards/g/GrimGiganotosaurus.java +++ b/Mage.Sets/src/mage/cards/g/GrimGiganotosaurus.java @@ -74,7 +74,7 @@ enum GrimGiganotosaurusAdjuster implements CostAdjuster { private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { Player controller = game.getPlayer(ability.getControllerId()); if (controller != null) { CardUtil.reduceCost(ability, xValue.calculate(game, ability, null)); diff --git a/Mage.Sets/src/mage/cards/g/GrimStrider.java b/Mage.Sets/src/mage/cards/g/GrimStrider.java index d1bea4bd1ee..34fb24c0a18 100644 --- a/Mage.Sets/src/mage/cards/g/GrimStrider.java +++ b/Mage.Sets/src/mage/cards/g/GrimStrider.java @@ -14,7 +14,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; /** * @@ -29,7 +28,7 @@ public final class GrimStrider extends CardImpl { this.toughness = new MageInt(6); // Grim Strider gets -1/-1 for each card in your hand. - DynamicValue count = new SignInversionDynamicValue(CardsInControllerHandCount.instance); + DynamicValue count = new SignInversionDynamicValue(CardsInControllerHandCount.ANY); Effect effect = new BoostSourceEffect(count, count, Duration.WhileOnBattlefield); effect.setText("{this} gets -1/-1 for each card in your hand"); this.addAbility(new SimpleStaticAbility(effect)); diff --git a/Mage.Sets/src/mage/cards/h/HamletGlutton.java b/Mage.Sets/src/mage/cards/h/HamletGlutton.java index 2108c8ad0a4..703877d465b 100644 --- a/Mage.Sets/src/mage/cards/h/HamletGlutton.java +++ b/Mage.Sets/src/mage/cards/h/HamletGlutton.java @@ -61,7 +61,7 @@ enum HamletGluttonAdjuster implements CostAdjuster { private static OptionalAdditionalCost bargainCost = BargainAbility.makeBargainCost(); @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (BargainedCondition.instance.apply(game, ability) || (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) { CardUtil.reduceCost(ability, 2); diff --git a/Mage.Sets/src/mage/cards/h/HammerheadTyrant.java b/Mage.Sets/src/mage/cards/h/HammerheadTyrant.java new file mode 100644 index 00000000000..0e10903c3a9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HammerheadTyrant.java @@ -0,0 +1,88 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.keyword.FlyingAbility; +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.StaticFilters; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HammerheadTyrant extends CardImpl { + + private static final FilterPermanent filter = new FilterNonlandPermanent( + "nonland permanent an opponent controls with mana value less than or equal to that spell's mana value" + ); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(HammerheadTyrantPredicate.instance); + } + + public HammerheadTyrant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast a spell, return up to one target nonland permanent an opponent controls with mana value less than or equal to that spell's mana value to its owner's hand. + Ability ability = new SpellCastControllerTriggeredAbility( + new ReturnToHandTargetEffect(), StaticFilters.FILTER_SPELL_A, false + ); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + } + + private HammerheadTyrant(final HammerheadTyrant card) { + super(card); + } + + @Override + public HammerheadTyrant copy() { + return new HammerheadTyrant(this); + } +} + +enum HammerheadTyrantPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input + .getObject() + .getManaValue() + <= CardUtil + .castStream( + input.getSource() + .getEffects() + .stream() + .map(effect -> effect.getValue("spellCast")), + Spell.class + ) + .findFirst() + .map(Spell::getManaValue) + .orElse(-1); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HandOfVecna.java b/Mage.Sets/src/mage/cards/h/HandOfVecna.java index 34a8024e9db..1e35ab4b8a2 100644 --- a/Mage.Sets/src/mage/cards/h/HandOfVecna.java +++ b/Mage.Sets/src/mage/cards/h/HandOfVecna.java @@ -47,7 +47,7 @@ public final class HandOfVecna extends CardImpl { this.addAbility(new EquipAbility( Outcome.Benefit, new PayLifeCost( - CardsInControllerHandCount.instance, "1 life for each card in your hand"), + CardsInControllerHandCount.ANY, "1 life for each card in your hand"), false )); diff --git a/Mage.Sets/src/mage/cards/h/HelmOfObedience.java b/Mage.Sets/src/mage/cards/h/HelmOfObedience.java index a0498fd6251..469d26fcb8a 100644 --- a/Mage.Sets/src/mage/cards/h/HelmOfObedience.java +++ b/Mage.Sets/src/mage/cards/h/HelmOfObedience.java @@ -2,9 +2,8 @@ package mage.cards.h; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.VariableCostType; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.mana.VariableManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.OneShotEffect; import mage.cards.*; @@ -29,9 +28,8 @@ public final class HelmOfObedience extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); // {X}, {T}: Target opponent puts cards from the top of their library into their graveyard until a creature card or X cards are put into that graveyard this way, whichever comes first. If a creature card is put into that graveyard this way, sacrifice Helm of Obedience and put that card onto the battlefield under your control. X can't be 0. - VariableManaCost xCosts = new VariableManaCost(VariableCostType.NORMAL); - xCosts.setMinX(1); - Ability ability = new SimpleActivatedAbility(new HelmOfObedienceEffect(), xCosts); + Ability ability = new SimpleActivatedAbility(new HelmOfObedienceEffect(), new ManaCostsImpl<>("{X}")); + ability.setVariableCostsMinMax(1, Integer.MAX_VALUE); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetOpponent()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/h/HerdHeirloom.java b/Mage.Sets/src/mage/cards/h/HerdHeirloom.java new file mode 100644 index 00000000000..e3c47f7abdf --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HerdHeirloom.java @@ -0,0 +1,72 @@ +package mage.cards.h; + +import mage.ConditionalMana; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.mana.ConditionalAnyColorManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.abilities.mana.conditional.CreatureCastConditionalMana; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HerdHeirloom extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("creature you control with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + public HerdHeirloom(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{G}"); + + // {T}: Add one mana of any color. Spend this mana only to cast a creature spell. + this.addAbility(new ConditionalAnyColorManaAbility(1, new HerdHeirloomManaBuilder())); + + // {T}: Until end of turn, target creature you control with power 4 or greater gains trample and "Whenever this creature deals combat damage to a player, draw a card." + Ability ability = new SimpleActivatedAbility(new GainAbilityTargetEffect(TrampleAbility.getInstance()) + .setText("until end of turn, target creature you control with power 4 or greater gains trample"), new TapSourceCost()); + ability.addEffect(new GainAbilityTargetEffect(new DealsCombatDamageToAPlayerTriggeredAbility(new DrawCardSourceControllerEffect(1))) + .setText("and \"Whenever this creature deals combat damage to a player, draw a card.\"")); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private HerdHeirloom(final HerdHeirloom card) { + super(card); + } + + @Override + public HerdHeirloom copy() { + return new HerdHeirloom(this); + } +} + +class HerdHeirloomManaBuilder extends ConditionalManaBuilder { + @Override + public ConditionalMana build(Object... options) { + return new CreatureCastConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to cast a creature spell"; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HighspireBellRinger.java b/Mage.Sets/src/mage/cards/h/HighspireBellRinger.java new file mode 100644 index 00000000000..c42a4e7c72c --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HighspireBellRinger.java @@ -0,0 +1,48 @@ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.YouCastExactOneSpellThisTurnCondition; +import mage.abilities.decorator.ConditionalCostModificationEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author androosss + */ +public final class HighspireBellRinger extends CardImpl { + + public HighspireBellRinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.DJINN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // The second spell you cast each turn costs {1} less to cast. + this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( + new SpellsCostReductionControllerEffect(StaticFilters.FILTER_CARD, 1), + YouCastExactOneSpellThisTurnCondition.instance, "the second spell you cast each turn costs {1} less to cast" + ))); + } + + private HighspireBellRinger(final HighspireBellRinger card) { + super(card); + } + + @Override + public HighspireBellRinger copy() { + return new HighspireBellRinger(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/h/HinataDawnCrowned.java b/Mage.Sets/src/mage/cards/h/HinataDawnCrowned.java index a685b776c34..27fa375465f 100644 --- a/Mage.Sets/src/mage/cards/h/HinataDawnCrowned.java +++ b/Mage.Sets/src/mage/cards/h/HinataDawnCrowned.java @@ -117,6 +117,10 @@ final class HinataDawnCrownedEffectUtility public static int getTargetCount(Game game, Ability abilityToModify) { if (game.inCheckPlayableState()) { + abilityToModify.getTargets().stream() + .mapToInt(a -> !a.isRequired() ? 0 : a.getMinNumberOfTargets()) + .min() + .orElse(0); Optional max = abilityToModify.getTargets().stream().map(x -> x.getMaxNumberOfTargets()).max(Integer::compare); int allPossibleSize = CardUtil.getAllPossibleTargets(abilityToModify, game).size(); return max.isPresent() ? diff --git a/Mage.Sets/src/mage/cards/h/HollowmurkSiege.java b/Mage.Sets/src/mage/cards/h/HollowmurkSiege.java new file mode 100644 index 00000000000..30836b1ff79 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HollowmurkSiege.java @@ -0,0 +1,94 @@ +package mage.cards.h; + +import java.util.Optional; +import java.util.UUID; + +import mage.abilities.TriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.ChooseModeEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ModeChoice; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +/** + * + * @author androosss + */ +public final class HollowmurkSiege extends CardImpl { + + public HollowmurkSiege(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}{G}"); + + // As this enchantment enters, choose Sultai or Abzan. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.SULTAI, ModeChoice.ABZAN))); + + // * Sultai -- Whenever a counter is put on a creature you control, draw a card. + // This ability triggers only once each turn. + this.addAbility(new SimpleStaticAbility( + new GainAnchorWordAbilitySourceEffect(new HollowmurkSiegeSultaiTriggeredAbility(), ModeChoice.SULTAI))); + + // * Abzan -- Whenever you attack, put a +1/+1 counter on target attacking + // creature. It gains menace until end of turn. + TriggeredAbility abzanAbility = new AttacksWithCreaturesTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(1)), 1); + abzanAbility.addEffect( + new GainAbilityTargetEffect(new MenaceAbility(false)).setText("It gains menace until end of turn")); + abzanAbility.addTarget(new TargetPermanent(StaticFilters.FILTER_ATTACKING_CREATURE)); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(abzanAbility, ModeChoice.ABZAN))); + } + + private HollowmurkSiege(final HollowmurkSiege card) { + super(card); + } + + @Override + public HollowmurkSiege copy() { + return new HollowmurkSiege(this); + } +} + +class HollowmurkSiegeSultaiTriggeredAbility extends TriggeredAbilityImpl { + + HollowmurkSiegeSultaiTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); + setTriggerPhrase("Whenever a counter is put on a creature you control, "); + setTriggersLimitEachTurn(1); + } + + private HollowmurkSiegeSultaiTriggeredAbility(final HollowmurkSiegeSultaiTriggeredAbility ability) { + super(ability); + } + + @Override + public HollowmurkSiegeSultaiTriggeredAbility copy() { + return new HollowmurkSiegeSultaiTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTER_ADDED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = Optional.ofNullable(game.getPermanentOrLKIBattlefield(event.getTargetId())).orElse(game.getPermanentEntering(event.getTargetId())); + + return permanent != null && permanent.isCreature(game) && permanent.isControlledBy(getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HostOfTheHereafter.java b/Mage.Sets/src/mage/cards/h/HostOfTheHereafter.java new file mode 100644 index 00000000000..d2d9ffd0d69 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HostOfTheHereafter.java @@ -0,0 +1,99 @@ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.MoveCountersFromSourceToTargetEffect; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.counters.Counters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.CounterAnyPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author Jmlundeen + */ +public final class HostOfTheHereafter extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("this creature or another creature you control"); + + static { + filter.add(CounterAnyPredicate.instance); + } + + public HostOfTheHereafter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{G}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // This creature enters with two +1/+1 counters on it. + this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + null, "This creature enters with two +1/+1 counters on it.", "") + ); + + // Whenever this creature or another creature you control dies, if it had counters on it, put its counters on up to one target creature you control. + Ability ability = new DiesCreatureTriggeredAbility(new HostOfTheHereafterDiesEffect(), false, filter) + .setTriggerPhrase("Whenever " + filter.getMessage() + " dies, if it had counters on it, "); + ability.addTarget(new TargetControlledCreaturePermanent(0, 1)); + this.addAbility(ability); + } + + private HostOfTheHereafter(final HostOfTheHereafter card) { + super(card); + } + + @Override + public HostOfTheHereafter copy() { + return new HostOfTheHereafter(this); + } +} + +class HostOfTheHereafterDiesEffect extends OneShotEffect { + + HostOfTheHereafterDiesEffect() { + super(Outcome.Benefit); + staticText = "put its counters on up to one target creature you control"; + } + + private HostOfTheHereafterDiesEffect(final HostOfTheHereafterDiesEffect effect) { + super(effect); + } + + @Override + public HostOfTheHereafterDiesEffect copy() { + return new HostOfTheHereafterDiesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + Permanent deadCreature = (Permanent) this.getValue("creatureDied"); + if (permanent == null || deadCreature == null) { + return false; + } + Counters counters = deadCreature.getCounters(game); + counters.values().stream() + .filter(counter -> counter.getCount() > 0) + .forEach(counter -> permanent.addCounters(counter, source, game)); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HuatliTheSunsHeart.java b/Mage.Sets/src/mage/cards/h/HuatliTheSunsHeart.java index d184a7ef852..37853526cdd 100644 --- a/Mage.Sets/src/mage/cards/h/HuatliTheSunsHeart.java +++ b/Mage.Sets/src/mage/cards/h/HuatliTheSunsHeart.java @@ -30,9 +30,9 @@ public final class HuatliTheSunsHeart extends CardImpl { // -3: You gain life equal to the greatest toughness among creatures you control. this.addAbility(new LoyaltyAbility(new GainLifeEffect( - GreatestToughnessAmongControlledCreaturesValue.instance, + GreatestToughnessAmongControlledCreaturesValue.ALL, "You gain life equal to the greatest toughness among creatures you control" - ), -3)); + ), -3).addHint(GreatestToughnessAmongControlledCreaturesValue.ALL.getHint())); } private HuatliTheSunsHeart(final HuatliTheSunsHeart card) { diff --git a/Mage.Sets/src/mage/cards/h/HundredBattleVeteran.java b/Mage.Sets/src/mage/cards/h/HundredBattleVeteran.java new file mode 100644 index 00000000000..e56ac094eca --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HundredBattleVeteran.java @@ -0,0 +1,109 @@ +package mage.cards.h; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.AbilityImpl; +import mage.abilities.SpellAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.MayCastFromGraveyardSourceAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CastFromGraveyardSourceCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.EntersBattlefieldEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.Card; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; + +/** + * + * @author Jmlundeen + */ +public final class HundredBattleVeteran extends CardImpl { + + private static final Hint hint = new ValueHint("different kinds of counters among creatures you control", CounterTypeCount.instance); + + public HundredBattleVeteran(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // As long as there are three or more different kinds of counters among creatures you control, this creature gets +2/+4. + Effect effect = new ConditionalContinuousEffect( + new BoostSourceEffect(2, 4, Duration.WhileOnBattlefield), + HundredBattleVeteranCondition.instance, + "As long as there are three or more different kinds of counters among creatures you control, this creature gets +2/+4" + ); + this.addAbility(new SimpleStaticAbility(effect).addHint(hint)); + + // You may cast this card from your graveyard. If you do, it enters with a finality counter on it. + AbilityImpl ability = new MayCastFromGraveyardSourceAbility(); + ability.appendToRule(" If you do, it enters with a finality counter on it."); + Effect effect1 = new AddCountersSourceEffect(CounterType.FINALITY.createInstance(), StaticValue.get(1)); + ability.addSubAbility(new EntersBattlefieldAbility(effect1, CastFromGraveyardSourceCondition.instance, "", "") + .setRuleVisible(false)); + this.addAbility(ability); + + } + + private HundredBattleVeteran(final HundredBattleVeteran card) { + super(card); + } + + @Override + public HundredBattleVeteran copy() { + return new HundredBattleVeteran(this); + } +} + +enum CounterTypeCount implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, sourceAbility.getControllerId(), game) + .stream() + .filter(permanent -> !permanent.getCounters(game).isEmpty()) + .mapToInt(permanent -> permanent.getCounters(game).size()) + .sum(); + } + + @Override + public CounterTypeCount copy() { + return this; + } + + @Override + public String getMessage() { + return "different kinds of counters among creatures you control"; + } +} + +enum HundredBattleVeteranCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + int sum = CounterTypeCount.instance.calculate(game, source, null); + return sum >= 3; + } +} + + diff --git a/Mage.Sets/src/mage/cards/h/HurkylsFinalMeditation.java b/Mage.Sets/src/mage/cards/h/HurkylsFinalMeditation.java index 4b3aa279774..3a7a896563f 100644 --- a/Mage.Sets/src/mage/cards/h/HurkylsFinalMeditation.java +++ b/Mage.Sets/src/mage/cards/h/HurkylsFinalMeditation.java @@ -49,7 +49,7 @@ enum HurkylsFinalMeditationAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void increaseCost(Ability ability, Game game) { if (!game.isActivePlayer(ability.getControllerId())) { CardUtil.increaseCost(ability, 3); } diff --git a/Mage.Sets/src/mage/cards/h/HyldasCrownOfWinter.java b/Mage.Sets/src/mage/cards/h/HyldasCrownOfWinter.java index 51d266276c8..0dc2a950b17 100644 --- a/Mage.Sets/src/mage/cards/h/HyldasCrownOfWinter.java +++ b/Mage.Sets/src/mage/cards/h/HyldasCrownOfWinter.java @@ -82,7 +82,7 @@ enum HyldasCrownOfWinterAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (ability.getControllerId().equals(game.getActivePlayerId())) { CardUtil.reduceCost(ability, 1); } diff --git a/Mage.Sets/src/mage/cards/i/IceOut.java b/Mage.Sets/src/mage/cards/i/IceOut.java index f98ed44b76e..6d4dcf0eeb3 100644 --- a/Mage.Sets/src/mage/cards/i/IceOut.java +++ b/Mage.Sets/src/mage/cards/i/IceOut.java @@ -52,7 +52,7 @@ enum IceOutAdjuster implements CostAdjuster { private static OptionalAdditionalCost bargainCost = BargainAbility.makeBargainCost(); @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (BargainedCondition.instance.apply(game, ability) || (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) { CardUtil.reduceCost(ability, 1); diff --git a/Mage.Sets/src/mage/cards/i/InfantryShield.java b/Mage.Sets/src/mage/cards/i/InfantryShield.java new file mode 100644 index 00000000000..7c776a83fd7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InfantryShield.java @@ -0,0 +1,49 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.MobilizeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InfantryShield extends CardImpl { + + public InfantryShield(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature has menace and mobilize X, where X is its power. + Ability ability = new SimpleStaticAbility(new GainAbilityAttachedEffect( + new MenaceAbility(false), AttachmentType.EQUIPMENT + )); + ability.addEffect(new GainAbilityAttachedEffect( + new MobilizeAbility(SourcePermanentPowerValue.NOT_NEGATIVE), AttachmentType.EQUIPMENT + ).setText("and mobilize X, where X is its power")); + this.addAbility(ability); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private InfantryShield(final InfantryShield card) { + super(card); + } + + @Override + public InfantryShield copy() { + return new InfantryShield(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InnerCalmOuterStrength.java b/Mage.Sets/src/mage/cards/i/InnerCalmOuterStrength.java index a38e294cc14..5d95823f88b 100644 --- a/Mage.Sets/src/mage/cards/i/InnerCalmOuterStrength.java +++ b/Mage.Sets/src/mage/cards/i/InnerCalmOuterStrength.java @@ -24,7 +24,7 @@ public final class InnerCalmOuterStrength extends CardImpl { this.subtype.add(SubType.ARCANE); // Target creature gets +X/+X until end of turn, where X is the number of cards in your hand. - DynamicValue xValue= CardsInControllerHandCount.instance; + DynamicValue xValue= CardsInControllerHandCount.ANY; Effect effect = new BoostTargetEffect(xValue, xValue, Duration.EndOfTurn); effect.setText("Target creature gets +X/+X until end of turn, where X is the number of cards in your hand"); this.getSpellAbility().addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/i/InnerFire.java b/Mage.Sets/src/mage/cards/i/InnerFire.java index f7d9754164b..1e286f7e104 100644 --- a/Mage.Sets/src/mage/cards/i/InnerFire.java +++ b/Mage.Sets/src/mage/cards/i/InnerFire.java @@ -20,7 +20,7 @@ public final class InnerFire extends CardImpl { // Add {R} for each card in your hand. - this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.RedMana(1), CardsInControllerHandCount.instance)); + this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.RedMana(1), CardsInControllerHandCount.ANY)); } private InnerFire(final InnerFire card) { diff --git a/Mage.Sets/src/mage/cards/i/InsidiousDreams.java b/Mage.Sets/src/mage/cards/i/InsidiousDreams.java index d435205d5de..6ec4080d071 100644 --- a/Mage.Sets/src/mage/cards/i/InsidiousDreams.java +++ b/Mage.Sets/src/mage/cards/i/InsidiousDreams.java @@ -25,7 +25,7 @@ public final class InsidiousDreams extends CardImpl { public InsidiousDreams(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}"); - // As an additional cost to cast Insidious Dreams, discard X cards. + // As an additional cost to cast this spell, discard X cards. this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true)); // Search your library for X cards. Then shuffle your library and put those cards on top of it in any order. diff --git a/Mage.Sets/src/mage/cards/i/IronwillForger.java b/Mage.Sets/src/mage/cards/i/IronwillForger.java new file mode 100644 index 00000000000..e933eeef99e --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IronwillForger.java @@ -0,0 +1,57 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.common.ControlYourCommanderCondition; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.MyriadAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +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.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IronwillForger extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("nonlegendary creature you control"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public IronwillForger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.ORC); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Lieutenant -- At the beginning of combat on your turn, if you control your commander, target nonlegendary creature you control gains myriad until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility( + new GainAbilityTargetEffect(new MyriadAbility(false)) + ).withInterveningIf(ControlYourCommanderCondition.instance); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability.setAbilityWord(AbilityWord.LIEUTENANT)); + } + + private IronwillForger(final IronwillForger card) { + super(card); + } + + @Override + public IronwillForger copy() { + return new IronwillForger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IsshinTwoHeavensAsOne.java b/Mage.Sets/src/mage/cards/i/IsshinTwoHeavensAsOne.java index 31f9cf1c370..689ed56b8b3 100644 --- a/Mage.Sets/src/mage/cards/i/IsshinTwoHeavensAsOne.java +++ b/Mage.Sets/src/mage/cards/i/IsshinTwoHeavensAsOne.java @@ -1,16 +1,13 @@ package mage.cards.i; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.replacement.AdditionalTriggersAttackingReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.NumberOfTriggersEvent; -import mage.game.permanent.Permanent; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import java.util.UUID; @@ -29,7 +26,7 @@ public final class IsshinTwoHeavensAsOne extends CardImpl { this.toughness = new MageInt(4); // If a creature attacking causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. - this.addAbility(new SimpleStaticAbility(new IsshinTwoHeavensAsOneEffect())); + this.addAbility(new SimpleStaticAbility(new AdditionalTriggersAttackingReplacementEffect(false))); } private IsshinTwoHeavensAsOne(final IsshinTwoHeavensAsOne card) { @@ -41,54 +38,3 @@ public final class IsshinTwoHeavensAsOne extends CardImpl { return new IsshinTwoHeavensAsOne(this); } } - -class IsshinTwoHeavensAsOneEffect extends ReplacementEffectImpl { - - IsshinTwoHeavensAsOneEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "if a creature attacking causes a triggered ability " + - "of a permanent you control to trigger, that ability triggers an additional time"; - } - - private IsshinTwoHeavensAsOneEffect(final IsshinTwoHeavensAsOneEffect effect) { - super(effect); - } - - @Override - public IsshinTwoHeavensAsOneEffect copy() { - return new IsshinTwoHeavensAsOneEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; - Permanent sourcePermanent = game.getPermanent(numberOfTriggersEvent.getSourceId()); - if (sourcePermanent == null || !sourcePermanent.isControlledBy(source.getControllerId())) { - return false; - } - - GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); - if (sourceEvent == null) { - return false; - } - - switch (sourceEvent.getType()) { - case ATTACKER_DECLARED: - case DECLARED_ATTACKERS: - case DEFENDER_ATTACKED: - return true; - } - return false; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() + 1); - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/j/JawsOfDefeat.java b/Mage.Sets/src/mage/cards/j/JawsOfDefeat.java new file mode 100644 index 00000000000..91ec5094f99 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JawsOfDefeat.java @@ -0,0 +1,70 @@ +package mage.cards.j; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JawsOfDefeat extends CardImpl { + + public JawsOfDefeat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}"); + + // Whenever a creature you control enters, target opponent loses life equal to the difference between that creature's power and its toughness. + Ability ability = new EntersBattlefieldAllTriggeredAbility( + new JawsOfDefeatEffect(), StaticFilters.FILTER_CONTROLLED_A_CREATURE + ); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private JawsOfDefeat(final JawsOfDefeat card) { + super(card); + } + + @Override + public JawsOfDefeat copy() { + return new JawsOfDefeat(this); + } +} + +class JawsOfDefeatEffect extends OneShotEffect { + + JawsOfDefeatEffect() { + super(Outcome.Benefit); + staticText = "target opponent loses life equal to the difference between that creature's power and its toughness"; + } + + private JawsOfDefeatEffect(final JawsOfDefeatEffect effect) { + super(effect); + } + + @Override + public JawsOfDefeatEffect copy() { + return new JawsOfDefeatEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + Permanent permanent = (Permanent) getValue("permanentEnteringBattlefield"); + if (player == null || permanent == null) { + return false; + } + int diff = Math.abs(permanent.getToughness().getValue() - permanent.getPower().getValue()); + return diff > 0 && player.loseLife(diff, game, source, false) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/j/JeskaiRevelation.java b/Mage.Sets/src/mage/cards/j/JeskaiRevelation.java new file mode 100644 index 00000000000..5a4a488cc07 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JeskaiRevelation.java @@ -0,0 +1,42 @@ +package mage.cards.j; + +import mage.abilities.effects.common.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.MonasteryMentorToken; +import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetSpellOrPermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JeskaiRevelation extends CardImpl { + + public JeskaiRevelation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{U}{R}{W}"); + + // Return target spell or permanent to its owner's hand. Jeskai Revelation deals 4 damage to any target. Create two 1/1 white Monk creature tokens with prowess. Draw two cards. You gain 4 life. + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetSpellOrPermanent()); + this.getSpellAbility().addEffect(new DamageTargetEffect( + 4, true, "any target", true + ).setTargetPointer(new SecondTargetPointer())); + this.getSpellAbility().addTarget(new TargetAnyTarget()); + this.getSpellAbility().addEffect(new CreateTokenEffect(new MonasteryMentorToken(), 2)); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + this.getSpellAbility().addEffect(new GainLifeEffect(4)); + } + + private JeskaiRevelation(final JeskaiRevelation card) { + super(card); + } + + @Override + public JeskaiRevelation copy() { + return new JeskaiRevelation(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JohannsStopgap.java b/Mage.Sets/src/mage/cards/j/JohannsStopgap.java index 8f08fa1a1ef..fc34f2aa974 100644 --- a/Mage.Sets/src/mage/cards/j/JohannsStopgap.java +++ b/Mage.Sets/src/mage/cards/j/JohannsStopgap.java @@ -54,7 +54,7 @@ enum JohannsStopgapAdjuster implements CostAdjuster { private static OptionalAdditionalCost bargainCost = BargainAbility.makeBargainCost(); @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (BargainedCondition.instance.apply(game, ability) || (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) { CardUtil.reduceCost(ability, 2); diff --git a/Mage.Sets/src/mage/cards/j/JolraelMwonvuliRecluse.java b/Mage.Sets/src/mage/cards/j/JolraelMwonvuliRecluse.java index 05e8ecc72e3..c3f0d6fc4ad 100644 --- a/Mage.Sets/src/mage/cards/j/JolraelMwonvuliRecluse.java +++ b/Mage.Sets/src/mage/cards/j/JolraelMwonvuliRecluse.java @@ -37,7 +37,7 @@ public final class JolraelMwonvuliRecluse extends CardImpl { // {4}{G}{G}: Until end of turn, creatures you control have base power and toughness X/X, where X is the number of cards in your hand. this.addAbility(new SimpleActivatedAbility(new SetBasePowerToughnessAllEffect( - CardsInControllerHandCount.instance, CardsInControllerHandCount.instance, + CardsInControllerHandCount.ANY, CardsInControllerHandCount.ANY, Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES ).setText("until end of turn, creatures you control have base power and toughness X/X, " + "where X is the number of cards in your hand"), new ManaCostsImpl<>("{4}{G}{G}"))); diff --git a/Mage.Sets/src/mage/cards/j/JushiApprentice.java b/Mage.Sets/src/mage/cards/j/JushiApprentice.java index d8162a7c2a9..3f6e98d144f 100644 --- a/Mage.Sets/src/mage/cards/j/JushiApprentice.java +++ b/Mage.Sets/src/mage/cards/j/JushiApprentice.java @@ -19,7 +19,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.ComparisonType; import mage.constants.SuperType; -import mage.constants.Zone; import mage.game.permanent.token.TokenImpl; import mage.target.TargetPlayer; @@ -70,7 +69,7 @@ class TomoyaTheRevealer extends TokenImpl { toughness = new MageInt(3); // {3}{U}{U},{T} : Target player draws X cards, where X is the number of cards in your hand. - Ability ability = new SimpleActivatedAbility(new DrawCardTargetEffect(CardsInControllerHandCount.instance), new ManaCostsImpl<>("{3}{U}{U}")); + Ability ability = new SimpleActivatedAbility(new DrawCardTargetEffect(CardsInControllerHandCount.ANY), new ManaCostsImpl<>("{3}{U}{U}")); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetPlayer()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/k/KagemaroFirstToSuffer.java b/Mage.Sets/src/mage/cards/k/KagemaroFirstToSuffer.java index 15349f4586f..fc6bb0e6d8a 100644 --- a/Mage.Sets/src/mage/cards/k/KagemaroFirstToSuffer.java +++ b/Mage.Sets/src/mage/cards/k/KagemaroFirstToSuffer.java @@ -37,7 +37,7 @@ public final class KagemaroFirstToSuffer extends CardImpl { this.power = new MageInt(0); this.toughness = new MageInt(0); - DynamicValue xValue = CardsInControllerHandCount.instance; + DynamicValue xValue = CardsInControllerHandCount.ANY; // Kagemaro, First to Suffer's power and toughness are each equal to the number of cards in your hand. this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(xValue))); // {B}, Sacrifice Kagemaro: All creatures get -X/-X until end of turn, where X is the number of cards in your hand. diff --git a/Mage.Sets/src/mage/cards/k/KagemarosClutch.java b/Mage.Sets/src/mage/cards/k/KagemarosClutch.java index e90a6ef1026..e5c94d58fcf 100644 --- a/Mage.Sets/src/mage/cards/k/KagemarosClutch.java +++ b/Mage.Sets/src/mage/cards/k/KagemarosClutch.java @@ -17,7 +17,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -40,7 +39,7 @@ public final class KagemarosClutch extends CardImpl { this.addAbility(ability); // Enchanted creature gets -X/-X, where X is the number of cards in your hand. - DynamicValue xMinusValue = new SignInversionDynamicValue(CardsInControllerHandCount.instance); + DynamicValue xMinusValue = new SignInversionDynamicValue(CardsInControllerHandCount.ANY); Effect effect = new BoostEnchantedEffect(xMinusValue, xMinusValue, Duration.WhileOnBattlefield); effect.setText("Enchanted creature gets -X/-X, where X is the number of cards in your hand"); this.addAbility(new SimpleStaticAbility(effect)); diff --git a/Mage.Sets/src/mage/cards/k/KamiOfJealousThirst.java b/Mage.Sets/src/mage/cards/k/KamiOfJealousThirst.java index fcd4bf15822..934bf79e8fb 100644 --- a/Mage.Sets/src/mage/cards/k/KamiOfJealousThirst.java +++ b/Mage.Sets/src/mage/cards/k/KamiOfJealousThirst.java @@ -60,7 +60,7 @@ enum KamiOfJealousThirstAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void increaseCost(Ability ability, Game game) { int amount = CardsDrawnThisTurnDynamicValue.instance.calculate(game, ability, null); if (amount >= 3) { CardUtil.adjustCost(ability, new ManaCostsImpl<>("{4}{B}"), false); diff --git a/Mage.Sets/src/mage/cards/k/KarakykGuardian.java b/Mage.Sets/src/mage/cards/k/KarakykGuardian.java new file mode 100644 index 00000000000..6fd569725a6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KarakykGuardian.java @@ -0,0 +1,110 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KarakykGuardian extends CardImpl { + + public KarakykGuardian(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{U}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // This creature has hexproof if it hasn't dealt damage yet. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(HexproofAbility.getInstance()), + KarakykGuardianCondition.instance, "{this} has hexproof if it hasn't dealt damage yet" + )), new KarakykGuardianWatcher()); + } + + private KarakykGuardian(final KarakykGuardian card) { + super(card); + } + + @Override + public KarakykGuardian copy() { + return new KarakykGuardian(this); + } +} + +enum KarakykGuardianCondition implements Condition { + + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getSourceId()); + KarakykGuardianWatcher watcher = game.getState().getWatcher(KarakykGuardianWatcher.class); + return permanent != null && !watcher.getDamagers().contains(new MageObjectReference(permanent, game)); + } + + @Override + public String toString() { + return "{this} hasn't dealt damage yet"; + } + +} + +class KarakykGuardianWatcher extends Watcher { + + private final Set damagers = new HashSet<>(); + + public KarakykGuardianWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGED_PERMANENT: + case DAMAGED_PLAYER: + break; + default: + return; + } + Permanent permanent = game.getPermanent(event.getSourceId()); + if (permanent != null) { + damagers.add(new MageObjectReference(permanent, game)); + } + } + + public Set getDamagers() { + return damagers; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KitsuneLoreweaver.java b/Mage.Sets/src/mage/cards/k/KitsuneLoreweaver.java index 2ac7d9148d3..4aa8eb389a9 100644 --- a/Mage.Sets/src/mage/cards/k/KitsuneLoreweaver.java +++ b/Mage.Sets/src/mage/cards/k/KitsuneLoreweaver.java @@ -14,7 +14,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; /** * @@ -30,7 +29,7 @@ public final class KitsuneLoreweaver extends CardImpl { this.toughness = new MageInt(1); // {1}{W}: Kitsune Loreweaver gets +0/+X until end of turn, where X is the number of cards in your hand. - Effect effect = new BoostSourceEffect(StaticValue.get(0), CardsInControllerHandCount.instance, Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(StaticValue.get(0), CardsInControllerHandCount.ANY, Duration.EndOfTurn); effect.setText("{this} gets +0/+X until end of turn, where X is the number of cards in your hand"); this.addAbility(new SimpleActivatedAbility(effect, new ManaCostsImpl<>("{1}{W}"))); } diff --git a/Mage.Sets/src/mage/cards/k/KiyomaroFirstToStand.java b/Mage.Sets/src/mage/cards/k/KiyomaroFirstToStand.java index 48a897d7fd5..dd95e476221 100644 --- a/Mage.Sets/src/mage/cards/k/KiyomaroFirstToStand.java +++ b/Mage.Sets/src/mage/cards/k/KiyomaroFirstToStand.java @@ -31,7 +31,7 @@ public final class KiyomaroFirstToStand extends CardImpl { this.toughness = new MageInt(0); // Kiyomaro, First to Stand's power and toughness are each equal to the number of cards in your hand. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.instance))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.ANY))); // As long as you have four or more cards in hand, Kiyomaro has vigilance. Condition condition = new CardsInHandCondition(ComparisonType.MORE_THAN, 3); diff --git a/Mage.Sets/src/mage/cards/k/KnollspineInvocation.java b/Mage.Sets/src/mage/cards/k/KnollspineInvocation.java index f63a809d417..32b51409d80 100644 --- a/Mage.Sets/src/mage/cards/k/KnollspineInvocation.java +++ b/Mage.Sets/src/mage/cards/k/KnollspineInvocation.java @@ -1,40 +1,43 @@ package mage.cards.k; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.EarlyTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.costs.mana.VariableManaCost; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.Zone; +import mage.constants.Outcome; import mage.filter.FilterCard; -import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.game.Game; +import mage.players.Player; +import mage.target.Target; import mage.target.common.TargetAnyTarget; import mage.target.common.TargetCardInHand; -import mage.util.CardUtil; import java.util.UUID; /** - * @author anonymous + * @author JayDi85 */ public final class KnollspineInvocation extends CardImpl { - private static final FilterCard filter = new FilterCard("a card with mana value X"); + protected static final FilterCard filter = new FilterCard("a card with mana value X"); public KnollspineInvocation(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}{R}"); - // {X}, Discard a card with converted mana cost X: Knollspine Invocation deals X damage to any target. + // {X}, Discard a card with mana value X: This enchantment deals X damage to any target. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(GetXValue.instance, true), new ManaCostsImpl<>("{X}")); - ability.addCost(new DiscardTargetCost(new TargetCardInHand(filter))); + ability.addCost(new KnollspineInvocationDiscardCost()); ability.addTarget(new TargetAnyTarget()); ability.setCostAdjuster(KnollspineInvocationAdjuster.instance); this.addAbility(ability); @@ -50,22 +53,103 @@ public final class KnollspineInvocation extends CardImpl { } } +class KnollspineInvocationDiscardCost extends CostImpl implements EarlyTargetCost { + + // discard card with early target selection, so {X} mana cost can be setup after choose + + public KnollspineInvocationDiscardCost() { + super(); + this.text = "Discard a card with mana value X"; + } + + public KnollspineInvocationDiscardCost(final KnollspineInvocationDiscardCost cost) { + super(cost); + } + + @Override + public KnollspineInvocationDiscardCost copy() { + return new KnollspineInvocationDiscardCost(this); + } + + @Override + public void chooseTarget(Game game, Ability source, Player controller) { + Target target = new TargetCardInHand().withChooseHint("to discard with mana value for X"); + controller.choose(Outcome.Discard, target, source, game); + addTarget(target); + } + + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + Player controller = game.getPlayer(controllerId); + return controller != null && !controller.getHand().isEmpty(); + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + this.paid = false; + + Player controller = game.getPlayer(controllerId); + if (controller == null) { + return false; + } + + Card card = controller.getHand().get(this.getTargets().getFirstTarget(), game); + if (card == null) { + return false; + } + + this.paid = controller.discard(card, true, source, game); + + return this.paid; + } +} + enum KnollspineInvocationAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { - int xValue = CardUtil.getSourceCostsTag(game, ability, "X", 0); - for (Cost cost : ability.getCosts()) { - if (!(cost instanceof DiscardTargetCost)) { - continue; - } - DiscardTargetCost discardCost = (DiscardTargetCost) cost; - discardCost.getTargets().clear(); - FilterCard adjustedFilter = new FilterCard("a card with mana value X"); - adjustedFilter.add(new ManaValuePredicate(ComparisonType.EQUAL_TO, xValue)); - discardCost.addTarget(new TargetCardInHand(adjustedFilter)); + public void prepareX(Ability ability, Game game) { + Player controller = game.getPlayer(ability.getControllerId()); + if (controller == null) { return; } + + // make sure early target used + VariableManaCost costX = ability.getManaCostsToPay().stream() + .filter(c -> c instanceof VariableManaCost) + .map(c -> (VariableManaCost) c) + .findFirst() + .orElse(null); + if (costX == null) { + throw new IllegalArgumentException("Wrong code usage: costX lost"); + } + KnollspineInvocationDiscardCost costDiscard = ability.getCosts().stream() + .filter(c -> c instanceof KnollspineInvocationDiscardCost) + .map(c -> (KnollspineInvocationDiscardCost) c) + .findFirst() + .orElse(null); + if (costDiscard == null) { + throw new IllegalArgumentException("Wrong code usage: costDiscard lost"); + } + + if (game.inCheckPlayableState()) { + // possible X + int minManaValue = controller.getHand().getCards(game).stream() + .mapToInt(MageObject::getManaValue) + .min() + .orElse(0); + int maxManaValue = controller.getHand().getCards(game).stream() + .mapToInt(MageObject::getManaValue) + .max() + .orElse(0); + ability.setVariableCostsMinMax(minManaValue, maxManaValue); + } else { + // real X + Card card = controller.getHand().get(costDiscard.getTargets().getFirstTarget(), game); + if (card == null) { + throw new IllegalStateException("Wrong code usage: card to discard lost"); + } + ability.setVariableCostsValue(card.getManaValue()); + } } } diff --git a/Mage.Sets/src/mage/cards/k/KotisTheFangkeeper.java b/Mage.Sets/src/mage/cards/k/KotisTheFangkeeper.java index 131dce312ea..ff609acbfcb 100644 --- a/Mage.Sets/src/mage/cards/k/KotisTheFangkeeper.java +++ b/Mage.Sets/src/mage/cards/k/KotisTheFangkeeper.java @@ -3,7 +3,6 @@ package mage.cards.k; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; -import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.IndestructibleAbility; import mage.cards.CardImpl; @@ -73,7 +72,7 @@ class KotisTheFangkeeperEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); - int xValue = GetXValue.instance.calculate(game, source, this); + int xValue = (Integer) getValue("damage"); if (controller == null || player == null || xValue < 1) { return false; } diff --git a/Mage.Sets/src/mage/cards/k/KrumarInitiate.java b/Mage.Sets/src/mage/cards/k/KrumarInitiate.java new file mode 100644 index 00000000000..6d04e88cde8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KrumarInitiate.java @@ -0,0 +1,48 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.keyword.EndureSourceEffect; +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 KrumarInitiate extends CardImpl { + + public KrumarInitiate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {X}{B}, {T}, Pay X life: This creature endures X. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new EndureSourceEffect(GetXValue.instance, "{this}"), new ManaCostsImpl<>("{X}{B}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new PayLifeCost(GetXValue.instance, "X life")); + this.addAbility(ability); + } + + private KrumarInitiate(final KrumarInitiate card) { + super(card); + } + + @Override + public KrumarInitiate copy() { + return new KrumarInitiate(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LastMarchOfTheEnts.java b/Mage.Sets/src/mage/cards/l/LastMarchOfTheEnts.java index b13c0c85eb7..95efa9a8b04 100644 --- a/Mage.Sets/src/mage/cards/l/LastMarchOfTheEnts.java +++ b/Mage.Sets/src/mage/cards/l/LastMarchOfTheEnts.java @@ -32,9 +32,10 @@ public final class LastMarchOfTheEnts extends CardImpl { // Draw cards equal to the greatest toughness among creatures you control, then put any number of creature cards from your hand onto the battlefield. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect( - GreatestToughnessAmongControlledCreaturesValue.instance + GreatestToughnessAmongControlledCreaturesValue.ALL ).setText("draw cards equal to the greatest toughness among creatures you control")); this.getSpellAbility().addEffect(new LastMarchOfTheEntsEffect()); + this.getSpellAbility().addHint(GreatestToughnessAmongControlledCreaturesValue.ALL.getHint()); } private LastMarchOfTheEnts(final LastMarchOfTheEnts card) { diff --git a/Mage.Sets/src/mage/cards/l/LasydProwler.java b/Mage.Sets/src/mage/cards/l/LasydProwler.java new file mode 100644 index 00000000000..1971addf543 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LasydProwler.java @@ -0,0 +1,67 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LasydProwler extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_LAND, null); + private static final Hint hint = new ValueHint("Land cards in your graveyard", xValue); + + public LasydProwler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.RANGER); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // When this creature enters, you may mill cards equal to the number of lands you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(LandsYouControlCount.instance) + .setText("mill cards equal to the number of lands you control"), true)); + + // Renew -- {1}{G}, Exile this card from your graveyard: Put X +1/+1 counters on target creature, where X is the number of land cards in your graveyard. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + Zone.GRAVEYARD, + new AddCountersTargetEffect(CounterType.P1P1.createInstance(0), xValue), + new ManaCostsImpl<>("{1}{G}") + ); + ability.addCost(new ExileSourceFromGraveCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability.setAbilityWord(AbilityWord.RENEW).addHint(hint)); + } + + private LasydProwler(final LasydProwler card) { + super(card); + } + + @Override + public LasydProwler copy() { + return new LasydProwler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LavabrinkVenturer.java b/Mage.Sets/src/mage/cards/l/LavabrinkVenturer.java index 97c807b7924..a51d0ff8407 100644 --- a/Mage.Sets/src/mage/cards/l/LavabrinkVenturer.java +++ b/Mage.Sets/src/mage/cards/l/LavabrinkVenturer.java @@ -10,6 +10,7 @@ import mage.abilities.keyword.ProtectionAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ModeChoice; import mage.constants.SubType; import mage.filter.FilterObject; import mage.filter.predicate.mageobject.ManaValueParityPredicate; @@ -31,10 +32,7 @@ public final class LavabrinkVenturer extends CardImpl { this.toughness = new MageInt(3); // As Lavabrink Venturer enters the battlefield, choose odd or even. - this.addAbility(new AsEntersBattlefieldAbility( - new ChooseModeEffect("Odd or even?", "Odd", "Even"), - "choose odd or even. (Zero is even.)" - )); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.ODD, ModeChoice.EVEN))); // Lavabrink Venturer has protection from each converted mana cost of the chosen value. this.addAbility(new SimpleStaticAbility(new LavabrinkVenturerEffect())); @@ -75,20 +73,13 @@ class LavabrinkVenturerEffect extends GainAbilitySourceEffect { @Override public boolean apply(Game game, Ability source) { - String chosenMode = (String) game.getState().getValue(source.getSourceId() + "_modeChoice"); - if (chosenMode == null) { + if (ModeChoice.ODD.checkMode(game, source)) { + this.ability = new ProtectionAbility(oddFilter); + } else if (ModeChoice.EVEN.checkMode(game, source)) { + this.ability = new ProtectionAbility(evenFilter); + } else { return false; } - switch (chosenMode) { - case "Odd": - this.ability = new ProtectionAbility(oddFilter); - break; - case "Even": - this.ability = new ProtectionAbility(evenFilter); - break; - default: - return false; - } return super.apply(game, source); } diff --git a/Mage.Sets/src/mage/cards/l/LaylaHassan.java b/Mage.Sets/src/mage/cards/l/LaylaHassan.java new file mode 100644 index 00000000000..b3eaf7c2a2e --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LaylaHassan.java @@ -0,0 +1,103 @@ +package mage.cards.l; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.common.FilterHistoricCard; +import mage.game.Game; +import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.OneOrMoreDamagePlayerTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * @author balazskristof + */ +public final class LaylaHassan extends CardImpl { + + private static final FilterHistoricCard filter = new FilterHistoricCard("historic card from your graveyard"); + + public LaylaHassan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // When Layla Hassan enters the battlefield and whenever one or more Assassins you control deal combat damage to a player, return target historic card from your graveyard to your hand. + Ability ability = new LaylaHassanTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + } + + private LaylaHassan(final LaylaHassan card) { + super(card); + } + + @Override + public LaylaHassan copy() { + return new LaylaHassan(this); + } +} + +class LaylaHassanTriggeredAbility extends OneOrMoreDamagePlayerTriggeredAbility { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Assassins"); + + static { + filter.add(SubType.ASSASSIN.getPredicate()); + } + + public LaylaHassanTriggeredAbility(Effect effect) { + super(effect, filter, true, true); + } + + private LaylaHassanTriggeredAbility(final LaylaHassanTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD + || event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD + && event.getTargetId().equals(getSourceId())) { + return true; + } + if (event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER) { + return super.checkTrigger(event, game); + } + return false; + } + + @Override + public String getRule() { + return "When {this} enters the battlefield and whenever one or more Assassins you control deal combat damage to a player, return target historic card from your graveyard to your hand."; + } + + @Override + public LaylaHassanTriggeredAbility copy() { + return new LaylaHassanTriggeredAbility(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LeonardoDaVinci.java b/Mage.Sets/src/mage/cards/l/LeonardoDaVinci.java index e29391f7e08..b21f2ecdb99 100644 --- a/Mage.Sets/src/mage/cards/l/LeonardoDaVinci.java +++ b/Mage.Sets/src/mage/cards/l/LeonardoDaVinci.java @@ -43,7 +43,7 @@ public final class LeonardoDaVinci extends CardImpl { this.power = new MageInt(3); this.toughness = new MageInt(3); - DynamicValue xValue = CardsInControllerHandCount.instance; + DynamicValue xValue = CardsInControllerHandCount.ANY; // {3}{U}{U}: Until end of turn, Thopters you control have base power and toughness X/X, where X is the number of cards in your hand. this.addAbility(new SimpleActivatedAbility(new BoostControlledEffect(xValue, xValue, Duration.EndOfTurn, filter, false).setText( "Until end of turn, Thopters you control have base power and toughness X/X, where X is the number of cards in your hand." diff --git a/Mage.Sets/src/mage/cards/l/LieInWait.java b/Mage.Sets/src/mage/cards/l/LieInWait.java new file mode 100644 index 00000000000..249818aa62a --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LieInWait.java @@ -0,0 +1,86 @@ +package mage.cards.l; + +import java.util.List; +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.EachTargetPointer; + +/** + * + * @author androosss + */ +public final class LieInWait extends CardImpl { + + public LieInWait(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}{G}{U}"); + + + // Return target creature card from your graveyard to your hand. Lie in Wait deals damage equal to that card's power to target creature. + this.getSpellAbility().addEffect(new LieInWaitTargetEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE)); + } + + private LieInWait(final LieInWait card) { + super(card); + } + + @Override + public LieInWait copy() { + return new LieInWait(this); + } + +} + +class LieInWaitTargetEffect extends OneShotEffect { + + LieInWaitTargetEffect() { + super(Outcome.Benefit); + staticText = "Return target creature card from your graveyard to your hand. " + + "{this} deals damage equal to that card's power to target creature"; + setTargetPointer(new EachTargetPointer()); + } + + private LieInWaitTargetEffect(final LieInWaitTargetEffect effect) { + super(effect); + } + + @Override + public LieInWaitTargetEffect copy() { + return new LieInWaitTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + List targets = getTargetPointer().getTargets(game, source); + Card card = game.getCard(targets.get(0)); + if (card == null) { + return false; + } + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + boolean result = card.moveToZone(Zone.HAND, source, game,false); + if (result && targets.size() >= 2) { + int power = card.getPower().getValue(); + Permanent permanent = game.getPermanent(targets.get(1)); + permanent.damage(power, source, game); + } + return result; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LoreseekersStone.java b/Mage.Sets/src/mage/cards/l/LoreseekersStone.java index ce1a04b8533..f059d4cbfc3 100644 --- a/Mage.Sets/src/mage/cards/l/LoreseekersStone.java +++ b/Mage.Sets/src/mage/cards/l/LoreseekersStone.java @@ -48,7 +48,7 @@ enum LoreseekersStoneAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void increaseCost(Ability ability, Game game) { Player player = game.getPlayer(ability.getControllerId()); if (player != null) { CardUtil.increaseCost(ability, player.getHand().size()); diff --git a/Mage.Sets/src/mage/cards/l/LotuslightDancers.java b/Mage.Sets/src/mage/cards/l/LotuslightDancers.java new file mode 100644 index 00000000000..998672a320b --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LotuslightDancers.java @@ -0,0 +1,135 @@ +package mage.cards.l; + +import java.util.UUID; +import mage.MageInt; +import mage.ObjectColor; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; +import mage.abilities.Ability; +import mage.abilities.assignment.common.ColorAssignment; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; + +/** + * + * @author androosss + */ +public final class LotuslightDancers extends CardImpl { + + public LotuslightDancers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{G}{U}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.BARD); + this.power = new MageInt(3); + this.toughness = new MageInt(6); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When this creature enters, search your library for a black card, a green card, and a blue card. Put those cards into your graveyard, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility(new LotuslightDancersEffect())); + } + + private LotuslightDancers(final LotuslightDancers card) { + super(card); + } + + @Override + public LotuslightDancers copy() { + return new LotuslightDancers(this); + } +} + +class LotuslightDancersEffect extends OneShotEffect { + + LotuslightDancersEffect() { + super(Outcome.Neutral); + staticText = "search your library for a black card, a green card, and a blue card. " + + "Put those cards into your graveyard, then shuffle."; + } + + private LotuslightDancersEffect(final LotuslightDancersEffect effect) { + super(effect); + } + + @Override + public LotuslightDancersEffect copy() { + return new LotuslightDancersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + TargetCardInLibrary target = new LotuslightDancersTarget(); + controller.searchLibrary(target, source, game); + Cards cards = new CardsImpl(target.getTargets()); + cards.retainZone(Zone.LIBRARY, game); + controller.moveCards(cards, Zone.GRAVEYARD, source, game); + controller.shuffleLibrary(source, game); + return true; + } +} + +class LotuslightDancersTarget extends TargetCardInLibrary { + + private static final FilterCard filter + = new FilterCard("a black card, a green card, and a blue card"); + + static { + filter.add(Predicates.or( + new ColorPredicate(ObjectColor.BLUE), + new ColorPredicate(ObjectColor.BLACK), + new ColorPredicate(ObjectColor.GREEN) + )); + } + + private static final ColorAssignment colorAssigner = new ColorAssignment("U", "B", "G"); + + LotuslightDancersTarget() { + super(0, 3, filter); + } + + private LotuslightDancersTarget(final LotuslightDancersTarget target) { + super(target); + } + + @Override + public LotuslightDancersTarget copy() { + return new LotuslightDancersTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { + return false; + } + Card card = game.getCard(id); + if (card == null) { + return false; + } + if (this.getTargets().isEmpty()) { + return true; + } + Cards cards = new CardsImpl(this.getTargets()); + cards.add(card); + return colorAssigner.getRoleCount(cards, game) >= cards.size(); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MaelstromOfTheSpiritDragon.java b/Mage.Sets/src/mage/cards/m/MaelstromOfTheSpiritDragon.java new file mode 100644 index 00000000000..1f17bf216b1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MaelstromOfTheSpiritDragon.java @@ -0,0 +1,65 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.mana.ColorlessManaAbility; +import mage.abilities.mana.ConditionalAnyColorManaAbility; +import mage.abilities.mana.conditional.ConditionalSpellManaBuilder; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MaelstromOfTheSpiritDragon extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a Dragon spell or an Omen spell"); + private static final FilterCard filter2 = new FilterCard("a Dragon card"); + + static { + filter.add(Predicates.or( + SubType.DRAGON.getPredicate(), + SubType.OMEN.getPredicate() + )); + filter2.add(SubType.DRAGON.getPredicate()); + } + + public MaelstromOfTheSpiritDragon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {T}: Add one mana of any color. Spend this mana only to cast a Dragon spell or an Omen spell. + this.addAbility(new ConditionalAnyColorManaAbility(1, new ConditionalSpellManaBuilder(filter))); + + // {4}, {T}, Sacrifice this land: Search your library for a Dragon card, reveal it, put it into your hand, then shuffle. + Ability ability = new SimpleActivatedAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter2), true), new GenericManaCost(4) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private MaelstromOfTheSpiritDragon(final MaelstromOfTheSpiritDragon card) { + super(card); + } + + @Override + public MaelstromOfTheSpiritDragon copy() { + return new MaelstromOfTheSpiritDragon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MagmaticHellkite.java b/Mage.Sets/src/mage/cards/m/MagmaticHellkite.java new file mode 100644 index 00000000000..6d7685aba95 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MagmaticHellkite.java @@ -0,0 +1,108 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.counters.Counters; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MagmaticHellkite extends CardImpl { + + private static final FilterPermanent filter = new FilterLandPermanent("nonbasic land an opponent controls"); + + static { + filter.add(Predicates.not(SuperType.BASIC.getPredicate())); + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public MagmaticHellkite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, destroy target nonbasic land an opponent controls. Its controller searches their library for a basic land card, puts it onto the battlefield tapped with a stun counter on it, then shuffles. + Ability ability = new EntersBattlefieldTriggeredAbility(new MagmaticHellkiteEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private MagmaticHellkite(final MagmaticHellkite card) { + super(card); + } + + @Override + public MagmaticHellkite copy() { + return new MagmaticHellkite(this); + } +} + +class MagmaticHellkiteEffect extends OneShotEffect { + + MagmaticHellkiteEffect() { + super(Outcome.Benefit); + staticText = "destroy target nonbasic land an opponent controls. " + + "Its controller searches their library for a basic land card, " + + "puts it onto the battlefield tapped with a stun counter on it, then shuffles"; + } + + private MagmaticHellkiteEffect(final MagmaticHellkiteEffect effect) { + super(effect); + } + + @Override + public MagmaticHellkiteEffect copy() { + return new MagmaticHellkiteEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + Player player = game.getPlayer(permanent.getControllerId()); + permanent.destroy(source, game); + if (player == null) { + return true; + } + TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); + player.searchLibrary(target, source, game); + Card card = player.getLibrary().getCard(target.getFirstTarget(), game); + if (card == null) { + player.shuffleLibrary(source, game); + return true; + } + game.setEnterWithCounters(card.getId(), new Counters().addCounter(CounterType.STUN.createInstance())); + player.moveCards( + card, Zone.BATTLEFIELD, source, game, true, + false, false, null + ); + player.shuffleLibrary(source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/Malignus.java b/Mage.Sets/src/mage/cards/m/Malignus.java index a0d51a95248..e8381a6154a 100644 --- a/Mage.Sets/src/mage/cards/m/Malignus.java +++ b/Mage.Sets/src/mage/cards/m/Malignus.java @@ -74,7 +74,7 @@ class HighestLifeTotalAmongOpponentsCount implements DynamicValue { @Override public DynamicValue copy() { - return CardsInControllerHandCount.instance; + return CardsInControllerHandCount.ANY; } @Override diff --git a/Mage.Sets/src/mage/cards/m/ManifestationSage.java b/Mage.Sets/src/mage/cards/m/ManifestationSage.java index fe1f6c90507..7adfcc0d4ef 100644 --- a/Mage.Sets/src/mage/cards/m/ManifestationSage.java +++ b/Mage.Sets/src/mage/cards/m/ManifestationSage.java @@ -26,7 +26,7 @@ public final class ManifestationSage extends CardImpl { // When Manifestation Sage enters the battlefield, create a 0/0 green and blue Fractal creature token. Put X +1/+1 counters on it, where X is the number of cards in your hand. this.addAbility(new EntersBattlefieldTriggeredAbility(FractalToken.getEffect( - CardsInControllerHandCount.instance, "Put X +1/+1 counters on it, " + + CardsInControllerHandCount.ANY, "Put X +1/+1 counters on it, " + "where X is the number of cards in your hand" ))); } diff --git a/Mage.Sets/src/mage/cards/m/MarangRiverRegent.java b/Mage.Sets/src/mage/cards/m/MarangRiverRegent.java new file mode 100644 index 00000000000..967ce00e83d --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MarangRiverRegent.java @@ -0,0 +1,53 @@ +package mage.cards.m; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.OmenCard; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.TargetPermanent; +import mage.target.common.TargetNonlandPermanent; + +/** + * + * @author Jmlundeen + */ +public final class MarangRiverRegent extends OmenCard { + + public MarangRiverRegent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.INSTANT}, "{4}{U}{U}", "Coil and Catch", "{3}{U}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(6); + this.toughness = new MageInt(7); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, return up to two other target nonland permanents to their owners' hands. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect()); + ability.addTarget(new TargetNonlandPermanent(0, 2)); + this.addAbility(ability); + + // Coil and Catch + // Draw three cards, then discard a card. + this.getSpellCard().getSpellAbility().addEffect(new DrawDiscardControllerEffect(3, 1)); + this.finalizeOmen(); + } + + private MarangRiverRegent(final MarangRiverRegent card) { + super(card); + } + + @Override + public MarangRiverRegent copy() { + return new MarangRiverRegent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java b/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java new file mode 100644 index 00000000000..ead29290170 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java @@ -0,0 +1,136 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.GameState; +import mage.game.permanent.token.Token; +import mage.target.TargetCard; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInExile; +import mage.target.targetpointer.FixedTargets; +import mage.util.CardUtil; +import mage.util.functions.CopyTokenFunction; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MarduSiegebreaker extends CardImpl { + + public MarduSiegebreaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When this creature enters, exile up to one other target creature you control until this creature leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_OTHER_CONTROLLED_CREATURE)); + this.addAbility(ability); + + // Whenever this creature attacks, for each opponent, create a tapped token that's a copy of the exiled card attacking that opponent. At the beginning of your end step, sacrifice those tokens. + this.addAbility(new AttacksTriggeredAbility(new MarduSiegebreakerEffect())); + } + + private MarduSiegebreaker(final MarduSiegebreaker card) { + super(card); + } + + @Override + public MarduSiegebreaker copy() { + return new MarduSiegebreaker(this); + } +} + +class MarduSiegebreakerEffect extends OneShotEffect { + + MarduSiegebreakerEffect() { + super(Outcome.Benefit); + staticText = "for each opponent, create a tapped token that's a copy of the exiled card " + + "attacking that opponent. At the beginning of your end step, sacrifice those tokens"; + } + + private MarduSiegebreakerEffect(final MarduSiegebreakerEffect effect) { + super(effect); + } + + @Override + public MarduSiegebreakerEffect copy() { + return new MarduSiegebreakerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Cards cards = Optional + .ofNullable(game) + .map(Game::getState) + .map(GameState::getExile) + .map(exile -> exile.getExileZone(CardUtil.getExileZoneId(game, source))) + .orElse(null); + if (cards == null) { + return false; + } + Card card; + switch (cards.size()) { + case 0: + return false; + case 1: + card = cards.getRandom(game); + break; + default: + card = Optional.ofNullable(game.getPlayer(source.getControllerId())).map(player -> { + TargetCard target = new TargetCardInExile(StaticFilters.FILTER_CARD); + target.withNotTarget(true); + target.withChooseHint("to copy"); + player.choose(Outcome.Neutral, cards, target, source, game); + return game.getCard(target.getFirstTarget()); + }).orElse(null); + } + if (card == null) { + return false; + } + Set addedTokens = new HashSet<>(); + Token token = CopyTokenFunction.createTokenCopy(card, game); + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + token.putOntoBattlefield(1, game, source, source.getControllerId(), true, true, opponentId); + token.getLastAddedTokenIds() + .stream() + .map(uuid -> new MageObjectReference(uuid, game)) + .forEach(addedTokens::add); + } + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new SacrificeTargetEffect().setTargetPointer(new FixedTargets(addedTokens)) + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MariposaMilitaryBase.java b/Mage.Sets/src/mage/cards/m/MariposaMilitaryBase.java index a655b6d8db5..43b5ffb7483 100644 --- a/Mage.Sets/src/mage/cards/m/MariposaMilitaryBase.java +++ b/Mage.Sets/src/mage/cards/m/MariposaMilitaryBase.java @@ -64,7 +64,7 @@ enum MariposaMilitaryBaseAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { CardUtil.reduceCost(ability, SourceControllerCountersCount.RAD.calculate(game, ability, null)); } } diff --git a/Mage.Sets/src/mage/cards/m/Maro.java b/Mage.Sets/src/mage/cards/m/Maro.java index 4e1aff0a3f6..b05ccffcbd8 100644 --- a/Mage.Sets/src/mage/cards/m/Maro.java +++ b/Mage.Sets/src/mage/cards/m/Maro.java @@ -26,7 +26,7 @@ public final class Maro extends CardImpl { this.toughness = new MageInt(0); // Maro's power and toughness are each equal to the number of cards in your hand. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.instance))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.ANY))); } private Maro(final Maro card) { diff --git a/Mage.Sets/src/mage/cards/m/MasterTheWay.java b/Mage.Sets/src/mage/cards/m/MasterTheWay.java index 526cf4fc1d4..ba07b492f7c 100644 --- a/Mage.Sets/src/mage/cards/m/MasterTheWay.java +++ b/Mage.Sets/src/mage/cards/m/MasterTheWay.java @@ -23,7 +23,7 @@ public final class MasterTheWay extends CardImpl { // Draw a card. Master the Way deals damage to any target equal to the number of cards in your hand. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); - Effect effect = new DamageTargetEffect(CardsInControllerHandCount.instance); + Effect effect = new DamageTargetEffect(CardsInControllerHandCount.ANY); effect.setText("{this} deals damage to any target equal to the number of cards in your hand"); this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetAnyTarget()); diff --git a/Mage.Sets/src/mage/cards/m/MasumaroFirstToLive.java b/Mage.Sets/src/mage/cards/m/MasumaroFirstToLive.java index 1771acc7653..7c4f870b4bc 100644 --- a/Mage.Sets/src/mage/cards/m/MasumaroFirstToLive.java +++ b/Mage.Sets/src/mage/cards/m/MasumaroFirstToLive.java @@ -31,7 +31,7 @@ public final class MasumaroFirstToLive extends CardImpl { this.toughness = new MageInt(0); // Masumaro, First to Live's power and toughness are each equal to twice the number of cards in your hand. - DynamicValue xValue= new MultipliedValue(CardsInControllerHandCount.instance, 2); + DynamicValue xValue= new MultipliedValue(CardsInControllerHandCount.ANY, 2); Effect effect = new SetBasePowerToughnessSourceEffect(xValue); effect.setText("{this}'s power and toughness are each equal to twice the number of cards in your hand"); this.addAbility(new SimpleStaticAbility(Zone.ALL, effect)); diff --git a/Mage.Sets/src/mage/cards/m/MeishinTheMindCage.java b/Mage.Sets/src/mage/cards/m/MeishinTheMindCage.java index 2261a7e6a4b..21b514a9162 100644 --- a/Mage.Sets/src/mage/cards/m/MeishinTheMindCage.java +++ b/Mage.Sets/src/mage/cards/m/MeishinTheMindCage.java @@ -12,7 +12,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.StaticFilters; /** @@ -26,7 +25,7 @@ public final class MeishinTheMindCage extends CardImpl { this.supertype.add(SuperType.LEGENDARY); // All creatures get -X/-0, where X is the number of cards in your hand. - this.addAbility(new SimpleStaticAbility(new BoostAllEffect(new SignInversionDynamicValue(CardsInControllerHandCount.instance), StaticValue.get(0), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURE, false, "All creatures get -X/-0, where X is the number of cards in your hand"))); + this.addAbility(new SimpleStaticAbility(new BoostAllEffect(new SignInversionDynamicValue(CardsInControllerHandCount.ANY), StaticValue.get(0), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURE, false, "All creatures get -X/-0, where X is the number of cards in your hand"))); } private MeishinTheMindCage(final MeishinTheMindCage card) { diff --git a/Mage.Sets/src/mage/cards/m/MinasTirithGarrison.java b/Mage.Sets/src/mage/cards/m/MinasTirithGarrison.java index 20c4ac70836..920205e9d57 100644 --- a/Mage.Sets/src/mage/cards/m/MinasTirithGarrison.java +++ b/Mage.Sets/src/mage/cards/m/MinasTirithGarrison.java @@ -39,7 +39,7 @@ public final class MinasTirithGarrison extends CardImpl { // Minas Tirith Garrison's power is equal to the number of cards in your hand. this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SetBasePowerSourceEffect(CardsInControllerHandCount.instance) + Zone.ALL, new SetBasePowerSourceEffect(CardsInControllerHandCount.ANY) )); // Whenever Minas Tirith Garrison attacks, you may tap any number of untapped Humans you control. Draw a card for each Human tapped this way. diff --git a/Mage.Sets/src/mage/cards/m/MirrodinBesieged.java b/Mage.Sets/src/mage/cards/m/MirrodinBesieged.java index 6af67534394..459afeaa1d6 100644 --- a/Mage.Sets/src/mage/cards/m/MirrodinBesieged.java +++ b/Mage.Sets/src/mage/cards/m/MirrodinBesieged.java @@ -1,22 +1,21 @@ package mage.cards.m; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; -import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ModeChoice; import mage.constants.Outcome; -import mage.filter.FilterSpell; import mage.filter.StaticFilters; -import mage.filter.common.FilterArtifactSpell; import mage.game.Game; import mage.game.permanent.token.MyrToken; import mage.players.Player; @@ -29,33 +28,23 @@ import java.util.UUID; */ public final class MirrodinBesieged extends CardImpl { - private static final String ruleTrigger1 = "&bull Mirran — Whenever you cast an artifact spell, " + - "create a 1/1 colorless Myr artifact creature token."; - private static final String ruleTrigger2 = "&bull Phyrexian — At the beginning of your end step, " + - "draw a card, then discard a card. Then if there are fifteen or more artifact cards in your graveyard, " + - "target opponent loses the game."; - private static final FilterSpell filter = new FilterArtifactSpell(); - public MirrodinBesieged(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); // As Mirrodin Besieged enters the battlefield, choose Mirran or Phyrexian. - this.addAbility(new EntersBattlefieldAbility( - new ChooseModeEffect("Mirran or Phyrexian?", "Mirran", "Phyrexian"), - null, "As {this} enters, choose Mirran or Phyrexian.", "" - )); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.MIRRAN, ModeChoice.PHYREXIAN))); // • Mirran — Whenever you cast an artifact spell, create a 1/1 colorless Myr artifact creature token. - this.addAbility(new ConditionalTriggeredAbility(new SpellCastControllerTriggeredAbility( - new CreateTokenEffect(new MyrToken()), filter, false - ), new ModeChoiceSourceCondition("Mirran"), ruleTrigger1)); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new MyrToken()), StaticFilters.FILTER_SPELL_AN_ARTIFACT, false + ), ModeChoice.MIRRAN + ))); // • Phyrexian — At the beginning of your end step, draw a card, then discard a card. Then if there are fifteen or more artifact cards in your graveyard, target opponent loses the game. - Ability ability = new ConditionalTriggeredAbility(new BeginningOfEndStepTriggeredAbility( - new MirrodinBesiegedEffect() - ), new ModeChoiceSourceCondition("Phyrexian"), ruleTrigger2); + Ability ability = new BeginningOfEndStepTriggeredAbility(new MirrodinBesiegedEffect()); ability.addTarget(new TargetOpponent()); - this.addAbility(ability); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability, ModeChoice.PHYREXIAN))); } private MirrodinBesieged(final MirrodinBesieged card) { diff --git a/Mage.Sets/src/mage/cards/m/MirrorOfGaladriel.java b/Mage.Sets/src/mage/cards/m/MirrorOfGaladriel.java index d9c02ce3b65..da685a95e24 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorOfGaladriel.java +++ b/Mage.Sets/src/mage/cards/m/MirrorOfGaladriel.java @@ -66,7 +66,7 @@ enum MirrorOfGaladrielAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { int value = game.getBattlefield().count( StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY, ability.getControllerId(), ability, game diff --git a/Mage.Sets/src/mage/cards/m/MistriseVillage.java b/Mage.Sets/src/mage/cards/m/MistriseVillage.java new file mode 100644 index 00000000000..ea2132cfd54 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MistriseVillage.java @@ -0,0 +1,112 @@ +package mage.cards.m; + +import java.util.UUID; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.CantBeCounteredSourceAbility; +import mage.abilities.common.EntersBattlefieldTappedUnlessAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.YouControlPermanentCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.effects.common.continuous.NextSpellCastHasAbilityEffect; +import mage.abilities.mana.BlueManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; + +/** + * + * @author Jmlundeen + */ +public final class MistriseVillage extends CardImpl { + + private static final FilterLandPermanent filter = new FilterLandPermanent("Mountain or a Forest"); + + static { + filter.add(Predicates.or(SubType.MOUNTAIN.getPredicate(), SubType.FOREST.getPredicate())); + } + + private static final YouControlPermanentCondition condition = new YouControlPermanentCondition(filter); + + public MistriseVillage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + + // This land enters tapped unless you control a Mountain or a Forest. + this.addAbility(new EntersBattlefieldTappedUnlessAbility(condition).addHint(condition.getHint())); + + // {T}: Add {U}. + this.addAbility(new BlueManaAbility()); + + // {U}, {T}: The next spell you cast this turn can't be countered. + Effect effect = new AddContinuousEffectToGame(new MistriseCantBeCounteredEffect()); + Ability ability = new SimpleActivatedAbility(effect, new ManaCostsImpl<>("{U}")); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private MistriseVillage(final MistriseVillage card) { + super(card); + } + + @Override + public MistriseVillage copy() { + return new MistriseVillage(this); + } +} + +class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl { + + public MistriseCantBeCounteredEffect() { + super(Duration.OneUse, Outcome.Benefit, false, true); + staticText = "the next spell you cast this turn can't be countered"; + } + + protected MistriseCantBeCounteredEffect(final MistriseCantBeCounteredEffect effect) { + super(effect); + } + + @Override + public MistriseCantBeCounteredEffect copy() { + return new MistriseCantBeCounteredEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTER; + } + + @Override + public String getInfoMessage(Ability source, GameEvent event, Game game) { + StackObject sourceObject = game.getStack().getStackObject(event.getSourceId()); + StackObject targetObject = game.getStack().getStackObject(event.getTargetId()); + if (sourceObject != null && targetObject != null) { + return targetObject.getName() + " cannot be countered by " + sourceObject.getName(); + } + return staticText; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + boolean res = spell != null && spell.isControlledBy(source.getControllerId()); + if (res) { + discard(); + } + return res; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MobilizedDistrict.java b/Mage.Sets/src/mage/cards/m/MobilizedDistrict.java index e6de269bb56..bff7732236f 100644 --- a/Mage.Sets/src/mage/cards/m/MobilizedDistrict.java +++ b/Mage.Sets/src/mage/cards/m/MobilizedDistrict.java @@ -74,7 +74,7 @@ enum MobilizedDistrictAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { Player controller = game.getPlayer(ability.getControllerId()); if (controller != null) { int count = cardsCount.calculate(game, ability, null); diff --git a/Mage.Sets/src/mage/cards/m/MonasterySiege.java b/Mage.Sets/src/mage/cards/m/MonasterySiege.java index ab0074437dd..69bab804c13 100644 --- a/Mage.Sets/src/mage/cards/m/MonasterySiege.java +++ b/Mage.Sets/src/mage/cards/m/MonasterySiege.java @@ -2,14 +2,13 @@ package mage.cards.m; import mage.abilities.Ability; import mage.abilities.SpellAbility; -import mage.abilities.triggers.BeginningOfDrawTriggeredAbility; -import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.triggers.BeginningOfDrawTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -31,17 +30,19 @@ public final class MonasterySiege extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); // As Monastery Siege enters the battlefield, choose Khans or Dragons. - this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Khans or Dragons?", "Khans", "Dragons"), null, - "As {this} enters, choose Khans or Dragons.", "")); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.KHANS, ModeChoice.DRAGONS))); // * Khans - At the beginning of your draw step, draw an additional card, then discard a card. - this.addAbility(new ConditionalTriggeredAbility( - new BeginningOfDrawTriggeredAbility(new DrawDiscardControllerEffect(1, 1), false), - new ModeChoiceSourceCondition("Khans"), - "• Khans — At the beginning of your draw step, draw an additional card, then discard a card.")); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new BeginningOfDrawTriggeredAbility( + new DrawDiscardControllerEffect(1, 1), false + ), ModeChoice.KHANS + ))); // * Dragons - Spells your opponents cast that target you or a permanent you control cost {2} more to cast. - this.addAbility(new SimpleStaticAbility(new MonasterySiegeCostIncreaseEffect())); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new MonasterySiegeCostIncreaseEffect(), ModeChoice.DRAGONS + ))); } private MonasterySiege(final MonasterySiege card) { @@ -56,11 +57,9 @@ public final class MonasterySiege extends CardImpl { class MonasterySiegeCostIncreaseEffect extends CostModificationEffectImpl { - private static final ModeChoiceSourceCondition modeDragons = new ModeChoiceSourceCondition("Dragons"); - MonasterySiegeCostIncreaseEffect() { super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); - staticText = "• Dragons — Spells your opponents cast that target you or a permanent you control cost {2} more to cast"; + staticText = "spells your opponents cast that target you or a permanent you control cost {2} more to cast"; } private MonasterySiegeCostIncreaseEffect(final MonasterySiegeCostIncreaseEffect effect) { @@ -75,10 +74,6 @@ class MonasterySiegeCostIncreaseEffect extends CostModificationEffectImpl { @Override public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (!modeDragons.apply(game, source)) { - return false; - } - if (!(abilityToModify instanceof SpellAbility)) { return false; } diff --git a/Mage.Sets/src/mage/cards/n/NagaFleshcrafter.java b/Mage.Sets/src/mage/cards/n/NagaFleshcrafter.java new file mode 100644 index 00000000000..f160e54b9f9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NagaFleshcrafter.java @@ -0,0 +1,102 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyPermanentEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NagaFleshcrafter extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("nonlegendary creature you control"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public NagaFleshcrafter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.SHAPESHIFTER); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // You may have this creature enter as a copy of any creature on the battlefield. + this.addAbility(new EntersBattlefieldAbility(new CopyPermanentEffect(), true)); + + // Renew -- {2}{U}, Exile this card from your graveyard: Put a +1/+1 counter on target nonlegendary creature you control. Each other creature you control becomes a copy of that creature until end of turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + Zone.GRAVEYARD, + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + new ManaCostsImpl<>("{2}{U}") + ); + ability.addCost(new ExileSourceFromGraveCost()); + ability.addEffect(new NagaFleshcrafterEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability.setAbilityWord(AbilityWord.RENEW)); + } + + private NagaFleshcrafter(final NagaFleshcrafter card) { + super(card); + } + + @Override + public NagaFleshcrafter copy() { + return new NagaFleshcrafter(this); + } +} + +class NagaFleshcrafterEffect extends OneShotEffect { + + NagaFleshcrafterEffect() { + super(Outcome.Benefit); + staticText = "each other creature you control becomes a copy of that creature until end of turn"; + } + + private NagaFleshcrafterEffect(final NagaFleshcrafterEffect effect) { + super(effect); + } + + @Override + public NagaFleshcrafterEffect copy() { + return new NagaFleshcrafterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + for (Permanent creature : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + source.getControllerId(), source, game + )) { + if (!permanent.getId().equals(creature.getId())) { + game.copyPermanent(Duration.EndOfTurn, permanent, creature.getId(), source, null); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/n/NahirisWrath.java b/Mage.Sets/src/mage/cards/n/NahirisWrath.java index 015faadbce0..b7bb3ad30f7 100644 --- a/Mage.Sets/src/mage/cards/n/NahirisWrath.java +++ b/Mage.Sets/src/mage/cards/n/NahirisWrath.java @@ -8,6 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCreatureOrPlaneswalker; import mage.target.targetadjustment.XTargetsCountAdjuster; @@ -21,8 +22,8 @@ public final class NahirisWrath extends CardImpl { public NahirisWrath(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); - // As an additional cost to cast Nahiri's Wrath, discard X cards. - this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true)); + // As an additional cost to cast this spell, discard X cards. + this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true)); // Nahiri's Wrath deals damage equal to the total converted mana cost of the discarded cards to each of up to X target creatures and/or planeswalkers. Effect effect = new DamageTargetEffect(DiscardCostCardManaValue.instance); diff --git a/Mage.Sets/src/mage/cards/n/NecropolisFiend.java b/Mage.Sets/src/mage/cards/n/NecropolisFiend.java index 2f491e5ac22..930fb06c2b2 100644 --- a/Mage.Sets/src/mage/cards/n/NecropolisFiend.java +++ b/Mage.Sets/src/mage/cards/n/NecropolisFiend.java @@ -6,11 +6,9 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.VariableCost; import mage.abilities.costs.common.ExileFromGraveCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.costs.mana.VariableManaCost; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; @@ -86,16 +84,12 @@ enum NecropolisFiendCostAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void prepareX(Ability ability, Game game) { Player controller = game.getPlayer(ability.getControllerId()); if (controller == null) { return; } - for (VariableCost variableCost : ability.getManaCostsToPay().getVariableCosts()) { - if (variableCost instanceof VariableManaCost) { - ((VariableManaCost) variableCost).setMaxX(controller.getGraveyard().size()); - } - } + ability.setVariableCostsMinMax(0, controller.getGraveyard().size()); } } diff --git a/Mage.Sets/src/mage/cards/n/NemesisOfMortals.java b/Mage.Sets/src/mage/cards/n/NemesisOfMortals.java index b238a6855e5..e6960031d07 100644 --- a/Mage.Sets/src/mage/cards/n/NemesisOfMortals.java +++ b/Mage.Sets/src/mage/cards/n/NemesisOfMortals.java @@ -63,7 +63,7 @@ enum NemesisOfMortalsAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { Player controller = game.getPlayer(ability.getControllerId()); if (controller != null) { CardUtil.reduceCost(ability, controller.getGraveyard().count(StaticFilters.FILTER_CARD_CREATURE, game)); diff --git a/Mage.Sets/src/mage/cards/n/NewWayForward.java b/Mage.Sets/src/mage/cards/n/NewWayForward.java new file mode 100644 index 00000000000..7b359e2b9ea --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NewWayForward.java @@ -0,0 +1,97 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.PreventionEffectData; +import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.target.TargetSource; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NewWayForward extends CardImpl { + + public NewWayForward(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}{R}{W}"); + + // The next time a source of your choice would deal damage to you this turn, prevent that damage. When damage is prevented this way, New Way Forward deals that much damage to that source's controller and you draw that many cards. + this.getSpellAbility().addEffect(new NewWayForwardEffect()); + } + + private NewWayForward(final NewWayForward card) { + super(card); + } + + @Override + public NewWayForward copy() { + return new NewWayForward(this); + } +} + +class NewWayForwardEffect extends PreventionEffectImpl { + + private final TargetSource target; + + NewWayForwardEffect() { + super(Duration.EndOfTurn, Integer.MAX_VALUE, false, false); + this.staticText = "the next time a source of your choice would deal damage to you this turn, " + + "prevent that damage. When damage is prevented this way, " + + "{this} deals that much damage to that source's controller and you draw that many cards"; + this.target = new TargetSource(); + } + + private NewWayForwardEffect(final NewWayForwardEffect effect) { + super(effect); + this.target = effect.target.copy(); + } + + @Override + public NewWayForwardEffect copy() { + return new NewWayForwardEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + PreventionEffectData preventionData = preventDamageAction(event, source, game); + this.used = true; + this.discard(); // only one use + if (preventionData.getPreventedDamage() < 1) { + return true; + } + UUID objectControllerId = game.getControllerId(target.getFirstTarget()); + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new DamageTargetEffect(preventionData.getPreventedDamage()) + .setTargetPointer(new FixedTarget(objectControllerId)), + false + ); + ability.addEffect(new DrawCardSourceControllerEffect(preventionData.getPreventedDamage())); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return !this.used + && super.applies(event, source, game) + && event.getTargetId().equals(source.getControllerId()) + && event.getSourceId().equals(target.getFirstTarget()); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NightmarishEnd.java b/Mage.Sets/src/mage/cards/n/NightmarishEnd.java index fb5f9587c9e..d2a939fc308 100644 --- a/Mage.Sets/src/mage/cards/n/NightmarishEnd.java +++ b/Mage.Sets/src/mage/cards/n/NightmarishEnd.java @@ -18,7 +18,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class NightmarishEnd extends CardImpl { - private static final DynamicValue xValue = new SignInversionDynamicValue(CardsInControllerHandCount.instance); + private static final DynamicValue xValue = new SignInversionDynamicValue(CardsInControllerHandCount.ANY); public NightmarishEnd(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{B}"); diff --git a/Mage.Sets/src/mage/cards/n/NostalgicDreams.java b/Mage.Sets/src/mage/cards/n/NostalgicDreams.java index 9bff99fef2d..bb453db207e 100644 --- a/Mage.Sets/src/mage/cards/n/NostalgicDreams.java +++ b/Mage.Sets/src/mage/cards/n/NostalgicDreams.java @@ -22,8 +22,8 @@ public final class NostalgicDreams extends CardImpl { public NostalgicDreams(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}{G}"); - // As an additional cost to cast Nostalgic Dreams, discard X cards. - this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true)); + // As an additional cost to cast this spell, discard X cards. + this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true)); // Return X target cards from your graveyard to your hand. Effect effect = new ReturnFromGraveyardToHandTargetEffect(); diff --git a/Mage.Sets/src/mage/cards/o/OboroEnvoy.java b/Mage.Sets/src/mage/cards/o/OboroEnvoy.java index d44b232d62e..0fcb580a0f5 100644 --- a/Mage.Sets/src/mage/cards/o/OboroEnvoy.java +++ b/Mage.Sets/src/mage/cards/o/OboroEnvoy.java @@ -28,7 +28,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class OboroEnvoy extends CardImpl { - private static final DynamicValue xValue = new SignInversionDynamicValue(CardsInControllerHandCount.instance); + private static final DynamicValue xValue = new SignInversionDynamicValue(CardsInControllerHandCount.ANY); public OboroEnvoy(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); diff --git a/Mage.Sets/src/mage/cards/o/OpenTheWay.java b/Mage.Sets/src/mage/cards/o/OpenTheWay.java index 52f49155a0a..c1d7faebbc6 100644 --- a/Mage.Sets/src/mage/cards/o/OpenTheWay.java +++ b/Mage.Sets/src/mage/cards/o/OpenTheWay.java @@ -3,7 +3,6 @@ package mage.cards.o; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.mana.VariableManaCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.InfoEffect; import mage.cards.*; @@ -28,7 +27,7 @@ public final class OpenTheWay extends CardImpl { this.addAbility(new SimpleStaticAbility( Zone.ALL, new InfoEffect("X can't be greater than the number of players in the game") ).setRuleAtTheTop(true)); - this.getSpellAbility().setCostAdjuster(OpenTheWayAdjuster.instance); + this.getSpellAbility().setCostAdjuster(OpenTheWayCostAdjuster.instance); // Reveal cards from the top of your library until you reveal X land cards. Put those land cards onto the battlefield tapped and the rest on the bottom of your library in a random order. this.getSpellAbility().addEffect(new OpenTheWayEffect()); @@ -44,14 +43,12 @@ public final class OpenTheWay extends CardImpl { } } -enum OpenTheWayAdjuster implements CostAdjuster { +enum OpenTheWayCostAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { - int playerCount = game.getPlayers().size(); - CardUtil.castStream(ability.getCosts().stream(), VariableManaCost.class) - .forEach(cost -> cost.setMaxX(playerCount)); + public void prepareX(Ability ability, Game game) { + ability.setVariableCostsMinMax(0, game.getState().getPlayersInRange(ability.getControllerId(), game, true).size()); } } diff --git a/Mage.Sets/src/mage/cards/o/OsgirTheReconstructor.java b/Mage.Sets/src/mage/cards/o/OsgirTheReconstructor.java index 5f2208a423b..6d72f04bcfb 100644 --- a/Mage.Sets/src/mage/cards/o/OsgirTheReconstructor.java +++ b/Mage.Sets/src/mage/cards/o/OsgirTheReconstructor.java @@ -33,7 +33,6 @@ import mage.util.CardUtil; import java.util.UUID; /** - * * @author Arketec */ public final class OsgirTheReconstructor extends CardImpl { @@ -81,15 +80,15 @@ enum OsgirTheReconstructorCostAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void prepareCost(Ability ability, Game game) { int xValue = CardUtil.getSourceCostsTag(game, ability, "X", 0); Player controller = game.getPlayer(ability.getControllerId()); if (controller == null) { return; } - FilterCard filter = new FilterArtifactCard("an artifact card with mana value "+xValue+" from your graveyard"); + FilterCard filter = new FilterArtifactCard("an artifact card with mana value " + xValue + " from your graveyard"); filter.add(new ManaValuePredicate(ComparisonType.EQUAL_TO, xValue)); - for (Cost cost: ability.getCosts()) { + for (Cost cost : ability.getCosts()) { if (cost instanceof ExileFromGraveCost) { cost.getTargets().set(0, new TargetCardInYourGraveyard(filter)); } @@ -104,7 +103,7 @@ class OsgirTheReconstructorCreateArtifactTokensEffect extends OneShotEffect { this.staticText = "Create two tokens that are copies of the exiled card."; } - private OsgirTheReconstructorCreateArtifactTokensEffect(final OsgirTheReconstructorCreateArtifactTokensEffect effect) { + private OsgirTheReconstructorCreateArtifactTokensEffect(final OsgirTheReconstructorCreateArtifactTokensEffect effect) { super(effect); } @@ -127,11 +126,11 @@ class OsgirTheReconstructorCreateArtifactTokensEffect extends OneShotEffect { effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId()))); effect.apply(game, source); - return true; + return true; } @Override - public OsgirTheReconstructorCreateArtifactTokensEffect copy() { + public OsgirTheReconstructorCreateArtifactTokensEffect copy() { return new OsgirTheReconstructorCreateArtifactTokensEffect(this); } } diff --git a/Mage.Sets/src/mage/cards/o/OutpostSiege.java b/Mage.Sets/src/mage/cards/o/OutpostSiege.java index 449ac8d4711..47827489fe1 100644 --- a/Mage.Sets/src/mage/cards/o/OutpostSiege.java +++ b/Mage.Sets/src/mage/cards/o/OutpostSiege.java @@ -1,54 +1,48 @@ package mage.cards.o; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.common.ZoneChangeAllTriggeredAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.constants.ModeChoice; +import mage.filter.StaticFilters; import mage.target.common.TargetAnyTarget; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class OutpostSiege extends CardImpl { - private static final String ruleTrigger1 = "&bull Khans — At the beginning of your upkeep, exile the top card of your library. Until end of turn, you may play that card."; - private static final String ruleTrigger2 = "&bull Dragons — Whenever a creature you control leaves the battlefield, {this} deals 1 damage to any target."; - public OutpostSiege(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}"); // As Outpost Siege enters the battlefield, choose Khans or Dragons. - this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Khans or Dragons?", "Khans", "Dragons"), null, - "As {this} enters, choose Khans or Dragons.", "")); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.KHANS, ModeChoice.DRAGONS))); // * Khans - At the beginning of your upkeep, exile the top card of your library. Until end of turn, you may play that card. - this.addAbility(new ConditionalTriggeredAbility( - new BeginningOfUpkeepTriggeredAbility(new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn)), - new ModeChoiceSourceCondition("Khans"), - ruleTrigger1)); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new BeginningOfUpkeepTriggeredAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn) + ), ModeChoice.KHANS + ))); // * Dragons - Whenever a creature you control leaves the battlefield, Outpost Siege deals 1 damage to any target. - Ability ability2 = new ConditionalTriggeredAbility( - new ZoneChangeAllTriggeredAbility(Zone.BATTLEFIELD, Zone.BATTLEFIELD, null, new DamageTargetEffect(1), - new FilterControlledCreaturePermanent(), "", false), - new ModeChoiceSourceCondition("Dragons"), - ruleTrigger2); - ability2.addTarget(new TargetAnyTarget()); - this.addAbility(ability2); - + Ability ability = new LeavesBattlefieldAllTriggeredAbility( + new DamageTargetEffect(1), StaticFilters.FILTER_CONTROLLED_A_CREATURE + ); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability, ModeChoice.DRAGONS))); } private OutpostSiege(final OutpostSiege card) { diff --git a/Mage.Sets/src/mage/cards/o/OverbeingOfMyth.java b/Mage.Sets/src/mage/cards/o/OverbeingOfMyth.java index 1f8fe4c40e2..3f9c8546f86 100644 --- a/Mage.Sets/src/mage/cards/o/OverbeingOfMyth.java +++ b/Mage.Sets/src/mage/cards/o/OverbeingOfMyth.java @@ -13,7 +13,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TargetController; import mage.constants.Zone; /** @@ -32,7 +31,7 @@ public final class OverbeingOfMyth extends CardImpl { this.toughness = new MageInt(0); // Overbeing of Myth's power and toughness are each equal to the number of cards in your hand. - DynamicValue number = CardsInControllerHandCount.instance; + DynamicValue number = CardsInControllerHandCount.ANY; this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(number))); // At the beginning of your draw step, draw an additional card. diff --git a/Mage.Sets/src/mage/cards/p/PalaceSiege.java b/Mage.Sets/src/mage/cards/p/PalaceSiege.java index 445a1411d5d..24b4a86a524 100644 --- a/Mage.Sets/src/mage/cards/p/PalaceSiege.java +++ b/Mage.Sets/src/mage/cards/p/PalaceSiege.java @@ -1,55 +1,43 @@ package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; -import mage.abilities.effects.Effect; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ModeChoice; import mage.filter.StaticFilters; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class PalaceSiege extends CardImpl { - private static final String ruleTrigger1 = "&bull Khans — At the beginning of your upkeep, return target creature card from your graveyard to your hand."; - private static final String ruleTrigger2 = "&bull Dragons — At the beginning of your upkeep, each opponent loses 2 life and you gain 2 life."; - public PalaceSiege(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}{B}"); // As Palace Siege enters the battlefield, choose Khans or Dragons. - this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Khans or Dragons?", "Khans", "Dragons"), null, - "As {this} enters, choose Khans or Dragons.", "")); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.KHANS, ModeChoice.DRAGONS))); // * Khans - At the beginning of your upkeep, return target creature card from your graveyard to your hand. - Ability ability1 = new ConditionalTriggeredAbility( - new BeginningOfUpkeepTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()), - new ModeChoiceSourceCondition("Khans"), - ruleTrigger1); + Ability ability1 = new BeginningOfUpkeepTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); ability1.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE)); - this.addAbility(ability1); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability1, ModeChoice.KHANS))); // * Dragons - At the beginning of your upkeep, each opponent loses 2 life and you gain 2 life. - Ability ability2 = new ConditionalTriggeredAbility( - new BeginningOfUpkeepTriggeredAbility(new LoseLifeOpponentsEffect(2)), - new ModeChoiceSourceCondition("Dragons"), - ruleTrigger2); - Effect effect = new GainLifeEffect(2); - effect.setText("and you gain 2 life"); - ability2.addEffect(effect); - this.addAbility(ability2); + Ability ability2 = new BeginningOfUpkeepTriggeredAbility(new LoseLifeOpponentsEffect(2)); + ability2.addEffect(new GainLifeEffect(2).setText("and you gain 2 life")); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect(ability2, ModeChoice.DRAGONS))); } diff --git a/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java b/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java index f6ea0a2222a..0a72089951c 100644 --- a/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java +++ b/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java @@ -96,7 +96,7 @@ class PawpatchRecruitTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/p/Perennation.java b/Mage.Sets/src/mage/cards/p/Perennation.java new file mode 100644 index 00000000000..ad266c96dce --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/Perennation.java @@ -0,0 +1,36 @@ +package mage.cards.p; + +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Perennation extends CardImpl { + + public Perennation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}{B}{G}"); + + // Return target permanent card from your graveyard to the battlefield with a hexproof counter and an indestructible counter on it. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect( + CounterType.HEXPROOF.createInstance(), CounterType.INDESTRUCTIBLE.createInstance() + )); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_PERMANENT)); + } + + private Perennation(final Perennation card) { + super(card); + } + + @Override + public Perennation copy() { + return new Perennation(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java b/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java index 366fe7c8fa9..2170f980cb3 100644 --- a/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java +++ b/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java @@ -1,23 +1,21 @@ package mage.cards.p; -import java.util.UUID; - import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.DiesCreatureTriggeredAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; -import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterNonlandPermanent; @@ -27,6 +25,8 @@ import mage.game.permanent.token.HorrorEnchantmentCreatureToken; import mage.players.Player; import mage.target.TargetPermanent; +import java.util.UUID; + /** * @author Cguy7777 */ @@ -41,28 +41,23 @@ public final class PhenomenonInvestigators extends CardImpl { this.toughness = new MageInt(4); // As Phenomenon Investigators enters, choose Believe or Doubt. - this.addAbility(new AsEntersBattlefieldAbility( - new ChooseModeEffect("Believe or Doubt?", "Believe", "Doubt"))); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.BELIEVE, ModeChoice.DOUBT))); // * Believe -- Whenever a nontoken creature you control dies, create a 2/2 black Horror enchantment creature token. - this.addAbility(new ConditionalTriggeredAbility( + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( new DiesCreatureTriggeredAbility( new CreateTokenEffect(new HorrorEnchantmentCreatureToken()), - false, - StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN), - new ModeChoiceSourceCondition("Believe"), - "&bull Believe — Whenever a nontoken creature you control dies, " + - "create a 2/2 black Horror enchantment creature token.")); + false, StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN + ), ModeChoice.BELIEVE + ))); // * Doubt -- At the beginning of your end step, you may return a nonland permanent you own to your hand. If you do, draw a card. - this.addAbility(new ConditionalTriggeredAbility( - new BeginningOfEndStepTriggeredAbility( - new DoIfCostPaid( - new DrawCardSourceControllerEffect(1), - new PhenomenonInvestigatorsReturnCost())), - new ModeChoiceSourceCondition("Doubt"), - "&bull Doubt — At the beginning of your end step, you may return a nonland permanent " + - "you own to your hand. If you do, draw a card.")); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new BeginningOfEndStepTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), + new PhenomenonInvestigatorsReturnCost() + )), ModeChoice.DOUBT + ))); } private PhenomenonInvestigators(final PhenomenonInvestigators card) { diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianPurge.java b/Mage.Sets/src/mage/cards/p/PhyrexianPurge.java index e0e5ac0e8b7..0e2b03b43a2 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianPurge.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianPurge.java @@ -46,7 +46,7 @@ enum PhyrexianPurgeCostAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void increaseCost(Ability ability, Game game) { int numTargets = ability.getTargets().get(0).getTargets().size(); if (numTargets > 0) { ability.addCost(new PayLifeCost(numTargets * 3)); diff --git a/Mage.Sets/src/mage/cards/p/PlateArmor.java b/Mage.Sets/src/mage/cards/p/PlateArmor.java index 3fe86c05be0..87f413a895d 100644 --- a/Mage.Sets/src/mage/cards/p/PlateArmor.java +++ b/Mage.Sets/src/mage/cards/p/PlateArmor.java @@ -81,7 +81,7 @@ enum PlateArmorAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { Player controller = game.getPlayer(ability.getControllerId()); if (controller != null) { int count = equipmentCount.calculate(game, ability, null); diff --git a/Mage.Sets/src/mage/cards/p/PresenceOfTheWise.java b/Mage.Sets/src/mage/cards/p/PresenceOfTheWise.java index de5b4794bad..433a2791e80 100644 --- a/Mage.Sets/src/mage/cards/p/PresenceOfTheWise.java +++ b/Mage.Sets/src/mage/cards/p/PresenceOfTheWise.java @@ -20,7 +20,7 @@ public final class PresenceOfTheWise extends CardImpl { // You gain 2 life for each card in your hand. this.getSpellAbility().addEffect(new GainLifeEffect( - new MultipliedValue(CardsInControllerHandCount.instance, 2),"You gain 2 life for each card in your hand")); + new MultipliedValue(CardsInControllerHandCount.ANY, 2),"You gain 2 life for each card in your hand")); } private PresenceOfTheWise(final PresenceOfTheWise card) { diff --git a/Mage.Sets/src/mage/cards/p/PrototypePortal.java b/Mage.Sets/src/mage/cards/p/PrototypePortal.java index 9f2e4ea31f7..d8a92eb098f 100644 --- a/Mage.Sets/src/mage/cards/p/PrototypePortal.java +++ b/Mage.Sets/src/mage/cards/p/PrototypePortal.java @@ -4,11 +4,8 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.VariableCost; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.costadjusters.ImprintedManaValueXCostAdjuster; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; @@ -42,10 +39,10 @@ public final class PrototypePortal extends CardImpl { .setAbilityWord(AbilityWord.IMPRINT) ); - // {X}, {tap}: Create a token that's a copy of the exiled card. X is the converted mana cost of that card. + // {X}, {T}: Create a token that's a copy of the exiled card. X is the converted mana cost of that card. Ability ability = new SimpleActivatedAbility(new PrototypePortalCreateTokenEffect(), new ManaCostsImpl<>("{X}")); ability.addCost(new TapSourceCost()); - ability.setCostAdjuster(PrototypePortalAdjuster.instance); + ability.setCostAdjuster(ImprintedManaValueXCostAdjuster.instance); this.addAbility(ability); } @@ -59,31 +56,6 @@ public final class PrototypePortal extends CardImpl { } } -enum PrototypePortalAdjuster implements CostAdjuster { - instance; - - @Override - public void adjustCosts(Ability ability, Game game) { - Permanent card = game.getPermanent(ability.getSourceId()); - if (card != null) { - if (!card.getImprinted().isEmpty()) { - Card imprinted = game.getCard(card.getImprinted().get(0)); - if (imprinted != null) { - ability.clearManaCostsToPay(); - ability.addManaCostsToPay(new GenericManaCost(imprinted.getManaValue())); - } - } - } - - // no {X} anymore as we already have imprinted the card with defined manacost - for (ManaCost cost : ability.getManaCostsToPay()) { - if (cost instanceof VariableCost) { - cost.setPaid(); - } - } - } -} - class PrototypePortalEffect extends OneShotEffect { PrototypePortalEffect() { diff --git a/Mage.Sets/src/mage/cards/p/PsychosisCrawler.java b/Mage.Sets/src/mage/cards/p/PsychosisCrawler.java index 4fbd2dd259f..bd99c7adad1 100644 --- a/Mage.Sets/src/mage/cards/p/PsychosisCrawler.java +++ b/Mage.Sets/src/mage/cards/p/PsychosisCrawler.java @@ -28,7 +28,7 @@ public final class PsychosisCrawler extends CardImpl { this.power = new MageInt(0); this.toughness = new MageInt(0); - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.instance))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.ANY))); this.addAbility(new DrawCardControllerTriggeredAbility(new LoseLifeOpponentsEffect(1), false)); } diff --git a/Mage.Sets/src/mage/cards/p/Pteramander.java b/Mage.Sets/src/mage/cards/p/Pteramander.java index c4fb5b9fe71..41a6acd784f 100644 --- a/Mage.Sets/src/mage/cards/p/Pteramander.java +++ b/Mage.Sets/src/mage/cards/p/Pteramander.java @@ -70,7 +70,7 @@ enum PteramanderAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { int count = cardsCount.calculate(game, ability, null); CardUtil.reduceCost(ability, count); } diff --git a/Mage.Sets/src/mage/cards/p/PurgingStormbrood.java b/Mage.Sets/src/mage/cards/p/PurgingStormbrood.java new file mode 100644 index 00000000000..e1566facf10 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PurgingStormbrood.java @@ -0,0 +1,70 @@ +package mage.cards.p; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.RemoveAllCountersPermanentTargetEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.OmenCard; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Jmlundeen + */ +public final class PurgingStormbrood extends OmenCard { + + public PurgingStormbrood(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.INSTANT}, "{4}{B}", "Absorb Essence", "{1}{W}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Ward--Pay 2 life. + this.addAbility(new WardAbility(new PayLifeCost(2))); + + // When this creature enters, remove all counters from up to one target creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new RemoveAllCountersPermanentTargetEffect()); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // Absorb Essence + // Target creature gets +2/+2 and gains lifelink and hexproof until end of turn. + this.getSpellCard().getSpellAbility().addEffect(new BoostTargetEffect(2, 2) + .setText("Target creature gets +2/+2")); + this.getSpellCard().getSpellAbility().addEffect(new GainAbilityTargetEffect(LifelinkAbility.getInstance()) + .setText("gains lifelink") + .concatBy("and")); + this.getSpellCard().getSpellAbility().addEffect(new GainAbilityTargetEffect(HexproofAbility.getInstance()) + .setText("hexproof until end of turn") + .concatBy("and")); + this.getSpellCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.finalizeOmen(); + } + + private PurgingStormbrood(final PurgingStormbrood card) { + super(card); + } + + @Override + public PurgingStormbrood copy() { + return new PurgingStormbrood(this); + } +} diff --git a/Mage.Sets/src/mage/cards/q/QuestForTheNecropolis.java b/Mage.Sets/src/mage/cards/q/QuestForTheNecropolis.java index 3283a8631ec..8bcd9da1095 100644 --- a/Mage.Sets/src/mage/cards/q/QuestForTheNecropolis.java +++ b/Mage.Sets/src/mage/cards/q/QuestForTheNecropolis.java @@ -56,7 +56,7 @@ enum QuestForTheNecropolisAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { int amount = Optional .ofNullable(ability.getSourcePermanentIfItStillExists(game)) .map(permanent -> permanent.getCounters(game).getCount(CounterType.QUEST)) diff --git a/Mage.Sets/src/mage/cards/r/RagingBattleMouse.java b/Mage.Sets/src/mage/cards/r/RagingBattleMouse.java index 3f7ae7785d6..fbd10b2475b 100644 --- a/Mage.Sets/src/mage/cards/r/RagingBattleMouse.java +++ b/Mage.Sets/src/mage/cards/r/RagingBattleMouse.java @@ -4,7 +4,6 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; import mage.abilities.condition.common.CelebrationCondition; import mage.abilities.decorator.ConditionalCostModificationEffect; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; @@ -14,10 +13,9 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; -import mage.game.Game; import mage.target.common.TargetControlledCreaturePermanent; import mage.watchers.common.PermanentsEnteredBattlefieldWatcher; -import mage.watchers.common.SpellsCastWatcher; +import mage.abilities.condition.common.YouCastExactOneSpellThisTurnCondition; import java.util.UUID; @@ -63,13 +61,3 @@ public final class RagingBattleMouse extends CardImpl { return new RagingBattleMouse(this); } } - -enum YouCastExactOneSpellThisTurnCondition implements Condition { - instance; - - @Override - public boolean apply(Game game, Ability source) { - SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); - return watcher != null && watcher.getSpellsCastThisTurn(source.getControllerId()).size() == 1; - } -} diff --git a/Mage.Sets/src/mage/cards/r/RakdosJoinsUp.java b/Mage.Sets/src/mage/cards/r/RakdosJoinsUp.java index 6750ef7d2c9..493077261e8 100644 --- a/Mage.Sets/src/mage/cards/r/RakdosJoinsUp.java +++ b/Mage.Sets/src/mage/cards/r/RakdosJoinsUp.java @@ -43,7 +43,7 @@ public final class RakdosJoinsUp extends CardImpl { // When Rakdos Joins Up enters the battlefield, return target creature card from your graveyard to the battlefield with two additional +1/+1 counters on it. Ability ability = new EntersBattlefieldTriggeredAbility( - new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.P1P1.createInstance(2), true) + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(true, CounterType.P1P1.createInstance(2)) ); ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/r/RallyTheMonastery.java b/Mage.Sets/src/mage/cards/r/RallyTheMonastery.java index 335b01cee14..7867c2c0c48 100644 --- a/Mage.Sets/src/mage/cards/r/RallyTheMonastery.java +++ b/Mage.Sets/src/mage/cards/r/RallyTheMonastery.java @@ -1,19 +1,14 @@ package mage.cards.r; -import java.util.List; -import java.util.Objects; import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CastAnotherSpellThisTurnCondition; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; 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; @@ -22,23 +17,15 @@ import mage.constants.Zone; import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.PowerPredicate; -import mage.game.Game; -import mage.game.permanent.token.BirdToken; import mage.game.permanent.token.MonasteryMentorToken; -import mage.game.stack.Spell; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.watchers.common.SpellsCastWatcher; /** * @author balazskristof */ public final class RallyTheMonastery extends CardImpl { - private static final Hint hint = new ConditionHint( - RallyTheMonasteryCondition.instance, "You've cast a spell this turn" - ); - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power 4 or greater"); static { @@ -51,8 +38,8 @@ public final class RallyTheMonastery extends CardImpl { // This spell costs {2} less to cast if you've cast another spell this turn. this.addAbility(new SimpleStaticAbility( Zone.ALL, - new SpellCostReductionSourceEffect(2, RallyTheMonasteryCondition.instance).setCanWorksOnStackOnly(true) - ).setRuleAtTheTop(true).addHint(hint)); + new SpellCostReductionSourceEffect(2, CastAnotherSpellThisTurnCondition.instance).setCanWorksOnStackOnly(true) + ).setRuleAtTheTop(true).addHint(CastAnotherSpellThisTurnCondition.instance.getHint())); // Choose one — this.getSpellAbility().getModes().setMinModes(1); @@ -79,25 +66,3 @@ public final class RallyTheMonastery extends CardImpl { } } -enum RallyTheMonasteryCondition implements Condition { - instance; - - @Override - public boolean apply(Game game, Ability source) { - SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); - if (watcher == null) { - return false; - } - List spells = watcher.getSpellsCastThisTurn(source.getControllerId()); - return spells != null && spells - .stream() - .filter(Objects::nonNull) - .anyMatch(spell -> !spell.getSourceId().equals(source.getSourceId())); - } - - @Override - public String toString() { - return "you've cast another spell this turn"; - } -} - diff --git a/Mage.Sets/src/mage/cards/r/RalsStaticaster.java b/Mage.Sets/src/mage/cards/r/RalsStaticaster.java index 0a7a502a778..d05013c785b 100644 --- a/Mage.Sets/src/mage/cards/r/RalsStaticaster.java +++ b/Mage.Sets/src/mage/cards/r/RalsStaticaster.java @@ -45,7 +45,7 @@ public final class RalsStaticaster extends CardImpl { // Whenever Ral's Staticaster attacks, if you control a Ral planeswalker, Ral's Staticaster gets +1/+0 for each card in your hand until end of turn. this.addAbility(new ConditionalInterveningIfTriggeredAbility( new AttacksTriggeredAbility(new BoostSourceEffect( - CardsInControllerHandCount.instance, StaticValue.get(0), + CardsInControllerHandCount.ANY, StaticValue.get(0), Duration.EndOfTurn), false), new PermanentsOnTheBattlefieldCondition(filter), "Whenever {this} attacks, if you control a Ral planeswalker, " diff --git a/Mage.Sets/src/mage/cards/r/RazorlashTransmogrant.java b/Mage.Sets/src/mage/cards/r/RazorlashTransmogrant.java index 22cdf9babf4..5ee5549c232 100644 --- a/Mage.Sets/src/mage/cards/r/RazorlashTransmogrant.java +++ b/Mage.Sets/src/mage/cards/r/RazorlashTransmogrant.java @@ -69,7 +69,7 @@ enum RazorlashTransmograntAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (makeMap(game, ability).values().stream().anyMatch(x -> x >= 4)) { CardUtil.reduceCost(ability, 4); } diff --git a/Mage.Sets/src/mage/cards/r/RedoubledStormsinger.java b/Mage.Sets/src/mage/cards/r/RedoubledStormsinger.java new file mode 100644 index 00000000000..ead0ddedaa9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RedoubledStormsinger.java @@ -0,0 +1,101 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.EnteredThisTurnPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTargets; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RedoubledStormsinger extends CardImpl { + + public RedoubledStormsinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.ORC); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Whenever this creature attacks, for each creature token you control that entered this turn, create a tapped and attacking token that's a copy of that token. At the beginning of the next end step, sacrifice those tokens. + this.addAbility(new AttacksTriggeredAbility(new RedoubledStormsingerEffect())); + } + + private RedoubledStormsinger(final RedoubledStormsinger card) { + super(card); + } + + @Override + public RedoubledStormsinger copy() { + return new RedoubledStormsinger(this); + } +} + +class RedoubledStormsingerEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(TokenPredicate.TRUE); + filter.add(EnteredThisTurnPredicate.instance); + } + + RedoubledStormsingerEffect() { + super(Outcome.Benefit); + staticText = "for each creature token you control that entered this turn, " + + "create a tapped and attacking token that's a copy of that token. " + + "At the beginning of the next end step, sacrifice those tokens"; + } + + private RedoubledStormsingerEffect(final RedoubledStormsingerEffect effect) { + super(effect); + } + + @Override + public RedoubledStormsingerEffect copy() { + return new RedoubledStormsingerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set addedTokens = new HashSet<>(); + for (Permanent permanent : game.getBattlefield().getActivePermanents( + filter, source.getControllerId(), source, game + )) { + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect( + null, null, false, + 1, true, true + ).setSavedPermanent(permanent); + effect.apply(game, source); + addedTokens.addAll(effect.getAddedPermanents()); + } + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new SacrificeTargetEffect().setTargetPointer(new FixedTargets(addedTokens, game)) + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RestlessDreams.java b/Mage.Sets/src/mage/cards/r/RestlessDreams.java index 278a177431d..a81e7d2e06a 100644 --- a/Mage.Sets/src/mage/cards/r/RestlessDreams.java +++ b/Mage.Sets/src/mage/cards/r/RestlessDreams.java @@ -20,7 +20,7 @@ public final class RestlessDreams extends CardImpl { public RestlessDreams(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}"); - // As an additional cost to cast Restless Dreams, discard X cards. + // As an additional cost to cast this spell, discard X cards. this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true)); // Return X target creature cards from your graveyard to your hand. diff --git a/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java b/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java new file mode 100644 index 00000000000..34f954a2b09 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java @@ -0,0 +1,86 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ReunionOfTheHouse extends CardImpl { + + public ReunionOfTheHouse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{W}{W}"); + + // Return any number of target creature cards with total power 10 or less from your graveyard to the battlefield. Exile Reunion of the House. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); + this.getSpellAbility().addTarget(new ReunionOfTheHouseTarget()); + } + + private ReunionOfTheHouse(final ReunionOfTheHouse card) { + super(card); + } + + @Override + public ReunionOfTheHouse copy() { + return new ReunionOfTheHouse(this); + } +} + +class ReunionOfTheHouseTarget extends TargetCardInYourGraveyard { + + private static final FilterCard filterStatic + = new FilterCreatureCard("creature cards with total power 10 or less from your graveyard"); + + ReunionOfTheHouseTarget() { + super(0, Integer.MAX_VALUE, filterStatic); + } + + private ReunionOfTheHouseTarget(final ReunionOfTheHouseTarget target) { + super(target); + } + + @Override + public ReunionOfTheHouseTarget copy() { + return new ReunionOfTheHouseTarget(this); + } + + @Override + public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + return super.canTarget(controllerId, id, source, game) + && CardUtil.checkCanTargetTotalValueLimit( + this.getTargets(), id, m -> m.getPower().getValue(), 10, game); + } + + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + return CardUtil.checkPossibleTargetsTotalValueLimit(this.getTargets(), + super.possibleTargets(sourceControllerId, source, game), + m -> m.getPower().getValue(), 10, game); + } + + @Override + public String getMessage(Game game) { + // shows selected total + int selectedValue = this.getTargets().stream() + .map(game::getObject) + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .sum(); + return super.getMessage(game) + " (selected total power " + selectedValue + ")"; + } +} diff --git a/Mage.Sets/src/mage/cards/r/ReverberatingSummons.java b/Mage.Sets/src/mage/cards/r/ReverberatingSummons.java new file mode 100644 index 00000000000..4ca6b91c46f --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReverberatingSummons.java @@ -0,0 +1,73 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.DiscardHandCost; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.game.Game; +import mage.game.permanent.token.custom.CreatureToken; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ReverberatingSummons extends CardImpl { + + public ReverberatingSummons(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); + + // At the beginning of each combat, if you've cast two or more spells this turn, this enchantment becomes a 3/3 Monk creature with haste in addition to its other types until end of turn. + this.addAbility(new BeginningOfCombatTriggeredAbility( + TargetController.ANY, + new BecomesCreatureSourceEffect( + new CreatureToken(3, 3, "3/3 Monk creature with haste") + .withSubType(SubType.MONK) + .withAbility(HasteAbility.getInstance()), + CardType.ENCHANTMENT, Duration.EndOfTurn + ), false + ).withInterveningIf(ReverberatingSummonsCondition.instance)); + + // {1}{R}, Discard your hand, Sacrifice this enchantment: Draw two cards. + Ability ability = new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(2), new ManaCostsImpl<>("{1}{R}") + ); + ability.addCost(new DiscardHandCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private ReverberatingSummons(final ReverberatingSummons card) { + super(card); + } + + @Override + public ReverberatingSummons copy() { + return new ReverberatingSummons(this); + } +} + +enum ReverberatingSummonsCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getState() + .getWatcher(SpellsCastWatcher.class) + .getCount(source.getControllerId()) >= 2; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RilingDawnbreaker.java b/Mage.Sets/src/mage/cards/r/RilingDawnbreaker.java new file mode 100644 index 00000000000..21921919b2b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RilingDawnbreaker.java @@ -0,0 +1,66 @@ +package mage.cards.r; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.OmenCard; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.permanent.token.Soldier22Token; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Jmlundeen + */ +public final class RilingDawnbreaker extends OmenCard { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("another target creature you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public RilingDawnbreaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{4}{W}", "Signaling Roar", "{1}{W}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // At the beginning of combat on your turn, another target creature you control gets +1/+0 until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility(new BoostTargetEffect(1, 0)); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + + // Signaling Roar + // Create a 2/2 white Soldier creature token. + this.getSpellCard().getSpellAbility().addEffect(new CreateTokenEffect(new Soldier22Token())); + this.finalizeOmen(); + } + + private RilingDawnbreaker(final RilingDawnbreaker card) { + super(card); + } + + @Override + public RilingDawnbreaker copy() { + return new RilingDawnbreaker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RiverwheelSweep.java b/Mage.Sets/src/mage/cards/r/RiverwheelSweep.java new file mode 100644 index 00000000000..94273f90e3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RiverwheelSweep.java @@ -0,0 +1,100 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RiverwheelSweep extends CardImpl { + + public RiverwheelSweep(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2/U}{2/R}{2/W}"); + + // Tap target creature. Put three stun counters on it. + this.getSpellAbility().addEffect(new TapTargetEffect()); + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance(3)) + .setText("put three stun counters on it")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // Exile the top two cards of your library. Choose one of them. Until the end of your next turn, you may play that card. + this.getSpellAbility().addEffect(new RiverwheelSweepEffect()); + } + + private RiverwheelSweep(final RiverwheelSweep card) { + super(card); + } + + @Override + public RiverwheelSweep copy() { + return new RiverwheelSweep(this); + } +} + +class RiverwheelSweepEffect extends OneShotEffect { + + RiverwheelSweepEffect() { + super(Outcome.Benefit); + staticText = "Exile the top two cards of your library. Choose one of them. " + + "Until the end of your next turn, you may play that card"; + this.concatBy("
"); + } + + private RiverwheelSweepEffect(final RiverwheelSweepEffect effect) { + super(effect); + } + + @Override + public RiverwheelSweepEffect copy() { + return new RiverwheelSweepEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 2)); + player.moveCards(cards, Zone.EXILED, source, game); + cards.retainZone(Zone.EXILED, game); + Card card; + switch (cards.size()) { + case 0: + return false; + case 1: + card = cards.getRandom(game); + break; + default: + TargetCard target = new TargetCardInExile(StaticFilters.FILTER_CARD); + target.withNotTarget(true); + player.choose(Outcome.DrawCard, cards, target, source, game); + card = game.getCard(target.getFirstTarget()); + } + if (card == null) { + return false; + } + CardUtil.makeCardPlayable( + game, source, card, false, + Duration.UntilEndOfYourNextTurn, false + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RobobrainWarMind.java b/Mage.Sets/src/mage/cards/r/RobobrainWarMind.java index 5040637c5a4..c827b72d06e 100644 --- a/Mage.Sets/src/mage/cards/r/RobobrainWarMind.java +++ b/Mage.Sets/src/mage/cards/r/RobobrainWarMind.java @@ -42,7 +42,7 @@ public final class RobobrainWarMind extends CardImpl { this.toughness = new MageInt(5); // Robobrain War Mind's power is equal to the number of cards in your hand. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(CardsInControllerHandCount.instance))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(CardsInControllerHandCount.ANY))); // When Robobrain War Mind enters the battlefield, you get an amount of {E} equal to the number of artifact creatures you control. this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(new PermanentsOnBattlefieldCount(filter)) diff --git a/Mage.Sets/src/mage/cards/r/RootsOfLife.java b/Mage.Sets/src/mage/cards/r/RootsOfLife.java index facf5d46af3..933133ec13b 100644 --- a/Mage.Sets/src/mage/cards/r/RootsOfLife.java +++ b/Mage.Sets/src/mage/cards/r/RootsOfLife.java @@ -1,57 +1,44 @@ package mage.cards.r; -import java.util.UUID; +import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.BecomesTappedTriggeredAbility; -import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ChooseModeEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ModeChoice; import mage.constants.SubType; import mage.constants.TargetController; import mage.filter.FilterPermanent; +import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; /** - * * @author fubs */ public final class RootsOfLife extends CardImpl { - private static final String ruleTrigger1 = "&bull Island — Whenever an Island an opponent controls becomes tapped, you gain 1 life"; - private static final String ruleTrigger2 = "&bull Swamp — Whenever a Swamp an opponent controls becomes tapped, you gain 1 life"; - - private static final FilterPermanent islandFilter = new FilterPermanent("an Island an opponent controls"); - private static final FilterPermanent swampFilter = new FilterPermanent("a Swamp an opponent controls"); + private static final FilterPermanent filter = new FilterLandPermanent("a land of the chosen type an opponent controls"); static { - islandFilter.add(SubType.ISLAND.getPredicate()); - islandFilter.add(TargetController.OPPONENT.getControllerPredicate()); - swampFilter.add(SubType.SWAMP.getPredicate()); - swampFilter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(RootsOfLifePredicate.instance); } public RootsOfLife(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}{G}"); // As Roots of Life enters the battlefield, choose Island or Swamp. - this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Island or Swamp?", "Island", "Swamp"), null, - "As {this} enters, choose Island or Swamp.", "")); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.ISLAND, ModeChoice.SWAMP))); // Whenever a land of the chosen type an opponent controls becomes tapped, you gain 1 life. - // * Island chosen - this.addAbility(new ConditionalTriggeredAbility( - new BecomesTappedTriggeredAbility(new GainLifeEffect(1), false, islandFilter), - new ModeChoiceSourceCondition("Island"), - ruleTrigger1)); - - // * Swamp chosen - this.addAbility(new ConditionalTriggeredAbility( - new BecomesTappedTriggeredAbility(new GainLifeEffect(1), false, swampFilter), - new ModeChoiceSourceCondition("Swamp"), - ruleTrigger2)); + this.addAbility(new BecomesTappedTriggeredAbility(new GainLifeEffect(1), false, filter)); } private RootsOfLife(final RootsOfLife card) { @@ -63,3 +50,18 @@ public final class RootsOfLife extends CardImpl { return new RootsOfLife(this); } } + +enum RootsOfLifePredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + if (ModeChoice.ISLAND.checkMode(game, input.getSource())) { + return input.getObject().hasSubtype(SubType.ISLAND, game); + } else if (ModeChoice.SWAMP.checkMode(game, input.getSource())) { + return input.getObject().hasSubtype(SubType.SWAMP, game); + } else { + return false; + } + } +} diff --git a/Mage.Sets/src/mage/cards/r/RotCurseRakshasa.java b/Mage.Sets/src/mage/cards/r/RotCurseRakshasa.java new file mode 100644 index 00000000000..e510fe217cd --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RotCurseRakshasa.java @@ -0,0 +1,63 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.DecayedAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.TargetsCountAdjuster; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RotCurseRakshasa extends CardImpl { + + public RotCurseRakshasa(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.DEMON); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Decayed + this.addAbility(new DecayedAbility()); + + // Renew -- {X}{B}{B}, Exile this card from your graveyard: Put a decayed counter on each of X target creatures. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + Zone.GRAVEYARD, + new AddCountersTargetEffect(CounterType.DECAYED.createInstance(1)) + .setText("put a decayed counter on each of X target creatures"), + new ManaCostsImpl<>("{X}{B}{B}") + ); + ability.addCost(new ExileSourceFromGraveCost()); + ability.addTarget(new TargetCreaturePermanent()); + ability.setTargetAdjuster(new TargetsCountAdjuster(GetXValue.instance)); + this.addAbility(ability.setAbilityWord(AbilityWord.RENEW)); + } + + private RotCurseRakshasa(final RotCurseRakshasa card) { + super(card); + } + + @Override + public RotCurseRakshasa copy() { + return new RotCurseRakshasa(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RunescaleStormbrood.java b/Mage.Sets/src/mage/cards/r/RunescaleStormbrood.java new file mode 100644 index 00000000000..bb106ea73e4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RunescaleStormbrood.java @@ -0,0 +1,65 @@ +package mage.cards.r; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.OmenCard; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.TargetSpell; + +/** + * + * @author Jmlundeen + */ +public final class RunescaleStormbrood extends OmenCard { + + private static final FilterSpell filter = new FilterSpell("spell with mana value 2 or less"); + private static final FilterSpell castFilter = new FilterSpell("noncreature spell or Dragon spell"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 2)); + castFilter.add(Predicates.or( + Predicates.not(CardType.CREATURE.getPredicate()), + SubType.DRAGON.getPredicate() + )); + } + + public RunescaleStormbrood(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.INSTANT}, "{3}{R}", "Chilling Screech", "{1}{U}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast a noncreature spell or a Dragon spell, this creature gets +2/+0 until end of turn. + this.addAbility(new SpellCastControllerTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn), castFilter, false)); + + // Chilling Screech + // Counter target spell with mana value 2 or less. + this.getSpellCard().getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellCard().getSpellAbility().addTarget(new TargetSpell(filter)); + this.finalizeOmen(); + } + + private RunescaleStormbrood(final RunescaleStormbrood card) { + super(card); + } + + @Override + public RunescaleStormbrood copy() { + return new RunescaleStormbrood(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SageOfAncientLore.java b/Mage.Sets/src/mage/cards/s/SageOfAncientLore.java index 91d14fcd33c..36105114896 100644 --- a/Mage.Sets/src/mage/cards/s/SageOfAncientLore.java +++ b/Mage.Sets/src/mage/cards/s/SageOfAncientLore.java @@ -35,7 +35,7 @@ public final class SageOfAncientLore extends CardImpl { this.secondSideCardClazz = mage.cards.w.WerewolfOfAncientHunger.class; // Sage of Ancient Lore's power and toughness are each equal to the number of cards in your hand. - DynamicValue xValue = CardsInControllerHandCount.instance; + DynamicValue xValue = CardsInControllerHandCount.ANY; this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConditionalContinuousEffect(new SetBasePowerToughnessSourceEffect(xValue), new TransformedCondition(true), "{this}'s power and toughness are each equal to the total number of cards in your hand"))); diff --git a/Mage.Sets/src/mage/cards/s/SageOfTheSkies.java b/Mage.Sets/src/mage/cards/s/SageOfTheSkies.java new file mode 100644 index 00000000000..f39973e8f30 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SageOfTheSkies.java @@ -0,0 +1,54 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.condition.common.CastAnotherSpellThisTurnCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CastSourceTriggeredAbility; +import mage.abilities.effects.common.CopySourceSpellEffect; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author Jmlundeen + */ +public final class SageOfTheSkies extends CardImpl { + + public SageOfTheSkies(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When you cast this spell, if you've cast another spell this turn, copy this spell. + OneShotEffect effect = new CopySourceSpellEffect().setText("copy this spell. (The copy becomes a token.)"); + this.addAbility(new CastSourceTriggeredAbility(effect) + .withInterveningIf(CastAnotherSpellThisTurnCondition.instance) + .addHint(CastAnotherSpellThisTurnCondition.instance.getHint()) + ); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + } + + private SageOfTheSkies(final SageOfTheSkies card) { + super(card); + } + + @Override + public SageOfTheSkies copy() { + return new SageOfTheSkies(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SaguWildling.java b/Mage.Sets/src/mage/cards/s/SaguWildling.java new file mode 100644 index 00000000000..8d08c6822c1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SaguWildling.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.cards.OmenCard; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterBasicLandCard; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author Jmlundeen + */ +public final class SaguWildling extends OmenCard { + + public SaguWildling(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{4}{G}", "Roost Seek", "{G}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, you gain 3 life. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(3))); + + // Roost Seek + // Search your library for a basic land card, reveal it, put it into your hand, then shuffle. + TargetCardInLibrary target = new TargetCardInLibrary(new FilterBasicLandCard()); + this.getSpellCard().getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(target, true)); + this.finalizeOmen(); + } + + private SaguWildling(final SaguWildling card) { + super(card); + } + + @Override + public SaguWildling copy() { + return new SaguWildling(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SanctumOfTranquilLight.java b/Mage.Sets/src/mage/cards/s/SanctumOfTranquilLight.java index 11eb65f1810..ab18f92bf92 100644 --- a/Mage.Sets/src/mage/cards/s/SanctumOfTranquilLight.java +++ b/Mage.Sets/src/mage/cards/s/SanctumOfTranquilLight.java @@ -65,7 +65,7 @@ enum SanctumOfTranquilLightAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { Player controller = game.getPlayer(ability.getControllerId()); if (controller != null) { CardUtil.reduceCost(ability, count.calculate(game, ability, null)); diff --git a/Mage.Sets/src/mage/cards/s/ScavengerRegent.java b/Mage.Sets/src/mage/cards/s/ScavengerRegent.java new file mode 100644 index 00000000000..3c4e6cb2996 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScavengerRegent.java @@ -0,0 +1,60 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.cards.OmenCard; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; + +/** + * + * @author Jmlundeen + */ +public final class ScavengerRegent extends OmenCard { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("each non-Dragon creature"); + private static final DynamicValue xValue = new SignInversionDynamicValue(GetXValue.instance); + + static { + filter.add(Predicates.not(SubType.DRAGON.getPredicate())); + } + + public ScavengerRegent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{3}{B}", "Exude Toxin", "{X}{B}{B}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Ward--Discard a card. + this.addAbility(new WardAbility(new DiscardCardCost())); + + // Exude Toxin + // Each non-Dragon creature gets -X/-X until end of turn. + this.getSpellCard().getSpellAbility().addEffect(new BoostAllEffect(xValue, xValue, Duration.EndOfTurn, filter, false)); + this.finalizeOmen(); + } + + private ScavengerRegent(final ScavengerRegent card) { + super(card); + } + + @Override + public ScavengerRegent copy() { + return new ScavengerRegent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScorchedEarth.java b/Mage.Sets/src/mage/cards/s/ScorchedEarth.java index 2eb04885c64..f9bfd97929a 100644 --- a/Mage.Sets/src/mage/cards/s/ScorchedEarth.java +++ b/Mage.Sets/src/mage/cards/s/ScorchedEarth.java @@ -1,23 +1,15 @@ - package mage.cards.s; -import mage.abilities.Ability; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.costs.costadjusters.DiscardXCardsCostAdjuster; +import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.effects.common.InfoEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; -import mage.filter.common.FilterLandCard; -import mage.game.Game; -import mage.target.common.TargetCardInHand; +import mage.filter.StaticFilters; import mage.target.common.TargetLandPermanent; import mage.target.targetadjustment.XTargetsCountAdjuster; -import mage.util.CardUtil; import java.util.UUID; @@ -29,10 +21,8 @@ public final class ScorchedEarth extends CardImpl { public ScorchedEarth(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}"); - // As an additional cost to cast Scorched Earth, discard X land cards. - Ability ability = new SimpleStaticAbility(Zone.ALL, new InfoEffect("as an additional cost to cast this spell, discard X land cards")); - ability.setRuleAtTheTop(true); - this.addAbility(ability); + // As an additional cost to cast this spell, discard X land cards. + DiscardXCardsCostAdjuster.addAdjusterAndMessage(this, StaticFilters.FILTER_CARD_LANDS); // Destroy X target lands. Effect effect = new DestroyTargetEffect(); @@ -40,7 +30,7 @@ public final class ScorchedEarth extends CardImpl { this.getSpellAbility().addTarget(new TargetLandPermanent()); this.getSpellAbility().addEffect(effect); this.getSpellAbility().setTargetAdjuster(new XTargetsCountAdjuster()); - this.getSpellAbility().setCostAdjuster(ScorchedEarthCostAdjuster.instance); + this.getSpellAbility().addHint(CardsInControllerHandCount.LANDS.getHint()); } private ScorchedEarth(final ScorchedEarth card) { @@ -51,16 +41,4 @@ public final class ScorchedEarth extends CardImpl { public ScorchedEarth copy() { return new ScorchedEarth(this); } -} - -enum ScorchedEarthCostAdjuster implements CostAdjuster { - instance; - - @Override - public void adjustCosts(Ability ability, Game game) { - int xValue = CardUtil.getSourceCostsTag(game, ability, "X", 0); - if (xValue > 0) { - ability.addCost(new DiscardTargetCost(new TargetCardInHand(xValue, xValue, new FilterLandCard("land cards")))); - } - } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SeaGateRestoration.java b/Mage.Sets/src/mage/cards/s/SeaGateRestoration.java index 208dea20e35..25752198cd4 100644 --- a/Mage.Sets/src/mage/cards/s/SeaGateRestoration.java +++ b/Mage.Sets/src/mage/cards/s/SeaGateRestoration.java @@ -22,7 +22,7 @@ import java.util.UUID; */ public final class SeaGateRestoration extends ModalDoubleFacedCard { - private static final DynamicValue xValue = new IntPlusDynamicValue(1, CardsInControllerHandCount.instance); + private static final DynamicValue xValue = new IntPlusDynamicValue(1, CardsInControllerHandCount.ANY); public SeaGateRestoration(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, diff --git a/Mage.Sets/src/mage/cards/s/SeafloorStalker.java b/Mage.Sets/src/mage/cards/s/SeafloorStalker.java index 443d5889fb3..21dcd5a12fa 100644 --- a/Mage.Sets/src/mage/cards/s/SeafloorStalker.java +++ b/Mage.Sets/src/mage/cards/s/SeafloorStalker.java @@ -60,7 +60,7 @@ enum SeafloorStalkerAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { Player controller = game.getPlayer(ability.getControllerId()); if (controller != null) { int count = PartyCount.instance.calculate(game, ability, null); diff --git a/Mage.Sets/src/mage/cards/s/SeverancePriest.java b/Mage.Sets/src/mage/cards/s/SeverancePriest.java new file mode 100644 index 00000000000..ff17d0efc7b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeverancePriest.java @@ -0,0 +1,98 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateXXTokenExiledEffectManaValueEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.SpiritXXToken; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInHand; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SeverancePriest extends CardImpl { + + public SeverancePriest(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{B}{G}"); + + this.subtype.add(SubType.DJINN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // When this creature enters, target opponent reveals their hand. You may choose a nonland card from it. If you do, exile that card. + Ability ability = new EntersBattlefieldTriggeredAbility(new SeverancePriestEffect()); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // When this creature leaves the battlefield, the exiled card's owner creates an X/X white Spirit creature token, where X is the mana value of the exiled card. + this.addAbility(new LeavesBattlefieldTriggeredAbility( + new CreateXXTokenExiledEffectManaValueEffect(SpiritXXToken::new, "white Spirit") + )); + } + + private SeverancePriest(final SeverancePriest card) { + super(card); + } + + @Override + public SeverancePriest copy() { + return new SeverancePriest(this); + } +} + +class SeverancePriestEffect extends OneShotEffect { + + SeverancePriestEffect() { + super(Outcome.Benefit); + staticText = "target opponent reveals their hand. You may choose " + + "a nonland card from it. If you do, exile that card"; + } + + private SeverancePriestEffect(final SeverancePriestEffect effect) { + super(effect); + } + + @Override + public SeverancePriestEffect copy() { + return new SeverancePriestEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (controller == null || opponent == null || opponent.getHand().isEmpty()) { + return false; + } + opponent.revealCards(source, opponent.getHand(), game); + TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_NON_LAND); + controller.choose(Outcome.Discard, opponent.getHand(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + return card != null && controller.moveCardsToExile( + card, source, game, true, + CardUtil.getExileZoneId(game, source), + CardUtil.getSourceName(game, source) + ); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SewerCrocodile.java b/Mage.Sets/src/mage/cards/s/SewerCrocodile.java index 71262d8589c..c7d5a2a3320 100644 --- a/Mage.Sets/src/mage/cards/s/SewerCrocodile.java +++ b/Mage.Sets/src/mage/cards/s/SewerCrocodile.java @@ -55,7 +55,7 @@ enum SewerCrocodileAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (DifferentManaValuesInGraveCondition.FIVE.apply(game, ability)) { CardUtil.reduceCost(ability, 3); } diff --git a/Mage.Sets/src/mage/cards/s/SickeningDreams.java b/Mage.Sets/src/mage/cards/s/SickeningDreams.java index 19079354d52..d8fd04a6bae 100644 --- a/Mage.Sets/src/mage/cards/s/SickeningDreams.java +++ b/Mage.Sets/src/mage/cards/s/SickeningDreams.java @@ -7,6 +7,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import java.util.UUID; @@ -19,8 +20,8 @@ public final class SickeningDreams extends CardImpl { public SickeningDreams(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); - // As an additional cost to cast Sickening Dreams, discard X cards. - this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true)); + // As an additional cost to cast this spell, discard X cards. + this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true)); // Sickening Dreams deals X damage to each creature and each player. this.getSpellAbility().addEffect(new DamageEverythingEffect(GetXValue.instance, new FilterCreaturePermanent())); diff --git a/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java b/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java new file mode 100644 index 00000000000..ac7656de699 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java @@ -0,0 +1,106 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.VariableCostImpl; +import mage.abilities.costs.VariableCostType; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.AdditiveDynamicValue; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetadjustment.ManaValueTargetAdjuster; + +/** + * + * @author Grath + */ +public final class SidisiRegentOfTheMire extends CardImpl { + + public SidisiRegentOfTheMire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // {T}, Sacrifice a creature you control with mana value X other than Sidisi: Return target creature card with mana value X plus 1 from your graveyard to the battlefield. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), new TapSourceCost() + ); + ability.addCost(new SidisiRegentOfTheMireCost()); + ability.addTarget(new TargetCardInYourGraveyard(new FilterCreatureCard("creature card with mana value X plus 1 from your graveyard"))); + ability.setTargetAdjuster(new SidisiRegentOfTheMireAdjuster()); + this.addAbility(ability); + } + + private SidisiRegentOfTheMire(final SidisiRegentOfTheMire card) { + super(card); + } + + @Override + public SidisiRegentOfTheMire copy() { + return new SidisiRegentOfTheMire(this); + } +} + +class SidisiRegentOfTheMireAdjuster extends ManaValueTargetAdjuster { + + public SidisiRegentOfTheMireAdjuster() { + super(new AdditiveDynamicValue(GetXValue.instance, StaticValue.get(1)), ComparisonType.EQUAL_TO); + } + +} + +class SidisiRegentOfTheMireCost extends VariableCostImpl { + + public SidisiRegentOfTheMireCost() { + super(VariableCostType.NORMAL, "mana value X"); + this.text = "Sacrifice a creature with mana value X"; + } + + protected SidisiRegentOfTheMireCost(final SidisiRegentOfTheMireCost cost) { + super(cost); + } + + @Override + public SidisiRegentOfTheMireCost copy() { + return new SidisiRegentOfTheMireCost(this); + } + + @Override + public Cost getFixedCostsFromAnnouncedValue(int xValue) { + FilterPermanent filter = new FilterControlledCreaturePermanent("another creature with mana value X"); + filter.add(AnotherPredicate.instance); + filter.add(new ManaValuePredicate(ComparisonType.EQUAL_TO, xValue)); + return new SacrificeTargetCost(filter); + } + + @Override + public int getMaxValue(Ability source, Game game) { + return game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE, + source.getControllerId(), source, game + ).stream().mapToInt(MageObject::getManaValue).max().orElse(0); + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SkeletalScrying.java b/Mage.Sets/src/mage/cards/s/SkeletalScrying.java index f89a8dded84..9ea156bbf0b 100644 --- a/Mage.Sets/src/mage/cards/s/SkeletalScrying.java +++ b/Mage.Sets/src/mage/cards/s/SkeletalScrying.java @@ -61,7 +61,7 @@ enum SkeletalScryingAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void prepareCost(Ability ability, Game game) { int xValue = CardUtil.getSourceCostsTag(game, ability, "X", 0); if (xValue > 0) { ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(xValue, xValue, StaticFilters.FILTER_CARDS_FROM_YOUR_GRAVEYARD))); diff --git a/Mage.Sets/src/mage/cards/s/SkirgeFamiliar.java b/Mage.Sets/src/mage/cards/s/SkirgeFamiliar.java index b7a95acc200..99398811bba 100644 --- a/Mage.Sets/src/mage/cards/s/SkirgeFamiliar.java +++ b/Mage.Sets/src/mage/cards/s/SkirgeFamiliar.java @@ -37,7 +37,7 @@ public final class SkirgeFamiliar extends CardImpl { new DiscardCardCost(false), // not perfect but for hand cards correct, activated abilities on Battlefield will miss one possible available mana // to solve this we have to do possible mana calculation per pell/ability to use. - new IntPlusDynamicValue(-1, CardsInControllerHandCount.instance) + new IntPlusDynamicValue(-1, CardsInControllerHandCount.ANY) )); } diff --git a/Mage.Sets/src/mage/cards/s/SkyclaveApparition.java b/Mage.Sets/src/mage/cards/s/SkyclaveApparition.java index a4997e653f1..5b17917216b 100644 --- a/Mage.Sets/src/mage/cards/s/SkyclaveApparition.java +++ b/Mage.Sets/src/mage/cards/s/SkyclaveApparition.java @@ -1,29 +1,24 @@ package mage.cards.s; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.LeavesBattlefieldTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateXXTokenExiledEffectManaValueEffect; import mage.abilities.effects.common.ExileTargetForSourceEffect; 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.TargetController; import mage.filter.FilterPermanent; import mage.filter.common.FilterNonlandPermanent; import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.filter.predicate.permanent.TokenPredicate; -import mage.game.ExileZone; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.game.permanent.token.CustomIllusionToken; import mage.target.TargetPermanent; -import mage.util.CardUtil; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; import java.util.UUID; /** @@ -55,7 +50,9 @@ public final class SkyclaveApparition extends CardImpl { this.addAbility(ability); // When Skyclave Apparition leaves the battlefield, the exiled card's owner creates an X/X blue Illusion creature token, where X is the converted mana cost of the exiled card. - this.addAbility(new LeavesBattlefieldTriggeredAbility(new SkyclaveApparitionEffect(), false)); + this.addAbility(new LeavesBattlefieldTriggeredAbility( + new CreateXXTokenExiledEffectManaValueEffect(CustomIllusionToken::new, "blue Illusion") + )); } private SkyclaveApparition(final SkyclaveApparition card) { @@ -67,50 +64,3 @@ public final class SkyclaveApparition extends CardImpl { return new SkyclaveApparition(this); } } - -class SkyclaveApparitionEffect extends OneShotEffect { - - SkyclaveApparitionEffect() { - super(Outcome.Benefit); - staticText = "the exiled card's owner creates an X/X blue Illusion creature token, " + - "where X is the mana value of the exiled card"; - } - - private SkyclaveApparitionEffect(final SkyclaveApparitionEffect effect) { - super(effect); - } - - @Override - public SkyclaveApparitionEffect copy() { - return new SkyclaveApparitionEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanentLeftBattlefield = (Permanent) getValue("permanentLeftBattlefield"); - ExileZone exile = game.getExile().getExileZone( - CardUtil.getExileZoneId(game, source.getSourceId(), permanentLeftBattlefield.getZoneChangeCounter(game)) - ); - if (exile == null || exile.isEmpty()) { - return false; - } - // From ZNR Release Notes: - // https://magic.wizards.com/en/articles/archive/feature/zendikar-rising-release-notes-2020-09-10 - // If Skyclave Apparition's first ability exiled more than one card owned by a single player, - // that player creates a token with power and toughness equal to the sum of those cards' converted mana costs. - // If the first ability exiled cards owned by more than one player, each of those players creates a token - // with power and toughness equal to the sum of the converted mana costs of all cards exiled by the first ability. - Set owners = new HashSet<>(); - int totalCMC = exile - .getCards(game) - .stream() - .filter(Objects::nonNull) - .map(card -> owners.add(card.getOwnerId()) ? card : card) - .mapToInt(MageObject::getManaValue) - .sum(); - for (UUID playerId : owners) { - new CustomIllusionToken(totalCMC).putOntoBattlefield(1, game, source, playerId); - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/s/SlickSequence.java b/Mage.Sets/src/mage/cards/s/SlickSequence.java index 4c5d96c7bd0..852d441cf0c 100644 --- a/Mage.Sets/src/mage/cards/s/SlickSequence.java +++ b/Mage.Sets/src/mage/cards/s/SlickSequence.java @@ -1,22 +1,13 @@ package mage.cards.s; -import mage.MageObjectReference; -import mage.abilities.Ability; -import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CastAnotherSpellThisTurnCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.hint.ConditionHint; -import mage.abilities.hint.Hint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.WatcherScope; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.stack.Spell; import mage.target.common.TargetAnyTarget; -import mage.watchers.Watcher; import java.util.*; @@ -34,11 +25,10 @@ public final class SlickSequence extends CardImpl { this.getSpellAbility().addEffect( new ConditionalOneShotEffect( new DrawCardSourceControllerEffect(1), - SlickSequenceCondition.instance + CastAnotherSpellThisTurnCondition.instance ) ); - this.getSpellAbility().addHint(SlickSequenceCondition.hint); - this.getSpellAbility().addWatcher(new SlickSequenceWatcher()); + this.getSpellAbility().addHint(CastAnotherSpellThisTurnCondition.instance.getHint()); } private SlickSequence(final SlickSequence card) { @@ -49,68 +39,4 @@ public final class SlickSequence extends CardImpl { public SlickSequence copy() { return new SlickSequence(this); } -} - - -enum SlickSequenceCondition implements Condition { - instance; - static final Hint hint = new ConditionHint(instance, "you've cast another spell this turn"); - - @Override - public boolean apply(Game game, Ability source) { - SlickSequenceWatcher watcher = game.getState().getWatcher(SlickSequenceWatcher.class); - if (watcher == null) { - return false; - } - // may be null, watcher will handle that. - Spell sourceSpell = game.getSpell(source.getSourceId()); - return watcher.didPlayerCastAnotherSpellThisTurn(source.getControllerId(), sourceSpell, game); - } - - @Override - public String toString() { - return "you've cast another spell this turn"; - } -} - -class SlickSequenceWatcher extends Watcher { - - // Per player, MOR of the spells cast this turn. - private final Map> spellsCastThisTurn = new HashMap<>(); - - /** - * Game default watcher - */ - public SlickSequenceWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.SPELL_CAST) { - UUID playerId = event.getPlayerId(); - if (playerId != null) { - MageObjectReference mor = new MageObjectReference(event.getTargetId(), game); - spellsCastThisTurn.computeIfAbsent(playerId, x -> new HashSet<>()).add(mor); - } - } - } - - @Override - public void reset() { - super.reset(); - spellsCastThisTurn.clear(); - } - - boolean didPlayerCastAnotherSpellThisTurn(UUID playerId, Spell spell, Game game) { - Set spells = spellsCastThisTurn.getOrDefault(playerId, new HashSet<>()); - if (spell == null) { - // Not currently a spell, so any spell recorded does count as another. - return !spells.isEmpty(); - } else { - // Is currently a spell, so need to exclude that one. - MageObjectReference spellMOR = new MageObjectReference(spell.getId(), game); - return spells.stream().anyMatch(mor -> !mor.equals(spellMOR)); - } - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SokenzanSpellblade.java b/Mage.Sets/src/mage/cards/s/SokenzanSpellblade.java index 768031cdac3..2d6542e2054 100644 --- a/Mage.Sets/src/mage/cards/s/SokenzanSpellblade.java +++ b/Mage.Sets/src/mage/cards/s/SokenzanSpellblade.java @@ -15,7 +15,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; /** * @@ -35,7 +34,7 @@ public final class SokenzanSpellblade extends CardImpl { // Bushido 1 this.addAbility(new BushidoAbility(1)); // {1}{R}: Sokenzan Spellblade gets +X/+0 until end of turn, where X is the number of cards in your hand. - Effect effect = new BoostSourceEffect(CardsInControllerHandCount.instance, StaticValue.get(0), Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(CardsInControllerHandCount.ANY, StaticValue.get(0), Duration.EndOfTurn); effect.setText("{this} gets +X/+0 until end of turn, where X is the number of cards in your hand"); this.addAbility(new SimpleActivatedAbility( effect, new ManaCostsImpl<>("{1}{R}") diff --git a/Mage.Sets/src/mage/cards/s/SophicCentaur.java b/Mage.Sets/src/mage/cards/s/SophicCentaur.java index 23c59fe3e70..8ec74b56c33 100644 --- a/Mage.Sets/src/mage/cards/s/SophicCentaur.java +++ b/Mage.Sets/src/mage/cards/s/SophicCentaur.java @@ -17,7 +17,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; /** * @@ -33,7 +32,7 @@ public final class SophicCentaur extends CardImpl { this.toughness = new MageInt(1); // {2}{G}{G}, {tap}, Discard a card: You gain 2 life for each card in your hand. - DynamicValue lifeToGainAmount = new MultipliedValue(CardsInControllerHandCount.instance, 2); + DynamicValue lifeToGainAmount = new MultipliedValue(CardsInControllerHandCount.ANY, 2); Effect effect = new GainLifeEffect(lifeToGainAmount); effect.setText("You gain 2 life for each card in your hand"); Ability ability = new SimpleActivatedAbility(effect, new ManaCostsImpl<>("{2}{G}{G}")); diff --git a/Mage.Sets/src/mage/cards/s/SoramaroFirstToDream.java b/Mage.Sets/src/mage/cards/s/SoramaroFirstToDream.java index 97af2c85091..1650d440d7c 100644 --- a/Mage.Sets/src/mage/cards/s/SoramaroFirstToDream.java +++ b/Mage.Sets/src/mage/cards/s/SoramaroFirstToDream.java @@ -38,7 +38,7 @@ public final class SoramaroFirstToDream extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // Soramaro, First to Dream's power and toughness are each equal to the number of cards in your hand. - DynamicValue xValue= CardsInControllerHandCount.instance; + DynamicValue xValue= CardsInControllerHandCount.ANY; this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(xValue))); // {4}, Return a land you control to its owner's hand: Draw a card. diff --git a/Mage.Sets/src/mage/cards/s/SoulFoundry.java b/Mage.Sets/src/mage/cards/s/SoulFoundry.java index 050a8387362..f8301c8d43a 100644 --- a/Mage.Sets/src/mage/cards/s/SoulFoundry.java +++ b/Mage.Sets/src/mage/cards/s/SoulFoundry.java @@ -3,11 +3,8 @@ package mage.cards.s; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.VariableCost; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.costadjusters.ImprintedManaValueXCostAdjuster; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenCopyTargetEffect; @@ -45,7 +42,7 @@ public final class SoulFoundry extends CardImpl { // {X}, {T}: Create a token that's a copy of the exiled card. X is the converted mana cost of that card. Ability ability = new SimpleActivatedAbility(new SoulFoundryEffect(), new ManaCostsImpl<>("{X}")); ability.addCost(new TapSourceCost()); - ability.setCostAdjuster(SoulFoundryAdjuster.instance); + ability.setCostAdjuster(ImprintedManaValueXCostAdjuster.instance); this.addAbility(ability); } @@ -59,31 +56,6 @@ public final class SoulFoundry extends CardImpl { } } -enum SoulFoundryAdjuster implements CostAdjuster { - instance; - - @Override - public void adjustCosts(Ability ability, Game game) { - Permanent sourcePermanent = game.getPermanent(ability.getSourceId()); - if (sourcePermanent != null) { - if (!sourcePermanent.getImprinted().isEmpty()) { - Card imprinted = game.getCard(sourcePermanent.getImprinted().get(0)); - if (imprinted != null) { - ability.clearManaCostsToPay(); - ability.addManaCostsToPay(new GenericManaCost(imprinted.getManaValue())); - } - } - } - - // no {X} anymore as we already have imprinted the card with defined manacost - for (ManaCost cost : ability.getManaCostsToPay()) { - if (cost instanceof VariableCost) { - cost.setPaid(); - } - } - } -} - class SoulFoundryImprintEffect extends OneShotEffect { private static final FilterCard filter = new FilterCard("creature card from your hand"); diff --git a/Mage.Sets/src/mage/cards/s/SpiralingEmbers.java b/Mage.Sets/src/mage/cards/s/SpiralingEmbers.java index d96cd42fa3a..5efbbc14f47 100644 --- a/Mage.Sets/src/mage/cards/s/SpiralingEmbers.java +++ b/Mage.Sets/src/mage/cards/s/SpiralingEmbers.java @@ -23,7 +23,7 @@ public final class SpiralingEmbers extends CardImpl { // Spiraling Embers deals damage to any target equal to the number of cards in your hand. - Effect effect = new DamageTargetEffect(CardsInControllerHandCount.instance); + Effect effect = new DamageTargetEffect(CardsInControllerHandCount.ANY); effect.setText("{this} deals damage to any target equal to the number of cards in your hand."); this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetAnyTarget()); diff --git a/Mage.Sets/src/mage/cards/s/SpontaneousGeneration.java b/Mage.Sets/src/mage/cards/s/SpontaneousGeneration.java index c57c475b00b..13a4fb5ea05 100644 --- a/Mage.Sets/src/mage/cards/s/SpontaneousGeneration.java +++ b/Mage.Sets/src/mage/cards/s/SpontaneousGeneration.java @@ -19,7 +19,7 @@ public final class SpontaneousGeneration extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{G}"); // Create a 1/1 green Saproling creature token for each card in your hand. - this.getSpellAbility().addEffect(new CreateTokenEffect(new SaprolingToken(), CardsInControllerHandCount.instance)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new SaprolingToken(), CardsInControllerHandCount.ANY)); } private SpontaneousGeneration(final SpontaneousGeneration card) { diff --git a/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java b/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java new file mode 100644 index 00000000000..ae0fbeacb18 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java @@ -0,0 +1,123 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StalwartSuccessor extends CardImpl { + + public StalwartSuccessor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever one or more counters are put on a creature you control, if it's the first time counters have been put on that creature this turn, put a +1/+1 counter on that creature. + this.addAbility(new StalwartSuccessorTriggeredAbility()); + } + + private StalwartSuccessor(final StalwartSuccessor card) { + super(card); + } + + @Override + public StalwartSuccessor copy() { + return new StalwartSuccessor(this); + } +} + +class StalwartSuccessorTriggeredAbility extends TriggeredAbilityImpl { + + StalwartSuccessorTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + this.setTriggerPhrase("Whenever one or more counters are put on a creature you control, " + + "if it's the first time counters have been put on that creature this turn, "); + this.addWatcher(new StalwartSuccessorWatcher()); + } + + private StalwartSuccessorTriggeredAbility(final StalwartSuccessorTriggeredAbility ability) { + super(ability); + } + + @Override + public StalwartSuccessorTriggeredAbility copy() { + return new StalwartSuccessorTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTERS_ADDED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent == null + || !permanent.isCreature(game) + || !permanent.isControlledBy(getControllerId()) + || !StalwartSuccessorWatcher.checkCreature(permanent, event, game)) { + return false; + } + this.getEffects().setTargetPointer(new FixedTarget(permanent, game)); + return true; + } +} + +class StalwartSuccessorWatcher extends Watcher { + + private final Map map = new HashMap<>(); + + StalwartSuccessorWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.COUNTERS_ADDED) { + map.putIfAbsent(new MageObjectReference(event.getTargetId(), game), event.getId()); + } + } + + @Override + public void reset() { + super.reset(); + map.clear(); + } + + static boolean checkCreature(Permanent permanent, GameEvent event, Game game) { + return Objects.equals( + event.getId(), + game.getState() + .getWatcher(StalwartSuccessorWatcher.class) + .map + .getOrDefault(new MageObjectReference(permanent, game), null) + ); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StarryEyedSkyrider.java b/Mage.Sets/src/mage/cards/s/StarryEyedSkyrider.java new file mode 100644 index 00000000000..4e1325fcc25 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StarryEyedSkyrider.java @@ -0,0 +1,65 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.permanent.AttackingPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StarryEyedSkyrider extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("attacking tokens"); + + static { + filter.add(AttackingPredicate.instance); + filter.add(TokenPredicate.TRUE); + } + + public StarryEyedSkyrider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever this creature attacks, another target creature you control gains flying until end of turn. + Ability ability = new AttacksTriggeredAbility(new GainAbilityTargetEffect(FlyingAbility.getInstance())); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + + // Attacking tokens you control have flying. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.WhileOnBattlefield, filter + ))); + } + + private StarryEyedSkyrider(final StarryEyedSkyrider card) { + super(card); + } + + @Override + public StarryEyedSkyrider copy() { + return new StarryEyedSkyrider(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StillnessInMotion.java b/Mage.Sets/src/mage/cards/s/StillnessInMotion.java new file mode 100644 index 00000000000..7a436bf0a0c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StillnessInMotion.java @@ -0,0 +1,80 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StillnessInMotion extends CardImpl { + + public StillnessInMotion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + + // At the beginning of your upkeep, mill three cards. Then if you have no cards in your library, exile this enchantment and put five cards from your graveyard on top of your library in any order. + Ability ability = new BeginningOfUpkeepTriggeredAbility(new MillCardsControllerEffect(3)); + ability.addEffect(new StillnessInMotionEffect()); + this.addAbility(ability); + } + + private StillnessInMotion(final StillnessInMotion card) { + super(card); + } + + @Override + public StillnessInMotion copy() { + return new StillnessInMotion(this); + } +} + +class StillnessInMotionEffect extends OneShotEffect { + + StillnessInMotionEffect() { + super(Outcome.Benefit); + staticText = "Then if you have no cards in your library, exile this enchantment " + + "and put five cards from your graveyard on top of your library in any order"; + } + + private StillnessInMotionEffect(final StillnessInMotionEffect effect) { + super(effect); + } + + @Override + public StillnessInMotionEffect copy() { + return new StillnessInMotionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getLibrary().hasCards()) { + return false; + } + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(permanent -> player.moveCards(permanent, Zone.EXILED, source, game)); + int graveCount = Math.min(player.getGraveyard().size(), 5); + if (graveCount < 1) { + return true; + } + TargetCard target = new TargetCardInYourGraveyard(graveCount); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + player.putCardsOnTopOfLibrary(new CardsImpl(target.getTargets()), game, source, true); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/StiltzkinMoogleMerchant.java b/Mage.Sets/src/mage/cards/s/StiltzkinMoogleMerchant.java new file mode 100644 index 00000000000..c361ffaa710 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StiltzkinMoogleMerchant.java @@ -0,0 +1,95 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; + +/** + * @author balazskristof + */ +public final class StiltzkinMoogleMerchant extends CardImpl { + + public StiltzkinMoogleMerchant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.MOOGLE); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // {2}, {T}: Target opponent gains control of another target permanent you control. If they do, you draw a card. + Ability ability = new SimpleActivatedAbility(new StiltzkinMoogleMerchantEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetOpponent()); + ability.addTarget(new TargetControlledPermanent(StaticFilters.FILTER_CONTROLLED_ANOTHER_PERMANENT)); + this.addAbility(ability); + } + + private StiltzkinMoogleMerchant(final StiltzkinMoogleMerchant card) { + super(card); + } + + @Override + public StiltzkinMoogleMerchant copy() { + return new StiltzkinMoogleMerchant(this); + } +} + +class StiltzkinMoogleMerchantEffect extends OneShotEffect { + + StiltzkinMoogleMerchantEffect() { + super(Outcome.Benefit); + staticText = "Target opponent gains control of another target permanent you control. If they do, you draw a card."; + } + + private StiltzkinMoogleMerchantEffect(StiltzkinMoogleMerchantEffect effect) { + super(effect); + } + + @Override + public StiltzkinMoogleMerchantEffect copy() { + return new StiltzkinMoogleMerchantEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); + if (permanent == null) { + return false; + } + UUID opponent = getTargetPointer().getFirst(game, source); + game.addEffect(new GainControlTargetEffect( + Duration.Custom, true, opponent + ).setTargetPointer(new FixedTarget(permanent.getId(), game)), source); + game.processAction(); + if (permanent.isControlledBy(opponent)) { + new DrawCardSourceControllerEffect(1).apply(game, source); + return true; + } + return false; + } +} + diff --git a/Mage.Sets/src/mage/cards/s/StingerbackTerror.java b/Mage.Sets/src/mage/cards/s/StingerbackTerror.java index dcef50e3f04..78a699c7efd 100644 --- a/Mage.Sets/src/mage/cards/s/StingerbackTerror.java +++ b/Mage.Sets/src/mage/cards/s/StingerbackTerror.java @@ -22,7 +22,7 @@ import java.util.UUID; */ public final class StingerbackTerror extends CardImpl { - private static final DynamicValue xValue = new SignInversionDynamicValue(CardsInControllerHandCount.instance); + private static final DynamicValue xValue = new SignInversionDynamicValue(CardsInControllerHandCount.ANY); public StingerbackTerror(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); diff --git a/Mage.Sets/src/mage/cards/s/StormshriekFeral.java b/Mage.Sets/src/mage/cards/s/StormshriekFeral.java new file mode 100644 index 00000000000..c6957a0f554 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StormshriekFeral.java @@ -0,0 +1,58 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.OmenCard; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author Jmlundeen + */ +public final class StormshriekFeral extends OmenCard { + + public StormshriekFeral(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{4}{R}", "Flush Out", "{1}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // {1}{R}: This creature gets +1/+0 until end of turn. + this.addAbility(new SimpleActivatedAbility(new BoostSourceEffect(1, 0 , Duration.EndOfTurn), new ManaCostsImpl<>("{1}{R}"))); + + // Flush Out + // Discard a card. If you do, draw two cards. + this.getSpellCard().getSpellAbility().addEffect(new DoIfCostPaid( + new DrawCardSourceControllerEffect(2), + null, new DiscardCardCost(), false + )); + this.finalizeOmen(); + } + + private StormshriekFeral(final StormshriekFeral card) { + super(card); + } + + @Override + public StormshriekFeral copy() { + return new StormshriekFeral(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StrategicBetrayal.java b/Mage.Sets/src/mage/cards/s/StrategicBetrayal.java new file mode 100644 index 00000000000..253659ce02d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StrategicBetrayal.java @@ -0,0 +1,78 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +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.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StrategicBetrayal extends CardImpl { + + public StrategicBetrayal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + // Target opponent exiles a creature they control and their graveyard. + this.getSpellAbility().addEffect(new StrategicBetrayalEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + private StrategicBetrayal(final StrategicBetrayal card) { + super(card); + } + + @Override + public StrategicBetrayal copy() { + return new StrategicBetrayal(this); + } +} + +class StrategicBetrayalEffect extends OneShotEffect { + + StrategicBetrayalEffect() { + super(Outcome.Benefit); + staticText = "target opponent exiles a creature they control and their graveyard"; + } + + private StrategicBetrayalEffect(final StrategicBetrayalEffect effect) { + super(effect); + } + + @Override + public StrategicBetrayalEffect copy() { + return new StrategicBetrayalEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(player.getGraveyard()); + if (game.getBattlefield().contains( + StaticFilters.FILTER_CONTROLLED_CREATURE, + player.getId(), source, game, 1 + )) { + TargetPermanent target = new TargetControlledCreaturePermanent(); + target.withNotTarget(true); + player.choose(Outcome.DestroyPermanent, target, source, game); + cards.add(target.getFirstTarget()); + } + return player.moveCards(cards, Zone.EXILED, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StruggleForProjectPurity.java b/Mage.Sets/src/mage/cards/s/StruggleForProjectPurity.java index 1599ab4bb2c..5a46b36aeb4 100644 --- a/Mage.Sets/src/mage/cards/s/StruggleForProjectPurity.java +++ b/Mage.Sets/src/mage/cards/s/StruggleForProjectPurity.java @@ -2,18 +2,19 @@ package mage.cards.s; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.condition.common.ModeChoiceSourceCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseModeEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ModeChoice; import mage.constants.Outcome; import mage.constants.Zone; import mage.counters.CounterType; @@ -34,29 +35,21 @@ import java.util.stream.Collectors; */ public final class StruggleForProjectPurity extends CardImpl { - private static final String ruleTrigger1 = "&bull Brotherhood — At the beginning of your upkeep, each opponent draws a card. You draw a card for each card drawn this way."; - private static final String ruleTrigger2 = "&bull Enclave — Whenever a player attacks you with one or more creatures, that player gets twice that many rad counters."; - public StruggleForProjectPurity(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}"); // As Struggle for Project Purity enters, choose Brotherhood or Enclave. - this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Brotherhood or Enclave?", "Brotherhood", "Enclave"), null, - "As {this} enters, choose Brotherhood or Enclave.", "")); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.BROTHERHOOD, ModeChoice.ENCLAVE))); // * Brotherhood - At the beginning of your upkeep, each opponent draws a card. You draw a card for each card drawn this way. - Ability ability = new ConditionalTriggeredAbility( - new BeginningOfUpkeepTriggeredAbility(new StruggleForProjectDrawEffect()), - new ModeChoiceSourceCondition("Brotherhood"), - ruleTrigger1); - this.addAbility(ability); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new BeginningOfUpkeepTriggeredAbility(new StruggleForProjectDrawEffect()), ModeChoice.BROTHERHOOD + ))); // * Enclave - Whenever a player attacks you with one or more creatures, that player gets twice that many rad counters. - ability = new ConditionalTriggeredAbility( - new StruggleForProjectRadCountersTriggeredAbility(), - new ModeChoiceSourceCondition("Enclave"), - ruleTrigger2); - this.addAbility(ability); + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new StruggleForProjectRadCountersTriggeredAbility(), ModeChoice.ENCLAVE + ))); } private StruggleForProjectPurity(final StruggleForProjectPurity card) { diff --git a/Mage.Sets/src/mage/cards/s/Sturmgeist.java b/Mage.Sets/src/mage/cards/s/Sturmgeist.java index 966a26ebba5..a3e67a820e6 100644 --- a/Mage.Sets/src/mage/cards/s/Sturmgeist.java +++ b/Mage.Sets/src/mage/cards/s/Sturmgeist.java @@ -30,7 +30,7 @@ public final class Sturmgeist extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Sturmgeist's power and toughness are each equal to the number of cards in your hand. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.instance))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.ANY))); // Whenever Sturmgeist deals combat damage to a player, draw a card. this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new DrawCardSourceControllerEffect(1), false)); } diff --git a/Mage.Sets/src/mage/cards/s/SunbringersTouch.java b/Mage.Sets/src/mage/cards/s/SunbringersTouch.java index 5d27a705998..63efe489b8e 100644 --- a/Mage.Sets/src/mage/cards/s/SunbringersTouch.java +++ b/Mage.Sets/src/mage/cards/s/SunbringersTouch.java @@ -23,7 +23,7 @@ public final class SunbringersTouch extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{G}{G}"); // Bolster X, where X is the number of cards in your hand. - this.getSpellAbility().addEffect(new BolsterEffect(CardsInControllerHandCount.instance).setText("Bolster X, where X is the number of cards in your hand.")); + this.getSpellAbility().addEffect(new BolsterEffect(CardsInControllerHandCount.ANY).setText("Bolster X, where X is the number of cards in your hand.")); // Each creature you control with a +1/+1 counter on it gains trample until end of turn. Effect effect = new GainAbilityControlledEffect( diff --git a/Mage.Sets/src/mage/cards/s/SunpearlKirin.java b/Mage.Sets/src/mage/cards/s/SunpearlKirin.java new file mode 100644 index 00000000000..3283e9eac23 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunpearlKirin.java @@ -0,0 +1,95 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SunpearlKirin extends CardImpl { + + private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanent you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public SunpearlKirin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.KIRIN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, return up to one other target nonland permanent you control to its owner's hand. If it was a token, draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new SunpearlKirinEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + } + + private SunpearlKirin(final SunpearlKirin card) { + super(card); + } + + @Override + public SunpearlKirin copy() { + return new SunpearlKirin(this); + } +} + +class SunpearlKirinEffect extends OneShotEffect { + + SunpearlKirinEffect() { + super(Outcome.Benefit); + staticText = "return up to one other target nonland permanent " + + "you control to its owner's hand. If it was a token, draw a card"; + } + + private SunpearlKirinEffect(final SunpearlKirinEffect effect) { + super(effect); + } + + @Override + public SunpearlKirinEffect copy() { + return new SunpearlKirinEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (player == null || permanent == null) { + return false; + } + boolean isToken = permanent instanceof PermanentToken; + player.moveCards(permanent, Zone.HAND, source, game); + if (isToken) { + player.drawCards(1, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SurrakElusiveHunter.java b/Mage.Sets/src/mage/cards/s/SurrakElusiveHunter.java new file mode 100644 index 00000000000..42b189c9e3c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SurrakElusiveHunter.java @@ -0,0 +1,98 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.CantBeCounteredSourceAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SurrakElusiveHunter extends CardImpl { + + public SurrakElusiveHunter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // This spell can't be countered. + this.addAbility(new CantBeCounteredSourceAbility()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever a creature you control or a creature spell you control becomes the target of a spell or ability an opponent controls, draw a card. + this.addAbility(new SurrakElusiveHunterTriggeredAbility()); + } + + private SurrakElusiveHunter(final SurrakElusiveHunter card) { + super(card); + } + + @Override + public SurrakElusiveHunter copy() { + return new SurrakElusiveHunter(this); + } +} + +class SurrakElusiveHunterTriggeredAbility extends TriggeredAbilityImpl { + + SurrakElusiveHunterTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); + this.setTriggerPhrase("Whenever a creature you control or a creature spell you control " + + "becomes the target of a spell or ability an opponent controls, "); + } + + private SurrakElusiveHunterTriggeredAbility(final SurrakElusiveHunterTriggeredAbility ability) { + super(ability); + } + + @Override + public SurrakElusiveHunterTriggeredAbility copy() { + return new SurrakElusiveHunterTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TARGETED; + } + + private boolean checkTargeted(UUID targetId, Game game) { + Permanent permanent = game.getPermanentOrLKIBattlefield(targetId); + if (permanent != null) { + return permanent.isCreature(game) && permanent.isControlledBy(getControllerId()); + } + Spell spell = game.getSpellOrLKIStack(targetId); + return spell != null && spell.isCreature(game) && spell.isControlledBy(getControllerId()); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!checkTargeted(event.getTargetId(), game)) { + return false; + } + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); + return targetingObject != null + && game.getOpponents(getControllerId()).contains(targetingObject.getControllerId()) + && !CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SwordOfWarAndPeace.java b/Mage.Sets/src/mage/cards/s/SwordOfWarAndPeace.java index 0556562a059..e42008056d8 100644 --- a/Mage.Sets/src/mage/cards/s/SwordOfWarAndPeace.java +++ b/Mage.Sets/src/mage/cards/s/SwordOfWarAndPeace.java @@ -62,7 +62,7 @@ class SwordOfWarAndPeaceAbility extends TriggeredAbilityImpl { public SwordOfWarAndPeaceAbility() { super(Zone.BATTLEFIELD, new SwordOfWarAndPeaceDamageEffect()); - this.addEffect(new GainLifeEffect(CardsInControllerHandCount.instance)); + this.addEffect(new GainLifeEffect(CardsInControllerHandCount.ANY)); } private SwordOfWarAndPeaceAbility(final SwordOfWarAndPeaceAbility ability) { diff --git a/Mage.Sets/src/mage/cards/s/SylvanYeti.java b/Mage.Sets/src/mage/cards/s/SylvanYeti.java index 936b1dfb8f9..1dc359027c1 100644 --- a/Mage.Sets/src/mage/cards/s/SylvanYeti.java +++ b/Mage.Sets/src/mage/cards/s/SylvanYeti.java @@ -26,7 +26,7 @@ public final class SylvanYeti extends CardImpl { this.toughness = new MageInt(4); // Sylvan Yeti's power is equal to the number of cards in your hand. - Effect effect = new SetBasePowerSourceEffect(CardsInControllerHandCount.instance); + Effect effect = new SetBasePowerSourceEffect(CardsInControllerHandCount.ANY); this.addAbility(new SimpleStaticAbility(Zone.ALL, effect)); } diff --git a/Mage.Sets/src/mage/cards/s/SynchronizedCharge.java b/Mage.Sets/src/mage/cards/s/SynchronizedCharge.java new file mode 100644 index 00000000000..2c7d78315b3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SynchronizedCharge.java @@ -0,0 +1,60 @@ +package mage.cards.s; + +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.counter.DistributeCountersEffect; +import mage.abilities.keyword.HarmonizeAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.CounterAnyPredicate; +import mage.target.common.TargetCreaturePermanentAmount; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SynchronizedCharge extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(CounterAnyPredicate.instance); + } + + public SynchronizedCharge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); + + // Distribute two +1/+1 counters among one or two target creatures you control. Creatures you control with counters on them gain vigilance and trample until end of turn. + this.getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.P1P1)); + this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount( + 2, 1, 2, + StaticFilters.FILTER_CONTROLLED_CREATURES + )); + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + VigilanceAbility.getInstance(), Duration.EndOfTurn, filter + ).setText("creatures you control with counters on them gain vigilance")); + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, filter + ).setText("and trample until end of turn")); + + // Harmonize {4}{G} + this.addAbility(new HarmonizeAbility(this, "{4}{G}")); + } + + private SynchronizedCharge(final SynchronizedCharge card) { + super(card); + } + + @Override + public SynchronizedCharge copy() { + return new SynchronizedCharge(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SyrElenoraTheDiscerning.java b/Mage.Sets/src/mage/cards/s/SyrElenoraTheDiscerning.java index aa7cacedc07..4e02196d807 100644 --- a/Mage.Sets/src/mage/cards/s/SyrElenoraTheDiscerning.java +++ b/Mage.Sets/src/mage/cards/s/SyrElenoraTheDiscerning.java @@ -30,7 +30,7 @@ public final class SyrElenoraTheDiscerning extends CardImpl { // Syr Elenora the Discerning's power is equal to the number of cards in your hand. this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SetBasePowerSourceEffect(CardsInControllerHandCount.instance) + Zone.ALL, new SetBasePowerSourceEffect(CardsInControllerHandCount.ANY) )); // When Syr Elenora enters the battlefield, draw a card. diff --git a/Mage.Sets/src/mage/cards/t/TaigamMasterOpportunist.java b/Mage.Sets/src/mage/cards/t/TaigamMasterOpportunist.java new file mode 100644 index 00000000000..025b1df41c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TaigamMasterOpportunist.java @@ -0,0 +1,94 @@ +package mage.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.FlurryAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainSuspendEffect; +import mage.abilities.keyword.SuspendAbility; +import mage.cards.Card; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; + +/** + * + * @author Jmlundeen + */ +public final class TaigamMasterOpportunist extends CardImpl { + + public TaigamMasterOpportunist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flurry -- Whenever you cast your second spell each turn, copy it, then exile the spell you cast with four time counters on it. If it doesn't have suspend, it gains suspend. + this.addAbility(new FlurryAbility(new TaigamMasterOpportunistEffect())); + } + + private TaigamMasterOpportunist(final TaigamMasterOpportunist card) { + super(card); + } + + @Override + public TaigamMasterOpportunist copy() { + return new TaigamMasterOpportunist(this); + } +} + +class TaigamMasterOpportunistEffect extends OneShotEffect { + + public TaigamMasterOpportunistEffect() { + super(Outcome.Copy); + this.staticText = "copy it, then exile the spell you cast with four time counters on it. " + + "If it doesn't have suspend, it gains suspend."; + } + + public TaigamMasterOpportunistEffect(final TaigamMasterOpportunistEffect effect) { + super(effect); + } + + @Override + public TaigamMasterOpportunistEffect copy() { + return new TaigamMasterOpportunistEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = (Spell) this.getValue("spellCast"); + Player controller = game.getPlayer(source.getControllerId()); + if (spell == null || controller == null) { + return false; + } + + // copy it + spell.createCopyOnStack(game, source, source.getControllerId(), false); + // exile it, if it doesn't have suspend, it gains suspend + // get main card to work with adventure/omen/split + Card card = spell.getMainCard(); + if (card == null) { + return false; + } + UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); + if (controller.moveCardsToExile(card, source, game, true, exileId, "Suspended cards of " + controller.getName())) { + boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class); + card.addCounters(CounterType.TIME.createInstance(4), source, game); + game.informPlayers(controller.getLogName() + " exiles " + spell.getLogName() + " with 3 time counters on it"); + if (!hasSuspend) { + game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TamiyosLogbook.java b/Mage.Sets/src/mage/cards/t/TamiyosLogbook.java index ea7780126be..1e50eae2df7 100644 --- a/Mage.Sets/src/mage/cards/t/TamiyosLogbook.java +++ b/Mage.Sets/src/mage/cards/t/TamiyosLogbook.java @@ -61,7 +61,7 @@ enum TamiyosLogbookAdjuster implements CostAdjuster { ); @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { int count = game.getBattlefield().count(filter, ability.getControllerId(), ability, game); CardUtil.reduceCost(ability, count); } diff --git a/Mage.Sets/src/mage/cards/t/TemurBattlecrier.java b/Mage.Sets/src/mage/cards/t/TemurBattlecrier.java new file mode 100644 index 00000000000..03c8765bbcf --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TemurBattlecrier.java @@ -0,0 +1,104 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TemurBattlecrier extends CardImpl { + + public TemurBattlecrier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}{R}"); + + this.subtype.add(SubType.ORC); + this.subtype.add(SubType.RANGER); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // During your turn, spells you cast cost {1} less to cast for each creature you control with power 4 or greater. + this.addAbility(new SimpleStaticAbility(new TemurBattlecrierEffect()).addHint(TemurBattlecrierEffect.getHint())); + } + + private TemurBattlecrier(final TemurBattlecrier card) { + super(card); + } + + @Override + public TemurBattlecrier copy() { + return new TemurBattlecrier(this); + } +} + +class TemurBattlecrierEffect extends CostModificationEffectImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + private static final Hint hint = new ValueHint( + "Creatures you control with power 4 or greater", new PermanentsOnBattlefieldCount(filter) + ); + + public static Hint getHint() { + return hint; + } + + TemurBattlecrierEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); + staticText = "during your turn, spells you cast cost {1} less to cast " + + "for each creature you control with power 4 or greater"; + } + + private TemurBattlecrierEffect(final TemurBattlecrierEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + Ability spellAbility = abilityToModify; + if (spellAbility == null) { + return false; + } + int amount = game.getBattlefield().count(filter, source.getControllerId(), source, game); + if (amount > 0) { + CardUtil.reduceCost(spellAbility, amount); + } + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (!(abilityToModify instanceof SpellAbility) + || !abilityToModify.isControlledBy(source.getControllerId()) + || !game.isActivePlayer(source.getControllerId())) { + return false; + } + Card spellCard = ((SpellAbility) abilityToModify).getCharacteristics(game); + return spellCard != null; + } + + @Override + public TemurBattlecrierEffect copy() { + return new TemurBattlecrierEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TersaLightshatter.java b/Mage.Sets/src/mage/cards/t/TersaLightshatter.java new file mode 100644 index 00000000000..ea13914f87e --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TersaLightshatter.java @@ -0,0 +1,85 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.ThresholdCondition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.discard.DiscardAndDrawThatManyEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TersaLightshatter extends CardImpl { + + public TersaLightshatter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ORC); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When Tersa Lightshatter enters, discard up to two cards, then draw that many cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DiscardAndDrawThatManyEffect(2))); + + // Whenever Tersa Lightshatter attacks, if there are seven or more cards in your graveyard, exile a card at random from your graveyard. You may play that card this turn. + this.addAbility(new AttacksTriggeredAbility(new TersaLightshatterEffect()).withInterveningIf(ThresholdCondition.instance)); + } + + private TersaLightshatter(final TersaLightshatter card) { + super(card); + } + + @Override + public TersaLightshatter copy() { + return new TersaLightshatter(this); + } +} + +class TersaLightshatterEffect extends OneShotEffect { + + TersaLightshatterEffect() { + super(Outcome.Benefit); + staticText = "exile a card at random from your graveyard. You may play that card this turn"; + } + + private TersaLightshatterEffect(final TersaLightshatterEffect effect) { + super(effect); + } + + @Override + public TersaLightshatterEffect copy() { + return new TersaLightshatterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = player.getGraveyard().getRandom(game); + if (card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + CardUtil.makeCardPlayable(game, source, card, false, Duration.EndOfTurn, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TevalArbiterOfVirtue.java b/Mage.Sets/src/mage/cards/t/TevalArbiterOfVirtue.java new file mode 100644 index 00000000000..c172f35b82b --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TevalArbiterOfVirtue.java @@ -0,0 +1,98 @@ +package mage.cards.t; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledSpellsEffect; +import mage.abilities.keyword.DelveAbility; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterNonlandCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.stack.Spell; + +/** + * + * @author Grath + */ +public final class TevalArbiterOfVirtue extends CardImpl { + + private static final FilterNonlandCard filter = new FilterNonlandCard("spells you cast"); + + static { + filter.add(Predicates.not(new AbilityPredicate(DelveAbility.class))); // So there are not redundant copies being added to each card + } + + public TevalArbiterOfVirtue(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{G}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Spells you cast have delve. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledSpellsEffect(new DelveAbility(false), filter))); + + // Whenever you cast a spell, you lose life equal to its mana value. + this.addAbility(new SpellCastControllerTriggeredAbility(new LoseLifeSourceControllerEffect(TevalArbiterOfVirtueValue.instance), false)); + } + + private TevalArbiterOfVirtue(final TevalArbiterOfVirtue card) { + super(card); + } + + @Override + public TevalArbiterOfVirtue copy() { + return new TevalArbiterOfVirtue(this); + } +} + +enum TevalArbiterOfVirtueValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return Optional + .ofNullable((Spell) effect.getValue("spellCast")) + .filter(Objects::nonNull) + .map(Spell::getManaValue) + .orElse(0); + } + + @Override + public TevalArbiterOfVirtueValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "X"; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheArchimandrite.java b/Mage.Sets/src/mage/cards/t/TheArchimandrite.java index 9127f3b5ae4..bad5a75b710 100644 --- a/Mage.Sets/src/mage/cards/t/TheArchimandrite.java +++ b/Mage.Sets/src/mage/cards/t/TheArchimandrite.java @@ -36,7 +36,7 @@ import java.util.UUID; public final class TheArchimandrite extends CardImpl { private static final DynamicValue xValue = new AdditiveDynamicValue( - CardsInControllerHandCount.instance, StaticValue.get(-4) + CardsInControllerHandCount.ANY, StaticValue.get(-4) ); private static final FilterPermanent filter = new FilterControlledPermanent(); private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent(); diff --git a/Mage.Sets/src/mage/cards/t/TheGreatSynthesis.java b/Mage.Sets/src/mage/cards/t/TheGreatSynthesis.java index ce1e78245dc..160a80da2a6 100644 --- a/Mage.Sets/src/mage/cards/t/TheGreatSynthesis.java +++ b/Mage.Sets/src/mage/cards/t/TheGreatSynthesis.java @@ -41,7 +41,7 @@ public class TheGreatSynthesis extends CardImpl { //I — Draw cards equal to the number of cards in your hand. You have no maximum hand size for as long as you //control The Great Synthesis. sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, - new DrawCardSourceControllerEffect(CardsInControllerHandCount.instance) + new DrawCardSourceControllerEffect(CardsInControllerHandCount.ANY) .setText("draw cards equal to the number of cards in your hand"), new MaximumHandSizeControllerEffect(Integer.MAX_VALUE, Duration.WhileOnBattlefield, MaximumHandSizeControllerEffect.HandSizeModification.SET) diff --git a/Mage.Sets/src/mage/cards/t/TheRevelationsOfEzio.java b/Mage.Sets/src/mage/cards/t/TheRevelationsOfEzio.java index 28664c0c876..cc52367e98d 100644 --- a/Mage.Sets/src/mage/cards/t/TheRevelationsOfEzio.java +++ b/Mage.Sets/src/mage/cards/t/TheRevelationsOfEzio.java @@ -61,7 +61,7 @@ public final class TheRevelationsOfEzio extends CardImpl { sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_III, SagaChapter.CHAPTER_III, ability -> { - ability.addEffect(new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.P1P1.createInstance(), true)); + ability.addEffect(new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(true, CounterType.P1P1.createInstance())); ability.addTarget(new TargetCardInYourGraveyard(assassinFilter)); } ); @@ -116,4 +116,4 @@ class TheRevelationsOfEzioTriggeredAbility extends DelayedTriggeredAbility { public String getRule() { return "Whenever an Assassin you control attacks this turn, put a +1/+1 counter on it."; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/t/TheRoyalScions.java b/Mage.Sets/src/mage/cards/t/TheRoyalScions.java index 790e8df37ed..52897d907de 100644 --- a/Mage.Sets/src/mage/cards/t/TheRoyalScions.java +++ b/Mage.Sets/src/mage/cards/t/TheRoyalScions.java @@ -88,7 +88,7 @@ class TheRoyalScionsCreateReflexiveTriggerEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { effect.apply(game, source); ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new DamageTargetEffect(CardsInControllerHandCount.instance), false, + new DamageTargetEffect(CardsInControllerHandCount.ANY), false, "{this} deals damage to any target equal to the number of cards in your hand" ); ability.addTarget(new TargetAnyTarget()); diff --git a/Mage.Sets/src/mage/cards/t/ThroneOfEldraine.java b/Mage.Sets/src/mage/cards/t/ThroneOfEldraine.java index 8c991ad97e2..5b503d04ec9 100644 --- a/Mage.Sets/src/mage/cards/t/ThroneOfEldraine.java +++ b/Mage.Sets/src/mage/cards/t/ThroneOfEldraine.java @@ -170,7 +170,7 @@ enum ThroneOfEldraineAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void prepareCost(Ability ability, Game game) { ObjectColor color = (ObjectColor) game.getState().getValue(ability.getSourceId() + "_color"); if (color == null) { return; diff --git a/Mage.Sets/src/mage/cards/t/TinybonesBaubleBurglar.java b/Mage.Sets/src/mage/cards/t/TinybonesBaubleBurglar.java index 0818d00cd1e..83473f261c3 100644 --- a/Mage.Sets/src/mage/cards/t/TinybonesBaubleBurglar.java +++ b/Mage.Sets/src/mage/cards/t/TinybonesBaubleBurglar.java @@ -211,7 +211,7 @@ class TinybonesBaubleBurglarSpendAnyManaEffect extends AsThoughEffectImpl implem CardState cardState; if (card instanceof SplitCard) { cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); } else if (card instanceof ModalDoubleFacedCard) { cardState = game.getLastKnownInformationCard(((ModalDoubleFacedCard) card).getLeftHalfCard().getId(), Zone.EXILED); diff --git a/Mage.Sets/src/mage/cards/t/TipTheScales.java b/Mage.Sets/src/mage/cards/t/TipTheScales.java new file mode 100644 index 00000000000..7cc4d12c271 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TipTheScales.java @@ -0,0 +1,84 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetSacrifice; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TipTheScales extends CardImpl { + + public TipTheScales(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // Sacrifice a creature. When you do, all creatures get -X/-X until end of turn, where X is the sacrificed creature's toughness. + this.getSpellAbility().addEffect(new TipTheScalesEffect()); + } + + private TipTheScales(final TipTheScales card) { + super(card); + } + + @Override + public TipTheScales copy() { + return new TipTheScales(this); + } +} + +class TipTheScalesEffect extends OneShotEffect { + + TipTheScalesEffect() { + super(Outcome.Benefit); + staticText = "sacrifice a creature. When you do, all creatures get " + + "-X/-X until end of turn, where X is the sacrificed creature's toughness"; + } + + private TipTheScalesEffect(final TipTheScalesEffect effect) { + super(effect); + } + + @Override + public TipTheScalesEffect copy() { + return new TipTheScalesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetPermanent target = new TargetSacrifice(StaticFilters.FILTER_CONTROLLED_CREATURE); + if (!target.canChoose(source.getControllerId(), source, game)) { + return false; + } + player.choose(Outcome.Sacrifice, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + return false; + } + int value = permanent.getToughness().getValue(); + if (!permanent.sacrifice(source, game) || value < 1) { + return false; + } + game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility( + new BoostAllEffect(-value, -value, Duration.EndOfTurn), false + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TishanaVoiceOfThunder.java b/Mage.Sets/src/mage/cards/t/TishanaVoiceOfThunder.java index 2fbb6ac1215..f7b2d96c3c6 100644 --- a/Mage.Sets/src/mage/cards/t/TishanaVoiceOfThunder.java +++ b/Mage.Sets/src/mage/cards/t/TishanaVoiceOfThunder.java @@ -32,7 +32,7 @@ public final class TishanaVoiceOfThunder extends CardImpl { this.toughness = new MageInt(0); // Tishana, Voice of Thunder's power and toughness are each equal to the number of cards in your hand. - DynamicValue xValue = CardsInControllerHandCount.instance; + DynamicValue xValue = CardsInControllerHandCount.ANY; this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(xValue))); // You have no maximum hand size. diff --git a/Mage.Sets/src/mage/cards/t/TlincalliHunter.java b/Mage.Sets/src/mage/cards/t/TlincalliHunter.java index 9cf8845dc43..732154314e4 100644 --- a/Mage.Sets/src/mage/cards/t/TlincalliHunter.java +++ b/Mage.Sets/src/mage/cards/t/TlincalliHunter.java @@ -65,7 +65,7 @@ enum ExiledCreatureSpellCondition implements Condition { @Override public boolean apply(Game game, Ability source) { MageObject object = game.getObject(source); - if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) { + if (object instanceof SplitCardHalf || object instanceof SpellOptionCard || object instanceof ModalDoubleFacedCardHalf) { UUID mainCardId = ((Card) object).getMainCard().getId(); object = game.getObject(mainCardId); } diff --git a/Mage.Sets/src/mage/cards/t/TowashiGuideBot.java b/Mage.Sets/src/mage/cards/t/TowashiGuideBot.java index 051da5a6ccc..15a26a67a7d 100644 --- a/Mage.Sets/src/mage/cards/t/TowashiGuideBot.java +++ b/Mage.Sets/src/mage/cards/t/TowashiGuideBot.java @@ -81,7 +81,7 @@ enum TowashiGuideBotAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { CardUtil.reduceCost(ability, game.getBattlefield().count( filter, ability.getControllerId(), ability, game )); diff --git a/Mage.Sets/src/mage/cards/t/TransformingFlourish.java b/Mage.Sets/src/mage/cards/t/TransformingFlourish.java new file mode 100644 index 00000000000..d11ff2b4f88 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TransformingFlourish.java @@ -0,0 +1,97 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.DemonstrateAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TransformingFlourish extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact or creature you don't control"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate() + )); + filter.add(TargetController.NOT_YOU.getControllerPredicate()); + } + + public TransformingFlourish(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + // Demonstrate + this.addAbility(new DemonstrateAbility()); + + // Destroy target artifact or creature you don't control. If that permanent is destroyed this way, its controller exiles cards from the top of their library until they exile a nonland card, then they may cast that card without paying its mana cost. + this.getSpellAbility().addEffect(new TransformingFlourishEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private TransformingFlourish(final TransformingFlourish card) { + super(card); + } + + @Override + public TransformingFlourish copy() { + return new TransformingFlourish(this); + } +} + +class TransformingFlourishEffect extends OneShotEffect { + + TransformingFlourishEffect() { + super(Outcome.Benefit); + staticText = "destroy target artifact or creature you don't control. " + + "If that permanent is destroyed this way, its controller exiles cards " + + "from the top of their library until they exile a nonland card, " + + "then they may cast that card without paying its mana cost"; + } + + private TransformingFlourishEffect(final TransformingFlourishEffect effect) { + super(effect); + } + + @Override + public TransformingFlourishEffect copy() { + return new TransformingFlourishEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null || !permanent.destroy(source, game)) { + return false; + } + Player player = game.getPlayer(permanent.getControllerId()); + if (player == null || !player.getLibrary().hasCards()) { + return true; + } + for (Card card : player.getLibrary().getCards(game)) { + player.moveCards(card, Zone.EXILED, source, game); + if (!card.isLand(game)) { + CardUtil.castSpellWithAttributesForFree(player, source, game, card); + return true; + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TurbulentDreams.java b/Mage.Sets/src/mage/cards/t/TurbulentDreams.java index eaf810befd6..db125c83c2f 100644 --- a/Mage.Sets/src/mage/cards/t/TurbulentDreams.java +++ b/Mage.Sets/src/mage/cards/t/TurbulentDreams.java @@ -20,7 +20,7 @@ public final class TurbulentDreams extends CardImpl { public TurbulentDreams(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}{U}"); - // As an additional cost to cast Turbulent Dreams, discard X cards. + // As an additional cost to cast this spell, discard X cards. this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true)); // Return X target nonland permanents to their owners' hands. diff --git a/Mage.Sets/src/mage/cards/t/TwiceUponATime.java b/Mage.Sets/src/mage/cards/t/TwiceUponATime.java index 9bf61c5f838..90a46602fbd 100644 --- a/Mage.Sets/src/mage/cards/t/TwiceUponATime.java +++ b/Mage.Sets/src/mage/cards/t/TwiceUponATime.java @@ -47,7 +47,7 @@ public final class TwiceUponATime extends AdventureCard { // Unlikely Meeting // Search your library for a Doctor card, reveal it, put it into your hand, then shuffle. this.getSpellCard().getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter2), true)); - this.getSpellCard().finalizeAdventure(); + this.finalizeAdventure(); } private TwiceUponATime(final TwiceUponATime card) { diff --git a/Mage.Sets/src/mage/cards/t/TwinmawStormbrood.java b/Mage.Sets/src/mage/cards/t/TwinmawStormbrood.java new file mode 100644 index 00000000000..41c021b2836 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TwinmawStormbrood.java @@ -0,0 +1,59 @@ +package mage.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.DealsDamageToACreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.OmenCard; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Jmlundeen + */ +public final class TwinmawStormbrood extends OmenCard { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature without flying"); + + static { + filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); + } + + public TwinmawStormbrood(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{5}{W}", "Charring Bite", "{1}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, you gain 5 life. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(5))); + + // Charring Bite + // deals 5 damage to target creature without flying. + this.getSpellCard().getSpellAbility().addEffect(new DamageTargetEffect(5)); + this.getSpellCard().getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + this.finalizeOmen(); + } + + private TwinmawStormbrood(final TwinmawStormbrood card) { + super(card); + } + + @Override + public TwinmawStormbrood copy() { + return new TwinmawStormbrood(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnionOfTheThirdPath.java b/Mage.Sets/src/mage/cards/u/UnionOfTheThirdPath.java index bd944e32708..b9e2707ef49 100644 --- a/Mage.Sets/src/mage/cards/u/UnionOfTheThirdPath.java +++ b/Mage.Sets/src/mage/cards/u/UnionOfTheThirdPath.java @@ -20,7 +20,7 @@ public final class UnionOfTheThirdPath extends CardImpl { // Draw a card, then you gain life equal to the number of cards in your hand. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); - this.getSpellAbility().addEffect(new GainLifeEffect(CardsInControllerHandCount.instance).concatBy(", then")); + this.getSpellAbility().addEffect(new GainLifeEffect(CardsInControllerHandCount.ANY).concatBy(", then")); } private UnionOfTheThirdPath(final UnionOfTheThirdPath card) { diff --git a/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java b/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java new file mode 100644 index 00000000000..0c00e9b48b5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java @@ -0,0 +1,58 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterCreatureCard; + +import java.util.UUID; + +/** + * @author ilyagart + */ +public final class UreniOfTheUnwritten extends CardImpl { + + static final FilterCreatureCard filter = new FilterCreatureCard("Dragon creature card"); + + static { + filter.add(SubType.DRAGON.getPredicate()); + } + + public UreniOfTheUnwritten(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever Ureni enters or attacks, look at the top eight cards of your library. You may put a Dragon creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. + Effect effect = new LookLibraryAndPickControllerEffect(8, 1, filter, PutCards.BATTLEFIELD, PutCards.BOTTOM_RANDOM); + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(effect, false)); + } + + private UreniOfTheUnwritten(final UreniOfTheUnwritten card) { + super(card); + } + + @Override + public UreniOfTheUnwritten copy() { + return new UreniOfTheUnwritten(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/u/UreniTheSongUnending.java b/Mage.Sets/src/mage/cards/u/UreniTheSongUnending.java new file mode 100644 index 00000000000..b1bd25b98a3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UreniTheSongUnending.java @@ -0,0 +1,65 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.common.DamageMultiEffect; +import mage.abilities.hint.common.LandsYouControlHint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.target.common.TargetPermanentAmount; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UreniTheSongUnending extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreatureOrPlaneswalkerPermanent("creatures and/or planeswalkers your opponents control"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public UreniTheSongUnending(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(10); + this.toughness = new MageInt(10); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Protection from white and from black + this.addAbility(ProtectionAbility.from(ObjectColor.WHITE, ObjectColor.BLACK)); + + // When Ureni enters, it deals X damage divided as you choose among any number of target creatures and/or planeswalkers your opponents control, where X is the number of lands you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageMultiEffect("it")); + ability.addTarget(new TargetPermanentAmount(LandsYouControlCount.instance, 0, filter)); + this.addAbility(ability.addHint(LandsYouControlHint.instance)); + } + + private UreniTheSongUnending(final UreniTheSongUnending card) { + super(card); + } + + @Override + public UreniTheSongUnending copy() { + return new UreniTheSongUnending(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UrgentNecropsy.java b/Mage.Sets/src/mage/cards/u/UrgentNecropsy.java index 7358884883b..27643226474 100644 --- a/Mage.Sets/src/mage/cards/u/UrgentNecropsy.java +++ b/Mage.Sets/src/mage/cards/u/UrgentNecropsy.java @@ -61,7 +61,7 @@ enum UrgentNecropsyAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void prepareCost(Ability ability, Game game) { int xValue = ability .getTargets() .stream() diff --git a/Mage.Sets/src/mage/cards/v/VengefulDreams.java b/Mage.Sets/src/mage/cards/v/VengefulDreams.java index a1d4a4c2363..47c70c73315 100644 --- a/Mage.Sets/src/mage/cards/v/VengefulDreams.java +++ b/Mage.Sets/src/mage/cards/v/VengefulDreams.java @@ -21,8 +21,8 @@ public final class VengefulDreams extends CardImpl { public VengefulDreams(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}{W}"); - // As an additional cost to cast Vengeful Dreams, discard X cards. - this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true)); + // As an additional cost to cast this spell, discard X cards. + this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true)); // Exile X target attacking creatures. Effect effect = new ExileTargetEffect(); diff --git a/Mage.Sets/src/mage/cards/v/VensersJournal.java b/Mage.Sets/src/mage/cards/v/VensersJournal.java index 717b3365950..bf96037fdf3 100644 --- a/Mage.Sets/src/mage/cards/v/VensersJournal.java +++ b/Mage.Sets/src/mage/cards/v/VensersJournal.java @@ -13,7 +13,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Zone; /** * @@ -29,7 +28,7 @@ public final class VensersJournal extends CardImpl { this.addAbility(new SimpleStaticAbility(effect)); // At the beginning of your upkeep, you gain 1 life for each card in your hand. - this.addAbility(new BeginningOfUpkeepTriggeredAbility(new GainLifeEffect(CardsInControllerHandCount.instance) + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new GainLifeEffect(CardsInControllerHandCount.ANY) .setText("you gain 1 life for each card in your hand"))); } diff --git a/Mage.Sets/src/mage/cards/v/VindictiveFlamestoker.java b/Mage.Sets/src/mage/cards/v/VindictiveFlamestoker.java index e52e393a821..75827a2ec5c 100644 --- a/Mage.Sets/src/mage/cards/v/VindictiveFlamestoker.java +++ b/Mage.Sets/src/mage/cards/v/VindictiveFlamestoker.java @@ -65,7 +65,7 @@ enum VindictiveFlamestokerAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { int amount = Optional .ofNullable(ability.getSourcePermanentIfItStillExists(game)) .filter(Objects::nonNull) diff --git a/Mage.Sets/src/mage/cards/v/VisionOfTheUnspeakable.java b/Mage.Sets/src/mage/cards/v/VisionOfTheUnspeakable.java index 4d8752569c1..a93dc245b06 100644 --- a/Mage.Sets/src/mage/cards/v/VisionOfTheUnspeakable.java +++ b/Mage.Sets/src/mage/cards/v/VisionOfTheUnspeakable.java @@ -36,8 +36,8 @@ public final class VisionOfTheUnspeakable extends CardImpl { // Vision of the Unspeakable gets +1/+1 for each card in your hand. this.addAbility(new SimpleStaticAbility(new BoostSourceEffect( - CardsInControllerHandCount.instance, - CardsInControllerHandCount.instance, + CardsInControllerHandCount.ANY, + CardsInControllerHandCount.ANY, Duration.WhileOnBattlefield ))); } diff --git a/Mage.Sets/src/mage/cards/v/VoldarenEstate.java b/Mage.Sets/src/mage/cards/v/VoldarenEstate.java index 96c78e9276c..84e6d25d367 100644 --- a/Mage.Sets/src/mage/cards/v/VoldarenEstate.java +++ b/Mage.Sets/src/mage/cards/v/VoldarenEstate.java @@ -132,7 +132,7 @@ enum VoldarenEstateCostAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { CardUtil.reduceCost(ability, vampireCount.calculate(game, ability, null)); } } diff --git a/Mage.Sets/src/mage/cards/v/VoodooDoll.java b/Mage.Sets/src/mage/cards/v/VoodooDoll.java index c19eec025f0..1117c54da00 100644 --- a/Mage.Sets/src/mage/cards/v/VoodooDoll.java +++ b/Mage.Sets/src/mage/cards/v/VoodooDoll.java @@ -72,7 +72,7 @@ enum VoodooDollAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void prepareCost(Ability ability, Game game) { Permanent sourcePermanent = game.getPermanent(ability.getSourceId()); if (sourcePermanent != null) { int pin = sourcePermanent.getCounters(game).getCount(CounterType.PIN); diff --git a/Mage.Sets/src/mage/cards/w/WailOfWar.java b/Mage.Sets/src/mage/cards/w/WailOfWar.java new file mode 100644 index 00000000000..5c40127e78a --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WailOfWar.java @@ -0,0 +1,55 @@ +package mage.cards.w; + +import mage.abilities.Mode; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.TargetController; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * Represents the "Wail of War" instant card. + * Author: @mikejcunn + */ +public final class WailOfWar extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures target opponent controls"); + + static { + filter.add(TargetController.SOURCE_TARGETS.getControllerPredicate()); + } + + public WailOfWar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); + + // Mode 1: Creatures target player controls get -1/-1 until end of turn. + this.getSpellAbility().addEffect(new BoostAllEffect( + -1, -1, Duration.EndOfTurn, filter, false + )); + this.getSpellAbility().addTarget(new TargetOpponent()); + + // Mode 2: Return creatures from graveyard + Mode returnCreaturesFromGraveyardMode = new Mode(new ReturnFromGraveyardToHandTargetEffect()); + returnCreaturesFromGraveyardMode.addTarget(new TargetCardInGraveyard( + 0, 2, StaticFilters.FILTER_CARD_CREATURES_YOUR_GRAVEYARD + )); + this.getSpellAbility().addMode(returnCreaturesFromGraveyardMode); + } + + private WailOfWar(final WailOfWar card) { + super(card); + } + + @Override + public WailOfWar copy() { + return new WailOfWar(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/w/WardenOfTheGrove.java b/Mage.Sets/src/mage/cards/w/WardenOfTheGrove.java new file mode 100644 index 00000000000..e612b21bb18 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WardenOfTheGrove.java @@ -0,0 +1,91 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.keyword.EndureSourceEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.counters.Counters; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WardenOfTheGrove extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("another nontoken creature you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TokenPredicate.FALSE); + } + + public WardenOfTheGrove(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HYDRA); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // At the beginning of your end step, put a +1/+1 counter on this creature. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); + + // Whenever another nontoken creature you control enters, it endures X, where X is the number of counters on this creature. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + Zone.BATTLEFIELD, new WardenOfTheGroveEffect(), + filter, false, SetTargetPointer.PERMANENT + )); + } + + private WardenOfTheGrove(final WardenOfTheGrove card) { + super(card); + } + + @Override + public WardenOfTheGrove copy() { + return new WardenOfTheGrove(this); + } +} + +class WardenOfTheGroveEffect extends OneShotEffect { + + WardenOfTheGroveEffect() { + super(Outcome.Benefit); + staticText = "it endures X, where X is the number of counters on {this}"; + } + + private WardenOfTheGroveEffect(final WardenOfTheGroveEffect effect) { + super(effect); + } + + @Override + public WardenOfTheGroveEffect copy() { + return new WardenOfTheGroveEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return EndureSourceEffect.doEndure( + this.getTargetPointer().getFirstTargetPermanentOrLKI(game, source), + Optional.ofNullable(source.getSourcePermanentOrLKI(game)) + .map(p -> p.getCounters(game)) + .map(Counters::getTotalCount) + .orElse(0), + game, source + ); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WarpedPhysique.java b/Mage.Sets/src/mage/cards/w/WarpedPhysique.java index 9e63b29a6a2..2a4a66a23d4 100644 --- a/Mage.Sets/src/mage/cards/w/WarpedPhysique.java +++ b/Mage.Sets/src/mage/cards/w/WarpedPhysique.java @@ -18,13 +18,13 @@ import mage.target.common.TargetCreaturePermanent; public final class WarpedPhysique extends CardImpl { - private static final DynamicValue xValue = new SignInversionDynamicValue(CardsInControllerHandCount.instance); + private static final DynamicValue xValue = new SignInversionDynamicValue(CardsInControllerHandCount.ANY); public WarpedPhysique(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{U}{B}"); // Target creature gets +X/-X until end of turn, where X is the number of cards in your hand. - this.getSpellAbility().addEffect(new BoostTargetEffect(CardsInControllerHandCount.instance, xValue, Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new BoostTargetEffect(CardsInControllerHandCount.ANY, xValue, Duration.EndOfTurn)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java b/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java index 780eaf2d97e..c7e7e20a89e 100644 --- a/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java +++ b/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java @@ -101,7 +101,7 @@ enum WaytaTrainerProdigyAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { if (game.inCheckPlayableState()) { int controllerTargets = 0; //number of possible targets controlled by the ability's controller for (UUID permId : CardUtil.getAllPossibleTargets(ability, game)) { diff --git a/Mage.Sets/src/mage/cards/w/WelcomeTheDead.java b/Mage.Sets/src/mage/cards/w/WelcomeTheDead.java new file mode 100644 index 00000000000..c813928f5b3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WelcomeTheDead.java @@ -0,0 +1,126 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.token.ZombieDruidToken; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WelcomeTheDead extends CardImpl { + + public WelcomeTheDead(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}"); + + // Draw two cards, then discard a card and you lose 2 life. Create X tapped 2/2 black Zombie Druid creature tokens, where X is the number of cards that were put into your graveyard from your hand or library this turn. + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(2, 1)); + this.getSpellAbility().addEffect(new LoseLifeSourceControllerEffect(2).concatBy("and")); + this.getSpellAbility().addEffect(new CreateTokenEffect( + new ZombieDruidToken(), WelcomeTheDeadValue.instance, true, false + )); + this.getSpellAbility().addHint(WelcomeTheDeadValue.getHint()); + this.getSpellAbility().addWatcher(new WelcomeTheDeadWatcher()); + + // Flashback {5}{B} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{5}{B}"))); + } + + private WelcomeTheDead(final WelcomeTheDead card) { + super(card); + } + + @Override + public WelcomeTheDead copy() { + return new WelcomeTheDead(this); + } +} + +enum WelcomeTheDeadValue implements DynamicValue { + instance; + private static final Hint hint = new ValueHint("Cards put into your graveyard from your hand or library", instance); + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return WelcomeTheDeadWatcher.getValue(game, sourceAbility); + } + + @Override + public WelcomeTheDeadValue copy() { + return this; + } + + @Override + public String getMessage() { + return "the number of cards that were put into your graveyard from your hand or library this turn"; + } + + @Override + public String toString() { + return "X"; + } + + public static Hint getHint() { + return hint; + } +} + +class WelcomeTheDeadWatcher extends Watcher { + + private final Map map = new HashMap<>(); + + WelcomeTheDeadWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ZONE_CHANGE) { + return; + } + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (!Zone.GRAVEYARD.match(zEvent.getToZone())) { + return; + } + switch (zEvent.getFromZone()) { + case HAND: + case LIBRARY: + map.compute(zEvent.getPlayerId(), CardUtil::setOrIncrementValue); + } + } + + @Override + public void reset() { + super.reset(); + map.clear(); + } + + static int getValue(Game game, Ability source) { + return game + .getState() + .getWatcher(WelcomeTheDeadWatcher.class) + .map + .getOrDefault(source.getControllerId(), 0); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WhirlwingStormbrood.java b/Mage.Sets/src/mage/cards/w/WhirlwingStormbrood.java new file mode 100644 index 00000000000..aa95f5123da --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WhirlwingStormbrood.java @@ -0,0 +1,72 @@ +package mage.cards.w; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashAllEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.OmenCard; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Jmlundeen + */ +public final class WhirlwingStormbrood extends OmenCard { + + private static final FilterCard filter = new FilterCard("sorcery spells and Dragon spells"); + + static { + filter.add(Predicates.or( + CardType.SORCERY.getPredicate(), + SubType.DRAGON.getPredicate() + )); + } + + public WhirlwingStormbrood(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{4}{U}", "Dynamic Soar", "{2}{G}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // You may cast sorcery spells and Dragon spells as though they had flash. + this.addAbility(new SimpleStaticAbility(new CastAsThoughItHadFlashAllEffect(Duration.WhileOnBattlefield, filter))); + + // Dynamic Soar + // Put three +1/+1 counters on target creature you control. + this.getSpellCard().getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(), StaticValue.get(3))); + this.getSpellCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.finalizeOmen(); + } + + private WhirlwingStormbrood(final WhirlwingStormbrood card) { + super(card); + } + + @Override + public WhirlwingStormbrood copy() { + return new WhirlwingStormbrood(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WillOfTheAbzan.java b/Mage.Sets/src/mage/cards/w/WillOfTheAbzan.java new file mode 100644 index 00000000000..3bbcf532696 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WillOfTheAbzan.java @@ -0,0 +1,61 @@ +package mage.cards.w; + +import mage.abilities.Mode; +import mage.abilities.condition.common.ControlACommanderCondition; +import mage.abilities.effects.common.LoseLifeTargetControllerEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.SacrificeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.GreatestPowerControlledPredicate; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WillOfTheAbzan extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("a creature with the greatest power among creatures you control"); + + static { + filter.add(GreatestPowerControlledPredicate.instance); + } + + public WillOfTheAbzan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}"); + + // Choose one. If you control a commander as you cast this spell, you may choose both instead. + this.getSpellAbility().getModes().setChooseText( + "Choose one. If you control a commander as you cast this spell, you may choose both instead." + ); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); + + // * Any number of target opponents each sacrifice a creature with the greatest power among creatures that player controls and lose 3 life. + this.getSpellAbility().addEffect(new SacrificeEffect(filter, 1, "") + .setText("any number of target opponents each sacrifice a creature " + + "with the greatest power among creatures that player controls")); + this.getSpellAbility().addEffect(new LoseLifeTargetControllerEffect(3).setText("and lose 3 life")); + this.getSpellAbility().addTarget(new TargetOpponent(0, 1, false)); + + // * Return target creature card from your graveyard to the battlefield. + this.getSpellAbility().addMode(new Mode(new ReturnFromGraveyardToBattlefieldTargetEffect()) + .addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD))); + } + + private WillOfTheAbzan(final WillOfTheAbzan card) { + super(card); + } + + @Override + public WillOfTheAbzan copy() { + return new WillOfTheAbzan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WillOfTheSultai.java b/Mage.Sets/src/mage/cards/w/WillOfTheSultai.java new file mode 100644 index 00000000000..fa49ce6513b --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WillOfTheSultai.java @@ -0,0 +1,57 @@ +package mage.cards.w; + +import mage.abilities.Mode; +import mage.abilities.condition.common.ControlACommanderCondition; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.effects.common.ReturnFromYourGraveyardToBattlefieldAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.LandsYouControlHint; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WillOfTheSultai extends CardImpl { + + public WillOfTheSultai(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}"); + + // Choose one. If you control a commander as you cast this spell, you may choose both instead. + this.getSpellAbility().getModes().setChooseText( + "Choose one. If you control a commander as you cast this spell, you may choose both instead." + ); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); + + // * Target player mills three cards. Return all land cards from your graveyard to the battlefield tapped. + this.getSpellAbility().addEffect(new MillCardsTargetEffect(3)); + this.getSpellAbility().addTarget(new TargetPlayer()); + this.getSpellAbility().addEffect(new ReturnFromYourGraveyardToBattlefieldAllEffect(StaticFilters.FILTER_CARD_LANDS)); + + // * Put X +1/+1 counters on target creature, where X is the number of lands you control. It gains trample until end of turn. + this.getSpellAbility().addMode(new Mode(new AddCountersTargetEffect( + CounterType.P1P1.createInstance(0), LandsYouControlCount.instance + )).addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance()) + .setText("it gains trample until end of turn")).addTarget(new TargetCreaturePermanent())); + this.getSpellAbility().addHint(LandsYouControlHint.instance); + } + + private WillOfTheSultai(final WillOfTheSultai card) { + super(card); + } + + @Override + public WillOfTheSultai copy() { + return new WillOfTheSultai(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WillOfTheTemur.java b/Mage.Sets/src/mage/cards/w/WillOfTheTemur.java new file mode 100644 index 00000000000..5588d9bf35d --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WillOfTheTemur.java @@ -0,0 +1,132 @@ +package mage.cards.w; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.condition.common.ControlACommanderCondition; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WillOfTheTemur extends CardImpl { + + public WillOfTheTemur(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{U}"); + + // Choose one. If you control a commander as you cast this spell, you may choose both instead. + this.getSpellAbility().getModes().setChooseText( + "Choose one. If you control a commander as you cast this spell, you may choose both instead." + ); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); + + // * Create a token that's a copy of target permanent, except it's a 4/4 Dragon creature with flying in addition to its other types. + this.getSpellAbility().addEffect(new WillOfTheTemurEffect()); + this.getSpellAbility().addTarget(new TargetPermanent()); + + // * Target player draws cards equal to the greatest mana value among permanents you control. + this.getSpellAbility().addMode(new Mode(new DrawCardTargetEffect(WillOfTheTemurValue.instance) + .setText("target player draws cards equal to the greatest mana value among permanents you control") + ).addTarget(new TargetPlayer())); + this.getSpellAbility().addHint(WillOfTheTemurValue.getHint()); + } + + private WillOfTheTemur(final WillOfTheTemur card) { + super(card); + } + + @Override + public WillOfTheTemur copy() { + return new WillOfTheTemur(this); + } +} + +enum WillOfTheTemurValue implements DynamicValue { + instance; + private static final Hint hint = new ValueHint("Greatest mana value among permanents you control", instance); + + public static Hint getHint() { + return hint; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT, + sourceAbility.getControllerId(), sourceAbility, game + ) + .stream() + .mapToInt(MageObject::getManaValue) + .max() + .orElse(0); + } + + @Override + public WillOfTheTemurValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "1"; + } +} + +class WillOfTheTemurEffect extends OneShotEffect { + + WillOfTheTemurEffect() { + super(Outcome.Benefit); + staticText = "create a token that's a copy of target permanent, " + + "except it's a 4/4 Dragon creature with flying in addition to its other types"; + } + + private WillOfTheTemurEffect(final WillOfTheTemurEffect effect) { + super(effect); + } + + @Override + public WillOfTheTemurEffect copy() { + return new WillOfTheTemurEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + return new CreateTokenCopyTargetEffect().setPermanentModifier(token -> { + token.addCardType(CardType.CREATURE); + token.addSubType(SubType.DRAGON); + token.setPower(4); + token.setToughness(4); + token.addAbility(FlyingAbility.getInstance()); + }).setSavedPermanent(permanent).apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WindcragSiege.java b/Mage.Sets/src/mage/cards/w/WindcragSiege.java new file mode 100644 index 00000000000..dac881a8419 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WindcragSiege.java @@ -0,0 +1,87 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseModeEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.GainAnchorWordAbilitySourceEffect; +import mage.abilities.effects.common.replacement.AdditionalTriggersAttackingReplacementEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.ModeChoice; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.token.GoblinToken; +import mage.game.permanent.token.Token; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WindcragSiege extends CardImpl { + + public WindcragSiege(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}{W}"); + + // As this enchantment enters, choose Mardu or Jeskai. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseModeEffect(ModeChoice.MARDU, ModeChoice.JESKAI))); + + // * Mardu -- If a creature attacking causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new AdditionalTriggersAttackingReplacementEffect(false), ModeChoice.MARDU + ))); + + // * Jeskai -- At the beginning of your upkeep, create a 1/1 red Goblin creature token. It gains lifelink and haste until end of turn. + this.addAbility(new SimpleStaticAbility(new GainAnchorWordAbilitySourceEffect( + new BeginningOfUpkeepTriggeredAbility(new WindcragSiegeEffect()), ModeChoice.JESKAI + ))); + } + + private WindcragSiege(final WindcragSiege card) { + super(card); + } + + @Override + public WindcragSiege copy() { + return new WindcragSiege(this); + } +} + +class WindcragSiegeEffect extends OneShotEffect { + + WindcragSiegeEffect() { + super(Outcome.Benefit); + staticText = "create a 1/1 red Goblin creature token. It gains lifelink and haste until end of turn"; + } + + private WindcragSiegeEffect(final WindcragSiegeEffect effect) { + super(effect); + } + + @Override + public WindcragSiegeEffect copy() { + return new WindcragSiegeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new GoblinToken(); + token.putOntoBattlefield(1, game, source); + game.addEffect(new GainAbilityTargetEffect( + LifelinkAbility.getInstance(), Duration.EndOfTurn + ).setTargetPointer(new FixedTargets(token, game)), source); + game.addEffect(new GainAbilityTargetEffect( + HasteAbility.getInstance(), Duration.EndOfTurn + ).setTargetPointer(new FixedTargets(token, game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/w/WithinRange.java b/Mage.Sets/src/mage/cards/w/WithinRange.java new file mode 100644 index 00000000000..e75296feaa7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WithinRange.java @@ -0,0 +1,80 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.permanent.token.RedWarriorToken; +import mage.players.Player; + +import java.util.List; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WithinRange extends CardImpl { + + public WithinRange(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}"); + + // When this enchantment enters, create two 1/1 red Warrior creature tokens. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new RedWarriorToken()))); + + // Whenever you attack, each opponent loses life equal to the number of creatures attacking them. + this.addAbility(new AttacksWithCreaturesTriggeredAbility(new WithinRangeEffect(), 1)); + } + + private WithinRange(final WithinRange card) { + super(card); + } + + @Override + public WithinRange copy() { + return new WithinRange(this); + } +} + +class WithinRangeEffect extends OneShotEffect { + + WithinRangeEffect() { + super(Outcome.Benefit); + staticText = "each opponent loses life equal to the number of creatures attacking them"; + } + + private WithinRangeEffect(final WithinRangeEffect effect) { + super(effect); + } + + @Override + public WithinRangeEffect copy() { + return new WithinRangeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + int count = game + .getCombat() + .getGroups() + .stream() + .filter(combatGroup -> player.getId().equals(combatGroup.getDefenderId())) + .map(CombatGroup::getAttackers) + .mapToInt(List::size) + .sum(); + player.loseLife(count, game, source, false); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/w/WulfgarOfIcewindDale.java b/Mage.Sets/src/mage/cards/w/WulfgarOfIcewindDale.java index 6b4c622c27d..798da523453 100644 --- a/Mage.Sets/src/mage/cards/w/WulfgarOfIcewindDale.java +++ b/Mage.Sets/src/mage/cards/w/WulfgarOfIcewindDale.java @@ -1,19 +1,14 @@ package mage.cards.w; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.replacement.AdditionalTriggersAttackingReplacementEffect; import mage.abilities.keyword.MeleeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Controllable; -import mage.game.Game; -import mage.game.events.DefenderAttackedEvent; -import mage.game.events.GameEvent; -import mage.game.events.NumberOfTriggersEvent; -import mage.game.permanent.Permanent; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import java.util.UUID; @@ -35,7 +30,7 @@ public final class WulfgarOfIcewindDale extends CardImpl { this.addAbility(new MeleeAbility()); // If a creature you control attacking would cause a triggered ability of a permanent you control to trigger, that ability triggers an additional time. - this.addAbility(new SimpleStaticAbility(new WulfgarOfIcewindDaleEffect())); + this.addAbility(new SimpleStaticAbility(new AdditionalTriggersAttackingReplacementEffect(true))); } private WulfgarOfIcewindDale(final WulfgarOfIcewindDale card) { @@ -47,64 +42,3 @@ public final class WulfgarOfIcewindDale extends CardImpl { return new WulfgarOfIcewindDale(this); } } - -class WulfgarOfIcewindDaleEffect extends ReplacementEffectImpl { - - WulfgarOfIcewindDaleEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "if a creature you control attacking causes a triggered ability " + - "of a permanent you control to trigger, that ability triggers an additional time"; - } - - private WulfgarOfIcewindDaleEffect(final WulfgarOfIcewindDaleEffect effect) { - super(effect); - } - - @Override - public WulfgarOfIcewindDaleEffect copy() { - return new WulfgarOfIcewindDaleEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; - Permanent sourcePermanent = game.getPermanent(numberOfTriggersEvent.getSourceId()); - if (sourcePermanent == null || !sourcePermanent.isControlledBy(source.getControllerId())) { - return false; - } - GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); - if (sourceEvent == null) { - return false; - } - - switch (sourceEvent.getType()) { - case ATTACKER_DECLARED: - return source.isControlledBy(sourceEvent.getPlayerId()); - case DECLARED_ATTACKERS: - return game - .getCombat() - .getAttackers() - .stream() - .map(game::getControllerId) - .anyMatch(source::isControlledBy); - case DEFENDER_ATTACKED: - return ((DefenderAttackedEvent) sourceEvent) - .getAttackers(game) - .stream() - .map(Controllable::getControllerId) - .anyMatch(source::isControlledBy); - } - return false; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() + 1); - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/z/ZenithFestival.java b/Mage.Sets/src/mage/cards/z/ZenithFestival.java new file mode 100644 index 00000000000..f37ef6e10e0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZenithFestival.java @@ -0,0 +1,38 @@ +package mage.cards.z; + +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.HarmonizeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ZenithFestival extends CardImpl { + + public ZenithFestival(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}{R}"); + + // Exile the top X cards of your library. You may play them until the end of your next turn. + this.getSpellAbility().addEffect(new ExileTopXMayPlayUntilEffect( + GetXValue.instance, false, Duration.UntilEndOfYourNextTurn + )); + + // Harmonize {X}{R}{R} + this.addAbility(new HarmonizeAbility(this, "{X}{R}{R}")); + } + + private ZenithFestival(final ZenithFestival card) { + super(card); + } + + @Override + public ZenithFestival copy() { + return new ZenithFestival(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AssassinsCreed.java b/Mage.Sets/src/mage/sets/AssassinsCreed.java index 63b30bf8a39..7fb2fcf3838 100644 --- a/Mage.Sets/src/mage/sets/AssassinsCreed.java +++ b/Mage.Sets/src/mage/sets/AssassinsCreed.java @@ -189,9 +189,9 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Kassandra, Eagle Bearer", 59, Rarity.MYTHIC, mage.cards.k.KassandraEagleBearer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Keen-Eyed Raven", 279, Rarity.UNCOMMON, mage.cards.k.KeenEyedRaven.class)); cards.add(new SetCardInfo("Labyrinth Adversary", 290, Rarity.UNCOMMON, mage.cards.l.LabyrinthAdversary.class)); - //cards.add(new SetCardInfo("Layla Hassan", 127, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Layla Hassan", 177, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Layla Hassan", 7, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Layla Hassan", 127, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Layla Hassan", 177, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Layla Hassan", 7, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leonardo da Vinci", "118z", Rarity.MYTHIC, mage.cards.l.LeonardoDaVinci.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leonardo da Vinci", 118, Rarity.MYTHIC, mage.cards.l.LeonardoDaVinci.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leonardo da Vinci", 193, Rarity.MYTHIC, mage.cards.l.LeonardoDaVinci.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/DoctorWho.java b/Mage.Sets/src/mage/sets/DoctorWho.java index e03ac42718d..61e361fa3a2 100644 --- a/Mage.Sets/src/mage/sets/DoctorWho.java +++ b/Mage.Sets/src/mage/sets/DoctorWho.java @@ -376,8 +376,8 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Frostboil Snarl", 282, Rarity.RARE, mage.cards.f.FrostboilSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Frostboil Snarl", 498, Rarity.RARE, mage.cards.f.FrostboilSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Frostboil Snarl", 873, Rarity.RARE, mage.cards.f.FrostboilSnarl.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Fugitive of the Judoon", 103, Rarity.RARE, mage.cards.f.FugitiveOfTheJudoon.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Fugitive of the Judoon", 708, Rarity.RARE, mage.cards.f.FugitiveOfTheJudoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fugitive of the Judoon", 103, Rarity.RARE, mage.cards.f.FugitiveOfTheJudoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fugitive of the Judoon", 708, Rarity.RARE, mage.cards.f.FugitiveOfTheJudoon.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Furycalm Snarl", 1090, Rarity.RARE, mage.cards.f.FurycalmSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Furycalm Snarl", 283, Rarity.RARE, mage.cards.f.FurycalmSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Furycalm Snarl", 499, Rarity.RARE, mage.cards.f.FurycalmSnarl.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/FinalFantasy.java b/Mage.Sets/src/mage/sets/FinalFantasy.java index bde77f989dc..463445ffd72 100644 --- a/Mage.Sets/src/mage/sets/FinalFantasy.java +++ b/Mage.Sets/src/mage/sets/FinalFantasy.java @@ -26,6 +26,8 @@ public final class FinalFantasy extends ExpansionSet { cards.add(new SetCardInfo("Sin, Spira's Punishment", 242, Rarity.RARE, mage.cards.s.SinSpirasPunishment.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sin, Spira's Punishment", 348, Rarity.RARE, mage.cards.s.SinSpirasPunishment.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sin, Spira's Punishment", 508, Rarity.RARE, mage.cards.s.SinSpirasPunishment.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stiltzkin, Moogle Merchant", 34, Rarity.RARE, mage.cards.s.StiltzkinMoogleMerchant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stiltzkin, Moogle Merchant", 327, Rarity.RARE, mage.cards.s.StiltzkinMoogleMerchant.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Summon: Shiva", 78, Rarity.UNCOMMON, mage.cards.s.SummonShiva.class)); cards.add(new SetCardInfo("Tonberry", 122, Rarity.UNCOMMON, mage.cards.t.Tonberry.class)); } diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 1a0c64cf5d8..866dfa8c84f 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -4,15 +4,11 @@ import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; -import java.util.Arrays; -import java.util.List; - /** * @author TheElk801 */ public final class TarkirDragonstorm extends ExpansionSet { - private static final List unfinished = Arrays.asList("Channeled Dragonfire", "Glacial Dragonhunt", "Mammoth Bellow", "Nature's Rhythm", "Roamer's Routine", "Songcrafter Mage", "Synchronized Charge", "Unending Whisper", "Ureni's Rebuff", "Wild Ride", "Winternight Stories"); private static final TarkirDragonstorm instance = new TarkirDragonstorm(); public static TarkirDragonstorm getInstance() { @@ -24,11 +20,14 @@ public final class TarkirDragonstorm extends ExpansionSet { this.blockName = "Tarkir: Dragonstorm"; // for sorting in GUI this.hasBasicLands = true; + this.enablePlayBooster(Integer.MAX_VALUE); + cards.add(new SetCardInfo("Abzan Devotee", 68, Rarity.COMMON, mage.cards.a.AbzanDevotee.class)); cards.add(new SetCardInfo("Abzan Monument", 238, Rarity.UNCOMMON, mage.cards.a.AbzanMonument.class)); cards.add(new SetCardInfo("Adorned Crocodile", 69, Rarity.COMMON, mage.cards.a.AdornedCrocodile.class)); cards.add(new SetCardInfo("Aegis Sculptor", 35, Rarity.UNCOMMON, mage.cards.a.AegisSculptor.class)); cards.add(new SetCardInfo("Agent of Kotis", 36, Rarity.COMMON, mage.cards.a.AgentOfKotis.class)); + cards.add(new SetCardInfo("Aggressive Negotiations", 70, Rarity.COMMON, mage.cards.a.AggressiveNegotiations.class)); cards.add(new SetCardInfo("Ainok Wayfarer", 134, Rarity.COMMON, mage.cards.a.AinokWayfarer.class)); cards.add(new SetCardInfo("Alchemist's Assistant", 71, Rarity.UNCOMMON, mage.cards.a.AlchemistsAssistant.class)); cards.add(new SetCardInfo("Alesha's Legacy", 72, Rarity.COMMON, mage.cards.a.AleshasLegacy.class)); @@ -42,8 +41,12 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Awaken the Honored Dead", 170, Rarity.RARE, mage.cards.a.AwakenTheHonoredDead.class)); cards.add(new SetCardInfo("Barrensteppe Siege", 171, Rarity.RARE, mage.cards.b.BarrensteppeSiege.class)); cards.add(new SetCardInfo("Bearer of Glory", 4, Rarity.COMMON, mage.cards.b.BearerOfGlory.class)); + cards.add(new SetCardInfo("Betor, Kin to All", 172, Rarity.MYTHIC, mage.cards.b.BetorKinToAll.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Betor, Kin to All", 308, Rarity.MYTHIC, mage.cards.b.BetorKinToAll.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Betor, Kin to All", 353, Rarity.MYTHIC, mage.cards.b.BetorKinToAll.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bewildering Blizzard", 38, Rarity.UNCOMMON, mage.cards.b.BewilderingBlizzard.class)); cards.add(new SetCardInfo("Bloodfell Caves", 250, Rarity.COMMON, mage.cards.b.BloodfellCaves.class)); + cards.add(new SetCardInfo("Bloomvine Regent", 136, Rarity.RARE, mage.cards.b.BloomvineRegent.class)); cards.add(new SetCardInfo("Blossoming Sands", 251, Rarity.COMMON, mage.cards.b.BlossomingSands.class)); cards.add(new SetCardInfo("Bone-Cairn Butcher", 173, Rarity.UNCOMMON, mage.cards.b.BoneCairnButcher.class)); cards.add(new SetCardInfo("Boulderborn Dragon", 239, Rarity.COMMON, mage.cards.b.BoulderbornDragon.class)); @@ -52,6 +55,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Caustic Exhale", 74, Rarity.COMMON, mage.cards.c.CausticExhale.class)); cards.add(new SetCardInfo("Champion of Dusan", 137, Rarity.COMMON, mage.cards.c.ChampionOfDusan.class)); cards.add(new SetCardInfo("Channeled Dragonfire", 102, Rarity.UNCOMMON, mage.cards.c.ChanneledDragonfire.class)); + cards.add(new SetCardInfo("Clarion Conqueror", 5, Rarity.RARE, mage.cards.c.ClarionConqueror.class)); cards.add(new SetCardInfo("Constrictor Sage", 39, Rarity.UNCOMMON, mage.cards.c.ConstrictorSage.class)); cards.add(new SetCardInfo("Coordinated Maneuver", 6, Rarity.COMMON, mage.cards.c.CoordinatedManeuver.class)); cards.add(new SetCardInfo("Cori Mountain Monastery", 252, Rarity.RARE, mage.cards.c.CoriMountainMonastery.class)); @@ -61,45 +65,71 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Craterhoof Behemoth", 138, Rarity.MYTHIC, mage.cards.c.CraterhoofBehemoth.class)); cards.add(new SetCardInfo("Cruel Truths", 76, Rarity.COMMON, mage.cards.c.CruelTruths.class)); cards.add(new SetCardInfo("Dalkovan Packbeasts", 7, Rarity.UNCOMMON, mage.cards.d.DalkovanPackbeasts.class)); + cards.add(new SetCardInfo("Death Begets Life", 176, Rarity.MYTHIC, mage.cards.d.DeathBegetsLife.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Death Begets Life", 354, Rarity.MYTHIC, mage.cards.d.DeathBegetsLife.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Death Begets Life", 406, Rarity.MYTHIC, mage.cards.d.DeathBegetsLife.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Death Begets Life", 416, Rarity.MYTHIC, mage.cards.d.DeathBegetsLife.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Defibrillating Current", 177, Rarity.UNCOMMON, mage.cards.d.DefibrillatingCurrent.class)); cards.add(new SetCardInfo("Delta Bloodflies", 77, Rarity.COMMON, mage.cards.d.DeltaBloodflies.class)); cards.add(new SetCardInfo("Descendant of Storms", 8, Rarity.UNCOMMON, mage.cards.d.DescendantOfStorms.class)); cards.add(new SetCardInfo("Devoted Duelist", 104, Rarity.COMMON, mage.cards.d.DevotedDuelist.class)); + cards.add(new SetCardInfo("Dirgur Island Dragon", 40, Rarity.COMMON, mage.cards.d.DirgurIslandDragon.class)); cards.add(new SetCardInfo("Dismal Backwater", 254, Rarity.COMMON, mage.cards.d.DismalBackwater.class)); cards.add(new SetCardInfo("Dispelling Exhale", 41, Rarity.COMMON, mage.cards.d.DispellingExhale.class)); + cards.add(new SetCardInfo("Disruptive Stormbrood", 178, Rarity.UNCOMMON, mage.cards.d.DisruptiveStormbrood.class)); cards.add(new SetCardInfo("Dracogenesis", 105, Rarity.MYTHIC, mage.cards.d.Dracogenesis.class)); cards.add(new SetCardInfo("Dragon Sniper", 139, Rarity.UNCOMMON, mage.cards.d.DragonSniper.class)); cards.add(new SetCardInfo("Dragon's Prey", 79, Rarity.COMMON, mage.cards.d.DragonsPrey.class)); cards.add(new SetCardInfo("Dragonback Assault", 179, Rarity.MYTHIC, mage.cards.d.DragonbackAssault.class)); cards.add(new SetCardInfo("Dragonback Lancer", 9, Rarity.COMMON, mage.cards.d.DragonbackLancer.class)); cards.add(new SetCardInfo("Dragonbroods' Relic", 140, Rarity.UNCOMMON, mage.cards.d.DragonbroodsRelic.class)); + cards.add(new SetCardInfo("Dragonclaw Strike", 180, Rarity.UNCOMMON, mage.cards.d.DragonclawStrike.class)); + cards.add(new SetCardInfo("Dragonfire Blade", 240, Rarity.RARE, mage.cards.d.DragonfireBlade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dragonfire Blade", 324, Rarity.RARE, mage.cards.d.DragonfireBlade.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dragonologist", 42, Rarity.RARE, mage.cards.d.Dragonologist.class)); cards.add(new SetCardInfo("Dragonstorm Forecaster", 43, Rarity.UNCOMMON, mage.cards.d.DragonstormForecaster.class)); cards.add(new SetCardInfo("Dragonstorm Globe", 241, Rarity.COMMON, mage.cards.d.DragonstormGlobe.class)); cards.add(new SetCardInfo("Dusyut Earthcarver", 141, Rarity.COMMON, mage.cards.d.DusyutEarthcarver.class)); cards.add(new SetCardInfo("Duty Beyond Death", 10, Rarity.UNCOMMON, mage.cards.d.DutyBeyondDeath.class)); + cards.add(new SetCardInfo("Effortless Master", 181, Rarity.UNCOMMON, mage.cards.e.EffortlessMaster.class)); cards.add(new SetCardInfo("Elspeth, Storm Slayer", 11, Rarity.MYTHIC, mage.cards.e.ElspethStormSlayer.class)); cards.add(new SetCardInfo("Embermouth Sentinel", 242, Rarity.COMMON, mage.cards.e.EmbermouthSentinel.class)); cards.add(new SetCardInfo("Encroaching Dragonstorm", 142, Rarity.UNCOMMON, mage.cards.e.EncroachingDragonstorm.class)); cards.add(new SetCardInfo("Equilibrium Adept", 106, Rarity.UNCOMMON, mage.cards.e.EquilibriumAdept.class)); + cards.add(new SetCardInfo("Eshki Dragonclaw", 182, Rarity.RARE, mage.cards.e.EshkiDragonclaw.class)); cards.add(new SetCardInfo("Essence Anchor", 44, Rarity.UNCOMMON, mage.cards.e.EssenceAnchor.class)); cards.add(new SetCardInfo("Evolving Wilds", 255, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); cards.add(new SetCardInfo("Fangkeeper's Familiar", 183, Rarity.RARE, mage.cards.f.FangkeepersFamiliar.class)); cards.add(new SetCardInfo("Felothar, Dawn of the Abzan", 184, Rarity.RARE, mage.cards.f.FelotharDawnOfTheAbzan.class)); + cards.add(new SetCardInfo("Feral Deathgorger", 80, Rarity.COMMON, mage.cards.f.FeralDeathgorger.class)); cards.add(new SetCardInfo("Fire-Rim Form", 107, Rarity.COMMON, mage.cards.f.FireRimForm.class)); + cards.add(new SetCardInfo("Flamehold Grappler", 185, Rarity.RARE, mage.cards.f.FlameholdGrappler.class)); cards.add(new SetCardInfo("Fleeting Effigy", 108, Rarity.UNCOMMON, mage.cards.f.FleetingEffigy.class)); + cards.add(new SetCardInfo("Focus the Mind", 45, Rarity.COMMON, mage.cards.f.FocusTheMind.class)); cards.add(new SetCardInfo("Forest", 285, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Formation Breaker", 143, Rarity.UNCOMMON, mage.cards.f.FormationBreaker.class)); cards.add(new SetCardInfo("Fortress Kin-Guard", 12, Rarity.COMMON, mage.cards.f.FortressKinGuard.class)); cards.add(new SetCardInfo("Fresh Start", 46, Rarity.UNCOMMON, mage.cards.f.FreshStart.class)); cards.add(new SetCardInfo("Frontier Bivouac", 256, Rarity.UNCOMMON, mage.cards.f.FrontierBivouac.class)); cards.add(new SetCardInfo("Frontline Rush", 186, Rarity.UNCOMMON, mage.cards.f.FrontlineRush.class)); + cards.add(new SetCardInfo("Frostcliff Siege", 187, Rarity.RARE, mage.cards.f.FrostcliffSiege.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Frostcliff Siege", 385, Rarity.RARE, mage.cards.f.FrostcliffSiege.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Furious Forebear", 13, Rarity.UNCOMMON, mage.cards.f.FuriousForebear.class)); cards.add(new SetCardInfo("Glacial Dragonhunt", 188, Rarity.UNCOMMON, mage.cards.g.GlacialDragonhunt.class)); + cards.add(new SetCardInfo("Glacierwood Siege", 189, Rarity.RARE, mage.cards.g.GlacierwoodSiege.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Glacierwood Siege", 386, Rarity.RARE, mage.cards.g.GlacierwoodSiege.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Great Arashin City", 257, Rarity.RARE, mage.cards.g.GreatArashinCity.class)); cards.add(new SetCardInfo("Gurmag Nightwatch", 190, Rarity.COMMON, mage.cards.g.GurmagNightwatch.class)); cards.add(new SetCardInfo("Gurmag Rakshasa", 81, Rarity.UNCOMMON, mage.cards.g.GurmagRakshasa.class)); cards.add(new SetCardInfo("Hardened Tactician", 191, Rarity.UNCOMMON, mage.cards.h.HardenedTactician.class)); + cards.add(new SetCardInfo("Herd Heirloom", 144, Rarity.RARE, mage.cards.h.HerdHeirloom.class)); cards.add(new SetCardInfo("Heritage Reclamation", 145, Rarity.COMMON, mage.cards.h.HeritageReclamation.class)); + cards.add(new SetCardInfo("Highspire Bell-Ringer", 47, Rarity.COMMON, mage.cards.h.HighspireBellRinger.class)); + cards.add(new SetCardInfo("Hollowmurk Siege", 192, Rarity.RARE, mage.cards.h.HollowmurkSiege.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hollowmurk Siege", 387, Rarity.RARE, mage.cards.h.HollowmurkSiege.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Host of the Hereafter", 193, Rarity.UNCOMMON, mage.cards.h.HostOfTheHereafter.class)); cards.add(new SetCardInfo("Humbling Elder", 48, Rarity.COMMON, mage.cards.h.HumblingElder.class)); + cards.add(new SetCardInfo("Hundred-Battle Veteran", 82, Rarity.UNCOMMON, mage.cards.h.HundredBattleVeteran.class)); cards.add(new SetCardInfo("Iceridge Serpent", 49, Rarity.COMMON, mage.cards.i.IceridgeSerpent.class)); cards.add(new SetCardInfo("Inevitable Defeat", 194, Rarity.RARE, mage.cards.i.InevitableDefeat.class)); cards.add(new SetCardInfo("Inspirited Vanguard", 146, Rarity.UNCOMMON, mage.cards.i.InspiritedVanguard.class)); @@ -109,8 +139,10 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Jeskai Brushmaster", 195, Rarity.UNCOMMON, mage.cards.j.JeskaiBrushmaster.class)); cards.add(new SetCardInfo("Jeskai Devotee", 110, Rarity.COMMON, mage.cards.j.JeskaiDevotee.class)); cards.add(new SetCardInfo("Jeskai Monument", 244, Rarity.UNCOMMON, mage.cards.j.JeskaiMonument.class)); + cards.add(new SetCardInfo("Jeskai Revelation", 196, Rarity.MYTHIC, mage.cards.j.JeskaiRevelation.class)); cards.add(new SetCardInfo("Jeskai Shrinekeeper", 197, Rarity.UNCOMMON, mage.cards.j.JeskaiShrinekeeper.class)); cards.add(new SetCardInfo("Jungle Hollow", 258, Rarity.COMMON, mage.cards.j.JungleHollow.class)); + cards.add(new SetCardInfo("Karakyk Guardian", 198, Rarity.UNCOMMON, mage.cards.k.KarakykGuardian.class)); cards.add(new SetCardInfo("Kheru Goldkeeper", 199, Rarity.UNCOMMON, mage.cards.k.KheruGoldkeeper.class)); cards.add(new SetCardInfo("Kin-Tree Nurturer", 83, Rarity.COMMON, mage.cards.k.KinTreeNurturer.class)); cards.add(new SetCardInfo("Kin-Tree Severance", 200, Rarity.UNCOMMON, mage.cards.k.KinTreeSeverance.class)); @@ -120,30 +152,44 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Knockout Maneuver", 147, Rarity.UNCOMMON, mage.cards.k.KnockoutManeuver.class)); cards.add(new SetCardInfo("Kotis, the Fangkeeper", 202, Rarity.RARE, mage.cards.k.KotisTheFangkeeper.class)); cards.add(new SetCardInfo("Krotiq Nestguard", 148, Rarity.COMMON, mage.cards.k.KrotiqNestguard.class)); + cards.add(new SetCardInfo("Krumar Initiate", 84, Rarity.UNCOMMON, mage.cards.k.KrumarInitiate.class)); + cards.add(new SetCardInfo("Lasyd Prowler", 149, Rarity.RARE, mage.cards.l.LasydProwler.class)); + cards.add(new SetCardInfo("Lie in Wait", 203, Rarity.UNCOMMON, mage.cards.l.LieInWait.class)); cards.add(new SetCardInfo("Lightfoot Technique", 14, Rarity.COMMON, mage.cards.l.LightfootTechnique.class)); + cards.add(new SetCardInfo("Lotuslight Dancers", 204, Rarity.RARE, mage.cards.l.LotuslightDancers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotuslight Dancers", 363, Rarity.RARE, mage.cards.l.LotuslightDancers.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Loxodon Battle Priest", 15, Rarity.UNCOMMON, mage.cards.l.LoxodonBattlePriest.class)); + cards.add(new SetCardInfo("Maelstrom of the Spirit Dragon", 260, Rarity.RARE, mage.cards.m.MaelstromOfTheSpiritDragon.class)); + cards.add(new SetCardInfo("Magmatic Hellkite", 111, Rarity.RARE, mage.cards.m.MagmaticHellkite.class)); cards.add(new SetCardInfo("Mammoth Bellow", 205, Rarity.UNCOMMON, mage.cards.m.MammothBellow.class)); + cards.add(new SetCardInfo("Marang River Regent", 51, Rarity.RARE, mage.cards.m.MarangRiverRegent.class)); cards.add(new SetCardInfo("Mardu Devotee", 16, Rarity.COMMON, mage.cards.m.MarduDevotee.class)); cards.add(new SetCardInfo("Mardu Monument", 245, Rarity.UNCOMMON, mage.cards.m.MarduMonument.class)); + cards.add(new SetCardInfo("Mardu Siegebreaker", 206, Rarity.RARE, mage.cards.m.MarduSiegebreaker.class)); cards.add(new SetCardInfo("Marshal of the Lost", 207, Rarity.UNCOMMON, mage.cards.m.MarshalOfTheLost.class)); cards.add(new SetCardInfo("Meticulous Artisan", 112, Rarity.COMMON, mage.cards.m.MeticulousArtisan.class)); + cards.add(new SetCardInfo("Mistrise Village", 261, Rarity.RARE, mage.cards.m.MistriseVillage.class)); cards.add(new SetCardInfo("Molten Exhale", 113, Rarity.COMMON, mage.cards.m.MoltenExhale.class)); cards.add(new SetCardInfo("Monastery Messenger", 208, Rarity.COMMON, mage.cards.m.MonasteryMessenger.class)); cards.add(new SetCardInfo("Mountain", 283, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mox Jasper", 246, Rarity.MYTHIC, mage.cards.m.MoxJasper.class)); cards.add(new SetCardInfo("Mystic Monastery", 262, Rarity.UNCOMMON, mage.cards.m.MysticMonastery.class)); + cards.add(new SetCardInfo("Naga Fleshcrafter", 52, Rarity.RARE, mage.cards.n.NagaFleshcrafter.class)); cards.add(new SetCardInfo("Narset's Rebuke", 114, Rarity.COMMON, mage.cards.n.NarsetsRebuke.class)); cards.add(new SetCardInfo("Narset, Jeskai Waymaster", 209, Rarity.RARE, mage.cards.n.NarsetJeskaiWaymaster.class)); cards.add(new SetCardInfo("Nature's Rhythm", 150, Rarity.RARE, mage.cards.n.NaturesRhythm.class)); cards.add(new SetCardInfo("Neriv, Heart of the Storm", 210, Rarity.MYTHIC, mage.cards.n.NerivHeartOfTheStorm.class)); + cards.add(new SetCardInfo("New Way Forward", 211, Rarity.RARE, mage.cards.n.NewWayForward.class)); cards.add(new SetCardInfo("Nightblade Brigade", 85, Rarity.COMMON, mage.cards.n.NightbladeBrigade.class)); cards.add(new SetCardInfo("Nomad Outpost", 263, Rarity.UNCOMMON, mage.cards.n.NomadOutpost.class)); cards.add(new SetCardInfo("Opulent Palace", 264, Rarity.UNCOMMON, mage.cards.o.OpulentPalace.class)); cards.add(new SetCardInfo("Osseous Exhale", 17, Rarity.COMMON, mage.cards.o.OsseousExhale.class)); cards.add(new SetCardInfo("Overwhelming Surge", 115, Rarity.UNCOMMON, mage.cards.o.OverwhelmingSurge.class)); + cards.add(new SetCardInfo("Perennation", 212, Rarity.MYTHIC, mage.cards.p.Perennation.class)); cards.add(new SetCardInfo("Piercing Exhale", 151, Rarity.COMMON, mage.cards.p.PiercingExhale.class)); cards.add(new SetCardInfo("Plains", 277, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Poised Practitioner", 18, Rarity.COMMON, mage.cards.p.PoisedPractitioner.class)); + cards.add(new SetCardInfo("Purging Stormbrood", 213, Rarity.UNCOMMON, mage.cards.p.PurgingStormbrood.class)); cards.add(new SetCardInfo("Qarsi Revenant", 86, Rarity.RARE, mage.cards.q.QarsiRevenant.class)); cards.add(new SetCardInfo("Rainveil Rejuvenator", 152, Rarity.UNCOMMON, mage.cards.r.RainveilRejuvenator.class)); cards.add(new SetCardInfo("Rakshasa's Bargain", 214, Rarity.UNCOMMON, mage.cards.r.RakshasasBargain.class)); @@ -153,27 +199,38 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Reigning Victor", 216, Rarity.COMMON, mage.cards.r.ReigningVictor.class)); cards.add(new SetCardInfo("Reputable Merchant", 217, Rarity.COMMON, mage.cards.r.ReputableMerchant.class)); cards.add(new SetCardInfo("Rescue Leopard", 116, Rarity.COMMON, mage.cards.r.RescueLeopard.class)); + cards.add(new SetCardInfo("Reverberating Summons", 117, Rarity.UNCOMMON, mage.cards.r.ReverberatingSummons.class)); cards.add(new SetCardInfo("Revival of the Ancestors", 218, Rarity.RARE, mage.cards.r.RevivalOfTheAncestors.class)); + cards.add(new SetCardInfo("Riling Dawnbreaker", 21, Rarity.COMMON, mage.cards.r.RilingDawnbreaker.class)); cards.add(new SetCardInfo("Ringing Strike Mastery", 53, Rarity.COMMON, mage.cards.r.RingingStrikeMastery.class)); cards.add(new SetCardInfo("Riverwalk Technique", 54, Rarity.COMMON, mage.cards.r.RiverwalkTechnique.class)); + cards.add(new SetCardInfo("Riverwheel Sweep", 219, Rarity.UNCOMMON, mage.cards.r.RiverwheelSweep.class)); cards.add(new SetCardInfo("Roamer's Routine", 154, Rarity.COMMON, mage.cards.r.RoamersRoutine.class)); cards.add(new SetCardInfo("Roar of Endless Song", 220, Rarity.RARE, mage.cards.r.RoarOfEndlessSong.class)); cards.add(new SetCardInfo("Roiling Dragonstorm", 55, Rarity.UNCOMMON, mage.cards.r.RoilingDragonstorm.class)); + cards.add(new SetCardInfo("Rot-Curse Rakshasa", 87, Rarity.MYTHIC, mage.cards.r.RotCurseRakshasa.class)); cards.add(new SetCardInfo("Rugged Highlands", 265, Rarity.COMMON, mage.cards.r.RuggedHighlands.class)); + cards.add(new SetCardInfo("Runescale Stormbrood", 221, Rarity.UNCOMMON, mage.cards.r.RunescaleStormbrood.class)); cards.add(new SetCardInfo("Sage of the Fang", 155, Rarity.UNCOMMON, mage.cards.s.SageOfTheFang.class)); + cards.add(new SetCardInfo("Sage of the Skies", 22, Rarity.RARE, mage.cards.s.SageOfTheSkies.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sage of the Skies", 328, Rarity.RARE, mage.cards.s.SageOfTheSkies.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sagu Pummeler", 156, Rarity.COMMON, mage.cards.s.SaguPummeler.class)); + cards.add(new SetCardInfo("Sagu Wildling", 157, Rarity.COMMON, mage.cards.s.SaguWildling.class)); cards.add(new SetCardInfo("Salt Road Packbeast", 23, Rarity.COMMON, mage.cards.s.SaltRoadPackbeast.class)); cards.add(new SetCardInfo("Salt Road Skirmish", 88, Rarity.UNCOMMON, mage.cards.s.SaltRoadSkirmish.class)); cards.add(new SetCardInfo("Sandskitter Outrider", 89, Rarity.COMMON, mage.cards.s.SandskitterOutrider.class)); cards.add(new SetCardInfo("Sandsteppe Citadel", 266, Rarity.UNCOMMON, mage.cards.s.SandsteppeCitadel.class)); cards.add(new SetCardInfo("Sarkhan's Resolve", 158, Rarity.COMMON, mage.cards.s.SarkhansResolve.class)); cards.add(new SetCardInfo("Sarkhan, Dragon Ascendant", 118, Rarity.RARE, mage.cards.s.SarkhanDragonAscendant.class)); + cards.add(new SetCardInfo("Scavenger Regent", 90, Rarity.RARE, mage.cards.s.ScavengerRegent.class)); cards.add(new SetCardInfo("Scoured Barrens", 267, Rarity.COMMON, mage.cards.s.ScouredBarrens.class)); cards.add(new SetCardInfo("Seize Opportunity", 119, Rarity.COMMON, mage.cards.s.SeizeOpportunity.class)); + cards.add(new SetCardInfo("Severance Priest", 222, Rarity.RARE, mage.cards.s.SeverancePriest.class)); cards.add(new SetCardInfo("Shiko, Paragon of the Way", 223, Rarity.MYTHIC, mage.cards.s.ShikoParagonOfTheWay.class)); cards.add(new SetCardInfo("Shock Brigade", 120, Rarity.COMMON, mage.cards.s.ShockBrigade.class)); cards.add(new SetCardInfo("Shocking Sharpshooter", 121, Rarity.UNCOMMON, mage.cards.s.ShockingSharpshooter.class)); cards.add(new SetCardInfo("Sibsig Appraiser", 56, Rarity.COMMON, mage.cards.s.SibsigAppraiser.class)); + cards.add(new SetCardInfo("Sidisi, Regent of the Mire", 92, Rarity.RARE, mage.cards.s.SidisiRegentOfTheMire.class)); cards.add(new SetCardInfo("Sinkhole Surveyor", 93, Rarity.RARE, mage.cards.s.SinkholeSurveyor.class)); cards.add(new SetCardInfo("Skirmish Rhino", 224, Rarity.UNCOMMON, mage.cards.s.SkirmishRhino.class)); cards.add(new SetCardInfo("Smile at Death", 24, Rarity.MYTHIC, mage.cards.s.SmileAtDeath.class)); @@ -183,21 +240,34 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Sonic Shrieker", 226, Rarity.UNCOMMON, mage.cards.s.SonicShrieker.class)); cards.add(new SetCardInfo("Spectral Denial", 58, Rarity.UNCOMMON, mage.cards.s.SpectralDenial.class)); cards.add(new SetCardInfo("Stadium Headliner", 122, Rarity.RARE, mage.cards.s.StadiumHeadliner.class)); + cards.add(new SetCardInfo("Stalwart Successor", 227, Rarity.UNCOMMON, mage.cards.s.StalwartSuccessor.class)); + cards.add(new SetCardInfo("Starry-Eyed Skyrider", 25, Rarity.UNCOMMON, mage.cards.s.StarryEyedSkyrider.class)); cards.add(new SetCardInfo("Static Snare", 26, Rarity.UNCOMMON, mage.cards.s.StaticSnare.class)); + cards.add(new SetCardInfo("Stillness in Motion", 59, Rarity.RARE, mage.cards.s.StillnessInMotion.class)); cards.add(new SetCardInfo("Stormbeacon Blade", 27, Rarity.UNCOMMON, mage.cards.s.StormbeaconBlade.class)); cards.add(new SetCardInfo("Stormplain Detainment", 28, Rarity.COMMON, mage.cards.s.StormplainDetainment.class)); cards.add(new SetCardInfo("Stormscale Scion", 123, Rarity.MYTHIC, mage.cards.s.StormscaleScion.class)); + cards.add(new SetCardInfo("Stormshriek Feral", 124, Rarity.COMMON, mage.cards.s.StormshriekFeral.class)); + cards.add(new SetCardInfo("Strategic Betrayal", 94, Rarity.UNCOMMON, mage.cards.s.StrategicBetrayal.class)); cards.add(new SetCardInfo("Sultai Devotee", 160, Rarity.COMMON, mage.cards.s.SultaiDevotee.class)); cards.add(new SetCardInfo("Sultai Monument", 247, Rarity.UNCOMMON, mage.cards.s.SultaiMonument.class)); cards.add(new SetCardInfo("Summit Intimidator", 125, Rarity.COMMON, mage.cards.s.SummitIntimidator.class)); + cards.add(new SetCardInfo("Sunpearl Kirin", 29, Rarity.UNCOMMON, mage.cards.s.SunpearlKirin.class)); cards.add(new SetCardInfo("Sunset Strikemaster", 126, Rarity.UNCOMMON, mage.cards.s.SunsetStrikemaster.class)); + cards.add(new SetCardInfo("Surrak, Elusive Hunter", 161, Rarity.RARE, mage.cards.s.SurrakElusiveHunter.class)); cards.add(new SetCardInfo("Swamp", 281, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swiftwater Cliffs", 268, Rarity.COMMON, mage.cards.s.SwiftwaterCliffs.class)); + cards.add(new SetCardInfo("Synchronized Charge", 162, Rarity.UNCOMMON, mage.cards.s.SynchronizedCharge.class)); + cards.add(new SetCardInfo("Taigam, Master Opportunist", 60, Rarity.MYTHIC, mage.cards.t.TaigamMasterOpportunist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Taigam, Master Opportunist", 335, Rarity.MYTHIC, mage.cards.t.TaigamMasterOpportunist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Teeming Dragonstorm", 30, Rarity.UNCOMMON, mage.cards.t.TeemingDragonstorm.class)); cards.add(new SetCardInfo("Tempest Hawk", 31, Rarity.COMMON, mage.cards.t.TempestHawk.class)); + cards.add(new SetCardInfo("Temur Battlecrier", 228, Rarity.RARE, mage.cards.t.TemurBattlecrier.class)); cards.add(new SetCardInfo("Temur Devotee", 61, Rarity.COMMON, mage.cards.t.TemurDevotee.class)); cards.add(new SetCardInfo("Temur Monument", 248, Rarity.UNCOMMON, mage.cards.t.TemurMonument.class)); cards.add(new SetCardInfo("Temur Tawnyback", 229, Rarity.COMMON, mage.cards.t.TemurTawnyback.class)); + cards.add(new SetCardInfo("Tersa Lightshatter", 127, Rarity.RARE, mage.cards.t.TersaLightshatter.class)); + cards.add(new SetCardInfo("Teval, Arbiter of Virtue", 230, Rarity.MYTHIC, mage.cards.t.TevalArbiterOfVirtue.class)); cards.add(new SetCardInfo("The Sibsig Ceremony", 91, Rarity.RARE, mage.cards.t.TheSibsigCeremony.class)); cards.add(new SetCardInfo("Thornwood Falls", 269, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); cards.add(new SetCardInfo("Thunder of Unity", 231, Rarity.RARE, mage.cards.t.ThunderOfUnity.class)); @@ -205,6 +275,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Tranquil Cove", 270, Rarity.COMMON, mage.cards.t.TranquilCove.class)); cards.add(new SetCardInfo("Traveling Botanist", 164, Rarity.UNCOMMON, mage.cards.t.TravelingBotanist.class)); cards.add(new SetCardInfo("Twin Bolt", 128, Rarity.COMMON, mage.cards.t.TwinBolt.class)); + cards.add(new SetCardInfo("Twinmaw Stormbrood", 232, Rarity.UNCOMMON, mage.cards.t.TwinmawStormbrood.class)); cards.add(new SetCardInfo("Ugin, Eye of the Storms", 1, Rarity.MYTHIC, mage.cards.u.UginEyeOfTheStorms.class)); cards.add(new SetCardInfo("Unburied Earthcarver", 95, Rarity.COMMON, mage.cards.u.UnburiedEarthcarver.class)); cards.add(new SetCardInfo("Underfoot Underdogs", 129, Rarity.COMMON, mage.cards.u.UnderfootUnderdogs.class)); @@ -214,14 +285,19 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Unrooted Ancestor", 96, Rarity.UNCOMMON, mage.cards.u.UnrootedAncestor.class)); cards.add(new SetCardInfo("Unsparing Boltcaster", 130, Rarity.UNCOMMON, mage.cards.u.UnsparingBoltcaster.class)); cards.add(new SetCardInfo("Ureni's Rebuff", 63, Rarity.UNCOMMON, mage.cards.u.UrenisRebuff.class)); + cards.add(new SetCardInfo("Ureni, the Song Unending", 233, Rarity.MYTHIC, mage.cards.u.UreniTheSongUnending.class)); cards.add(new SetCardInfo("Venerated Stormsinger", 97, Rarity.UNCOMMON, mage.cards.v.VeneratedStormsinger.class)); cards.add(new SetCardInfo("Veteran Ice Climber", 64, Rarity.UNCOMMON, mage.cards.v.VeteranIceClimber.class)); cards.add(new SetCardInfo("Voice of Victory", 33, Rarity.RARE, mage.cards.v.VoiceOfVictory.class)); cards.add(new SetCardInfo("War Effort", 131, Rarity.UNCOMMON, mage.cards.w.WarEffort.class)); + cards.add(new SetCardInfo("Wail of War", 98, Rarity.UNCOMMON, mage.cards.w.WailOfWar.class)); + cards.add(new SetCardInfo("Warden of the Grove", 166, Rarity.RARE, mage.cards.w.WardenOfTheGrove.class)); cards.add(new SetCardInfo("Watcher of the Wayside", 249, Rarity.COMMON, mage.cards.w.WatcherOfTheWayside.class)); cards.add(new SetCardInfo("Wayspeaker Bodyguard", 34, Rarity.UNCOMMON, mage.cards.w.WayspeakerBodyguard.class)); + cards.add(new SetCardInfo("Whirlwing Stormbrood", 234, Rarity.UNCOMMON, mage.cards.w.WhirlwingStormbrood.class)); cards.add(new SetCardInfo("Wild Ride", 132, Rarity.COMMON, mage.cards.w.WildRide.class)); cards.add(new SetCardInfo("Wind-Scarred Crag", 271, Rarity.COMMON, mage.cards.w.WindScarredCrag.class)); + cards.add(new SetCardInfo("Windcrag Siege", 235, Rarity.RARE, mage.cards.w.WindcragSiege.class)); cards.add(new SetCardInfo("Wingblade Disciple", 65, Rarity.UNCOMMON, mage.cards.w.WingbladeDisciple.class)); cards.add(new SetCardInfo("Wingspan Stride", 66, Rarity.COMMON, mage.cards.w.WingspanStride.class)); cards.add(new SetCardInfo("Winternight Stories", 67, Rarity.RARE, mage.cards.w.WinternightStories.class)); @@ -230,7 +306,5 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Yathan Tombguard", 100, Rarity.UNCOMMON, mage.cards.y.YathanTombguard.class)); cards.add(new SetCardInfo("Zurgo's Vanguard", 133, Rarity.UNCOMMON, mage.cards.z.ZurgosVanguard.class)); cards.add(new SetCardInfo("Zurgo, Thunder's Decree", 237, Rarity.RARE, mage.cards.z.ZurgoThundersDecree.class)); - - cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); } } diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java b/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java index 15997a8fce0..277b0c89500 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java @@ -21,13 +21,16 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Abrade", 203, Rarity.UNCOMMON, mage.cards.a.Abrade.class)); cards.add(new SetCardInfo("Access Tunnel", 337, Rarity.UNCOMMON, mage.cards.a.AccessTunnel.class)); + cards.add(new SetCardInfo("Adaptive Training Post", 18, Rarity.RARE, mage.cards.a.AdaptiveTrainingPost.class)); cards.add(new SetCardInfo("Adarkar Wastes", 338, Rarity.RARE, mage.cards.a.AdarkarWastes.class)); cards.add(new SetCardInfo("Adeline, Resplendent Cathar", 108, Rarity.RARE, mage.cards.a.AdelineResplendentCathar.class)); + cards.add(new SetCardInfo("Aligned Heart", 12, Rarity.RARE, mage.cards.a.AlignedHeart.class)); cards.add(new SetCardInfo("Amphin Mutineer", 143, Rarity.RARE, mage.cards.a.AmphinMutineer.class)); cards.add(new SetCardInfo("Ancestral Vision", 144, Rarity.RARE, mage.cards.a.AncestralVision.class)); cards.add(new SetCardInfo("Angel of Invention", 109, Rarity.MYTHIC, mage.cards.a.AngelOfInvention.class)); cards.add(new SetCardInfo("Anguished Unmaking", 279, Rarity.RARE, mage.cards.a.AnguishedUnmaking.class)); cards.add(new SetCardInfo("Arasta of the Endless Web", 244, Rarity.RARE, mage.cards.a.ArastaOfTheEndlessWeb.class)); + cards.add(new SetCardInfo("Arbor Adherent", 42, Rarity.RARE, mage.cards.a.ArborAdherent.class)); cards.add(new SetCardInfo("Arboreal Grazer", 245, Rarity.COMMON, mage.cards.a.ArborealGrazer.class)); cards.add(new SetCardInfo("Arcane Signet", 105, Rarity.UNCOMMON, mage.cards.a.ArcaneSignet.class)); cards.add(new SetCardInfo("Archmage Emeritus", 145, Rarity.RARE, mage.cards.a.ArchmageEmeritus.class)); @@ -45,6 +48,7 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Bastion of Remembrance", 171, Rarity.UNCOMMON, mage.cards.b.BastionOfRemembrance.class)); cards.add(new SetCardInfo("Battlefield Forge", 340, Rarity.RARE, mage.cards.b.BattlefieldForge.class)); cards.add(new SetCardInfo("Beast Within", 249, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class)); + cards.add(new SetCardInfo("Become the Avalanche", 43, Rarity.RARE, mage.cards.b.BecomeTheAvalanche.class)); cards.add(new SetCardInfo("Beetleback Chief", 205, Rarity.UNCOMMON, mage.cards.b.BeetlebackChief.class)); cards.add(new SetCardInfo("Behind the Scenes", 172, Rarity.UNCOMMON, mage.cards.b.BehindTheScenes.class)); cards.add(new SetCardInfo("Betor, Ancestor's Voice", 1, Rarity.MYTHIC, mage.cards.b.BetorAncestorsVoice.class)); @@ -54,9 +58,12 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Blasphemous Act", 207, Rarity.RARE, mage.cards.b.BlasphemousAct.class)); cards.add(new SetCardInfo("Blight Pile", 174, Rarity.UNCOMMON, mage.cards.b.BlightPile.class)); cards.add(new SetCardInfo("Bojuka Bog", 341, Rarity.COMMON, mage.cards.b.BojukaBog.class)); + cards.add(new SetCardInfo("Bone Devourer", 26, Rarity.RARE, mage.cards.b.BoneDevourer.class)); cards.add(new SetCardInfo("Boros Signet", 314, Rarity.UNCOMMON, mage.cards.b.BorosSignet.class)); cards.add(new SetCardInfo("Bountiful Landscape", 342, Rarity.COMMON, mage.cards.b.BountifulLandscape.class)); + cards.add(new SetCardInfo("Broodcaller Scourge", 44, Rarity.RARE, mage.cards.b.BroodcallerScourge.class)); cards.add(new SetCardInfo("Caldera Pyremaw", 33, Rarity.RARE, mage.cards.c.CalderaPyremaw.class)); + cards.add(new SetCardInfo("Canopy Gargantuan", 45, Rarity.RARE, mage.cards.c.CanopyGargantuan.class)); cards.add(new SetCardInfo("Canopy Vista", 343, Rarity.RARE, mage.cards.c.CanopyVista.class)); cards.add(new SetCardInfo("Canyon Slough", 344, Rarity.RARE, mage.cards.c.CanyonSlough.class)); cards.add(new SetCardInfo("Carven Caryatid", 250, Rarity.UNCOMMON, mage.cards.c.CarvenCaryatid.class)); @@ -71,6 +78,7 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Cinder Glade", 350, Rarity.RARE, mage.cards.c.CinderGlade.class)); cards.add(new SetCardInfo("Clifftop Retreat", 351, Rarity.RARE, mage.cards.c.ClifftopRetreat.class)); cards.add(new SetCardInfo("Colfenor's Urn", 315, Rarity.RARE, mage.cards.c.ColfenorsUrn.class)); + cards.add(new SetCardInfo("Colossal Grave-Reaver", 50, Rarity.RARE, mage.cards.c.ColossalGraveReaver.class)); cards.add(new SetCardInfo("Command Beacon", 352, Rarity.RARE, mage.cards.c.CommandBeacon.class)); cards.add(new SetCardInfo("Command Tower", 107, Rarity.COMMON, mage.cards.c.CommandTower.class)); cards.add(new SetCardInfo("Commander's Insignia", 111, Rarity.RARE, mage.cards.c.CommandersInsignia.class)); @@ -88,6 +96,7 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Darkwater Catacombs", 355, Rarity.RARE, mage.cards.d.DarkwaterCatacombs.class)); cards.add(new SetCardInfo("Dauthi Voidwalker", 176, Rarity.RARE, mage.cards.d.DauthiVoidwalker.class)); cards.add(new SetCardInfo("Deadly Dispute", 177, Rarity.COMMON, mage.cards.d.DeadlyDispute.class)); + cards.add(new SetCardInfo("Deceptive Frostkite", 19, Rarity.RARE, mage.cards.d.DeceptiveFrostkite.class)); cards.add(new SetCardInfo("Deceptive Landscape", 356, Rarity.COMMON, mage.cards.d.DeceptiveLandscape.class)); cards.add(new SetCardInfo("Deep Analysis", 150, Rarity.COMMON, mage.cards.d.DeepAnalysis.class)); cards.add(new SetCardInfo("Despark", 284, Rarity.UNCOMMON, mage.cards.d.Despark.class)); @@ -107,7 +116,9 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Electrodominance", 212, Rarity.RARE, mage.cards.e.Electrodominance.class)); cards.add(new SetCardInfo("Elemental Bond", 254, Rarity.UNCOMMON, mage.cards.e.ElementalBond.class)); cards.add(new SetCardInfo("Eliminate the Competition", 179, Rarity.RARE, mage.cards.e.EliminateTheCompetition.class)); + cards.add(new SetCardInfo("Elsha, Threefold Master", 2, Rarity.MYTHIC, mage.cards.e.ElshaThreefoldMaster.class)); cards.add(new SetCardInfo("Emeria Angel", 114, Rarity.RARE, mage.cards.e.EmeriaAngel.class)); + cards.add(new SetCardInfo("Eshki, Temur's Roar", 3, Rarity.MYTHIC, mage.cards.e.EshkiTemursRoar.class)); cards.add(new SetCardInfo("Exotic Orchard", 360, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); cards.add(new SetCardInfo("Expansion // Explosion", 287, Rarity.RARE, mage.cards.e.ExpansionExplosion.class)); cards.add(new SetCardInfo("Expel the Interlopers", 115, Rarity.RARE, mage.cards.e.ExpelTheInterlopers.class)); @@ -117,10 +128,12 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Farseek", 255, Rarity.COMMON, mage.cards.f.Farseek.class)); cards.add(new SetCardInfo("Feed the Swarm", 180, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); cards.add(new SetCardInfo("Fellwar Stone", 318, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); + cards.add(new SetCardInfo("Felothar the Steadfast", 4, Rarity.MYTHIC, mage.cards.f.FelotharTheSteadfast.class)); cards.add(new SetCardInfo("Ferrous Lake", 361, Rarity.RARE, mage.cards.f.FerrousLake.class)); cards.add(new SetCardInfo("Fetid Heath", 362, Rarity.RARE, mage.cards.f.FetidHeath.class)); cards.add(new SetCardInfo("Fetid Pools", 363, Rarity.RARE, mage.cards.f.FetidPools.class)); cards.add(new SetCardInfo("Flooded Grove", 364, Rarity.RARE, mage.cards.f.FloodedGrove.class)); + cards.add(new SetCardInfo("Floral Evoker", 46, Rarity.RARE, mage.cards.f.FloralEvoker.class)); cards.add(new SetCardInfo("Forbidden Alchemy", 152, Rarity.COMMON, mage.cards.f.ForbiddenAlchemy.class)); cards.add(new SetCardInfo("Foreboding Landscape", 365, Rarity.COMMON, mage.cards.f.ForebodingLandscape.class)); cards.add(new SetCardInfo("Fortified Village", 366, Rarity.RARE, mage.cards.f.FortifiedVillage.class)); @@ -132,6 +145,7 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Glacial Fortress", 367, Rarity.RARE, mage.cards.g.GlacialFortress.class)); cards.add(new SetCardInfo("Glorybringer", 215, Rarity.RARE, mage.cards.g.Glorybringer.class)); cards.add(new SetCardInfo("Goblin Electromancer", 99, Rarity.COMMON, mage.cards.g.GoblinElectromancer.class)); + cards.add(new SetCardInfo("Goldlust Triad", 34, Rarity.RARE, mage.cards.g.GoldlustTriad.class)); cards.add(new SetCardInfo("Goldnight Commander", 117, Rarity.UNCOMMON, mage.cards.g.GoldnightCommander.class)); cards.add(new SetCardInfo("Golgari Rot Farm", 368, Rarity.UNCOMMON, mage.cards.g.GolgariRotFarm.class)); cards.add(new SetCardInfo("Grand Crescendo", 118, Rarity.RARE, mage.cards.g.GrandCrescendo.class)); @@ -140,6 +154,7 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Grenzo, Havoc Raiser", 216, Rarity.RARE, mage.cards.g.GrenzoHavocRaiser.class)); cards.add(new SetCardInfo("Grisly Salvage", 290, Rarity.COMMON, mage.cards.g.GrislySalvage.class)); cards.add(new SetCardInfo("Guttersnipe", 217, Rarity.UNCOMMON, mage.cards.g.Guttersnipe.class)); + cards.add(new SetCardInfo("Hammerhead Tyrant", 21, Rarity.RARE, mage.cards.h.HammerheadTyrant.class)); cards.add(new SetCardInfo("Harbinger of the Hunt", 291, Rarity.RARE, mage.cards.h.HarbingerOfTheHunt.class)); cards.add(new SetCardInfo("Harrow", 258, Rarity.COMMON, mage.cards.h.Harrow.class)); cards.add(new SetCardInfo("Haughty Djinn", 154, Rarity.RARE, mage.cards.h.HaughtyDjinn.class)); @@ -155,12 +170,15 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Ikra Shidiqi, the Usurper", 100, Rarity.MYTHIC, mage.cards.i.IkraShidiqiTheUsurper.class)); cards.add(new SetCardInfo("Indomitable Ancients", 121, Rarity.RARE, mage.cards.i.IndomitableAncients.class)); cards.add(new SetCardInfo("Indulging Patrician", 292, Rarity.UNCOMMON, mage.cards.i.IndulgingPatrician.class)); + cards.add(new SetCardInfo("Infantry Shield", 35, Rarity.RARE, mage.cards.i.InfantryShield.class)); cards.add(new SetCardInfo("Infernal Grasp", 182, Rarity.UNCOMMON, mage.cards.i.InfernalGrasp.class)); + cards.add(new SetCardInfo("Ironwill Forger", 13, Rarity.RARE, mage.cards.i.IronwillForger.class)); cards.add(new SetCardInfo("Irrigated Farmland", 372, Rarity.RARE, mage.cards.i.IrrigatedFarmland.class)); cards.add(new SetCardInfo("Isolated Chapel", 373, Rarity.RARE, mage.cards.i.IsolatedChapel.class)); cards.add(new SetCardInfo("Izzet Signet", 320, Rarity.COMMON, mage.cards.i.IzzetSignet.class)); cards.add(new SetCardInfo("Jaddi Offshoot", 260, Rarity.UNCOMMON, mage.cards.j.JaddiOffshoot.class)); cards.add(new SetCardInfo("Jarad, Golgari Lich Lord", 293, Rarity.MYTHIC, mage.cards.j.JaradGolgariLichLord.class)); + cards.add(new SetCardInfo("Jaws of Defeat", 27, Rarity.RARE, mage.cards.j.JawsOfDefeat.class)); cards.add(new SetCardInfo("Junji, the Midnight Sky", 183, Rarity.MYTHIC, mage.cards.j.JunjiTheMidnightSky.class)); cards.add(new SetCardInfo("Karplusan Forest", 374, Rarity.RARE, mage.cards.k.KarplusanForest.class)); cards.add(new SetCardInfo("Kaya, Geist Hunter", 294, Rarity.MYTHIC, mage.cards.k.KayaGeistHunter.class)); @@ -228,9 +246,11 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Rapid Hybridization", 162, Rarity.UNCOMMON, mage.cards.r.RapidHybridization.class)); cards.add(new SetCardInfo("Reality Shift", 163, Rarity.UNCOMMON, mage.cards.r.RealityShift.class)); cards.add(new SetCardInfo("Reassembling Skeleton", 195, Rarity.UNCOMMON, mage.cards.r.ReassemblingSkeleton.class)); + cards.add(new SetCardInfo("Redoubled Stormsinger", 37, Rarity.RARE, mage.cards.r.RedoubledStormsinger.class)); cards.add(new SetCardInfo("Reflections of Littjara", 164, Rarity.RARE, mage.cards.r.ReflectionsOfLittjara.class)); cards.add(new SetCardInfo("Release the Dogs", 127, Rarity.UNCOMMON, mage.cards.r.ReleaseTheDogs.class)); cards.add(new SetCardInfo("Reliquary Tower", 386, Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class)); + cards.add(new SetCardInfo("Reunion of the House", 15, Rarity.RARE, mage.cards.r.ReunionOfTheHouse.class)); cards.add(new SetCardInfo("Rhox Faithmender", 128, Rarity.RARE, mage.cards.r.RhoxFaithmender.class)); cards.add(new SetCardInfo("Rite of Replication", 165, Rarity.RARE, mage.cards.r.RiteOfReplication.class)); cards.add(new SetCardInfo("River Kelpie", 166, Rarity.RARE, mage.cards.r.RiverKelpie.class)); @@ -313,13 +333,16 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Thunderbreak Regent", 241, Rarity.RARE, mage.cards.t.ThunderbreakRegent.class)); cards.add(new SetCardInfo("Time Wipe", 308, Rarity.RARE, mage.cards.t.TimeWipe.class)); cards.add(new SetCardInfo("Timeless Witness", 274, Rarity.UNCOMMON, mage.cards.t.TimelessWitness.class)); + cards.add(new SetCardInfo("Tip the Scales", 29, Rarity.RARE, mage.cards.t.TipTheScales.class)); cards.add(new SetCardInfo("Tocasia's Welcome", 135, Rarity.RARE, mage.cards.t.TocasiasWelcome.class)); cards.add(new SetCardInfo("Tower Defense", 275, Rarity.UNCOMMON, mage.cards.t.TowerDefense.class)); cards.add(new SetCardInfo("Towering Titan", 276, Rarity.MYTHIC, mage.cards.t.ToweringTitan.class)); + cards.add(new SetCardInfo("Transforming Flourish", 39, Rarity.RARE, mage.cards.t.TransformingFlourish.class)); cards.add(new SetCardInfo("Treasure Cruise", 169, Rarity.COMMON, mage.cards.t.TreasureCruise.class)); cards.add(new SetCardInfo("Tree of Redemption", 97, Rarity.MYTHIC, mage.cards.t.TreeOfRedemption.class)); cards.add(new SetCardInfo("Twilight Drover", 136, Rarity.RARE, mage.cards.t.TwilightDrover.class)); cards.add(new SetCardInfo("Twilight Mire", 409, Rarity.RARE, mage.cards.t.TwilightMire.class)); + cards.add(new SetCardInfo("Ureni of the Unwritten", 9, Rarity.MYTHIC, mage.cards.u.UreniOfTheUnwritten.class)); cards.add(new SetCardInfo("Vanquish the Horde", 91, Rarity.RARE, mage.cards.v.VanquishTheHorde.class)); cards.add(new SetCardInfo("Vault of the Archangel", 410, Rarity.RARE, mage.cards.v.VaultOfTheArchangel.class)); cards.add(new SetCardInfo("Velomachus Lorehold", 309, Rarity.MYTHIC, mage.cards.v.VelomachusLorehold.class)); @@ -337,16 +360,22 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Wall of Roots", 278, Rarity.COMMON, mage.cards.w.WallOfRoots.class)); cards.add(new SetCardInfo("Wayfarer's Bauble", 335, Rarity.COMMON, mage.cards.w.WayfarersBauble.class)); cards.add(new SetCardInfo("Weathered Sentinels", 336, Rarity.RARE, mage.cards.w.WeatheredSentinels.class)); + cards.add(new SetCardInfo("Welcome the Dead", 30, Rarity.RARE, mage.cards.w.WelcomeTheDead.class)); cards.add(new SetCardInfo("Welcoming Vampire", 140, Rarity.RARE, mage.cards.w.WelcomingVampire.class)); cards.add(new SetCardInfo("Whirlwind of Thought", 311, Rarity.RARE, mage.cards.w.WhirlwindOfThought.class)); + cards.add(new SetCardInfo("Will of the Abzan", 31, Rarity.RARE, mage.cards.w.WillOfTheAbzan.class)); + cards.add(new SetCardInfo("Will of the Sultai", 49, Rarity.RARE, mage.cards.w.WillOfTheSultai.class)); + cards.add(new SetCardInfo("Will of the Temur", 24, Rarity.RARE, mage.cards.w.WillOfTheTemur.class)); cards.add(new SetCardInfo("Windbrisk Heights", 411, Rarity.RARE, mage.cards.w.WindbriskHeights.class)); cards.add(new SetCardInfo("Wingmantle Chaplain", 141, Rarity.UNCOMMON, mage.cards.w.WingmantleChaplain.class)); + cards.add(new SetCardInfo("Within Range", 32, Rarity.RARE, mage.cards.w.WithinRange.class)); cards.add(new SetCardInfo("Woe Strider", 201, Rarity.RARE, mage.cards.w.WoeStrider.class)); cards.add(new SetCardInfo("Wonder", 170, Rarity.UNCOMMON, mage.cards.w.Wonder.class)); cards.add(new SetCardInfo("Woodland Cemetery", 412, Rarity.RARE, mage.cards.w.WoodlandCemetery.class)); cards.add(new SetCardInfo("Yahenni, Undying Partisan", 202, Rarity.RARE, mage.cards.y.YahenniUndyingPartisan.class)); cards.add(new SetCardInfo("Yavimaya Coast", 413, Rarity.RARE, mage.cards.y.YavimayaCoast.class)); cards.add(new SetCardInfo("Young Pyromancer", 95, Rarity.UNCOMMON, mage.cards.y.YoungPyromancer.class)); + cards.add(new SetCardInfo("Zenith Festival", 41, Rarity.RARE, mage.cards.z.ZenithFestival.class)); cards.add(new SetCardInfo("Zetalpa, Primal Dawn", 142, Rarity.RARE, mage.cards.z.ZetalpaPrimalDawn.class)); } } diff --git a/Mage.Tests/pom.xml b/Mage.Tests/pom.xml index feba3cf97ca..a99b1948896 100644 --- a/Mage.Tests/pom.xml +++ b/Mage.Tests/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-tests diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/BlockSimulationAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/BlockSimulationAITest.java index af1e99a689e..f7c0158b60d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/BlockSimulationAITest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/BlockSimulationAITest.java @@ -309,4 +309,47 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps { assertGraveyardCount(playerA, "Balduvian Bears", 1); assertDamageReceived(playerB, "Graveblade Marauder", 2); } + + @Test + public void test_Block_deathtouch_attacker_vs_menace() { + // possible bug: AI freeze, see https://github.com/magefree/mage/issues/13342 + // it's only HumanPlayer related and can't be tested here + + // First strike, Deathtouch + // Whenever Glissa Sunslayer deals combat damage to a player, choose one — + // • You draw a card and you lose 1 life. + // • Destroy target enchantment. + // • Remove up to three counters from target permanent. + addCard(Zone.BATTLEFIELD, playerA, "Glissa Sunslayer", 1); // 3/3 + // Deathtouch + // Whenever a creature you control with deathtouch deals combat damage to a player, that player gets two poison counters. + addCard(Zone.BATTLEFIELD, playerA, "Fynn, the Fangbearer", 1); // 1/3 + // Deathtouch + // Toxic 1 (Players dealt combat damage by this creature also get a poison counter.) + addCard(Zone.BATTLEFIELD, playerA, "Bilious Skulldweller", 1); // 1/1 + // + // Menace + // At the beginning of each player’s upkeep, Furnace Punisher deals 2 damage to that player unless they control + // two or more basic lands. + addCard(Zone.BATTLEFIELD, playerB, "Furnace Punisher", 1); // 3/3 + + attack(1, playerA, "Glissa Sunslayer"); + attack(1, playerA, "Fynn, the Fangbearer"); + attack(1, playerA, "Bilious Skulldweller"); + setChoice(playerA, "Whenever a creature you control", 2); // x3 triggers + setModeChoice(playerA, "1"); // you draw a card and you lose 1 life + + // ai must not block attacker with Deathtouch + aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB); + checkBlockers("no blockers", 1, playerB, ""); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerA, 20 - 1 - 2); // from draw, from furnace damage + assertLife(playerB, 20 - 3 - 1 - 1); + assertPermanentCount(playerA, "Glissa Sunslayer", 1); + assertGraveyardCount(playerB, 0); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HarmonizeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HarmonizeTest.java new file mode 100644 index 00000000000..5508f1e612c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/HarmonizeTest.java @@ -0,0 +1,55 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class HarmonizeTest extends CardTestPlayerBase { + + private static final String bear = "Grizzly Bears"; + private static final String dragonfire = "Channeled Dragonfire"; + + @Test + public void testNoTap() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.GRAVEYARD, playerA, dragonfire); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Harmonize", playerB); + setChoice(playerA, false); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(bear, false); + assertLife(playerB, 20 - 2); + assertExileCount(playerA, dragonfire, 1); + assertTappedCount("Mountain", true, 7); + } + + @Test + public void testTap() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.GRAVEYARD, playerA, dragonfire); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Harmonize", playerB); + setChoice(playerA, true); + setChoice(playerA, bear); + setChoice(playerA, bear); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(bear, true); + assertLife(playerB, 20 - 2); + assertExileCount(playerA, dragonfire, 1); + assertTappedCount("Mountain", true, 5); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java index 7cd178aa7e6..dd292c7229a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java @@ -42,6 +42,40 @@ public class SuspendTest extends CardTestPlayerBase { } + /** + * Tests bug that was mentioned in suspend ability, but does not appear to still be an issue. + * Epochrasite being unable to be cast after casting from suspend and returning to hand. + */ + @Test + public void test_Single_Epochrasite_Recast_After_Suspend() { + // Bug was mentioned in suspend ability, but does not appear to still be an issue + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + // Epochrasite enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand. + // When Epochrasite dies, exile it with three time counters on it and it gains suspend. + addCard(Zone.HAND, playerA, "Epochrasite", 1); + addCard(Zone.HAND, playerB, "Lightning Bolt", 1); + addCard(Zone.HAND, playerB, "Boomerang", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Epochrasite"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Epochrasite"); + castSpell(7, PhaseStep.DRAW, playerB, "Boomerang", "Epochrasite"); + castSpell(7, PhaseStep.PRECOMBAT_MAIN, playerA, "Epochrasite"); + + + setChoice(playerA, true); // choose yes to cast + + setStrictChooseMode(true); + setStopAt(7, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerB, "Lightning Bolt", 1); + assertPermanentCount(playerA, "Epochrasite", 1); // returned on turn 7 and cast again after going to hand + assertPowerToughness(playerA, "Epochrasite", 1, 1); + assertAbility(playerA, "Epochrasite", HasteAbility.getInstance(), false); + } + /** * Tests Jhoira of the Ghitu works (give suspend to a exiled card) {2}, * Exile a nonland card from your hand: Put four time counters on the exiled @@ -275,6 +309,7 @@ public class SuspendTest extends CardTestPlayerBase { // 3 time counters removes on upkeep (3, 5, 7) and cast again setChoice(playerA, true); // choose yes to cast + setChoice(playerA, "Cast Wear"); addTarget(playerA, "Bident of Thassa"); checkPermanentCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, "Bident of Thassa", 0); checkPermanentCount("after suspend", 7, PhaseStep.PRECOMBAT_MAIN, playerB, "Bow of Nylea", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java index b200bb76bfb..323d9b4cdd3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java @@ -30,4 +30,54 @@ public class WardTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Solitude", 1); assertPermanentCount(playerB, "Waterfall Aerialist", 1); } + + @Test + public void wardPanharmonicon() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Panharmonicon"); + addCard(Zone.BATTLEFIELD, playerA, "Young Red Dragon"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.BATTLEFIELD, playerB, "Roaming Throne"); + addCard(Zone.HAND, playerA, "Scourge of Valkas"); + + setChoice(playerB, "Dragon"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of Valkas"); + setChoice(playerA, "Whenever {this} or another Dragon"); + addTarget(playerA, "Roaming Throne"); + addTarget(playerA, "Roaming Throne"); + setChoice(playerB, "ward {2}"); + setChoice(playerA, "Yes"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertPermanentCount(playerB, "Roaming Throne", 1); + assertDamageReceived(playerB, "Roaming Throne", 2); + } + + @Test + public void wardPanharmoniconCounter() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Panharmonicon"); + addCard(Zone.BATTLEFIELD, playerA, "Young Red Dragon"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.BATTLEFIELD, playerB, "Roaming Throne"); + addCard(Zone.HAND, playerA, "Scourge of Valkas"); + + setChoice(playerB, "Dragon"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of Valkas"); + setChoice(playerA, "Whenever {this} or another Dragon"); + addTarget(playerA, "Roaming Throne"); + addTarget(playerA, "Roaming Throne"); + setChoice(playerB, "ward {2}"); + setChoice(playerA, "No"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertPermanentCount(playerB, "Roaming Throne", 1); + assertDamageReceived(playerB, "Roaming Throne", 0); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/AdjusterCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/AdjusterCostTest.java new file mode 100644 index 00000000000..35c05e4c0ff --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/AdjusterCostTest.java @@ -0,0 +1,617 @@ +package org.mage.test.cards.cost.additional; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.token.custom.CreatureToken; +import mage.util.CardUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +import java.util.Arrays; + +/** + * @author JayDi85 + */ +public class AdjusterCostTest extends CardTestPlayerBaseWithAIHelps { + + private void prepareCustomCardInHand(String cardName, String spellManaCost, CostAdjuster costAdjuster) { + SpellAbility spellAbility = new SpellAbility(new ManaCostsImpl<>(spellManaCost), cardName); + if (costAdjuster != null) { + spellAbility.setCostAdjuster(costAdjuster); + } + addCustomCardWithAbility( + cardName, + playerA, + null, + spellAbility, + CardType.ENCHANTMENT, + spellManaCost, + Zone.HAND + ); + } + + private void prepareCustomPermanent(String cardName, String abilityName, String abilityManaCost, CostAdjuster costAdjuster) { + Ability ability = new SimpleActivatedAbility( + new CreateTokenEffect(new CreatureToken(1, 1).withName("test token")).setText(abilityName), + new ManaCostsImpl<>(abilityManaCost) + ); + if (costAdjuster != null) { + ability.setCostAdjuster(costAdjuster); + } + addCustomCardWithAbility(cardName, playerA, ability); + } + + @Test + public void test_DistributeValues() { + // make sure it can distribute values between min and max and skip useless values (example: mana optimization) + + Assert.assertEquals(Arrays.asList(), CardUtil.distributeValues(0, 0, 0)); + Assert.assertEquals(Arrays.asList(), CardUtil.distributeValues(0, -10, 10)); + Assert.assertEquals(Arrays.asList(), CardUtil.distributeValues(0, Integer.MIN_VALUE, Integer.MAX_VALUE)); + + Assert.assertEquals(Arrays.asList(0), CardUtil.distributeValues(1, 0, 0)); + Assert.assertEquals(Arrays.asList(0), CardUtil.distributeValues(1, 0, 1)); + Assert.assertEquals(Arrays.asList(0), CardUtil.distributeValues(1, 0, 2)); + Assert.assertEquals(Arrays.asList(0), CardUtil.distributeValues(1, 0, 3)); + Assert.assertEquals(Arrays.asList(0), CardUtil.distributeValues(1, 0, 9)); + + Assert.assertEquals(Arrays.asList(0), CardUtil.distributeValues(2, 0, 0)); + Assert.assertEquals(Arrays.asList(0, 1), CardUtil.distributeValues(2, 0, 1)); + Assert.assertEquals(Arrays.asList(0, 2), CardUtil.distributeValues(2, 0, 2)); + Assert.assertEquals(Arrays.asList(0, 3), CardUtil.distributeValues(2, 0, 3)); + Assert.assertEquals(Arrays.asList(0, 9), CardUtil.distributeValues(2, 0, 9)); + + Assert.assertEquals(Arrays.asList(0), CardUtil.distributeValues(3, 0, 0)); + Assert.assertEquals(Arrays.asList(0, 1), CardUtil.distributeValues(3, 0, 1)); + Assert.assertEquals(Arrays.asList(0, 1, 2), CardUtil.distributeValues(3, 0, 2)); + Assert.assertEquals(Arrays.asList(0, 2, 3), CardUtil.distributeValues(3, 0, 3)); + Assert.assertEquals(Arrays.asList(0, 5, 9), CardUtil.distributeValues(3, 0, 9)); + + Assert.assertEquals(Arrays.asList(10, 15, 20), CardUtil.distributeValues(3, 10, 20)); + Assert.assertEquals(Arrays.asList(10, 16, 21), CardUtil.distributeValues(3, 10, 21)); + Assert.assertEquals(Arrays.asList(10, 16, 22), CardUtil.distributeValues(3, 10, 22)); + Assert.assertEquals(Arrays.asList(10, 17, 23), CardUtil.distributeValues(3, 10, 23)); + Assert.assertEquals(Arrays.asList(10, 20, 29), CardUtil.distributeValues(3, 10, 29)); + + Assert.assertEquals(Arrays.asList(10), CardUtil.distributeValues(5, 10, 10)); + Assert.assertEquals(Arrays.asList(10, 11), CardUtil.distributeValues(5, 10, 11)); + Assert.assertEquals(Arrays.asList(10, 11, 12), CardUtil.distributeValues(5, 10, 12)); + Assert.assertEquals(Arrays.asList(10, 11, 12, 13), CardUtil.distributeValues(5, 10, 13)); + Assert.assertEquals(Arrays.asList(10, 11, 13, 14, 15), CardUtil.distributeValues(5, 10, 15)); + Assert.assertEquals(Arrays.asList(10, 13, 15, 18, 20), CardUtil.distributeValues(5, 10, 20)); + } + + @Test + public void test_X_SpellAbility() { + prepareCustomCardInHand("test card", "{X}{1}", null); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "test card"); + setChoice(playerA, "X=2"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "test card", 1); + } + + @Test + public void test_X_ActivatedAbility() { + prepareCustomPermanent("test card", "test ability", "{X}{1}", null); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}{1}:"); + setChoice(playerA, "X=2"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "test token", 1); + } + + @Test + public void test_prepareX_SpellAbility_TestFrameworkMustCatchLimits() { + prepareCustomCardInHand("test card", "{X}{1}", new CostAdjuster() { + @Override + public void prepareX(Ability ability, Game game) { + ability.setVariableCostsMinMax(0, 1); + } + }); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "test card"); + setChoice(playerA, "X=2"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + try { + execute(); + } catch (AssertionError e) { + Assert.assertTrue("X must have limits: " + e.getMessage(), e.getMessage().contains("Found wrong X value = 2")); + Assert.assertTrue("X must have limits: " + e.getMessage(), e.getMessage().contains("from 0 to 1")); + return; + } + Assert.fail("test must fail"); + } + + @Test + @Ignore // TODO: AI must support game simulations for X choice, see announceXMana + public void test_prepareX_SpellAbility_AI() { + prepareCustomCardInHand("test card", "{X}{1}", new CostAdjuster() { + @Override + public void prepareX(Ability ability, Game game) { + ability.setVariableCostsMinMax(0, 10); + } + }); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + // AI must play card and use min good value + // it's bad to set X=1 for battlefield score cause card will give same score for X=0, X=1 + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "test card", 1); + assertTappedCount("Forest", true, 1); // must choose X=0 + } + + @Test + public void test_prepareX_ActivatedAbility_TestFrameworkMustCatchLimits() { + prepareCustomPermanent("test card", "test ability", "{X}{1}", new CostAdjuster() { + @Override + public void prepareX(Ability ability, Game game) { + ability.setVariableCostsMinMax(0, 1); + } + }); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}{1}:"); + setChoice(playerA, "X=2"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + try { + execute(); + } catch (AssertionError e) { + Assert.assertTrue("X must have limits: " + e.getMessage(), e.getMessage().contains("Found wrong X value = 2")); + Assert.assertTrue("X must have limits: " + e.getMessage(), e.getMessage().contains("from 0 to 1")); + return; + } + Assert.fail("test must fail"); + } + + @Test + @Ignore // TODO: AI must support game simulations for X choice, see announceXMana + // TODO: implement AI and add tests for non-mana X values (announceXCost) + public void test_prepareX_ActivatedAbility_AI() { + prepareCustomPermanent("test card", "test ability", "{X}{1}", new CostAdjuster() { + @Override + public void prepareX(Ability ability, Game game) { + ability.setVariableCostsMinMax(0, 10); + } + }); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + // AI must activate ability with min good value for X + // it's bad to set X=1 for battlefield score cause card will give same score for X=0, X=1 + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "test token", 1); + assertTappedCount("Forest", true, 1); // must choose X=0 + } + + @Test + public void test_prepareX_SpellAbility_ScorchedEarth_PayZero() { + // with X announce + + // As an additional cost to cast this spell, discard X land cards. + // Destroy X target lands. + addCard(Zone.HAND, playerA, "Scorched Earth"); // {X}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 0 + 1); + addCard(Zone.HAND, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scorched Earth"); + setChoice(playerA, "X=0"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_prepareX_SpellAbility_ScorchedEarth_PaySome() { + // with X announce + + // As an additional cost to cast this spell, discard X land cards. + // Destroy X target lands. + addCard(Zone.HAND, playerA, "Scorched Earth"); // {X}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 + 1); + addCard(Zone.HAND, playerA, "Island", 10); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scorched Earth"); + setChoice(playerA, "X=2"); + addTarget(playerA, "Forest", 2); // to destroy + setChoice(playerA, "Island", 2); // discard cost + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_prepareX_SpellAbility_ScorchedEarth_PayAll() { + // with X announce + + // As an additional cost to cast this spell, discard X land cards. + // Destroy X target lands. + addCard(Zone.HAND, playerA, "Scorched Earth"); // {X}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10 + 1); + addCard(Zone.HAND, playerA, "Island", 10); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scorched Earth"); + setChoice(playerA, "X=10"); + addTarget(playerA, "Forest", 10); // to destroy + setChoice(playerA, "Island", 10); // discard cost + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_prepareX_ActivatedAbility_BargainingTable_PayZero() { + // with direct X (without announce) + + // {X}, {T}: Draw a card. X is the number of cards in an opponent's hand. + addCard(Zone.BATTLEFIELD, playerA, "Bargaining Table"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // + // no cards in opponent's hand + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, {T}:"); + setChoice(playerA, playerB.getName()); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, 1); + } + + @Test + public void test_prepareX_ActivatedAbility_BargainingTable_PaySome() { + // with direct X (without announce) + + // {X}, {T}: Draw a card. X is the number of cards in an opponent's hand. + addCard(Zone.BATTLEFIELD, playerA, "Bargaining Table"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // + addCard(Zone.HAND, playerB, "Forest", 3); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, {T}:"); + setChoice(playerA, playerB.getName()); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, 1); + } + + @Test + public void test_prepareX_ActivatedAbility_BargainingTable_CantPay() { + // with direct X (without announce) + + // {X}, {T}: Draw a card. X is the number of cards in an opponent's hand. + addCard(Zone.BATTLEFIELD, playerA, "Bargaining Table"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.HAND, playerB, "Forest", 3); + + // must not request opponent choice because it must see min hand size as 3 + checkPlayableAbility("must not able to activate due lack of mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, {T}:", false); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, 0); + } + + @Test + public void test_prepareX_ActivatedAbility_BargainingTable_UnboundFlourishingMustCopy() { + // with direct X (without announce) + + // {X}, {T}: Draw a card. X is the number of cards in an opponent's hand. + addCard(Zone.BATTLEFIELD, playerA, "Bargaining Table"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // + addCard(Zone.HAND, playerB, "Forest", 3); + // + // Whenever you cast a permanent spell with a mana cost that contains {X}, double the value of X. + // Whenever you cast an instant or sorcery spell or activate an ability, if that spell’s mana cost or that + // ability’s activation cost contains {X}, copy that spell or ability. You may choose new targets for the copy. + addCard(Zone.BATTLEFIELD, playerA, "Unbound Flourishing", 1); + + // Unbound Flourishing must see {X} mana cost and duplicate ability on stack + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, {T}:"); + setChoice(playerA, playerB.getName()); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, 2); // from original and copied abilities + } + + @Test + public void test_prepareX_NecropolisFiend() { + // {X}, {T}, Exile X cards from your graveyard: Target creature gets -X/-X until end of turn. + addCard(Zone.BATTLEFIELD, playerA, "Necropolis Fiend"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.GRAVEYARD, playerA, "Grizzly Bears", 3); + // + addCard(Zone.BATTLEFIELD, playerB, "Ancient Bronze Dragon"); // 7/7 + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, {T}, Exile"); + setChoice(playerA, "X=2"); + addTarget(playerA, "Ancient Bronze Dragon"); // to -2/-2 + setChoice(playerA, "Grizzly Bears", 2); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerB, "Ancient Bronze Dragon", 7 - 2, 7 - 2); + } + + @Test + public void test_prepareX_OpenTheWay() { + skipInitShuffling(); + + // X can't be greater than the number of players in the game. + // Reveal cards from the top of your library until you reveal X land cards. + // Put those land cards onto the battlefield tapped and the rest on the bottom of your library in a random order. + addCard(Zone.HAND, playerA, "Open the Way"); // {X}{G}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2 + 2); + addCard(Zone.LIBRARY, playerA, "Island", 5); + + // min/max test require multiple tests (see above), so just disable setChoice and look at logs for good limits + // example: Message: Announce the value for {X} (any value) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Open the Way"); + setChoice(playerA, "X=2"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Island", 2); + } + + @Test + public void test_prepareX_KnollspineInvocation() { + skipInitShuffling(); + + // {X}, Discard a card with mana value X: This enchantment deals X damage to any target. + addCard(Zone.BATTLEFIELD, playerA, "Knollspine Invocation"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + // + addCard(Zone.LIBRARY, playerA, "Grizzly Bears", 1); // {1}{G} + + // turn 1 - can't play due empty hand + checkPlayableAbility("no cards to discard", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, Discard", false); + + // turn 3 - can't play due no mana + activateManaAbility(3, PhaseStep.UPKEEP, playerA, "{T}: Add {G}", 2); + checkPlayableAbility("no mana to activate", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, Discard", false); + + // turn 5 - can play + checkPlayableAbility("must able to activate", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, Discard", true); + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, Discard"); + setChoice(playerA, "Grizzly Bears"); // discard + addTarget(playerA, playerB); // damage + + setStrictChooseMode(true); + setStopAt(5, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 2); + } + + @Test + public void test_prepareX_EliteArcanist() { + // When Elite Arcanist enters the battlefield, you may exile an instant card from your hand. + // {X}, {T}: Copy the exiled card. You may cast the copy without paying its mana cost. X is the converted mana cost of the exiled card. + addCard(Zone.HAND, playerA, "Elite Arcanist"); // {3}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4 + 1); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + + // turn 1 + // prepare arcanist + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elite Arcanist"); + setChoice(playerA, true); // use exile + setChoice(playerA, "Lightning Bolt"); // to exile and copy later + + // turn 3 + // cast copy + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, {T}: Copy"); + setChoice(playerA, true); // cast copy + addTarget(playerA, playerB); // damage + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertTappedCount("Island", true, 1); // used to cast copy for {1} + assertLife(playerB, 20 - 3); + } + + @Test + public void test_prepareX_AladdinsLamp() { + skipInitShuffling(); + + // {X}, {T}: The next time you would draw a card this turn, instead look at the top X cards of your library, + // put all but one of them on the bottom of your library in a random order, then draw a card. X can't be 0. + addCard(Zone.BATTLEFIELD, playerA, "Aladdin's Lamp"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + // {T}: Draw a card. + addCard(Zone.BATTLEFIELD, playerA, "Archivist"); + addCard(Zone.LIBRARY, playerA, "Island", 1); + addCard(Zone.LIBRARY, playerA, "Grizzly Bears", 1); + addCard(Zone.LIBRARY, playerA, "Island", 3); + + // prepare effect + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}, {T}: The next time"); + setChoice(playerA, "X=5"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // improved draw + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw"); + setChoice(playerA, "Grizzly Bears"); // keep on top and draw + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, "Grizzly Bears", 1); + } + + @Test + public void test_modifyCost_Fireball() { + // This spell costs {1} more to cast for each target beyond the first. + // Fireball deals X damage divided evenly, rounded down, among any number of targets. + addCard(Zone.HAND, playerA, "Fireball"); // {X}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 1); // 3 for x=2 cast, 1 for x2 targets + // + addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 3); // 1/1 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fireball"); + setChoice(playerA, "X=2"); + addTarget(playerA, "Arbor Elf^Arbor Elf"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + assertTappedCount("Mountain", true, 3 + 1); // 3 for x=2 cast, 1 for x2 targets + assertGraveyardCount(playerA, "Arbor Elf", 2); + assertPermanentCount(playerA, "Arbor Elf", 1); + } + + @Test + public void test_modifyCost_DeepwoodDenizen() { + // {5}{G}, {T}: Draw a card. This ability costs {1} less to activate for each +1/+1 counter on creatures you control. + addCard(Zone.BATTLEFIELD, playerA, "Deepwood Denizen"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 6 - 3); + // + // +1: Distribute three +1/+1 counters among one, two, or three target creatures you control + addCard(Zone.BATTLEFIELD, playerA, "Ajani, Mentor of Heroes", 1); + addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); + + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}, {T}: Draw", false); + + // add +3 counters and get -3 cost decrease + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Distribute"); + addTargetAmount(playerA, "Arbor Elf", 2); + addTargetAmount(playerA, "Deepwood Denizen", 1); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPlayableAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}, {T}: Draw", true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}, {T}: Draw"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, 1); // +1 from draw ability + } + + @Test + public void test_modifyCost_BaruWurmspeaker() { + // Wurms you control get +2/+2 and have trample. + // {7}{G}, {T}: Create a 4/4 green Wurm creature token. This ability costs {X} less to activate, where X is the greatest power among Wurms you control. + addCard(Zone.BATTLEFIELD, playerA, "Baru, Wurmspeaker"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + // + // When this creature dies, create a 3/3 colorless Phyrexian Wurm artifact creature token with deathtouch + // and a 3/3 colorless Phyrexian Wurm artifact creature token with lifelink. + addCard(Zone.HAND, playerA, "Wurmcoil Engine", 1); // {6} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{7}{G}, {T}: Create", false); + + // turn 1 + // prepare wurm and get -8 cost decrease (6 wurm + 2 boost) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wurmcoil Engine"); + + // turn 3 + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "{7}{G}, {T}: Create", true); + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{7}{G}, {T}: Create"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertTokenCount(playerA, "Wurm Token", 1); + assertTappedCount("Forest", true, 1); + assertTappedCount("Mountain", true, 0); + } + + @Test + public void test_Other_AbandonHope() { + // used both modify and cost reduction in one cost adjuster + + // As an additional cost to cast this spell, discard X cards. + // Look at target opponent's hand and choose X cards from it. That player discards those cards. + addCard(Zone.HAND, playerA, "Abandon Hope"); // {X}{1}{B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2 + 2); + addCard(Zone.HAND, playerA, "Forest", 2); + addCard(Zone.HAND, playerB, "Grizzly Bears", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Abandon Hope"); + setChoice(playerA, "X=2"); + addTarget(playerA, playerB); + setChoice(playerA, "Forest^Forest"); // discard cost + setChoice(playerA, "Grizzly Bears^Grizzly Bears"); // discard from hand + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Forest", 2); + assertGraveyardCount(playerB, "Grizzly Bears", 2); + } + + // additional tasks to improve code base: + // TODO: OsgirTheReconstructorCostAdjuster - migrate to EarlyTargetCost + // TODO: SkeletalScryingAdjuster - migrate to EarlyTargetCost + // TODO: NecropolisFiend - migrate to EarlyTargetCost + // TODO: KnollspineInvocation - migrate to EarlyTargetCost + // TODO: ExileCardsFromHandAdjuster - need rework to remove dialog from inside, e.g. migrate to EarlyTargetCost? + // TODO: CallerOfTheHuntAdjuster - research and add test + // TODO: VoodooDoll - research and add test +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/omen/OmenCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/omen/OmenCardsTest.java new file mode 100644 index 00000000000..e6572b126be --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/omen/OmenCardsTest.java @@ -0,0 +1,77 @@ +package org.mage.test.cards.cost.omen; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class OmenCardsTest extends CardTestPlayerBase { + + @Test + public void testDirgurIslandDragonShuffle() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.HAND, playerA, "Dirgur Island Dragon"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB, "Bear Cub"); + addCard(Zone.LIBRARY, playerA, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Skimming Strike", "Bear Cub"); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertLibraryCount(playerA, "Dirgur Island Dragon", 1); + assertTapped("Bear Cub", true); + assertHandCount(playerA, 1); + } + + @Test + public void testDirgurIslandDragonShuffleAndPlay() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + addCard(Zone.BATTLEFIELD, playerB, "Bear Cub"); + addCard(Zone.LIBRARY, playerA, "Mountain"); + addCard(Zone.HAND, playerA, "Dirgur Island Dragon"); + + castSpell(2, PhaseStep.BEGIN_COMBAT, playerA, "Skimming Strike", "Bear Cub"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Dirgur Island Dragon"); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertPermanentCount(playerA, "Dirgur Island Dragon", 1); + } + + @Test + public void testCounteredInGraveyard() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB,"Island", 4); + addCard(Zone.BATTLEFIELD, playerB,"Bear Cub"); + addCard(Zone.HAND, playerA, "Dirgur Island Dragon"); + addCard(Zone.HAND, playerB, "Counterspell"); + + castSpell(2, PhaseStep.BEGIN_COMBAT, playerA, "Skimming Strike", "Bear Cub"); + castSpell(2, PhaseStep.BEGIN_COMBAT, playerB, "Counterspell", "Skimming Strike", "Skimming Strike", StackClause.WHILE_ON_STACK); + attack(2, playerB, "Bear Cub"); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertGraveyardCount(playerA, "Dirgur Island Dragon", 1); + assertLife(playerA, 20 - 2); + } + + @Test + public void testGraveyardCast() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Kess, Dissident Mage"); + addCard(Zone.GRAVEYARD, playerA, "Dirgur Island Dragon"); + addCard(Zone.BATTLEFIELD, playerB, "Bear Cub"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Skimming Strike", "Bear Cub"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLibraryCount(playerA, "Dirgur Island Dragon", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/BeltOfGiantStrengthTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/BeltOfGiantStrengthTest.java index f79679ec443..d10b3497e0f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/BeltOfGiantStrengthTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/BeltOfGiantStrengthTest.java @@ -13,8 +13,12 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class BeltOfGiantStrengthTest extends CardTestPlayerBase { + /** + * Equipped creature has base power and toughness 10/10. + * Equip {10}. This ability costs {X} less to activate where X is the power of the creature it targets. + */ private static final String belt = "Belt of Giant Strength"; - private static final String gigantosauras = "Gigantosaurus"; + private static final String gigantosauras = "Gigantosaurus"; // 10/10 @Test public void testWithManaAvailable() { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java new file mode 100644 index 00000000000..343d8267800 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java @@ -0,0 +1,41 @@ +package org.mage.test.cards.single.blb; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class PawpatchRecruitTest extends CardTestPlayerBase { + + private static final String paw = "Pawpatch Recruit"; + private static final String cub = "Bear Cub"; + private static final String panharm = "Panharmonicon"; + private static final String prowler = "Chrome Prowler"; + + @Test + public void testCopiedTriggerAbility() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, paw); + addCard(Zone.BATTLEFIELD, playerB, cub); + addCard(Zone.BATTLEFIELD, playerA, panharm); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.HAND, playerA, prowler); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, prowler); + setChoice(playerA, "When {this} enters"); + addTarget(playerA, paw); + addTarget(playerA, cub); + setChoice(playerB, "Whenever a creature"); + addTarget(playerB, paw); + addTarget(playerB, cub); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertTapped(paw, true); + assertTapped(cub, true); + assertCounterCount(playerB, paw, CounterType.P1P1, 1); + assertCounterCount(playerB, cub, CounterType.P1P1, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/PramikonSkyRampartTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/PramikonSkyRampartTest.java index f51b08d613c..8e5f1b9cc16 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/PramikonSkyRampartTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/PramikonSkyRampartTest.java @@ -1,10 +1,6 @@ package org.mage.test.cards.single.c19; -import mage.abilities.effects.common.continuous.PlayerCanOnlyAttackInDirectionRestrictionEffect; -import mage.constants.MultiplayerAttackOption; -import mage.constants.PhaseStep; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; +import mage.constants.*; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; @@ -63,7 +59,7 @@ public class PramikonSkyRampartTest extends CardTestMultiPlayerBase { addCard(Zone.BATTLEFIELD, playerD, devil); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, pramikon); - setChoice(playerA, PlayerCanOnlyAttackInDirectionRestrictionEffect.ALLOW_ATTACKING_LEFT); + setChoice(playerA, ModeChoice.LEFT.toString()); // A has pramikon, and chose left. // @@ -128,7 +124,7 @@ public class PramikonSkyRampartTest extends CardTestMultiPlayerBase { addCard(Zone.BATTLEFIELD, playerD, devil); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, pramikon); - setChoice(playerA, PlayerCanOnlyAttackInDirectionRestrictionEffect.ALLOW_ATTACKING_RIGHT); + setChoice(playerA, ModeChoice.RIGHT.toString()); // A has pramikon, and chose right. // @@ -197,10 +193,10 @@ public class PramikonSkyRampartTest extends CardTestMultiPlayerBase { addCard(Zone.BATTLEFIELD, playerD, devil); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, pramikon); - setChoice(playerA, PlayerCanOnlyAttackInDirectionRestrictionEffect.ALLOW_ATTACKING_RIGHT); + setChoice(playerA, ModeChoice.RIGHT.toString()); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, pramikon); - setChoice(playerD, PlayerCanOnlyAttackInDirectionRestrictionEffect.ALLOW_ATTACKING_LEFT); + setChoice(playerD, ModeChoice.LEFT.toString()); // A has pramikon, and chose right. // D has pramikon, and chose left. @@ -270,10 +266,10 @@ public class PramikonSkyRampartTest extends CardTestMultiPlayerBase { addCard(Zone.BATTLEFIELD, playerD, devil); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, pramikon); - setChoice(playerA, PlayerCanOnlyAttackInDirectionRestrictionEffect.ALLOW_ATTACKING_LEFT); + setChoice(playerA, ModeChoice.LEFT.toString()); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, pramikon); - setChoice(playerD, PlayerCanOnlyAttackInDirectionRestrictionEffect.ALLOW_ATTACKING_RIGHT); + setChoice(playerD, ModeChoice.RIGHT.toString()); // A has pramikon, and chose left. // D has pramikon, and chose right. @@ -323,4 +319,4 @@ public class PramikonSkyRampartTest extends CardTestMultiPlayerBase { setStopAt(5, PhaseStep.END_TURN); execute(); } -} \ No newline at end of file +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java index 0df627d9194..349dda8af12 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java @@ -67,4 +67,35 @@ public class AgrusKosEternalSoldierTest extends CardTestPlayerBase { assertLife(playerB, 20); } + @Test + public void testCopiedTriggerAbility() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, agrus); + addCard(Zone.BATTLEFIELD, playerB, turtle); + addCard(Zone.BATTLEFIELD, playerB, firewalker); + addCard(Zone.BATTLEFIELD, playerB, "Plateau", 4); + addCard(Zone.BATTLEFIELD, playerA, "Panharmonicon"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.HAND, playerA, "Smoldering Werewolf"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Smoldering Werewolf"); + setChoice(playerA, "When {this} enters, it deals"); + addTarget(playerA, agrus); + addTarget(playerA, agrus); + setChoice(playerB, true); // gain life + setChoice(playerB, "Whenever {this} becomes"); + setChoice(playerB, true); // pay to copy + setChoice(playerB, true); // pay to copy + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertDamageReceived(playerB, agrus, 2); + assertDamageReceived(playerB, turtle, 2); + assertDamageReceived(playerB, firewalker, 0); + assertLife(playerA, 20); + assertLife(playerB, 21); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/DragonfireBladeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/DragonfireBladeTest.java new file mode 100644 index 00000000000..81c117e21ba --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/DragonfireBladeTest.java @@ -0,0 +1,49 @@ +package org.mage.test.cards.single.tdm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import static org.junit.jupiter.api.Assertions.*; + +public class DragonfireBladeTest extends CardTestPlayerBase { + + private static final String blade = "Dragonfire Blade"; + private static final String equipText = "Equip {4}"; + public static final String ornithopter = "Ornithopter"; // colorless 0/1 creature + private static final String turtle = "Aegis Turtle"; // U 0/5 creature + private static final String leotau = "Grizzled Leotau"; // GW 1/5 creature + private static final String mantis = "Mantis Rider"; // URW 3/3 creature + private static final String glint = "Glint-Eye Nephilim"; // UBRG 2/2 creature + + @Test + public void colorsTest() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, blade); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4 + 3 + 2 + 1); + addCard(Zone.BATTLEFIELD, playerA, ornithopter); + addCard(Zone.BATTLEFIELD, playerA, turtle); + addCard(Zone.BATTLEFIELD, playerA, leotau); + addCard(Zone.BATTLEFIELD, playerA, mantis); + addCard(Zone.BATTLEFIELD, playerA, glint); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, equipText, ornithopter); + waitStackResolved(1,PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, equipText, turtle); + waitStackResolved(1,PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, equipText, leotau); + waitStackResolved(1,PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, equipText, mantis); + waitStackResolved(1,PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, equipText, glint); + waitStackResolved(1,PhaseStep.PRECOMBAT_MAIN); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAttachedTo(playerA, blade, glint, true); + assertPowerToughness(playerA, glint, 4, 4); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/HostOfTheHereafterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/HostOfTheHereafterTest.java new file mode 100644 index 00000000000..69624983da0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/HostOfTheHereafterTest.java @@ -0,0 +1,94 @@ +package org.mage.test.cards.single.tdm; + + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class HostOfTheHereafterTest extends CardTestPlayerBase { + + public static final String HOST = "Host of the Hereafter"; + public static final String BAYOU = "Bayou"; + public static final String CUB = "Bear Cub"; + public static final String DOWNFALL = "Hero's Downfall"; + public static final String FEED = "Feed the Serpent"; + + @Test + public void testEntersWithCounters() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, HOST); + addCard(Zone.BATTLEFIELD, playerA, BAYOU, 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, HOST); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, HOST, 1); + assertCounterCount(playerA, HOST, CounterType.P1P1, 2); + } + + @Test + public void testDiesMoveCounters() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, HOST); + addCard(Zone.HAND, playerA, DOWNFALL); + addCard(Zone.BATTLEFIELD, playerA, BAYOU, 7); + addCard(Zone.BATTLEFIELD, playerA, CUB); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, HOST); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, DOWNFALL, HOST); + addTarget(playerA, CUB); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, CUB, 1); + assertCounterCount(playerA, CUB, CounterType.P1P1, 2); + } + + @Test + public void exileNoCounters() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, HOST); + addCard(Zone.HAND, playerA, FEED); + addCard(Zone.BATTLEFIELD, playerA, BAYOU, 8); + addCard(Zone.BATTLEFIELD, playerA, CUB); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, HOST); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, FEED, HOST); + + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, CUB, 1); + assertCounterCount(playerA, CUB, CounterType.P1P1, 0); + } + + @Test + public void testOtherCounters() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, HOST); + addCard(Zone.HAND, playerA, DOWNFALL); + addCard(Zone.BATTLEFIELD, playerA, BAYOU, 4); + addCard(Zone.BATTLEFIELD, playerA, CUB); + + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, CUB, CounterType.FLYING, 1); + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, CUB, CounterType.LIFELINK, 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, DOWNFALL, CUB); + addTarget(playerA, HOST); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, HOST, 1); + assertCounterCount(playerA, HOST, CounterType.FLYING, 1); + assertCounterCount(playerA, HOST, CounterType.LIFELINK, 1); + } + +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/HundredBattleVeteranTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/HundredBattleVeteranTest.java new file mode 100644 index 00000000000..bab36a7880d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/HundredBattleVeteranTest.java @@ -0,0 +1,51 @@ +package org.mage.test.cards.single.tdm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class HundredBattleVeteranTest extends CardTestPlayerBase { + + private static final String VETERAN = "Hundred-Battle Veteran"; // 4/2 + private static final String CUB = "Bear Cub"; // 2/2 + + @Test + public void testBoostEffect() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, VETERAN); + addCard(Zone.BATTLEFIELD, playerA, CUB); + + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, CUB, CounterType.FINALITY, 1); + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, CUB, CounterType.P1P1, 3); + checkPT("no boost", 1, PhaseStep.BEGIN_COMBAT, playerA, VETERAN, 4, 2); + addCounters(1, PhaseStep.POSTCOMBAT_MAIN, playerA, CUB, CounterType.LIFELINK, 1); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, CUB, 1); + assertCounterCount(playerA, VETERAN, CounterType.FINALITY, 0); + assertPowerToughness(playerA, VETERAN, 6, 6); + } + + @Test + public void testCastFromGraveyard() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerA, VETERAN); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, VETERAN); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, VETERAN, 1); + assertCounterCount(playerA, VETERAN, CounterType.FINALITY, 1); + } + +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/MistriseVillageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/MistriseVillageTest.java new file mode 100644 index 00000000000..98c2cbee505 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/MistriseVillageTest.java @@ -0,0 +1,42 @@ +package org.mage.test.cards.single.tdm; + + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class MistriseVillageTest extends CardTestPlayerBase { + + private static final String MISTRISE = "Mistrise Village"; + private static final String COUNTER = "Counterspell"; + private static final String CUB = "Bear Cub"; + private static final String BEARS = "Balduvian Bears"; + + @Test + public void testCounter() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, MISTRISE); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.BATTLEFIELD, playerB, "Island", 4); + addCard(Zone.HAND, playerB, COUNTER, 2); + addCard(Zone.HAND, playerA, CUB); + addCard(Zone.HAND, playerA, BEARS); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, CUB, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, COUNTER, CUB, CUB); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, BEARS, true); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, COUNTER, BEARS, BEARS); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, CUB, 1); + assertGraveyardCount(playerA, BEARS, 1); + assertGraveyardCount(playerB, COUNTER, 2); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/TaigamMasterOpportunistTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/TaigamMasterOpportunistTest.java new file mode 100644 index 00000000000..9cda471dcce --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/TaigamMasterOpportunistTest.java @@ -0,0 +1,105 @@ +package org.mage.test.cards.single.tdm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class TaigamMasterOpportunistTest extends CardTestPlayerBase { + + private static final String TAIGAM = "Taigam, Master Opportunist"; + private static final String ORNITHOPTER = "Ornithopter"; + private static final String TWINMAW = "Twinmaw Stormbrood"; + private static final String BITE = "Charring Bite"; + private static final String TURTLE = "Aegis Turtle"; + private static final String AKOUM = "Akoum Warrior"; + + @Test + public void testCardWithSpellOption() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, TAIGAM); + addCard(Zone.HAND, playerA, ORNITHOPTER); + addCard(Zone.HAND, playerA, TWINMAW); + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 6); + addCard(Zone.BATTLEFIELD, playerB, TURTLE, 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ORNITHOPTER, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, BITE, TURTLE); + checkCardCounters("time counters", 1, PhaseStep.BEGIN_COMBAT, playerA, TWINMAW, CounterType.TIME, 4); + setChoice(playerA, true); + setChoice(playerA, "Cast " + BITE); + addTarget(playerA, TURTLE); + + setStopAt(9, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertLibraryCount(playerA, TWINMAW, 1); + assertGraveyardCount(playerB, TURTLE, 2); + } + + @Test + public void testMDFC() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, TAIGAM); + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 6); + addCard(Zone.HAND, playerA, ORNITHOPTER); + addCard(Zone.HAND, playerA, AKOUM); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ORNITHOPTER, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, AKOUM); + setChoice(playerA, true); + setChoice(playerA, "Play Akoum Teeth"); + + setStopAt(9, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Akoum Teeth", 1); + assertTapped("Akoum Teeth", true); + } + + @Test + public void testMDFC2() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 6); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + addCard(Zone.HAND, playerB, "Delay"); + addCard(Zone.HAND, playerA, AKOUM); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, AKOUM); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Delay", AKOUM); + setChoice(playerA, true); + setChoice(playerA, "Play Akoum Teeth"); + + setStopAt(7, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Akoum Teeth", 1); + assertTapped("Akoum Teeth", true); + } + + @Test + public void test() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 6); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB, TURTLE); + addCard(Zone.HAND, playerB, "Delay"); + addCard(Zone.HAND, playerA, TWINMAW); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, TWINMAW); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Delay", TWINMAW); + setChoice(playerA, true); + setChoice(playerA, "Cast " + BITE); + addTarget(playerA, TURTLE); + + setStopAt(7, PhaseStep.PRECOMBAT_MAIN); + execute(); + + } + +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayersListAndOrderTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayersListAndOrderTest.java index 0e4d6bed1f1..1d1fbc6ad87 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayersListAndOrderTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayersListAndOrderTest.java @@ -115,6 +115,36 @@ public class PlayersListAndOrderTest extends CardTestMultiPlayerBase { execute(); } + @Test + public void test_Game_PlayerListMustHaveAccessByIndex() { + // make sure CircularList return same data by index, see #13526 + + PlayerList players = new PlayerList(); + players.add(playerA.getId()); + players.add(playerB.getId()); + players.add(playerC.getId()); + players.add(playerD.getId()); + List staticList = new ArrayList<>(players); + Assert.assertEquals("last added player must be current", staticList.get(0), playerD.getId()); + + // normal + Assert.assertEquals(players.get(0), staticList.get(0)); + Assert.assertEquals(players.get(1), staticList.get(1)); + Assert.assertEquals(players.get(2), staticList.get(2)); + Assert.assertEquals(players.get(3), staticList.get(3)); + Assert.assertEquals(players.get(2), staticList.get(2)); // make sure no depends on calls order + + // make sure CircularList keeps inner structure + players.setCurrent(playerC.getId()); + Assert.assertEquals(players.get(0), staticList.get(0)); + Assert.assertEquals(players.get(1), staticList.get(1)); + Assert.assertEquals(players.get(2), staticList.get(2)); + Assert.assertEquals(players.get(3), staticList.get(3)); + Assert.assertEquals(players.get(2), staticList.get(2)); // make sure no depends on calls order + + Assert.assertNull("must return null on non existing item", players.get(999)); + } + @Test public void test_Game_GetPlayerList() { // game.getPlayerList() - APNAP order, for cards usage diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index e33796e3a0f..2238c6dffeb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -2927,6 +2927,7 @@ public class TestPlayer implements Player { for (String choice : choices) { if (choice.startsWith("X=")) { int xValue = Integer.parseInt(choice.substring(2)); + assertXMinMaxValue(game, ability, xValue, min, max); choices.remove(choice); return xValue; } @@ -2934,7 +2935,7 @@ public class TestPlayer implements Player { } this.chooseStrictModeFailed("choice", game, getInfo(ability, game) - + "\nMessage: " + message); + + "\nMessage: " + message + prepareXMaxInfo(min, max)); return computerPlayer.announceXMana(min, max, message, game, ability); } @@ -2944,16 +2945,34 @@ public class TestPlayer implements Player { if (!choices.isEmpty()) { if (choices.get(0).startsWith("X=")) { int xValue = Integer.parseInt(choices.get(0).substring(2)); + assertXMinMaxValue(game, ability, xValue, min, max); choices.remove(0); return xValue; } } this.chooseStrictModeFailed("choice", game, getInfo(ability, game) - + "\nMessage: " + message); + + "\nMessage: " + message + prepareXMaxInfo(min, max)); return computerPlayer.announceXCost(min, max, message, game, ability, null); } + private String prepareXMaxInfo(int min, int max) { + if (min == 0 && max == Integer.MAX_VALUE) { + return " (any value)"; + } else { + return String.format(" (from %s to %s)", + min, + (max == Integer.MAX_VALUE) ? "any" : String.valueOf(max) + ); + } + } + + private void assertXMinMaxValue(Game game, Ability source, int xValue, int min, int max) { + if (xValue < min || xValue > max) { + Assert.fail("Found wrong X value = " + xValue + ", for " + CardUtil.getSourceName(game, source) + ", must" + prepareXMaxInfo(min, max)); + } + } + @Override public int getAmount(int min, int max, String message, Game game) { assertAliasSupportInChoices(false); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index c78ea5c0fd7..228e51880ab 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -4,6 +4,9 @@ import mage.MageObject; import mage.Mana; import mage.ObjectColor; import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ContinuousEffectsList; +import mage.abilities.effects.Effect; import mage.cards.Card; import mage.cards.decks.Deck; import mage.cards.decks.DeckCardLists; @@ -29,6 +32,7 @@ import mage.players.Player; import mage.server.game.GameSessionPlayer; import mage.util.CardUtil; import mage.util.ThreadUtils; +import mage.utils.StreamUtils; import mage.utils.SystemUtil; import mage.view.GameView; import org.junit.Assert; @@ -320,6 +324,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } assertAllCommandsUsed(); + + //assertNoDuplicatedEffects(); } protected TestPlayer createNewPlayer(String playerName, RangeOfInfluence rangeOfInfluence) { @@ -1702,6 +1708,57 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } } + /** + * Make sure game state do not contain any duplicated effects, e.g. all effect's durations are fine + */ + private void assertNoDuplicatedEffects() { + // to find bugs like https://github.com/magefree/mage/issues/12932 + + // TODO: simulate full end turn to end all effects and make it default? + + // some effects can generate duplicated effects by design + // example: Tamiyo, Inquisitive Student + x2 attack in TamiyoInquisitiveStudentTest.test_PlusTwo + // +2: Until your next turn, whenever a creature attacks you or a planeswalker you control, it gets -1/-0 until end of turn. + // TODO: add targets and affected objects check to unique key? + + // one effect can be used multiple times by different sources + // so use group key like: effect + ability + source + Map> groups = new HashMap<>(); + for (ContinuousEffectsList layer : currentGame.getState().getContinuousEffects().allEffectsLists) { + for (Object effectObj : layer) { + ContinuousEffect effect = (ContinuousEffect) effectObj; + for (Object abilityObj : layer.getAbility(effect.getId())) { + Ability ability = (Ability) abilityObj; + MageObject sourceObject = currentGame.getObject(ability.getSourceId()); + String groupKey = "effectClass_" + effect.getClass().getCanonicalName() + + "_abilityClass_" + ability.getClass().getCanonicalName() + + "_sourceName_" + (sourceObject == null ? "null" : sourceObject.getIdName()); + List groupList = groups.getOrDefault(groupKey, null); + if (groupList == null) { + groupList = new ArrayList<>(); + groups.put(groupKey, groupList); + } + groupList.add(effect); + } + } + } + + // analyse + List duplicatedGroups = groups.keySet().stream() + .filter(groupKey -> groups.get(groupKey).size() > 1) + .collect(Collectors.toList()); + if (duplicatedGroups.size() > 0) { + System.out.println("Duplicated effect groups: " + duplicatedGroups.size()); + duplicatedGroups.forEach(groupKey -> { + System.out.println("group " + groupKey + ": "); + groups.get(groupKey).forEach(e -> { + System.out.println(" - " + e.getId() + " - " + e.getDuration() + " - " + e); + }); + }); + Assert.fail("Found duplicated effects: " + duplicatedGroups.size()); + } + } + public void assertActivePlayer(TestPlayer player) { Assert.assertEquals("message", currentGame.getState().getActivePlayerId(), player.getId()); } diff --git a/Mage.Verify/pom.xml b/Mage.Verify/pom.xml index 31b9493ede0..9ce6635cd66 100644 --- a/Mage.Verify/pom.xml +++ b/Mage.Verify/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage-verify diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 892bfadafbe..7013bdd1ab3 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -306,9 +306,9 @@ public class VerifyCardDataTest { if (card instanceof CardWithHalves) { check(((CardWithHalves) card).getLeftHalfCard(), cardIndex); check(((CardWithHalves) card).getRightHalfCard(), cardIndex); - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { check(card, cardIndex); - check(((AdventureCard) card).getSpellCard(), cardIndex); + check(((CardWithSpellOption) card).getSpellCard(), cardIndex); } else { check(card, cardIndex); } @@ -1696,7 +1696,7 @@ public class VerifyCardDataTest { checkCardCanBeCopied(card); } } - checkWrongAbilitiesText(card, ref, cardIndex); + checkWrongAbilitiesText(card, ref, cardIndex, false); } private void checkColors(Card card, MtgJsonCard ref) { @@ -2161,8 +2161,8 @@ public class VerifyCardDataTest { ); // card can contain rules text from both sides, so must search ref card for all sides too String additionalName; - if (card instanceof AdventureCard) { - additionalName = ((AdventureCard) card).getSpellCard().getName(); + if (card instanceof CardWithSpellOption) { + additionalName = ((CardWithSpellOption) card).getSpellCard().getName(); } else if (card.isTransformable() && !card.isNightCard()) { additionalName = card.getSecondCardFace().getName(); } else { @@ -2403,7 +2403,11 @@ public class VerifyCardDataTest { System.out.println(); System.out.println(card.getName() + " " + card.getManaCost().getText()); - if (card instanceof SplitCard || card instanceof ModalDoubleFacedCard) { + if (card instanceof CardWithSpellOption) { + // format to print main card then spell card + card.getInitAbilities().getRules().forEach(this::printAbilityText); + ((CardWithSpellOption) card).getSpellCard().getAbilities().getRules().forEach(r -> printAbilityText(r.replace("— ", "\n"))); + } else if (card instanceof SplitCard || card instanceof ModalDoubleFacedCard) { card.getAbilities().getRules().forEach(this::printAbilityText); } else { card.getRules().forEach(this::printAbilityText); @@ -2411,16 +2415,31 @@ public class VerifyCardDataTest { // ref card System.out.println(); - MtgJsonCard ref = MtgJsonService.card(card.getName()); - if (ref == null) { - ref = MtgJsonService.cardByClassName(foundClassName); + MtgJsonCard refMain = MtgJsonService.card(card.getName()); + MtgJsonCard refSpell = null; + if (card instanceof CardWithSpellOption) { + refSpell = MtgJsonService.card(((CardWithSpellOption) card).getSpellCard().getName()); } - if (ref != null) { - System.out.println("ref: " + ref.getNameAsFace() + " " + ref.manaCost); - System.out.println(ref.text); + if (refMain == null) { + refMain = MtgJsonService.cardByClassName(foundClassName); + } + if (refMain != null) { + System.out.println("ref: " + refMain.getNameAsFace() + " " + refMain.manaCost); + System.out.println(refMain.text); + if (refSpell != null) { + System.out.println(refSpell.getNameAsFace() + " " + refSpell.manaCost); + System.out.println(refSpell.text); + } } else { System.out.println("WARNING, can't find mtgjson ref for " + card.getName()); } + + // additional check to simulate diff in rules + if (refMain != null) { + checkWrongAbilitiesText(card, refMain, 0, true); + } else if (refSpell != null) { + checkWrongAbilitiesText(((CardWithSpellOption) card).getSpellCard(), refSpell, 0, true); + } }); } @@ -2471,9 +2490,9 @@ public class VerifyCardDataTest { System.out.println(); } - private void checkWrongAbilitiesText(Card card, MtgJsonCard ref, int cardIndex) { + private void checkWrongAbilitiesText(Card card, MtgJsonCard ref, int cardIndex, boolean forceToCheck) { // checks missing or wrong text - if (!FULL_ABILITIES_CHECK_SET_CODES.equals("*") && !FULL_ABILITIES_CHECK_SET_CODES.contains(card.getExpansionSetCode())) { + if (!forceToCheck && !FULL_ABILITIES_CHECK_SET_CODES.equals("*") && !FULL_ABILITIES_CHECK_SET_CODES.contains(card.getExpansionSetCode())) { return; } if (wasCheckedByAbilityText(ref)) { @@ -2547,12 +2566,20 @@ public class VerifyCardDataTest { refRules[i]; } } - + if (ref.subtypes.contains("Omen")) { + for (int i = 0; i < refRules.length; i++) { + refRules[i] = "Omen " + + ref.types.get(0) + " - " + + ref.faceName + ' ' + + ref.manaCost + " - " + + refRules[i]; + } + } String[] cardRules = card .getRules() .stream() - .filter(s -> !(card instanceof AdventureCard) || !s.startsWith("Adventure ")) + .filter(s -> !(card instanceof CardWithSpellOption) || !(s.startsWith("Adventure ") || s.startsWith("Omen "))) .collect(Collectors.joining("\n")) .replace("
", "\n") .replace("
", "\n") @@ -2607,10 +2634,10 @@ public class VerifyCardDataTest { } // extra message for easy checks - if (!isFine) { + if (forceToCheck || !isFine) { System.out.println(); - System.out.println("Wrong card " + cardIndex + ": " + card.getName()); + System.out.println((isFine ? "Good" : "Wrong") + " card " + cardIndex + ": " + card.getName()); Arrays.sort(cardRules); for (String s : cardRules) { System.out.println(s); diff --git a/Mage/pom.xml b/Mage/pom.xml index 6713c55dccb..f148552c45f 100644 --- a/Mage/pom.xml +++ b/Mage/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 mage diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index 96f1a194195..e1bb0a5618b 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -126,7 +126,7 @@ public interface Ability extends Controllable, Serializable { /** * Gets all {@link ManaCosts} associated with this ability. These returned * costs should never be modified as they represent the base costs before - * any modifications. + * any modifications (only cost adjusters can change it, e.g. set min/max values) * * @return All {@link ManaCosts} that must be paid. */ @@ -151,6 +151,17 @@ public interface Ability extends Controllable, Serializable { void addManaCostsToPay(ManaCost manaCost); + /** + * Helper method to setup actual min/max limits of current X costs BEFORE player's X announcement + */ + void setVariableCostsMinMax(int min, int max); + + /** + * Helper method to replace X by direct value BEFORE player's X announcement + * If you need additional target for X then use CostAdjuster + EarlyTargetCost (example: Bargaining Table) + */ + void setVariableCostsValue(int xValue); + /** * Gets a map of the cost tags (set while casting/activating) of this ability, can be null if no tags have been set yet. * Does NOT return the source permanent's tags. @@ -356,7 +367,7 @@ public interface Ability extends Controllable, Serializable { * - for dies triggers - override and use TriggeredAbilityImpl.isInUseableZoneDiesTrigger inside + set setLeavesTheBattlefieldTrigger(true) * * @param sourceObject can be null for static continues effects checking like rules modification (example: Yixlid Jailer) - * @param event can be null for state base effects checking like "when you control seven or more" (example: Endrek Sahr, Master Breeder) + * @param event can be null for state base effects checking like "when you control seven or more" (example: Endrek Sahr, Master Breeder) */ boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event); @@ -533,11 +544,25 @@ public interface Ability extends Controllable, Serializable { void adjustTargets(Game game); + /** + * Dynamic X and cost modification, see CostAdjuster for more details on usage + */ Ability setCostAdjuster(CostAdjuster costAdjuster); - CostAdjuster getCostAdjuster(); + /** + * Prepare {X} settings for announce + */ + void adjustX(Game game); - void adjustCosts(Game game); + /** + * Prepare costs (generate due game state or announce) + */ + void adjustCostsPrepare(Game game); + + /** + * Apply additional cost modifications logic/effects + */ + void adjustCostsModify(Game game, CostModificationType costModificationType); List getHints(); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 114e88343f5..2fd281c59a8 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -291,6 +291,8 @@ public abstract class AbilityImpl implements Ability { // fused or spliced spells contain multiple abilities (e.g. fused, left, right) // optional costs and cost modification must be applied only to the first/main ability + // TODO: need tests with X announced costs, cost modification effects, CostAdjuster, early cost target, etc + // can be bugged due multiple calls (not all code parts below use isMainPartAbility) boolean isMainPartAbility = !CardUtil.isFusedPartAbility(this, game); /* 20220908 - 601.2b @@ -326,6 +328,16 @@ public abstract class AbilityImpl implements Ability { return false; } + // 20241022 - 601.2b + // Choose targets for costs that have to be chosen early + // Not yet included in 601.2b but this is where it will be + handleChooseCostTargets(game, controller); + + // prepare dynamic costs (must be called before any x announce) + if (isMainPartAbility) { + adjustX(game); + } + // 20121001 - 601.2b // If the spell has a variable cost that will be paid as it's being cast (such as an {X} in // its mana cost; see rule 107.3), the player announces the value of that variable. @@ -402,7 +414,7 @@ public abstract class AbilityImpl implements Ability { // Note: ActivatedAbility does include SpellAbility & PlayLandAbility, but those should be able to be canceled too. boolean canCancel = this instanceof ActivatedAbility && controller.isHuman(); if (!getTargets().chooseTargets(outcome, this.controllerId, this, noMana, game, canCancel)) { - // was canceled during targer selection + // was canceled during target selection return false; } } @@ -428,7 +440,7 @@ public abstract class AbilityImpl implements Ability { //20101001 - 601.2e if (isMainPartAbility) { - adjustCosts(game); // still needed for CostAdjuster objects (to handle some types of dynamic costs) + // adjustX already called before any announces game.getContinuousEffects().costModification(this, game); } @@ -483,6 +495,7 @@ public abstract class AbilityImpl implements Ability { // A player can't apply two alternative methods of casting or two alternative costs to a single spell. switch (((SpellAbility) this).getSpellAbilityCastMode()) { case FLASHBACK: + case HARMONIZE: case MADNESS: case TRANSFORMED: case DISTURB: @@ -726,6 +739,11 @@ public abstract class AbilityImpl implements Ability { ((EarlyTargetCost) cost).chooseTarget(game, this, controller); } } + for (ManaCost cost : getManaCostsToPay()) { + if (cost instanceof EarlyTargetCost && cost.getTargets().isEmpty()) { + ((EarlyTargetCost) cost).chooseTarget(game, this, controller); + } + } } /** @@ -764,8 +782,15 @@ public abstract class AbilityImpl implements Ability { if (!variableManaCost.isPaid()) { // should only happen for human players int xValue; if (!noMana || variableManaCost.getCostType().canUseAnnounceOnFreeCast()) { - xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), - "Announce the value for " + variableManaCost.getText(), game, this); + if (variableManaCost.wasAnnounced()) { + // announce by rules + xValue = variableManaCost.getAmount(); + } else { + // announce by player + xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), + "Announce the value for " + variableManaCost.getText(), game, this); + } + int amountMana = xValue * variableManaCost.getXInstancesCount(); StringBuilder manaString = threadLocalBuilder.get(); if (variableManaCost.getFilter() == null || variableManaCost.getFilter().isGeneric()) { @@ -1072,6 +1097,60 @@ public abstract class AbilityImpl implements Ability { } } + @Override + public void setVariableCostsMinMax(int min, int max) { + // modify all values (mtg rules allow only one type of X, so min/max must be shared between all X instances) + + // base cost + for (ManaCost cost : getManaCosts()) { + if (cost instanceof MinMaxVariableCost) { + MinMaxVariableCost minMaxCost = (MinMaxVariableCost) cost; + minMaxCost.setMinX(min); + minMaxCost.setMaxX(max); + } + } + + // prepared cost + for (ManaCost cost : getManaCostsToPay()) { + if (cost instanceof MinMaxVariableCost) { + MinMaxVariableCost minMaxCost = (MinMaxVariableCost) cost; + minMaxCost.setMinX(min); + minMaxCost.setMaxX(max); + } + } + } + + @Override + public void setVariableCostsValue(int xValue) { + // only mana cost supported + + // base cost + boolean foundBaseCost = false; + for (ManaCost cost : getManaCosts()) { + if (cost instanceof VariableManaCost) { + foundBaseCost = true; + ((VariableManaCost) cost).setMinX(xValue); + ((VariableManaCost) cost).setMaxX(xValue); + ((VariableManaCost) cost).setAmount(xValue, xValue, false); + } + } + + // prepared cost + boolean foundPreparedCost = false; + for (ManaCost cost : getManaCostsToPay()) { + if (cost instanceof VariableManaCost) { + foundPreparedCost = true; + ((VariableManaCost) cost).setMinX(xValue); + ((VariableManaCost) cost).setMaxX(xValue); + ((VariableManaCost) cost).setAmount(xValue, xValue, false); + } + } + + if (!foundPreparedCost || !foundBaseCost) { + throw new IllegalArgumentException("Wrong code usage: auto-announced X values allowed in mana costs only"); + } + } + @Override public void addEffect(Effect effect) { if (effect != null) { @@ -1676,17 +1755,6 @@ public abstract class AbilityImpl implements Ability { } } - /** - * Dynamic cost modification for ability.
- * Example: if it need stack related info (like real targets) then must - * check two states (game.inCheckPlayableState):
- * 1. In playable state it must check all possible use cases (e.g. allow to - * reduce on any available target and modes)
- * 2. In real cast state it must check current use case (e.g. real selected - * targets and modes) - * - * @param costAdjuster - */ @Override public AbilityImpl setCostAdjuster(CostAdjuster costAdjuster) { this.costAdjuster = costAdjuster; @@ -1694,14 +1762,23 @@ public abstract class AbilityImpl implements Ability { } @Override - public CostAdjuster getCostAdjuster() { - return costAdjuster; + public void adjustX(Game game) { + if (costAdjuster != null) { + costAdjuster.prepareX(this, game); + } } @Override - public void adjustCosts(Game game) { + public void adjustCostsPrepare(Game game) { if (costAdjuster != null) { - costAdjuster.adjustCosts(this, game); + costAdjuster.prepareCost(this, game); + } + } + + @Override + public void adjustCostsModify(Game game, CostModificationType costModificationType) { + if (costAdjuster != null) { + costAdjuster.modifyCost(this, game, costModificationType); } } diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 2243a693b61..2c5c03ef220 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -6,8 +6,8 @@ import mage.MageObject; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.VariableManaCost; import mage.abilities.keyword.FlashAbility; -import mage.cards.AdventureCardSpell; import mage.cards.Card; +import mage.cards.SpellOptionCard; import mage.cards.SplitCard; import mage.constants.*; import mage.game.Game; @@ -99,7 +99,7 @@ public class SpellAbility extends ActivatedAbilityImpl { // forced to cast (can be part id or main id) Set idsToCheck = new HashSet<>(); idsToCheck.add(object.getId()); - if (object instanceof Card && !(object instanceof AdventureCardSpell)) { + if (object instanceof Card && !(object instanceof SpellOptionCard)) { idsToCheck.add(((Card) object).getMainCard().getId()); } for (UUID idToCheck : idsToCheck) { diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java index 9e5a7490e98..13377578b94 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java @@ -70,7 +70,7 @@ public class BecomesTargetAnyTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java index 2995cacb11b..2c4c8d13a85 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java @@ -54,7 +54,7 @@ public class BecomesTargetAttachedTriggeredAbility extends TriggeredAbilityImpl if (enchantment == null || enchantment.getAttachedTo() == null || !event.getTargetId().equals(enchantment.getAttachedTo())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java index c31b39b22d2..9b60880a21d 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java @@ -63,7 +63,7 @@ public class BecomesTargetControllerTriggeredAbility extends TriggeredAbilityImp return false; } } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java index e1fcc98a772..0b410c7e078 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java @@ -57,7 +57,7 @@ public class BecomesTargetSourceTriggeredAbility extends TriggeredAbilityImpl { if (!event.getTargetId().equals(getSourceId())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/condition/common/CastAnotherSpellThisTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CastAnotherSpellThisTurnCondition.java new file mode 100644 index 00000000000..10ee9bdd27f --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/CastAnotherSpellThisTurnCondition.java @@ -0,0 +1,44 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.List; +import java.util.Objects; + +/** + * @author balazskristof, Jmlundeen + */ +public enum CastAnotherSpellThisTurnCondition implements Condition { + instance; + private final Hint hint = new ConditionHint( + this, "You've cast another spell this turn" + ); + + @Override + public boolean apply(Game game, Ability source) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + if (watcher == null) { + return false; + } + List spells = watcher.getSpellsCastThisTurn(source.getControllerId()); + return spells != null && spells + .stream() + .filter(Objects::nonNull) + .anyMatch(spell -> !spell.getSourceId().equals(source.getSourceId()) || spell.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter()); + } + + public Hint getHint() { + return hint; + } + + @Override + public String toString() { + return "you've cast another spell this turn"; + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java index fe3ca1e23ce..6e0e5b65174 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java @@ -4,7 +4,7 @@ package mage.abilities.condition.common; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.condition.Condition; -import mage.cards.AdventureCardSpell; +import mage.cards.SpellOptionCard; import mage.cards.Card; import mage.cards.ModalDoubleFacedCardHalf; import mage.cards.SplitCardHalf; @@ -20,7 +20,7 @@ public enum IsBeingCastFromHandCondition implements Condition { @Override public boolean apply(Game game, Ability source) { MageObject object = game.getObject(source); - if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) { + if (object instanceof SplitCardHalf || object instanceof SpellOptionCard || object instanceof ModalDoubleFacedCardHalf) { UUID mainCardId = ((Card) object).getMainCard().getId(); object = game.getObject(mainCardId); } diff --git a/Mage/src/main/java/mage/abilities/condition/common/ModeChoiceSourceCondition.java b/Mage/src/main/java/mage/abilities/condition/common/ModeChoiceSourceCondition.java deleted file mode 100644 index 8ce77f24633..00000000000 --- a/Mage/src/main/java/mage/abilities/condition/common/ModeChoiceSourceCondition.java +++ /dev/null @@ -1,26 +0,0 @@ - -package mage.abilities.condition.common; - -import mage.abilities.Ability; -import mage.abilities.condition.Condition; -import mage.game.Game; - -/** - * - * @author LevelX2 - */ -public class ModeChoiceSourceCondition implements Condition { - - private final String mode; - - - public ModeChoiceSourceCondition(String mode) { - this.mode = mode; - } - - @Override - public boolean apply(Game game, Ability source) { - String chosenMode = (String) game.getState().getValue(source.getSourceId() + "_modeChoice"); - return chosenMode != null && chosenMode.equals(mode); - } -} diff --git a/Mage/src/main/java/mage/abilities/condition/common/YouCastExactOneSpellThisTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/YouCastExactOneSpellThisTurnCondition.java new file mode 100644 index 00000000000..98aa5c75dc0 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/YouCastExactOneSpellThisTurnCondition.java @@ -0,0 +1,19 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +/** + * @author androosss + */ +public enum YouCastExactOneSpellThisTurnCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + return watcher != null && watcher.getSpellsCastThisTurn(source.getControllerId()).size() == 1; + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/CostAdjuster.java b/Mage/src/main/java/mage/abilities/costs/CostAdjuster.java index ea1d77449d0..86bc96ee985 100644 --- a/Mage/src/main/java/mage/abilities/costs/CostAdjuster.java +++ b/Mage/src/main/java/mage/abilities/costs/CostAdjuster.java @@ -1,24 +1,86 @@ package mage.abilities.costs; import mage.abilities.Ability; +import mage.constants.CostModificationType; import mage.game.Game; import java.io.Serializable; /** - * @author TheElk801 + * Dynamic costs implementation to control {X} or other costs, can be used in spells and abilities + *

+ * Possible use cases: + * - define {X} costs like X cards to discard (mana and non-mana values); + * - define {X} limits before announce (to help in UX and AI logic); + * - define any dynamic costs; + * - use as simple cost increase/reduce effect; + *

+ * Calls order by game engine: + * - ... early cost target selection for EarlyTargetCost ... + * - prepareX + * - ... x announce ... + * - prepareCost + * - increaseCost + * - reduceCost + * - ... normal target selection and payment ... + * + * @author TheElk801, JayDi85 */ -@FunctionalInterface public interface CostAdjuster extends Serializable { /** - * Must check playable and real cast states. - * Example: if it need stack related info (like real targets) then must check two states (game.inCheckPlayableState): - * 1. In playable state it must check all possible use cases (e.g. allow to reduce on any available target and modes) - * 2. In real cast state it must check current use case (e.g. real selected targets and modes) - * - * @param ability - * @param game + * Prepare {X} costs settings or define auto-announced mana values + *

+ * Usage example: + * - define auto-announced mana value {X} by ability.setVariableCostsValue + * - define possible {X} settings by ability.setVariableCostsMinMax */ - void adjustCosts(Ability ability, Game game); + default void prepareX(Ability ability, Game game) { + // do nothing + } + + /** + * Prepare any dynamic costs + *

+ * Usage example: + * - add real cost after {X} mana value announce by CardUtil.getSourceCostsTagX + * - add dynamic cost from game data + */ + default void prepareCost(Ability ability, Game game) { + // do nothing + } + + /** + * Simple cost reduction effect + */ + default void reduceCost(Ability ability, Game game) { + // do nothing + } + + /** + * Simple cost increase effect + */ + default void increaseCost(Ability ability, Game game) { + // do nothing + } + + /** + * Default implementation. Override reduceCost or increaseCost instead + * TODO: make it private after java 9+ migrate + */ + default void modifyCost(Ability ability, Game game, CostModificationType costModificationType) { + switch (costModificationType) { + case REDUCE_COST: + reduceCost(ability, game); + break; + case INCREASE_COST: + increaseCost(ability, game); + break; + case SET_COST: + // do nothing + break; + default: + throw new IllegalArgumentException("Unknown mod type: " + costModificationType); + } + } } diff --git a/Mage/src/main/java/mage/abilities/costs/EarlyTargetCost.java b/Mage/src/main/java/mage/abilities/costs/EarlyTargetCost.java index 359188f7fdc..23e71faa203 100644 --- a/Mage/src/main/java/mage/abilities/costs/EarlyTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/EarlyTargetCost.java @@ -6,20 +6,11 @@ import mage.players.Player; /** * @author Grath - * Costs which extend this class need to have targets chosen, and those targets must be chosen during 601.2b step. + *

+ * Support 601.2b rules for ealry target choice before X announce and other actions */ -public abstract class EarlyTargetCost extends CostImpl { +public interface EarlyTargetCost { - protected EarlyTargetCost() { - super(); - } + void chooseTarget(Game game, Ability source, Player controller); - protected EarlyTargetCost(final EarlyTargetCost cost) { - super(cost); - } - - @Override - public abstract EarlyTargetCost copy(); - - public abstract void chooseTarget(Game game, Ability source, Player controller); } diff --git a/Mage/src/main/java/mage/abilities/costs/MinMaxVariableCost.java b/Mage/src/main/java/mage/abilities/costs/MinMaxVariableCost.java new file mode 100644 index 00000000000..80b8a1d12ed --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/MinMaxVariableCost.java @@ -0,0 +1,15 @@ +package mage.abilities.costs; + +/** + * @author jayDi85 + */ +public interface MinMaxVariableCost extends VariableCost { + + int getMinX(); + + void setMinX(int minX); + + int getMaxX(); + + void setMaxX(int maxX); +} diff --git a/Mage/src/main/java/mage/abilities/costs/common/DiscardXTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/DiscardXTargetCost.java index 8fc089b12de..ac863a0e819 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/DiscardXTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/DiscardXTargetCost.java @@ -10,11 +10,20 @@ import mage.players.Player; import mage.target.common.TargetCardInHand; /** + * Used to setup discard cost WITHOUT {X} mana cost + *

+ * If you have {X} in spell's mana cost then use DiscardXCardsCostAdjuster instead + *

+ * Example: + * - {2}{U}{R} + * - As an additional cost to cast this spell, discard X cards. + * * @author LevelX2 */ public class DiscardXTargetCost extends VariableCostImpl { protected FilterCard filter; + protected boolean isRandom = false; public DiscardXTargetCost(FilterCard filter) { this(filter, false); @@ -30,6 +39,12 @@ public class DiscardXTargetCost extends VariableCostImpl { protected DiscardXTargetCost(final DiscardXTargetCost cost) { super(cost); this.filter = cost.filter; + this.isRandom = cost.isRandom; + } + + public DiscardXTargetCost withRandom() { + this.isRandom = true; + return this; } @Override @@ -49,6 +64,6 @@ public class DiscardXTargetCost extends VariableCostImpl { @Override public Cost getFixedCostsFromAnnouncedValue(int xValue) { TargetCardInHand target = new TargetCardInHand(xValue, filter); - return new DiscardTargetCost(target); + return new DiscardTargetCost(target, this.isRandom); } } diff --git a/Mage/src/main/java/mage/abilities/costs/common/PayLoyaltyCost.java b/Mage/src/main/java/mage/abilities/costs/common/PayLoyaltyCost.java index 2b8885471c0..fff0baae6bb 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/PayLoyaltyCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/PayLoyaltyCost.java @@ -35,10 +35,10 @@ public class PayLoyaltyCost extends CostImpl { int loyaltyCost = amount; - // apply cost modification + // apply dynamic costs and cost modification if (ability instanceof LoyaltyAbility) { LoyaltyAbility copiedAbility = ((LoyaltyAbility) ability).copy(); - copiedAbility.adjustCosts(game); + copiedAbility.adjustX(game); game.getContinuousEffects().costModification(copiedAbility, game); loyaltyCost = 0; for (Cost cost : copiedAbility.getCosts()) { diff --git a/Mage/src/main/java/mage/abilities/costs/common/PayVariableLoyaltyCost.java b/Mage/src/main/java/mage/abilities/costs/common/PayVariableLoyaltyCost.java index 42bca28413b..4806e359f85 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/PayVariableLoyaltyCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/PayVariableLoyaltyCost.java @@ -59,10 +59,10 @@ public class PayVariableLoyaltyCost extends VariableCostImpl { int maxValue = permanent.getCounters(game).getCount(CounterType.LOYALTY); - // apply cost modification + // apply dynamic costs and cost modification if (source instanceof LoyaltyAbility) { LoyaltyAbility copiedAbility = ((LoyaltyAbility) source).copy(); - copiedAbility.adjustCosts(game); + copiedAbility.adjustX(game); game.getContinuousEffects().costModification(copiedAbility, game); for (Cost cost : copiedAbility.getCosts()) { if (cost instanceof PayVariableLoyaltyCost) { diff --git a/Mage/src/main/java/mage/abilities/costs/costadjusters/CommanderManaValueAdjuster.java b/Mage/src/main/java/mage/abilities/costs/costadjusters/CommanderManaValueAdjuster.java index cd018459433..d168aa46850 100644 --- a/Mage/src/main/java/mage/abilities/costs/costadjusters/CommanderManaValueAdjuster.java +++ b/Mage/src/main/java/mage/abilities/costs/costadjusters/CommanderManaValueAdjuster.java @@ -13,7 +13,7 @@ public enum CommanderManaValueAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { CardUtil.reduceCost(ability, GreatestCommanderManaValue.instance.calculate(game, ability, null)); } } diff --git a/Mage/src/main/java/mage/abilities/costs/costadjusters/DiscardXCardsCostAdjuster.java b/Mage/src/main/java/mage/abilities/costs/costadjusters/DiscardXCardsCostAdjuster.java new file mode 100644 index 00000000000..302d4d7e354 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/costadjusters/DiscardXCardsCostAdjuster.java @@ -0,0 +1,73 @@ +package mage.abilities.costs.costadjusters; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.effects.common.InfoEffect; +import mage.cards.Card; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInHand; +import mage.util.CardUtil; + +/** + * Used to setup discard cost with {X} mana cost + *

+ * If you don't have {X} then use DiscardXTargetCost instead + *

+ * Example: + * - {X}{1}{B} + * - As an additional cost to cast this spell, discard X cards. + * + * @author JayDi85 + */ +public class DiscardXCardsCostAdjuster implements CostAdjuster { + + private final FilterCard filter; + + private DiscardXCardsCostAdjuster(FilterCard filter) { + this.filter = filter; + } + + @Override + public void prepareX(Ability ability, Game game) { + Player controller = game.getPlayer(ability.getControllerId()); + if (controller == null) { + return; + } + + int minX = 0; + int maxX = controller.getHand().getCards(this.filter, ability.getControllerId(), ability, game).size(); + ability.setVariableCostsMinMax(minX, maxX); + } + + @Override + public void prepareCost(Ability ability, Game game) { + int x = CardUtil.getSourceCostsTagX(game, ability, -1); + if (x >= 0) { + ability.addCost(new DiscardTargetCost(new TargetCardInHand(x, x, this.filter))); + } + } + + public static void addAdjusterAndMessage(Card card, FilterCard filter) { + addAdjusterAndMessage(card, filter, false); + } + + public static void addAdjusterAndMessage(Card card, FilterCard filter, boolean isRandom) { + if (card.getSpellAbility().getManaCosts().getVariableCosts().isEmpty()) { + // how to fix: use DiscardXTargetCost + throw new IllegalArgumentException("Wrong code usage: that's cost adjuster must be used with {X} in mana costs only - " + card); + } + + Ability ability = new SimpleStaticAbility( + Zone.ALL, new InfoEffect("As an additional cost to cast this spell, discard X " + filter.getMessage()) + ); + ability.setRuleAtTheTop(true); + card.addAbility(ability); + + card.getSpellAbility().setCostAdjuster(new DiscardXCardsCostAdjuster(filter)); + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/costadjusters/DomainAdjuster.java b/Mage/src/main/java/mage/abilities/costs/costadjusters/DomainAdjuster.java index 37cf5e704a2..5037eedb47d 100644 --- a/Mage/src/main/java/mage/abilities/costs/costadjusters/DomainAdjuster.java +++ b/Mage/src/main/java/mage/abilities/costs/costadjusters/DomainAdjuster.java @@ -13,7 +13,7 @@ public enum DomainAdjuster implements CostAdjuster { instance; @Override - public void adjustCosts(Ability ability, Game game) { + public void reduceCost(Ability ability, Game game) { CardUtil.reduceCost(ability, DomainValue.REGULAR.calculate(game, ability, null)); } } diff --git a/Mage/src/main/java/mage/abilities/costs/costadjusters/ExileCardsFromHandAdjuster.java b/Mage/src/main/java/mage/abilities/costs/costadjusters/ExileCardsFromHandAdjuster.java index 742e315efcb..9d9d287eae2 100644 --- a/Mage/src/main/java/mage/abilities/costs/costadjusters/ExileCardsFromHandAdjuster.java +++ b/Mage/src/main/java/mage/abilities/costs/costadjusters/ExileCardsFromHandAdjuster.java @@ -25,22 +25,29 @@ public class ExileCardsFromHandAdjuster implements CostAdjuster { } @Override - public void adjustCosts(Ability ability, Game game) { - if (game.inCheckPlayableState()) { - return; - } + public void reduceCost(Ability ability, Game game) { Player player = game.getPlayer(ability.getControllerId()); if (player == null) { return; } + int cardCount = player.getHand().count(filter, game); - int toExile = cardCount > 0 ? player.getAmount( - 0, cardCount, "Choose how many " + filter.getMessage() + " to exile", game - ) : 0; - if (toExile > 0) { - ability.addCost(new ExileFromHandCost(new TargetCardInHand(toExile, filter))); - CardUtil.reduceCost(ability, 2 * toExile); + int reduceCount; + if (game.inCheckPlayableState()) { + // possible + reduceCount = 2 * cardCount; + } else { + // real - need to choose + // TODO: need early target cost instead dialog here + int toExile = cardCount == 0 ? 0 : player.getAmount( + 0, cardCount, "Choose how many " + filter.getMessage() + " to exile", game + ); + reduceCount = 2 * toExile; + if (toExile > 0) { + ability.addCost(new ExileFromHandCost(new TargetCardInHand(toExile, filter))); + } } + CardUtil.reduceCost(ability, 2 * reduceCount); } public static final void addAdjusterAndMessage(Card card, FilterCard filter) { diff --git a/Mage/src/main/java/mage/abilities/costs/costadjusters/ImprintedManaValueXCostAdjuster.java b/Mage/src/main/java/mage/abilities/costs/costadjusters/ImprintedManaValueXCostAdjuster.java new file mode 100644 index 00000000000..76ac9ca2a0a --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/costadjusters/ImprintedManaValueXCostAdjuster.java @@ -0,0 +1,37 @@ +package mage.abilities.costs.costadjusters; + +import mage.abilities.Ability; +import mage.abilities.costs.CostAdjuster; +import mage.cards.Card; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * Used for {X} mana cost that must be replaced by imprinted mana value + *

+ * Example: + * - Elite Arcanist + * - {X}, {T}: Copy the exiled card. ... X is the converted mana cost of the exiled card. + * + * @author JayDi85 + */ +public enum ImprintedManaValueXCostAdjuster implements CostAdjuster { + instance; + + @Override + public void prepareX(Ability ability, Game game) { + int manaValue = Integer.MAX_VALUE; + + Permanent sourcePermanent = game.getPermanent(ability.getSourceId()); + if (sourcePermanent != null + && sourcePermanent.getImprinted() != null + && !sourcePermanent.getImprinted().isEmpty()) { + Card imprintedInstant = game.getCard(sourcePermanent.getImprinted().get(0)); + if (imprintedInstant != null) { + manaValue = imprintedInstant.getManaValue(); + } + } + + ability.setVariableCostsValue(manaValue); + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/costadjusters/LegendaryCreatureCostAdjuster.java b/Mage/src/main/java/mage/abilities/costs/costadjusters/LegendaryCreatureCostAdjuster.java index cd2406ed5f3..3d4f08fc785 100644 --- a/Mage/src/main/java/mage/abilities/costs/costadjusters/LegendaryCreatureCostAdjuster.java +++ b/Mage/src/main/java/mage/abilities/costs/costadjusters/LegendaryCreatureCostAdjuster.java @@ -29,10 +29,8 @@ public enum LegendaryCreatureCostAdjuster implements CostAdjuster { ); @Override - public void adjustCosts(Ability ability, Game game) { - int count = game.getBattlefield().count( - filter, ability.getControllerId(), ability, game - ); + public void reduceCost(Ability ability, Game game) { + int count = game.getBattlefield().count(filter, ability.getControllerId(), ability, game); if (count > 0) { CardUtil.reduceCost(ability, count); } diff --git a/Mage/src/main/java/mage/abilities/costs/mana/ManaCost.java b/Mage/src/main/java/mage/abilities/costs/mana/ManaCost.java index b0d51dca448..f72b7fa0a86 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/ManaCost.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/ManaCost.java @@ -32,7 +32,7 @@ public interface ManaCost extends Cost { ManaOptions getOptions(); /** - * Return all options for paying the mana cost (this) while taking into accoutn if the player can pay life. + * Return all options for paying the mana cost (this) while taking into account if the player can pay life. * Used to correctly highlight (or not) spells with Phyrexian mana depending on if the player can pay life costs. *

* E.g. Tezzeret's Gambit has a cost of {3}{U/P}. diff --git a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java index 1fd9bdd5f90..c557d260326 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java @@ -72,7 +72,7 @@ public abstract class ManaCostImpl extends CostImpl implements ManaCost { } @Override - public ManaOptions getOptions() { + public final ManaOptions getOptions() { return getOptions(true); } diff --git a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java index 118379ccef8..70ba8044ff5 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java @@ -439,7 +439,7 @@ public class ManaCostsImpl extends ArrayList implements M this.add(new ColoredManaCost(ColoredManaSymbol.lookup(symbol.charAt(0)))); } else // check X wasn't added before if (modifierForX == 0) { - // count X occurence + // count X occurrence for (String s : symbols) { if (s.equals("X")) { modifierForX++; diff --git a/Mage/src/main/java/mage/abilities/costs/mana/VariableManaCost.java b/Mage/src/main/java/mage/abilities/costs/mana/VariableManaCost.java index d2fa2b16f75..8a8d3e25a00 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/VariableManaCost.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/VariableManaCost.java @@ -3,17 +3,19 @@ package mage.abilities.costs.mana; import mage.Mana; import mage.abilities.Ability; import mage.abilities.costs.Cost; -import mage.abilities.costs.VariableCost; +import mage.abilities.costs.MinMaxVariableCost; import mage.abilities.costs.VariableCostType; +import mage.abilities.mana.ManaOptions; import mage.constants.ColoredManaSymbol; import mage.filter.FilterMana; import mage.game.Game; import mage.players.ManaPool; +import mage.util.CardUtil; /** * @author BetaSteward_at_googlemail.com, JayDi85 */ -public final class VariableManaCost extends ManaCostImpl implements VariableCost { +public class VariableManaCost extends ManaCostImpl implements MinMaxVariableCost { // variable mana cost usage on 2019-06-20: // 1. as X value in spell/ability cast (announce X, set VariableManaCost as paid and add generic mana to pay instead) @@ -23,7 +25,7 @@ public final class VariableManaCost extends ManaCostImpl implements VariableCost protected int xInstancesCount; // number of {X} instances in cost like {X} or {X}{X} protected int xValue = 0; // final X value after announce and replace events protected int xPay = 0; // final/total need pay after announce and replace events (example: {X}{X}, X=3, xPay = 6) - protected boolean wasAnnounced = false; + protected boolean wasAnnounced = false; // X was announced by player or auto-defined by CostAdjuster protected FilterMana filter; // mana filter that can be used for that cost protected int minX = 0; @@ -59,6 +61,18 @@ public final class VariableManaCost extends ManaCostImpl implements VariableCost return 0; } + @Override + public ManaOptions getOptions(boolean canPayLifeCost) { + ManaOptions res = new ManaOptions(); + + // limit mana options for better performance + CardUtil.distributeValues(10, getMinX(), getMaxX()).forEach(value -> { + res.add(Mana.GenericMana(value)); + }); + + return res; + } + @Override public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costToPay) { // X mana cost always pays as generic mana @@ -86,6 +100,10 @@ public final class VariableManaCost extends ManaCostImpl implements VariableCost return this.isColorlessPaid(xPay); } + public boolean wasAnnounced() { + return this.wasAnnounced; + } + @Override public VariableManaCost getUnpaid() { return this; @@ -122,18 +140,22 @@ public final class VariableManaCost extends ManaCostImpl implements VariableCost return this.xInstancesCount; } + @Override public int getMinX() { return minX; } + @Override public void setMinX(int minX) { this.minX = minX; } + @Override public int getMaxX() { return maxX; } + @Override public void setMaxX(int maxX) { this.maxX = maxX; } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInControllerHandCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInControllerHandCount.java index ea8cde0572d..6163ffdf6a2 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInControllerHandCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInControllerHandCount.java @@ -3,35 +3,58 @@ package mage.abilities.dynamicvalue.common; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.Controllable; import mage.game.Game; import mage.players.Player; +import java.util.Optional; +import java.util.Set; + public enum CardsInControllerHandCount implements DynamicValue { - instance; + + ANY(StaticFilters.FILTER_CARD_CARDS), + CREATURES(StaticFilters.FILTER_CARD_CREATURES), + LANDS(StaticFilters.FILTER_CARD_LANDS); + + private final FilterCard filter; + private final ValueHint hint; + + CardsInControllerHandCount(FilterCard filter) { + this.filter = filter; + this.hint = new ValueHint(filter.getMessage() + " in your hand", this); + } @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - if (sourceAbility != null) { - Player controller = game.getPlayer(sourceAbility.getControllerId()); - if (controller != null) { - return controller.getHand().size(); - } - } - return 0; + return Optional + .ofNullable(sourceAbility) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .map(Player::getHand) + .map(Set::size) + .orElse(0); } @Override public CardsInControllerHandCount copy() { - return CardsInControllerHandCount.instance; + return this; } @Override public String getMessage() { - return "cards in your hand"; + return this.filter.getMessage() + " in your hand"; } @Override public String toString() { return "1"; } + + public Hint getHint() { + return this.hint; + } } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java index bd60ddb158f..4ec730c0f08 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java @@ -1,4 +1,3 @@ - package mage.abilities.dynamicvalue.common; import mage.MageInt; @@ -6,6 +5,9 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.game.Game; @@ -13,16 +15,21 @@ import mage.game.Game; * @author TheElk801 */ public enum GreatestToughnessAmongControlledCreaturesValue implements DynamicValue { - instance; + ALL(StaticFilters.FILTER_CONTROLLED_CREATURES), + OTHER(StaticFilters.FILTER_OTHER_CONTROLLED_CREATURES); + private final FilterPermanent filter; + private final Hint hint; + + GreatestToughnessAmongControlledCreaturesValue(FilterPermanent filter) { + this.filter = filter; + this.hint = new ValueHint("The greatest toughness among " + filter.getMessage(), this); + } @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { return game .getBattlefield() - .getActivePermanents( - StaticFilters.FILTER_CONTROLLED_CREATURE, - sourceAbility.getControllerId(), game - ) + .getActivePermanents(filter, sourceAbility.getControllerId(), game) .stream() .map(MageObject::getToughness) .mapToInt(MageInt::getValue) @@ -32,12 +39,12 @@ public enum GreatestToughnessAmongControlledCreaturesValue implements DynamicVal @Override public GreatestToughnessAmongControlledCreaturesValue copy() { - return GreatestToughnessAmongControlledCreaturesValue.instance; + return this; } @Override public String getMessage() { - return "the greatest toughness among creatures you control"; + return "the greatest toughness among " + filter.getMessage(); } @Override @@ -45,4 +52,7 @@ public enum GreatestToughnessAmongControlledCreaturesValue implements DynamicVal return "X"; } + public Hint getHint() { + return hint; + } } diff --git a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java index 09f1c74629b..6d8e2d6fea4 100644 --- a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java @@ -6,14 +6,13 @@ import mage.abilities.ActivatedAbility; import mage.cards.Card; import mage.cards.ModalDoubleFacedCard; import mage.cards.SplitCard; +import mage.cards.CardWithSpellOption; import mage.constants.*; import mage.game.Game; import mage.players.Player; import java.util.UUID; -import mage.cards.AdventureCard; - /** * @author BetaSteward_at_googlemail.com */ @@ -103,9 +102,9 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements if (!rightCard.isLand(game)) { player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts(), identifier); } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { Card creatureCard = card.getMainCard(); - Card spellCard = ((AdventureCard) card).getSpellCard(); + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); player.setCastSourceIdWithAlternateMana(creatureCard.getId(), null, creatureCard.getSpellAbility().getCosts(), identifier); player.setCastSourceIdWithAlternateMana(spellCard.getId(), null, spellCard.getSpellAbility().getCosts(), identifier); } diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index ea47fe4f39e..a60322ba64a 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -549,9 +549,9 @@ public class ContinuousEffects implements Serializable { // rules: // 708.4. In every zone except the stack, the characteristics of a split card are those of its two halves combined. idToCheck = ((SplitCardHalf) objectToCheck).getMainCard().getId(); - } else if (!type.needPlayCardAbility() && objectToCheck instanceof AdventureCardSpell) { - // adventure spell uses alternative characteristics for spell/stack, all other cases must use main card - idToCheck = ((AdventureCardSpell) objectToCheck).getMainCard().getId(); + } else if (!type.needPlayCardAbility() && objectToCheck instanceof CardWithSpellOption) { + // adventure/omen spell uses alternative characteristics for spell/stack, all other cases must use main card + idToCheck = ((CardWithSpellOption) objectToCheck).getMainCard().getId(); } else if (!type.needPlayCardAbility() && objectToCheck instanceof ModalDoubleFacedCardHalf) { // each mdf side uses own characteristics to check for playing, all other cases must use main card // rules: @@ -688,6 +688,8 @@ public class ContinuousEffects implements Serializable { * the battlefield for * {@link CostModificationEffect cost modification effects} and applies them * if necessary. + *

+ * Warning, don't forget to call ability.adjustX before any cost modifications * * @param abilityToModify * @param game @@ -695,6 +697,10 @@ public class ContinuousEffects implements Serializable { public void costModification(Ability abilityToModify, Game game) { List costEffects = getApplicableCostModificationEffects(game); + // add dynamic costs from X and other places + abilityToModify.adjustCostsPrepare(game); + + abilityToModify.adjustCostsModify(game, CostModificationType.INCREASE_COST); for (CostModificationEffect effect : costEffects) { if (effect.getModificationType() == CostModificationType.INCREASE_COST) { Set abilities = costModificationEffects.getAbility(effect.getId()); @@ -706,6 +712,7 @@ public class ContinuousEffects implements Serializable { } } + abilityToModify.adjustCostsModify(game, CostModificationType.REDUCE_COST); for (CostModificationEffect effect : costEffects) { if (effect.getModificationType() == CostModificationType.REDUCE_COST) { Set abilities = costModificationEffects.getAbility(effect.getId()); @@ -717,6 +724,7 @@ public class ContinuousEffects implements Serializable { } } + abilityToModify.adjustCostsModify(game, CostModificationType.SET_COST); for (CostModificationEffect effect : costEffects) { if (effect.getModificationType() == CostModificationType.SET_COST) { Set abilities = costModificationEffects.getAbility(effect.getId()); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChooseModeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChooseModeEffect.java index 5ca8e531c68..978e0cb8f2e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChooseModeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseModeEffect.java @@ -1,39 +1,41 @@ package mage.abilities.effects.common; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.choices.Choice; import mage.choices.ChoiceImpl; +import mage.constants.ModeChoice; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.CardUtil; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + /** * @author LevelX2 */ public class ChooseModeEffect extends OneShotEffect { - protected final List modes = new ArrayList<>(); - protected final String choiceMessage; + protected final List modes = new ArrayList<>(); + protected final String message; - public ChooseModeEffect(String choiceMessage, String... modes) { + public ChooseModeEffect(ModeChoice... modes) { super(Outcome.Neutral); - this.choiceMessage = choiceMessage; this.modes.addAll(Arrays.asList(modes)); - this.staticText = setText(); + this.message = makeMessage(this.modes); + this.staticText = "choose " + this.message; } protected ChooseModeEffect(final ChooseModeEffect effect) { super(effect); this.modes.addAll(effect.modes); - this.choiceMessage = effect.choiceMessage; + this.message = effect.message; } @Override @@ -48,34 +50,27 @@ public class ChooseModeEffect extends OneShotEffect { if (sourcePermanent == null) { sourcePermanent = game.getPermanentEntering(source.getSourceId()); } - if (controller != null && sourcePermanent != null) { - Choice choice = new ChoiceImpl(true); - choice.setMessage(choiceMessage + CardUtil.getSourceLogName(game, source)); - choice.getChoices().addAll(modes); - if (controller.choose(Outcome.Neutral, choice, game)) { - if (!game.isSimulation()) { - game.informPlayers(sourcePermanent.getLogName() + ": " + controller.getLogName() + " has chosen " + choice.getChoice()); - } - game.getState().setValue(source.getSourceId() + "_modeChoice", choice.getChoice()); - sourcePermanent.addInfo("_modeChoice", "Chosen mode: " + choice.getChoice() + "", game); - return true; - } + if (controller == null || sourcePermanent == null) { + return false; } - return false; + Choice choice = new ChoiceImpl(true); + choice.setMessage(message + "? (" + CardUtil.getSourceLogName(game, source) + ')'); + choice.getChoices().addAll(modes.stream().map(ModeChoice::toString).collect(Collectors.toList())); + if (!controller.choose(Outcome.Neutral, choice, game)) { + return false; + } + game.informPlayers(sourcePermanent.getLogName() + ": " + controller.getLogName() + " has chosen " + choice.getChoice()); + game.getState().setValue(source.getSourceId() + "_modeChoice", choice.getChoice()); + sourcePermanent.addInfo("_modeChoice", "Chosen mode: " + choice.getChoice() + "", game); + return true; } - private String setText() { - StringBuilder sb = new StringBuilder("choose "); - int count = 0; - for (String choice : modes) { - count++; - sb.append(choice); - if (count + 1 < modes.size()) { - sb.append(", "); - } else if (count < modes.size()) { - sb.append(" or "); - } - } - return sb.toString(); + private static String makeMessage(List modeChoices) { + return CardUtil.concatWithOr( + modeChoices + .stream() + .map(ModeChoice::toString) + .collect(Collectors.toList()) + ); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateXXTokenExiledEffectManaValueEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateXXTokenExiledEffectManaValueEffect.java new file mode 100644 index 00000000000..3b58617a27c --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateXXTokenExiledEffectManaValueEffect.java @@ -0,0 +1,74 @@ +package mage.abilities.effects.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.Token; +import mage.util.CardUtil; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; + +/** + * @author TheElk801 + */ +public class CreateXXTokenExiledEffectManaValueEffect extends OneShotEffect { + + private final Function tokenMaker; + + public CreateXXTokenExiledEffectManaValueEffect(Function tokenMaker, String description) { + super(Outcome.Benefit); + this.tokenMaker = tokenMaker; + staticText = "the exiled card's owner creates an X/X " + description + + "creature token, where X is the mana value of the exiled card"; + } + + private CreateXXTokenExiledEffectManaValueEffect(final CreateXXTokenExiledEffectManaValueEffect effect) { + super(effect); + this.tokenMaker = effect.tokenMaker; + } + + @Override + public CreateXXTokenExiledEffectManaValueEffect copy() { + return new CreateXXTokenExiledEffectManaValueEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanentLeftBattlefield = (Permanent) getValue("permanentLeftBattlefield"); + ExileZone exile = game.getExile().getExileZone( + CardUtil.getExileZoneId(game, source.getSourceId(), permanentLeftBattlefield.getZoneChangeCounter(game)) + ); + if (exile == null || exile.isEmpty()) { + return false; + } + // From ZNR Release Notes: + // https://magic.wizards.com/en/articles/archive/feature/zendikar-rising-release-notes-2020-09-10 + // If Skyclave Apparition's first ability exiled more than one card owned by a single player, + // that player creates a token with power and toughness equal to the sum of those cards' converted mana costs. + // If the first ability exiled cards owned by more than one player, each of those players creates a token + // with power and toughness equal to the sum of the converted mana costs of all cards exiled by the first ability. + Set owners = new HashSet<>(); + int totalCMC = exile + .getCards(game) + .stream() + .filter(Objects::nonNull) + .map(card -> { + owners.add(card.getOwnerId()); + return card; + }) + .mapToInt(MageObject::getManaValue) + .sum(); + for (UUID playerId : owners) { + tokenMaker.apply(totalCMC).putOntoBattlefield(1, game, source, playerId); + } + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java index 907881dea21..706af3cd62a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java @@ -5,7 +5,7 @@ import mage.abilities.MageSingleton; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; -import mage.cards.AdventureCardSpell; +import mage.cards.AdventureSpellCard; import mage.cards.Card; import mage.constants.AsThoughEffectType; import mage.constants.Duration; @@ -51,10 +51,10 @@ public class ExileAdventureSpellEffect extends OneShotEffect implements MageSing Spell spell = game.getStack().getSpell(source.getId()); if (spell != null) { Card spellCard = spell.getCard(); - if (spellCard instanceof AdventureCardSpell) { + if (spellCard instanceof AdventureSpellCard) { UUID exileId = adventureExileId(controller.getId(), game); game.getExile().createZone(exileId, "On an Adventure from " + controller.getName()); - AdventureCardSpell adventureSpellCard = (AdventureCardSpell) spellCard; + AdventureSpellCard adventureSpellCard = (AdventureSpellCard) spellCard; Card parentCard = adventureSpellCard.getParentCard(); if (controller.moveCardsToExile(parentCard, source, game, true, exileId, "On an Adventure from " + controller.getName())) { ContinuousEffect effect = new AdventureCastFromExileEffect(); diff --git a/Mage/src/main/java/mage/abilities/effects/common/PermanentsEnterBattlefieldTappedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PermanentsEnterBattlefieldTappedEffect.java index e9d949ac4ec..7eb63a0be7f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PermanentsEnterBattlefieldTappedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PermanentsEnterBattlefieldTappedEffect.java @@ -63,7 +63,7 @@ public class PermanentsEnterBattlefieldTappedEffect extends ReplacementEffectImp return staticText; } return filter.getMessage() - + " enter tapped" - + (duration == Duration.EndOfTurn ? " this turn" : ""); + + " enter" + (filter.getMessage().startsWith("each") ? "s" : "") + + " tapped" + (duration == Duration.EndOfTurn ? " this turn" : ""); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldWithCounterTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldWithCounterTargetEffect.java index 2bf4ce8bc1f..7d85a7a1a30 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldWithCounterTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldWithCounterTargetEffect.java @@ -7,6 +7,8 @@ import mage.counters.Counters; import mage.game.Game; import mage.util.CardUtil; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; /** @@ -17,15 +19,17 @@ public class ReturnFromGraveyardToBattlefieldWithCounterTargetEffect extends Ret private final Counters counters; private final String counterText; - public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(Counter counter) { - this(counter, false); + public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(Counter... counters) { + this(false, counters); } - public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(Counter counter, boolean additional) { + public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(boolean additional, Counter... counters) { super(false); this.counters = new Counters(); - this.counters.addCounter(counter); - this.counterText = makeText(counter, additional); + for (Counter counter : counters) { + this.counters.addCounter(counter); + } + this.counterText = makeText(additional, counters); } protected ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(final ReturnFromGraveyardToBattlefieldWithCounterTargetEffect effect) { @@ -47,22 +51,26 @@ public class ReturnFromGraveyardToBattlefieldWithCounterTargetEffect extends Ret return super.apply(game, source); } - private String makeText(Counter counter, boolean additional) { - StringBuilder sb = new StringBuilder(" with "); - if (counter.getCount() == 1) { - if (additional) { - sb.append("an additional ").append(counter.getName()); + private static String makeText(boolean additional, Counter... counters) { + List strings = new ArrayList<>(); + for (Counter counter : counters) { + StringBuilder sb = new StringBuilder(); + if (counter.getCount() == 1) { + if (additional) { + sb.append("an additional ").append(counter.getName()); + } else { + sb.append(CardUtil.addArticle(counter.getName())); + } + sb.append(" counter"); } else { - sb.append(CardUtil.addArticle(counter.getName())); + sb.append(CardUtil.numberToText(counter.getCount())); + sb.append(additional ? " additional " : " "); + sb.append(counter.getName()); + sb.append(" counters"); } - sb.append(" counter"); - } else { - sb.append(CardUtil.numberToText(counter.getCount())); - sb.append(additional ? " additional " : " "); - sb.append(counter.getName()); - sb.append(" counters"); + strings.add(sb.toString()); } - return sb.toString(); + return " with " + CardUtil.concatWithAnd(strings); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAnchorWordAbilitySourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAnchorWordAbilitySourceEffect.java new file mode 100644 index 00000000000..f4af6963691 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAnchorWordAbilitySourceEffect.java @@ -0,0 +1,52 @@ +package mage.abilities.effects.common.continuous; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.Effect; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * @author TheElk801 + */ +public class GainAnchorWordAbilitySourceEffect extends ContinuousEffectImpl { + + private final Ability ability; + private final ModeChoice modeChoice; + + public GainAnchorWordAbilitySourceEffect(Effect effect, ModeChoice modeChoice) { + this(new SimpleStaticAbility(effect), modeChoice); + } + + public GainAnchorWordAbilitySourceEffect(Ability ability, ModeChoice modeChoice) { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.staticText = "&bull " + modeChoice + " — " + ability.getRule(); + this.ability = ability; + this.modeChoice = modeChoice; + this.ability.setRuleVisible(false); + this.generateGainAbilityDependencies(ability, null); + } + + private GainAnchorWordAbilitySourceEffect(final GainAnchorWordAbilitySourceEffect effect) { + super(effect); + this.modeChoice = effect.modeChoice; + this.ability = effect.ability; + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null || !modeChoice.checkMode(game, source)) { + return false; + } + permanent.addAbility(ability, source.getSourceId(), game); + return true; + } + + @Override + public GainAnchorWordAbilitySourceEffect copy() { + return new GainAnchorWordAbilitySourceEffect(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayerCanOnlyAttackInDirectionRestrictionEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayerCanOnlyAttackInDirectionRestrictionEffect.java index b74ab65717c..8aaefded6da 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayerCanOnlyAttackInDirectionRestrictionEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayerCanOnlyAttackInDirectionRestrictionEffect.java @@ -6,6 +6,7 @@ import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.ChooseModeEffect; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.ModeChoice; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.Permanent; @@ -19,9 +20,6 @@ import java.util.UUID; */ public class PlayerCanOnlyAttackInDirectionRestrictionEffect extends RestrictionEffect { - public static final String ALLOW_ATTACKING_LEFT = "Allow attacking left"; - public static final String ALLOW_ATTACKING_RIGHT = "Allow attacking right"; - public PlayerCanOnlyAttackInDirectionRestrictionEffect(Duration duration, String directionText) { super(duration, Outcome.Neutral); staticText = duration + (duration.toString().isEmpty() ? "" : ", ") @@ -39,10 +37,7 @@ public class PlayerCanOnlyAttackInDirectionRestrictionEffect extends Restriction } public static Effect choiceEffect() { - return new ChooseModeEffect( - "Choose a direction to allow attacking in.", - ALLOW_ATTACKING_LEFT, ALLOW_ATTACKING_RIGHT - ).setText("choose left or right"); + return new ChooseModeEffect(ModeChoice.LEFT, ModeChoice.RIGHT); } @Override @@ -55,10 +50,13 @@ public class PlayerCanOnlyAttackInDirectionRestrictionEffect extends Restriction if (defenderId == null) { return true; } - - String allowedDirection = (String) game.getState().getValue(source.getSourceId() + "_modeChoice"); - if (allowedDirection == null) { - return true; // If no choice was made, the ability has no effect. + boolean left; + if (ModeChoice.LEFT.checkMode(game, source)) { + left = true; + } else if (ModeChoice.RIGHT.checkMode(game, source)) { + left = false; + } else { + return false; // If no choice was made, the ability has no effect. } Player playerAttacking = game.getPlayer(attacker.getControllerId()); @@ -83,18 +81,7 @@ public class PlayerCanOnlyAttackInDirectionRestrictionEffect extends Restriction } PlayerList playerList = game.getState().getPlayerList(playerAttacking.getId()); - if (allowedDirection.equals(ALLOW_ATTACKING_LEFT) - && !playerList.getNext().equals(playerDefending.getId())) { - // the defender is not the player to the left - return false; - } - if (allowedDirection.equals(ALLOW_ATTACKING_RIGHT) - && !playerList.getPrevious().equals(playerDefending.getId())) { - // the defender is not the player to the right - return false; - } - - return true; + return (!left || playerList.getNext().equals(playerDefending.getId())) + && (left || playerList.getPrevious().equals(playerDefending.getId())); } - } diff --git a/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java index e8dc09bd813..7cacad39c33 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java @@ -62,6 +62,9 @@ public class LookTargetHandChooseDiscardEffect extends OneShotEffect { } TargetCard target = new TargetCardInHand(upTo ? 0 : num, num, filter); if (controller.choose(Outcome.Discard, player.getHand(), target, source, game)) { + // TODO: must fizzle discard effect on not full choice + // - tests: affected (allow to choose and discard 1 instead 2) + // - real game: need to check player.discard(new CardsImpl(target.getTargets()), false, source, game); } return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/AdditionalTriggersAttackingReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/AdditionalTriggersAttackingReplacementEffect.java new file mode 100644 index 00000000000..85516648f74 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/AdditionalTriggersAttackingReplacementEffect.java @@ -0,0 +1,80 @@ +package mage.abilities.effects.common.replacement; + +import mage.abilities.Ability; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.events.DefenderAttackedEvent; +import mage.game.events.GameEvent; +import mage.game.events.NumberOfTriggersEvent; +import mage.game.permanent.Permanent; + +/** + * @author TheElk801 + */ +public class AdditionalTriggersAttackingReplacementEffect extends ReplacementEffectImpl { + + private final boolean onlyControlled; + + public AdditionalTriggersAttackingReplacementEffect(boolean onlyControlled) { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + this.onlyControlled = onlyControlled; + staticText = "if a creature " + (onlyControlled ? "you control " : "") + "attacking causes a triggered ability " + + "of a permanent you control to trigger, that ability triggers an additional time"; + } + + private AdditionalTriggersAttackingReplacementEffect(final AdditionalTriggersAttackingReplacementEffect effect) { + super(effect); + this.onlyControlled = effect.onlyControlled; + } + + @Override + public AdditionalTriggersAttackingReplacementEffect copy() { + return new AdditionalTriggersAttackingReplacementEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; + Permanent sourcePermanent = game.getPermanent(numberOfTriggersEvent.getSourceId()); + if (sourcePermanent == null || !sourcePermanent.isControlledBy(source.getControllerId())) { + return false; + } + GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); + if (sourceEvent == null) { + return false; + } + + switch (sourceEvent.getType()) { + case ATTACKER_DECLARED: + return !onlyControlled || source.isControlledBy(sourceEvent.getPlayerId()); + case DECLARED_ATTACKERS: + return !onlyControlled || game + .getCombat() + .getAttackers() + .stream() + .map(game::getControllerId) + .anyMatch(source::isControlledBy); + case DEFENDER_ATTACKED: + return !onlyControlled || ((DefenderAttackedEvent) sourceEvent) + .getAttackers(game) + .stream() + .map(Controllable::getControllerId) + .anyMatch(source::isControlledBy); + } + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(event.getAmount() + 1); + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/EndureSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/EndureSourceEffect.java index d6a8b4e9fea..a9f23f7bcea 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/EndureSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/EndureSourceEffect.java @@ -1,6 +1,8 @@ package mage.abilities.effects.keyword; import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; import mage.counters.CounterType; @@ -15,13 +17,17 @@ import mage.util.CardUtil; */ public class EndureSourceEffect extends OneShotEffect { - private final int amount; + private final DynamicValue amount; public EndureSourceEffect(int amount) { this(amount, "it"); } public EndureSourceEffect(int amount, String selfText) { + this(StaticValue.get(amount), selfText); + } + + public EndureSourceEffect(DynamicValue amount, String selfText) { super(Outcome.Benefit); staticText = selfText + " endures " + amount; this.amount = amount; @@ -39,12 +45,23 @@ public class EndureSourceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { + return doEndure( + source.getSourcePermanentOrLKI(game), + amount.calculate(game, source, this), + game, source + ); + } + + public static boolean doEndure(Permanent permanent, int amount, Game game, Ability source) { + if (permanent == null || amount < 1) { return false; } - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null && player.chooseUse( + Player controller = game.getPlayer(permanent.getControllerId()); + if (controller == null) { + return false; + } + if (permanent.getZoneChangeCounter(game) == game.getState().getZoneChangeCounter(permanent.getId()) + && controller.chooseUse( Outcome.BoostCreature, "Put " + CardUtil.numberToText(amount, "a") + " +1/+1 counter" + (amount > 1 ? "s" : "") + " on " + permanent.getName() + " or create " + CardUtil.addArticle("" + amount) + ' ' + amount + '/' + amount + " Spirit token?", diff --git a/Mage/src/main/java/mage/abilities/keyword/CastFromGraveyardAbility.java b/Mage/src/main/java/mage/abilities/keyword/CastFromGraveyardAbility.java new file mode 100644 index 00000000000..73de96cf5e7 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/CastFromGraveyardAbility.java @@ -0,0 +1,197 @@ +package mage.abilities.keyword; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.Card; +import mage.cards.ModalDoubleFacedCard; +import mage.cards.SplitCard; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * Base class for Flashback and Harmonize and any future ability which works similarly + * + * @author TheElk801 + */ +public abstract class CastFromGraveyardAbility extends SpellAbility { + + protected String abilityName; + private SpellAbility spellAbilityToResolve; + + protected CastFromGraveyardAbility(Card card, Cost cost, SpellAbilityCastMode spellAbilityCastMode) { + super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, spellAbilityCastMode); + this.setAdditionalCostsRuleVisible(false); + this.name = spellAbilityCastMode + " " + cost.getText(); + this.addCost(cost); + this.timing = card.isSorcery() ? TimingRule.SORCERY : TimingRule.INSTANT; + } + + protected CastFromGraveyardAbility(final CastFromGraveyardAbility ability) { + super(ability); + this.abilityName = ability.abilityName; + this.spellAbilityToResolve = ability.spellAbilityToResolve; + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + // flashback ability dynamicly added to all card's parts (split cards) + if (!super.canActivate(playerId, game).canActivate()) { + return ActivationStatus.getFalse(); + } + Card card = game.getCard(getSourceId()); + if (card == null) { + return ActivationStatus.getFalse(); + } + // Card must be in the graveyard zone + if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) { + return ActivationStatus.getFalse(); + } + // Cards with no Mana Costs cant't be flashbacked (e.g. Ancestral Vision) + if (card.getManaCost().isEmpty()) { + return ActivationStatus.getFalse(); + } + // CastFromGraveyard can never cast a split card by Fuse, because Fuse only works from hand + // https://tappedout.net/mtg-questions/snapcaster-mage-and-flashback-on-a-fuse-card-one-or-both-halves-legal-targets/ + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } else if (card instanceof ModalDoubleFacedCard) { + if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } + return card.getSpellAbility().canActivate(playerId, game); + } + + @Override + public SpellAbility getSpellAbilityToResolve(Game game) { + Card card = game.getCard(getSourceId()); + if (card == null || spellAbilityToResolve != null) { + return spellAbilityToResolve; + } + SpellAbility spellAbilityCopy; + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); + } else { + spellAbilityCopy = null; + } + } else if (card instanceof ModalDoubleFacedCard) { + if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); + } else { + spellAbilityCopy = null; + } + } else { + spellAbilityCopy = card.getSpellAbility().copy(); + } + if (spellAbilityCopy == null) { + return null; + } + spellAbilityCopy.setId(this.getId()); + spellAbilityCopy.clearManaCosts(); + spellAbilityCopy.clearManaCostsToPay(); + spellAbilityCopy.addCost(this.getCosts().copy()); + spellAbilityCopy.addCost(this.getManaCosts().copy()); + spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); + spellAbilityToResolve = spellAbilityCopy; + ContinuousEffect effect = new CastFromGraveyardReplacementEffect(); + effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId()))); + game.addEffect(effect, this); + return spellAbilityToResolve; + } + + @Override + public Costs getCosts() { + if (spellAbilityToResolve == null) { + return super.getCosts(); + } + return spellAbilityToResolve.getCosts(); + } + + @Override + public String getRule(boolean all) { + return this.getRule(); + } + + /** + * Used for split card in PlayerImpl method: + * getOtherUseableActivatedAbilities + * + * @param abilityName + */ + public CastFromGraveyardAbility setAbilityName(String abilityName) { + this.abilityName = abilityName; + return this; + } +} + +class CastFromGraveyardReplacementEffect extends ReplacementEffectImpl { + + public CastFromGraveyardReplacementEffect() { + super(Duration.OneUse, Outcome.Exile); + } + + protected CastFromGraveyardReplacementEffect(final CastFromGraveyardReplacementEffect effect) { + super(effect); + } + + @Override + public CastFromGraveyardReplacementEffect copy() { + return new CastFromGraveyardReplacementEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card card = game.getCard(event.getTargetId()); + if (card == null) { + return false; + } + discard(); + return controller.moveCards( + card, Zone.EXILED, source, game, false, + false, false, event.getAppliedEffects() + ); + } + + @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) { + UUID cardId = CardUtil.getMainCardId(game, source.getSourceId()); // for split cards + if (!cardId.equals(event.getTargetId()) + || ((ZoneChangeEvent) event).getFromZone() != Zone.STACK + || ((ZoneChangeEvent) event).getToZone() == Zone.EXILED) { + return false; + } + int zcc = game.getState().getZoneChangeCounter(cardId); + return ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java index e88cf333684..4b7dae1d709 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java @@ -1,23 +1,8 @@ package mage.abilities.keyword; -import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.costs.Cost; -import mage.abilities.costs.Costs; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.ReplacementEffectImpl; import mage.cards.Card; -import mage.cards.ModalDoubleFacedCard; -import mage.cards.SplitCard; -import mage.constants.*; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.players.Player; -import mage.target.targetpointer.FixedTarget; -import mage.util.CardUtil; - -import java.util.UUID; +import mage.constants.SpellAbilityCastMode; /** * 702.32. Flashback @@ -33,106 +18,14 @@ import java.util.UUID; * * @author nantuko */ -public class FlashbackAbility extends SpellAbility { - - private String abilityName; - private SpellAbility spellAbilityToResolve; +public class FlashbackAbility extends CastFromGraveyardAbility { public FlashbackAbility(Card card, Cost cost) { - super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.FLASHBACK); - this.setAdditionalCostsRuleVisible(false); - this.name = "Flashback " + cost.getText(); - this.addCost(cost); - this.timing = card.isSorcery() ? TimingRule.SORCERY : TimingRule.INSTANT; + super(card, cost, SpellAbilityCastMode.FLASHBACK); } protected FlashbackAbility(final FlashbackAbility ability) { super(ability); - this.spellAbilityType = ability.spellAbilityType; - this.abilityName = ability.abilityName; - this.spellAbilityToResolve = ability.spellAbilityToResolve; - } - - @Override - public ActivationStatus canActivate(UUID playerId, Game game) { - // flashback ability dynamicly added to all card's parts (split cards) - if (super.canActivate(playerId, game).canActivate()) { - Card card = game.getCard(getSourceId()); - if (card != null) { - // Card must be in the graveyard zone - if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) { - return ActivationStatus.getFalse(); - } - // Cards with no Mana Costs cant't be flashbacked (e.g. Ancestral Vision) - if (card.getManaCost().isEmpty()) { - return ActivationStatus.getFalse(); - } - // Flashback can never cast a split card by Fuse, because Fuse only works from hand - // https://tappedout.net/mtg-questions/snapcaster-mage-and-flashback-on-a-fuse-card-one-or-both-halves-legal-targets/ - if (card instanceof SplitCard) { - if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { - return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); - } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { - return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); - } - } else if (card instanceof ModalDoubleFacedCard) { - if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { - return ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); - } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { - return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); - } - } - return card.getSpellAbility().canActivate(playerId, game); - } - } - return ActivationStatus.getFalse(); - } - - @Override - public SpellAbility getSpellAbilityToResolve(Game game) { - Card card = game.getCard(getSourceId()); - if (card != null) { - if (spellAbilityToResolve == null) { - SpellAbility spellAbilityCopy = null; - if (card instanceof SplitCard) { - if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); - } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); - } - } else if (card instanceof ModalDoubleFacedCard) { - if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().copy(); - } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); - } - } else { - spellAbilityCopy = card.getSpellAbility().copy(); - } - if (spellAbilityCopy == null) { - return null; - } - spellAbilityCopy.setId(this.getId()); - spellAbilityCopy.clearManaCosts(); - spellAbilityCopy.clearManaCostsToPay(); - spellAbilityCopy.addCost(this.getCosts().copy()); - spellAbilityCopy.addCost(this.getManaCosts().copy()); - spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); - spellAbilityToResolve = spellAbilityCopy; - ContinuousEffect effect = new FlashbackReplacementEffect(); - effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId()))); - game.addEffect(effect, this); - } - } - return spellAbilityToResolve; - } - - @Override - public Costs getCosts() { - if (spellAbilityToResolve == null) { - return super.getCosts(); - } - return spellAbilityToResolve.getCosts(); } @Override @@ -140,11 +33,6 @@ public class FlashbackAbility extends SpellAbility { return new FlashbackAbility(this); } - @Override - public String getRule(boolean all) { - return this.getRule(); - } - @Override public String getRule() { StringBuilder sbRule = new StringBuilder("Flashback"); @@ -170,66 +58,4 @@ public class FlashbackAbility extends SpellAbility { sbRule.append(" (You may cast this card from your graveyard for its flashback cost. Then exile it.)"); return sbRule.toString(); } - - /** - * Used for split card in PlayerImpl method: - * getOtherUseableActivatedAbilities - * - * @param abilityName - */ - public FlashbackAbility setAbilityName(String abilityName) { - this.abilityName = abilityName; - return this; - } - -} - -class FlashbackReplacementEffect extends ReplacementEffectImpl { - - public FlashbackReplacementEffect() { - super(Duration.OneUse, Outcome.Exile); - staticText = "(If the flashback cost was paid, exile this card instead of putting it anywhere else any time it would leave the stack)"; - } - - protected FlashbackReplacementEffect(final FlashbackReplacementEffect effect) { - super(effect); - } - - @Override - public FlashbackReplacementEffect copy() { - return new FlashbackReplacementEffect(this); - } - - @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) { - UUID cardId = CardUtil.getMainCardId(game, source.getSourceId()); // for split cards - if (cardId.equals(event.getTargetId()) - && ((ZoneChangeEvent) event).getFromZone() == Zone.STACK - && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { - - int zcc = game.getState().getZoneChangeCounter(cardId); - return ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc; - - } - return false; - } } diff --git a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java index 9287ae99694..d094a49f537 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java @@ -257,7 +257,7 @@ public class ForetellAbility extends SpecialAction { game.getState().addOtherAbility(rightHalfCard, ability); } } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (foretellCost != null) { Card creatureCard = card.getMainCard(); ForetellCostAbility ability = new ForetellCostAbility(foretellCost); @@ -268,7 +268,7 @@ public class ForetellAbility extends SpecialAction { game.getState().addOtherAbility(creatureCard, ability); } if (foretellSplitCost != null) { - Card spellCard = ((AdventureCard) card).getSpellCard(); + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); ability.setSourceId(spellCard.getId()); ability.setControllerId(source.getControllerId()); @@ -360,11 +360,11 @@ public class ForetellAbility extends SpecialAction { } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (card.getMainCard().getName().equals(abilityName)) { return card.getMainCard().getSpellAbility().canActivate(playerId, game); - } else if (((AdventureCard) card).getSpellCard().getName().equals(abilityName)) { - return ((AdventureCard) card).getSpellCard().getSpellAbility().canActivate(playerId, game); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { + return ((CardWithSpellOption) card).getSpellCard().getSpellAbility().canActivate(playerId, game); } } return card.getSpellAbility().canActivate(playerId, game); @@ -391,11 +391,11 @@ public class ForetellAbility extends SpecialAction { } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (card.getMainCard().getName().equals(abilityName)) { spellAbilityCopy = card.getMainCard().getSpellAbility().copy(); - } else if (((AdventureCard) card).getSpellCard().getName().equals(abilityName)) { - spellAbilityCopy = ((AdventureCard) card).getSpellCard().getSpellAbility().copy(); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { + spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy(); } } else { spellAbilityCopy = card.getSpellAbility().copy(); diff --git a/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java b/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java index 92f6310401c..d18de35de8c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java @@ -1,19 +1,43 @@ package mage.abilities.keyword; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.VariableCostImpl; +import mage.abilities.costs.VariableCostType; +import mage.abilities.costs.common.TapTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.cards.Card; -import mage.constants.Zone; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledPermanent; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.UUID; /** - * TODO: Implement this - * * @author TheElk801 */ -public class HarmonizeAbility extends SpellAbility { +public class HarmonizeAbility extends CastFromGraveyardAbility { + + private String abilityName; + private SpellAbility spellAbilityToResolve; public HarmonizeAbility(Card card, String manaString) { - super(new ManaCostsImpl<>(manaString), card.getName(), Zone.GRAVEYARD); + super(card, new ManaCostsImpl<>(manaString), SpellAbilityCastMode.HARMONIZE); + this.addCost(new HarmonizeCost()); + this.addSubAbility(new SimpleStaticAbility(Zone.ALL, new HarmonizeCostReductionEffect()).setRuleVisible(false)); } private HarmonizeAbility(final HarmonizeAbility ability) { @@ -24,4 +48,132 @@ public class HarmonizeAbility extends SpellAbility { public HarmonizeAbility copy() { return new HarmonizeAbility(this); } + + @Override + public String getRule() { + return name + " (You may cast this card from your graveyard for its harmonize cost. " + + "You may tap a creature you control to reduce that cost by {X}, " + + "where X is its power. Then exile this spell.)"; + } +} + +class HarmonizeCostReductionEffect extends CostModificationEffectImpl { + + HarmonizeCostReductionEffect() { + super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); + } + + private HarmonizeCostReductionEffect(final HarmonizeCostReductionEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + SpellAbility spellAbility = (SpellAbility) abilityToModify; + int power; + if (game.inCheckPlayableState()) { + power = game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE, + source.getControllerId(), source, game + ).stream() + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .max() + .orElse(0); + } else { + power = CardUtil + .castStream(spellAbility.getCosts().stream(), HarmonizeCost.class) + .map(HarmonizeCost::getChosenCreature) + .map(game::getPermanent) + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .map(x -> Math.max(x, 0)) + .sum(); + } + if (power > 0) { + CardUtil.adjustCost(spellAbility, power); + } + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + return abilityToModify instanceof SpellAbility + && abilityToModify.getSourceId().equals(source.getSourceId()); + } + + @Override + public HarmonizeCostReductionEffect copy() { + return new HarmonizeCostReductionEffect(this); + } +} + +class HarmonizeCost extends VariableCostImpl { + + private UUID chosenCreature = null; + + HarmonizeCost() { + super(VariableCostType.ADDITIONAL, "", ""); + } + + private HarmonizeCost(final HarmonizeCost cost) { + super(cost); + this.chosenCreature = cost.chosenCreature; + } + + @Override + public HarmonizeCost copy() { + return new HarmonizeCost(this); + } + + @Override + public void clearPaid() { + super.clearPaid(); + chosenCreature = null; + } + + @Override + public int getMaxValue(Ability source, Game game) { + return game.getBattlefield().contains(StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE, source, game, 1) ? 1 : 0; + } + + @Override + public int announceXValue(Ability source, Game game) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || !game.getBattlefield().contains( + StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE, source, game, 1 + ) || !player.chooseUse( + Outcome.Benefit, "Tap an untapped creature you control for harmonize?", source, game + )) { + return 0; + } + TargetPermanent target = new TargetPermanent(StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE); + target.withNotTarget(true); + target.withChooseHint("for harmonize"); + player.choose(Outcome.PlayForFree, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + return 0; + } + chosenCreature = permanent.getId(); + return 1; + } + + private FilterControlledPermanent makeFilter() { + FilterControlledPermanent filter = new FilterControlledPermanent("tap the chosen creature"); + filter.add(new PermanentIdPredicate(chosenCreature)); + return filter; + } + + @Override + public Cost getFixedCostsFromAnnouncedValue(int xValue) { + return new TapTargetCost(new TargetControlledPermanent(xValue, xValue, makeFilter(), true)); + } + + public UUID getChosenCreature() { + return chosenCreature; + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java b/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java index c91a6f2cbbd..93375e704b3 100644 --- a/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java @@ -268,11 +268,11 @@ class PlotSpellAbility extends SpellAbility { } else if (((CardWithHalves) mainCard).getRightHalfCard().getName().equals(faceCardName)) { return ((CardWithHalves) mainCard).getRightHalfCard().getSpellAbility().canActivate(playerId, game); } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (card.getMainCard().getName().equals(faceCardName)) { return card.getMainCard().getSpellAbility().canActivate(playerId, game); - } else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) { - return ((AdventureCard) card).getSpellCard().getSpellAbility().canActivate(playerId, game); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(faceCardName)) { + return ((CardWithSpellOption) card).getSpellCard().getSpellAbility().canActivate(playerId, game); } } return card.getSpellAbility().canActivate(playerId, game); @@ -294,11 +294,11 @@ class PlotSpellAbility extends SpellAbility { } else if (((CardWithHalves) card).getRightHalfCard().getName().equals(faceCardName)) { spellAbilityCopy = ((CardWithHalves) card).getRightHalfCard().getSpellAbility().copy(); } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (card.getMainCard().getName().equals(faceCardName)) { spellAbilityCopy = card.getMainCard().getSpellAbility().copy(); - } else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) { - spellAbilityCopy = ((AdventureCard) card).getSpellCard().getSpellAbility().copy(); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(faceCardName)) { + spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy(); } } else { spellAbilityCopy = card.getSpellAbility().copy(); diff --git a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java index 94972323c83..1114ecebc64 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java @@ -1,6 +1,5 @@ package mage.abilities.keyword; -import mage.ApprovingObject; import mage.MageIdentifier; import mage.abilities.Ability; import mage.abilities.SpecialAction; @@ -17,8 +16,11 @@ import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; import mage.cards.Card; +import mage.cards.CardsImpl; +import mage.cards.ModalDoubleFacedCard; import mage.constants.*; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -26,8 +28,6 @@ import mage.players.Player; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; -import java.util.ArrayList; -import java.util.List; import java.util.Set; import java.util.UUID; @@ -112,7 +112,6 @@ import java.util.UUID; public class SuspendAbility extends SpecialAction { private final String ruleText; - private boolean gainedTemporary; /** * Gives the card the SuspendAbility @@ -132,6 +131,8 @@ public class SuspendAbility extends SpecialAction { this.addEffect(new SuspendExileEffect(suspend)); this.usesStack = false; if (suspend == Integer.MAX_VALUE) { + // example: Suspend X-{X}{W}{W}. X can't be 0. + // TODO: replace by costAdjuster for shared logic VariableManaCost xCosts = new VariableManaCost(VariableCostType.ALTERNATIVE); xCosts.setMinX(1); this.addCost(xCosts); @@ -175,6 +176,11 @@ public class SuspendAbility extends SpecialAction { * or added by Jhoira of the Ghitu */ public static void addSuspendTemporaryToCard(Card card, Ability source, Game game) { + if (card instanceof ModalDoubleFacedCard) { + // Need to ensure the suspend ability gets put on the left side card + // since counters get added to this card. + card = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + } SuspendAbility ability = new SuspendAbility(0, null, card, false); ability.setSourceId(card.getId()); ability.setControllerId(card.getOwnerId()); @@ -204,7 +210,6 @@ public class SuspendAbility extends SpecialAction { private SuspendAbility(final SuspendAbility ability) { super(ability); this.ruleText = ability.ruleText; - this.gainedTemporary = ability.gainedTemporary; } @Override @@ -230,10 +235,6 @@ public class SuspendAbility extends SpecialAction { return ruleText; } - public boolean isGainedTemporary() { - return gainedTemporary; - } - @Override public SuspendAbility copy() { return new SuspendAbility(this); @@ -343,40 +344,16 @@ class SuspendPlayCardEffect extends OneShotEffect { if (player == null || card == null) { return false; } - if (!player.chooseUse(Outcome.Benefit, "Play " + card.getLogName() + " without paying its mana cost?", source, game)) { + // ensure we're getting the main card when passing to CardUtil to check all parts of card + // MDFC points to left half card + card = card.getMainCard(); + // cast/play the card for free + if (!CardUtil.castSpellWithAttributesForFree(player, source, game, new CardsImpl(card), + StaticFilters.FILTER_CARD, null, true)) { return true; } - // remove temporary suspend ability (used e.g. for Epochrasite) - // TODO: isGainedTemporary is not set or use in other places, so it can be deleted?! - List abilitiesToRemove = new ArrayList<>(); - for (Ability ability : card.getAbilities(game)) { - if (ability instanceof SuspendAbility && (((SuspendAbility) ability).isGainedTemporary())) { - abilitiesToRemove.add(ability); - } - } - if (!abilitiesToRemove.isEmpty()) { - for (Ability ability : card.getAbilities(game)) { - if (ability instanceof SuspendBeginningOfUpkeepInterveningIfTriggeredAbility - || ability instanceof SuspendPlayCardAbility) { - abilitiesToRemove.add(ability); - } - } - // remove the abilities from the card - // TODO: will not work with Adventure Cards and another auto-generated abilities list - // TODO: is it work after blink or return to hand? - /* - bug example: - Epochrasite bug: It comes out of suspend, is cast and enters the battlefield. THEN if it's returned to - its owner's hand from battlefield, the bounced Epochrasite can't be cast for the rest of the game. - */ - card.getAbilities().removeAll(abilitiesToRemove); - } - // cast the card for free - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - boolean cardWasCast = player.cast(player.chooseAbilityForCast(card, game, true), - game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - if (cardWasCast && (card.isCreature(game))) { + // creatures cast from suspend gain haste + if ((card.isCreature(game))) { ContinuousEffect effect = new GainHasteEffect(); effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game) + 1)); game.addEffect(effect, source); diff --git a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java index b6bebf87769..c68ff475567 100644 --- a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java @@ -77,7 +77,7 @@ public class WardAbility extends TriggeredAbilityImpl { if (!getSourceId().equals(event.getTargetId())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())) { return false; } diff --git a/Mage/src/main/java/mage/cards/AdventureCard.java b/Mage/src/main/java/mage/cards/AdventureCard.java index 413d9d6e026..af6e67ca7c1 100644 --- a/Mage/src/main/java/mage/cards/AdventureCard.java +++ b/Mage/src/main/java/mage/cards/AdventureCard.java @@ -1,98 +1,29 @@ package mage.cards; -import mage.abilities.Abilities; -import mage.abilities.AbilitiesImpl; -import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.constants.CardType; import mage.constants.SpellAbilityType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.ZoneChangeEvent; -import mage.util.CardUtil; -import java.util.List; import java.util.UUID; /** * @author phulin */ -public abstract class AdventureCard extends CardImpl { - - /* The adventure spell card, i.e. Swift End. */ - protected AdventureCardSpell spellCard; +public abstract class AdventureCard extends CardWithSpellOption { public AdventureCard(UUID ownerId, CardSetInfo setInfo, CardType[] types, CardType[] typesSpell, String costs, String adventureName, String costsSpell) { super(ownerId, setInfo, types, costs); - this.spellCard = new AdventureCardSpellImpl(ownerId, setInfo, adventureName, typesSpell, costsSpell, this); + this.spellCard = new AdventureSpellCard(ownerId, setInfo, adventureName, typesSpell, costsSpell, this); + } + + public AdventureCard(AdventureCard card) { + super(card); } public void finalizeAdventure() { - spellCard.finalizeAdventure(); - } - - protected AdventureCard(final AdventureCard card) { - super(card); - this.spellCard = card.getSpellCard().copy(); - this.spellCard.setParentCard(this); - } - - public AdventureCardSpell getSpellCard() { - return spellCard; - } - - public void setParts(AdventureCardSpell cardSpell) { - // for card copy only - set new parts - this.spellCard = cardSpell; - cardSpell.setParentCard(this); - } - - @Override - public void assignNewId() { - super.assignNewId(); - spellCard.assignNewId(); - } - - @Override - public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { - if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { - Zone currentZone = game.getState().getZone(getId()); - game.getState().setZone(getSpellCard().getId(), currentZone); - return true; - } - return false; - } - - @Override - public void setZone(Zone zone, Game game) { - super.setZone(zone, game); - game.setZone(getSpellCard().getId(), zone); - } - - @Override - public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { - if (super.moveToExile(exileId, name, source, game, appliedEffects)) { - Zone currentZone = game.getState().getZone(getId()); - game.getState().setZone(getSpellCard().getId(), currentZone); - return true; - } - return false; - } - - @Override - public boolean removeFromZone(Game game, Zone fromZone, Ability source) { - // zone contains only one main card - return super.removeFromZone(game, fromZone, source); - } - - @Override - public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { - if (isCopy()) { // same as meld cards - super.updateZoneChangeCounter(game, event); - return; - } - super.updateZoneChangeCounter(game, event); - getSpellCard().updateZoneChangeCounter(game, event); + spellCard.finalizeSpell(); } @Override @@ -103,45 +34,4 @@ public abstract class AdventureCard extends CardImpl { this.getSpellCard().getSpellAbility().setControllerId(controllerId); return super.cast(game, fromZone, ability, controllerId); } - - @Override - public Abilities getAbilities() { - Abilities allAbilities = new AbilitiesImpl<>(); - allAbilities.addAll(spellCard.getAbilities()); - allAbilities.addAll(super.getAbilities()); - return allAbilities; - } - - @Override - public Abilities getInitAbilities() { - // must init only parent related abilities, spell card must be init separately - return super.getAbilities(); - } - - @Override - public Abilities getAbilities(Game game) { - Abilities allAbilities = new AbilitiesImpl<>(); - allAbilities.addAll(spellCard.getAbilities(game)); - allAbilities.addAll(super.getAbilities(game)); - return allAbilities; - } - - public Abilities getSharedAbilities(Game game) { - // abilities without spellcard - return super.getAbilities(game); - } - - public List getSharedRules(Game game) { - // rules without spellcard - Abilities sourceAbilities = this.getSharedAbilities(game); - return CardUtil.getCardRulesWithAdditionalInfo(game, this, sourceAbilities, sourceAbilities); - } - - @Override - public void setOwnerId(UUID ownerId) { - super.setOwnerId(ownerId); - abilities.setControllerId(ownerId); - spellCard.getAbilities().setControllerId(ownerId); - spellCard.setOwnerId(ownerId); - } } diff --git a/Mage/src/main/java/mage/cards/AdventureCardSpell.java b/Mage/src/main/java/mage/cards/AdventureCardSpell.java deleted file mode 100644 index 27f14c5493a..00000000000 --- a/Mage/src/main/java/mage/cards/AdventureCardSpell.java +++ /dev/null @@ -1,12 +0,0 @@ -package mage.cards; - -/** - * @author phulin - */ -public interface AdventureCardSpell extends SubCard { - - @Override - AdventureCardSpell copy(); - - void finalizeAdventure(); -} diff --git a/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java b/Mage/src/main/java/mage/cards/AdventureSpellCard.java similarity index 88% rename from Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java rename to Mage/src/main/java/mage/cards/AdventureSpellCard.java index 2062b27ca9e..ed6efa5f1f1 100644 --- a/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java +++ b/Mage/src/main/java/mage/cards/AdventureSpellCard.java @@ -19,11 +19,11 @@ import java.util.stream.Collectors; /** * @author phulin */ -public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpell { +public class AdventureSpellCard extends CardImpl implements SpellOptionCard { private AdventureCard adventureCardParent; - public AdventureCardSpellImpl(UUID ownerId, CardSetInfo setInfo, String adventureName, CardType[] cardTypes, String costs, AdventureCard adventureCardParent) { + public AdventureSpellCard(UUID ownerId, CardSetInfo setInfo, String adventureName, CardType[] cardTypes, String costs, AdventureCard adventureCardParent) { super(ownerId, setInfo, cardTypes, costs, SpellAbilityType.ADVENTURE_SPELL); this.subtype.add(SubType.ADVENTURE); @@ -35,13 +35,13 @@ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpe this.adventureCardParent = adventureCardParent; } - public void finalizeAdventure() { + public void finalizeSpell() { if (spellAbility instanceof AdventureCardSpellAbility) { ((AdventureCardSpellAbility) spellAbility).finalizeAdventure(); } } - protected AdventureCardSpellImpl(final AdventureCardSpellImpl card) { + protected AdventureSpellCard(final AdventureSpellCard card) { super(card); this.adventureCardParent = card.adventureCardParent; } @@ -83,13 +83,13 @@ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpe } @Override - public AdventureCardSpellImpl copy() { - return new AdventureCardSpellImpl(this); + public AdventureSpellCard copy() { + return new AdventureSpellCard(this); } @Override - public void setParentCard(AdventureCard card) { - this.adventureCardParent = card; + public void setParentCard(CardWithSpellOption card) { + this.adventureCardParent = (AdventureCard) card; } @Override @@ -102,6 +102,11 @@ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpe // id must send to main card (popup card hint in game logs) return getName() + " [" + adventureCardParent.getId().toString().substring(0, 3) + ']'; } + + @Override + public String getSpellType() { + return "Adventure"; + } } class AdventureCardSpellAbility extends SpellAbility { @@ -141,8 +146,8 @@ class AdventureCardSpellAbility extends SpellAbility { public ActivationStatus canActivate(UUID playerId, Game game) { ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(playerId, game)); Card spellCard = game.getCard(this.getSourceId()); - if (spellCard instanceof AdventureCardSpell) { - Card card = ((AdventureCardSpell) spellCard).getParentCard(); + if (spellCard instanceof AdventureSpellCard) { + Card card = ((AdventureSpellCard) spellCard).getParentCard(); if (adventureExileZone != null && adventureExileZone.contains(card.getId())) { return ActivationStatus.getFalse(); } diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index e6c7aa4b9ae..2515d21c1fd 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -530,8 +530,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } } - if (stackObject == null && (this instanceof AdventureCard)) { - stackObject = game.getStack().getSpell(((AdventureCard) this).getSpellCard().getId(), false); + if (stackObject == null && (this instanceof CardWithSpellOption)) { + stackObject = game.getStack().getSpell(((CardWithSpellOption) this).getSpellCard().getId(), false); } if (stackObject == null) { diff --git a/Mage/src/main/java/mage/cards/CardWithSpellOption.java b/Mage/src/main/java/mage/cards/CardWithSpellOption.java new file mode 100644 index 00000000000..b1470f7dedc --- /dev/null +++ b/Mage/src/main/java/mage/cards/CardWithSpellOption.java @@ -0,0 +1,131 @@ +package mage.cards; + +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; +import mage.abilities.Ability; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.ZoneChangeEvent; +import mage.util.CardUtil; + +import java.util.List; +import java.util.UUID; + +/** + * @author phulin, jmlundeen + */ +public abstract class CardWithSpellOption extends CardImpl { + + /* The adventure/omen spell card, i.e. Swift End. */ + protected SpellOptionCard spellCard; + + public CardWithSpellOption(UUID ownerId, CardSetInfo setInfo, CardType[] types, String costs) { + super(ownerId, setInfo, types, costs); + } + + public CardWithSpellOption(CardWithSpellOption card) { + super(card); + this.spellCard = card.getSpellCard().copy(); + this.spellCard.setParentCard(this); + } + + public SpellOptionCard getSpellCard() { + return spellCard; + } + + public void setParts(SpellOptionCard cardSpell) { + // for card copy only - set new parts + this.spellCard = cardSpell; + cardSpell.setParentCard(this); + } + + @Override + public void assignNewId() { + super.assignNewId(); + spellCard.assignNewId(); + } + + @Override + public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { + if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { + Zone currentZone = game.getState().getZone(getId()); + game.getState().setZone(getSpellCard().getId(), currentZone); + return true; + } + return false; + } + + @Override + public void setZone(Zone zone, Game game) { + super.setZone(zone, game); + game.setZone(getSpellCard().getId(), zone); + } + + @Override + public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { + if (super.moveToExile(exileId, name, source, game, appliedEffects)) { + Zone currentZone = game.getState().getZone(getId()); + game.getState().setZone(getSpellCard().getId(), currentZone); + return true; + } + return false; + } + + @Override + public boolean removeFromZone(Game game, Zone fromZone, Ability source) { + // zone contains only one main card + return super.removeFromZone(game, fromZone, source); + } + + @Override + public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { + if (isCopy()) { // same as meld cards + super.updateZoneChangeCounter(game, event); + return; + } + super.updateZoneChangeCounter(game, event); + getSpellCard().updateZoneChangeCounter(game, event); + } + + @Override + public Abilities getAbilities() { + Abilities allAbilities = new AbilitiesImpl<>(); + allAbilities.addAll(spellCard.getAbilities()); + allAbilities.addAll(super.getAbilities()); + return allAbilities; + } + + @Override + public Abilities getInitAbilities() { + // must init only parent related abilities, spell card must be init separately + return super.getAbilities(); + } + + @Override + public Abilities getAbilities(Game game) { + Abilities allAbilities = new AbilitiesImpl<>(); + allAbilities.addAll(spellCard.getAbilities(game)); + allAbilities.addAll(super.getAbilities(game)); + return allAbilities; + } + + public Abilities getSharedAbilities(Game game) { + // abilities without spellCard + return super.getAbilities(game); + } + + public List getSharedRules(Game game) { + // rules without spellCard + Abilities sourceAbilities = this.getSharedAbilities(game); + return CardUtil.getCardRulesWithAdditionalInfo(game, this, sourceAbilities, sourceAbilities); + } + + @Override + public void setOwnerId(UUID ownerId) { + super.setOwnerId(ownerId); + abilities.setControllerId(ownerId); + spellCard.getAbilities().setControllerId(ownerId); + spellCard.setOwnerId(ownerId); + } +} diff --git a/Mage/src/main/java/mage/cards/OmenCard.java b/Mage/src/main/java/mage/cards/OmenCard.java new file mode 100644 index 00000000000..82401304f12 --- /dev/null +++ b/Mage/src/main/java/mage/cards/OmenCard.java @@ -0,0 +1,34 @@ +package mage.cards; + +import mage.abilities.SpellAbility; +import mage.constants.CardType; +import mage.constants.SpellAbilityType; +import mage.constants.Zone; +import mage.game.Game; + +import java.util.UUID; + +public abstract class OmenCard extends CardWithSpellOption { + + public OmenCard(UUID ownerId, CardSetInfo setInfo, CardType[] types, CardType[] typesSpell, String costs, String omenName, String costsSpell) { + super(ownerId, setInfo, types, costs); + this.spellCard = new OmenSpellCard(ownerId, setInfo, omenName, typesSpell, costsSpell, this); + } + + public OmenCard(OmenCard card) { + super(card); + } + + public void finalizeOmen() { + spellCard.finalizeSpell(); + } + + @Override + public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { + if (ability.getSpellAbilityType() == SpellAbilityType.OMEN_SPELL) { + return this.getSpellCard().cast(game, fromZone, ability, controllerId); + } + this.getSpellCard().getSpellAbility().setControllerId(controllerId); + return super.cast(game, fromZone, ability, controllerId); + } +} diff --git a/Mage/src/main/java/mage/cards/OmenSpellCard.java b/Mage/src/main/java/mage/cards/OmenSpellCard.java new file mode 100644 index 00000000000..040bba51217 --- /dev/null +++ b/Mage/src/main/java/mage/cards/OmenSpellCard.java @@ -0,0 +1,174 @@ +package mage.cards; + +import mage.abilities.Ability; +import mage.abilities.Modes; +import mage.abilities.SpellAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ShuffleIntoLibrarySourceEffect; +import mage.constants.CardType; +import mage.constants.SpellAbilityType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class OmenSpellCard extends CardImpl implements SpellOptionCard { + + private OmenCard omenCardParent; + + public OmenSpellCard(UUID ownerId, CardSetInfo setInfo, String omenName, CardType[] cardTypes, String costs, OmenCard omenCard) { + super(ownerId, setInfo, cardTypes, costs, SpellAbilityType.OMEN_SPELL); + this.subtype.add(SubType.OMEN); + + OmenCardSpellAbility newSpellAbility = new OmenCardSpellAbility(getSpellAbility(), omenName, cardTypes, costs); + this.replaceSpellAbility(newSpellAbility); + spellAbility = newSpellAbility; + + this.setName(omenName); + this.omenCardParent = omenCard; + } + + public void finalizeSpell() { + if (spellAbility instanceof OmenCardSpellAbility) { + ((OmenCardSpellAbility) spellAbility).finalizeOmen(); + } + } + + protected OmenSpellCard(final OmenSpellCard card) { + super(card); + this.omenCardParent = card.omenCardParent; + } + + @Override + public UUID getOwnerId() { + return omenCardParent.getOwnerId(); + } + + @Override + public String getExpansionSetCode() { + return omenCardParent.getExpansionSetCode(); + } + + @Override + public String getCardNumber() { + return omenCardParent.getCardNumber(); + } + + @Override + public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { + return omenCardParent.moveToZone(toZone, source, game, flag, appliedEffects); + } + + @Override + public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { + return omenCardParent.moveToExile(exileId, name, source, game, appliedEffects); + } + + @Override + public OmenCard getMainCard() { + return omenCardParent; + } + + @Override + public void setZone(Zone zone, Game game) { + game.setZone(omenCardParent.getId(), zone); + game.setZone(omenCardParent.getSpellCard().getId(), zone); + } + + @Override + public OmenSpellCard copy() { + return new OmenSpellCard(this); + } + + @Override + public void setParentCard(CardWithSpellOption card) { + this.omenCardParent = (OmenCard) card; + } + + @Override + public OmenCard getParentCard() { + return this.omenCardParent; + } + + @Override + public String getIdName() { + // id must send to main card (popup card hint in game logs) + return getName() + " [" + omenCardParent.getId().toString().substring(0, 3) + ']'; + } + + @Override + public String getSpellType() { + return "Omen"; + } +} + +class OmenCardSpellAbility extends SpellAbility { + + private String nameFull; + private boolean finalized = false; + + public OmenCardSpellAbility(final SpellAbility baseSpellAbility, String omenName, CardType[] cardTypes, String costs) { + super(baseSpellAbility); + this.setName(cardTypes, omenName, costs); + this.setCardName(omenName); + } + + public void finalizeOmen() { + if (finalized) { + throw new IllegalStateException("Wrong code usage. " + + "Omen (" + cardName + ") " + + "need to call finalizeOmen() exactly once."); + } + Effect effect = new ShuffleIntoLibrarySourceEffect(); + effect.setText(""); + this.addEffect(effect); + this.finalized = true; + } + + protected OmenCardSpellAbility(final OmenCardSpellAbility ability) { + super(ability); + this.nameFull = ability.nameFull; + if (!ability.finalized) { + throw new IllegalStateException("Wrong code usage. " + + "Omen (" + cardName + ") " + + "need to call finalizeOmen() at the very end of the card's constructor."); + } + this.finalized = true; + } + + public void setName(CardType[] cardTypes, String omenName, String costs) { + this.nameFull = "Omen " + Arrays.stream(cardTypes).map(CardType::toString).collect(Collectors.joining(" ")) + " — " + omenName; + this.name = this.nameFull + " " + costs; + } + + @Override + public String getRule(boolean all) { + return this.getRule(); + } + + @Override + public String getRule() { + StringBuilder sbRule = new StringBuilder(); + sbRule.append(this.nameFull); + sbRule.append(" "); + sbRule.append(getManaCosts().getText()); + sbRule.append(" — "); + Modes modes = this.getModes(); + if (modes.size() <= 1) { + sbRule.append(modes.getMode().getEffects().getTextStartingUpperCase(modes.getMode())); + } else { + sbRule.append(getModes().getText()); + } + sbRule.append(" (Then shuffle this card into its owner's library.)"); + return sbRule.toString(); + } + + @Override + public OmenCardSpellAbility copy() { + return new OmenCardSpellAbility(this); + } +} diff --git a/Mage/src/main/java/mage/cards/SpellOptionCard.java b/Mage/src/main/java/mage/cards/SpellOptionCard.java new file mode 100644 index 00000000000..0007d70fcbc --- /dev/null +++ b/Mage/src/main/java/mage/cards/SpellOptionCard.java @@ -0,0 +1,18 @@ +package mage.cards; + +public interface SpellOptionCard extends SubCard { + + @Override + SpellOptionCard copy(); + + /** + * Adds the final shared ability to the card. e.g. Adventure exile effect / Omen shuffle effect + */ + void finalizeSpell(); + + /** + * Used to get the card type text such as Adventure. Currently only used in {@link mage.game.stack.Spell#getSpellCastText Spell} for logging the spell + * being cast as part of the two part card. + */ + String getSpellType(); +} diff --git a/Mage/src/main/java/mage/cards/mock/MockCard.java b/Mage/src/main/java/mage/cards/mock/MockCard.java index ae406a39dca..6d768ef8b67 100644 --- a/Mage/src/main/java/mage/cards/mock/MockCard.java +++ b/Mage/src/main/java/mage/cards/mock/MockCard.java @@ -20,7 +20,7 @@ import java.util.List; */ public class MockCard extends CardImpl implements MockableCard { - public static String ADVENTURE_NAME_SEPARATOR = " // "; + public static String CARD_WITH_SPELL_OPTION_NAME_SEPARATOR = " // "; public static String MODAL_DOUBLE_FACES_NAME_SEPARATOR = " // "; // Needs to be here, as it is normally calculated from the @@ -34,7 +34,7 @@ public class MockCard extends CardImpl implements MockableCard { protected List manaCostLeftStr; protected List manaCostRightStr; protected List manaCostStr; - protected String adventureSpellName; + protected String spellOptionName; // adventure/omen spell name protected boolean isModalDoubleFacedCard; protected int manaValue; @@ -71,8 +71,8 @@ public class MockCard extends CardImpl implements MockableCard { this.secondSideCard = new MockCard(CardRepository.instance.findCardWithPreferredSetAndNumber(card.getSecondSideName(), card.getSetCode(), card.getCardNumber())); } - if (card.isAdventureCard()) { - this.adventureSpellName = card.getAdventureSpellName(); + if (card.isCardWithSpellOption()) { + this.spellOptionName = card.getSpellOptionCardName(); } if (card.isModalDoubleFacedCard()) { @@ -101,7 +101,7 @@ public class MockCard extends CardImpl implements MockableCard { this.manaCostLeftStr = new ArrayList<>(card.manaCostLeftStr); this.manaCostRightStr = new ArrayList<>(card.manaCostRightStr); this.manaCostStr = new ArrayList<>(card.manaCostStr); - this.adventureSpellName = card.adventureSpellName; + this.spellOptionName = card.spellOptionName; this.isModalDoubleFacedCard = card.isModalDoubleFacedCard; this.manaValue = card.manaValue; } @@ -155,8 +155,8 @@ public class MockCard extends CardImpl implements MockableCard { return getName(); } - if (adventureSpellName != null) { - return getName() + ADVENTURE_NAME_SEPARATOR + adventureSpellName; + if (spellOptionName != null) { + return getName() + CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + spellOptionName; } else if (isModalDoubleFacedCard) { return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.getSecondCardFace().getName(); } else { diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index 7477dc9bee3..d870673e76d 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -106,9 +106,9 @@ public class CardInfo { @DatabaseField protected String secondSideName; @DatabaseField - protected boolean adventureCard; + protected boolean cardWithSpellOption; @DatabaseField - protected String adventureSpellName; + protected String spellOptionCardName; @DatabaseField protected boolean modalDoubleFacedCard; @DatabaseField @@ -157,9 +157,9 @@ public class CardInfo { this.secondSideName = secondSide.getName(); } - if (card instanceof AdventureCard) { - this.adventureCard = true; - this.adventureSpellName = ((AdventureCard) card).getSpellCard().getName(); + if (card instanceof CardWithSpellOption) { + this.cardWithSpellOption = true; + this.spellOptionCardName = ((CardWithSpellOption) card).getSpellCard().getName(); } if (card instanceof ModalDoubleFacedCard) { @@ -189,8 +189,8 @@ public class CardInfo { List manaCostLeft = ((ModalDoubleFacedCard) card).getLeftHalfCard().getManaCostSymbols(); List manaCostRight = ((ModalDoubleFacedCard) card).getRightHalfCard().getManaCostSymbols(); this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight)); - } else if (card instanceof AdventureCard) { - List manaCostLeft = ((AdventureCard) card).getSpellCard().getManaCostSymbols(); + } else if (card instanceof CardWithSpellOption) { + List manaCostLeft = ((CardWithSpellOption) card).getSpellCard().getManaCostSymbols(); List manaCostRight = card.getManaCostSymbols(); this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight)); } else { @@ -469,12 +469,16 @@ public class CardInfo { return secondSideName; } - public boolean isAdventureCard() { - return adventureCard; + public boolean isCardWithSpellOption() { + return cardWithSpellOption; } - public String getAdventureSpellName() { - return adventureSpellName; + /** + * used for spell card portion of adventure/omen cards + * @return name of the spell + */ + public String getSpellOptionCardName() { + return spellOptionCardName; } public boolean isModalDoubleFacedCard() { diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 24484308724..88cb2fbadda 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -147,8 +147,8 @@ public enum CardRepository { if (card.getMeldsToCardName() != null && !card.getMeldsToCardName().isEmpty()) { namesList.add(card.getMeldsToCardName()); } - if (card.getAdventureSpellName() != null && !card.getAdventureSpellName().isEmpty()) { - namesList.add(card.getAdventureSpellName()); + if (card.getSpellOptionCardName() != null && !card.getSpellOptionCardName().isEmpty()) { + namesList.add(card.getSpellOptionCardName()); } } @@ -160,7 +160,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { addNewNames(card, names); @@ -176,7 +176,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().not().like("types", new SelectArg('%' + CardType.LAND.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -193,7 +193,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); Where where = qb.where(); where.and( where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'), @@ -214,7 +214,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().not().like("supertypes", new SelectArg('%' + SuperType.BASIC.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -231,7 +231,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().like("types", new SelectArg('%' + CardType.CREATURE.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -248,7 +248,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().like("types", new SelectArg('%' + CardType.ARTIFACT.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -265,7 +265,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); Where where = qb.where(); where.and( where.not().like("types", '%' + CardType.CREATURE.name() + '%'), @@ -286,7 +286,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); Where where = qb.where(); where.and( where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'), @@ -511,7 +511,7 @@ public enum CardRepository { queryBuilder.where() .eq("flipCardName", new SelectArg(name)).or() .eq("secondSideName", new SelectArg(name)).or() - .eq("adventureSpellName", new SelectArg(name)).or() + .eq("spellOptionCardName", new SelectArg(name)).or() .eq("modalDoubleFacedSecondSideName", new SelectArg(name)); results = cardsDao.query(queryBuilder.prepare()); } else { diff --git a/Mage/src/main/java/mage/constants/ModeChoice.java b/Mage/src/main/java/mage/constants/ModeChoice.java new file mode 100644 index 00000000000..1759d2dece8 --- /dev/null +++ b/Mage/src/main/java/mage/constants/ModeChoice.java @@ -0,0 +1,75 @@ +package mage.constants; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; + +import java.util.Objects; + +public enum ModeChoice { + + KHANS("Khans"), + DRAGONS("Dragons"), + + MARDU("Mardu"), + TEMUR("Temur"), + ABZAN("Abzan"), + JESKAI("Jeskai"), + SULTAI("Sultai"), + + MIRRAN("Mirran"), + PHYREXIAN("Phyrexian "), + + ODD("odd"), + EVEN("even"), + + BELIEVE("Believe"), + DOUBT("Doubt"), + + NCR("NCR"), + LEGION("Legion"), + + BROTHERHOOD("Brotherhood"), + ENCLAVE("Enclave"), + + ISLAND("Island"), + SWAMP("Swamp"), + + LEFT("left"), + RIGHT("right"); + + private static class ModeChoiceCondition implements Condition { + + private final ModeChoice modeChoice; + + ModeChoiceCondition(ModeChoice modeChoice) { + this.modeChoice = modeChoice; + } + + @Override + public boolean apply(Game game, Ability source) { + return modeChoice.checkMode(game, source); + } + } + + private final String name; + private final ModeChoiceCondition condition; + + ModeChoice(String name) { + this.name = name; + this.condition = new ModeChoiceCondition(this); + } + + @Override + public String toString() { + return name; + } + + public ModeChoiceCondition getCondition() { + return condition; + } + + public boolean checkMode(Game game, Ability source) { + return Objects.equals(game.getState().getValue(source.getSourceId() + "_modeChoice"), name); + } +} diff --git a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java index a57e28325f6..6eedb18741f 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java @@ -15,6 +15,7 @@ public enum SpellAbilityCastMode { NORMAL("Normal"), MADNESS("Madness"), FLASHBACK("Flashback"), + HARMONIZE("Harmonize"), BESTOW("Bestow"), PROTOTYPE("Prototype"), MORPH("Morph", false, true), // and megamorph @@ -91,6 +92,7 @@ public enum SpellAbilityCastMode { case NORMAL: case MADNESS: case FLASHBACK: + case HARMONIZE: case DISTURB: case PLOT: case MORE_THAN_MEETS_THE_EYE: diff --git a/Mage/src/main/java/mage/constants/SpellAbilityType.java b/Mage/src/main/java/mage/constants/SpellAbilityType.java index cb95677e060..fac7a218aef 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityType.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityType.java @@ -15,7 +15,8 @@ public enum SpellAbilityType { MODAL_LEFT("LeftModal SpellAbility"), MODAL_RIGHT("RightModal SpellAbility"), SPLICE("Spliced SpellAbility"), - ADVENTURE_SPELL("Adventure SpellAbility"); + ADVENTURE_SPELL("Adventure SpellAbility"), + OMEN_SPELL("Omen SpellAbility"); private final String text; diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 5037f60d44a..cbf9f006c19 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -13,6 +13,7 @@ public enum SubType { ADVENTURE("Adventure", SubTypeSet.SpellType), ARCANE("Arcane", SubTypeSet.SpellType), LESSON("Lesson", SubTypeSet.SpellType), + OMEN("Omen", SubTypeSet.SpellType), TRAP("Trap", SubTypeSet.SpellType), // Battle subtypes @@ -274,6 +275,7 @@ public enum SubType { MONGOOSE("Mongoose", SubTypeSet.CreatureType), MONK("Monk", SubTypeSet.CreatureType), MONKEY("Monkey", SubTypeSet.CreatureType), + MOOGLE("Moogle", SubTypeSet.CreatureType), MOONFOLK("Moonfolk", SubTypeSet.CreatureType), MOUNT("Mount", SubTypeSet.CreatureType), MOUSE("Mouse", SubTypeSet.CreatureType), diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index dcb5353e8b3..0afd7f62cb5 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -54,6 +54,7 @@ public enum CounterType { CURRENCY("currency"), DEATH("death"), DEATHTOUCH("deathtouch"), + DECAYED("decayed"), DEFENSE("defense"), DELAY("delay"), DEPLETION("depletion"), @@ -184,6 +185,7 @@ public enum CounterType { PREY("prey"), PUPA("pupa"), RAD("rad"), + RALLY("rally"), REACH("reach"), REJECTION("rejection"), REPAIR("repair"), @@ -314,6 +316,8 @@ public enum CounterType { return new BoostCounter(-2, -2, amount); case DEATHTOUCH: return new AbilityCounter(DeathtouchAbility.getInstance(), amount); + case DECAYED: + return new AbilityCounter(new DecayedAbility(), amount); case DOUBLE_STRIKE: return new AbilityCounter(DoubleStrikeAbility.getInstance(), amount); case EXALTED: diff --git a/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java b/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java index 7f412b0e45f..dc15882ba86 100644 --- a/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java @@ -1,9 +1,6 @@ package mage.filter.predicate.card; -import mage.cards.AdventureCard; -import mage.cards.Card; -import mage.cards.ModalDoubleFacedCard; -import mage.cards.SplitCard; +import mage.cards.*; import mage.cards.mock.MockCard; import mage.constants.SubType; import mage.constants.SuperType; @@ -55,8 +52,8 @@ public class CardTextPredicate implements Predicate { fullName = ((MockCard) input).getFullName(true); } else if (input instanceof ModalDoubleFacedCard) { fullName = input.getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + ((ModalDoubleFacedCard) input).getRightHalfCard().getName(); - } else if (input instanceof AdventureCard) { - fullName = input.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + ((AdventureCard) input).getSpellCard().getName(); + } else if (input instanceof CardWithSpellOption) { + fullName = input.getName() + MockCard.CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + ((CardWithSpellOption) input).getSpellCard().getName(); } if (fullName.toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) { @@ -107,8 +104,8 @@ public class CardTextPredicate implements Predicate { } } - if (input instanceof AdventureCard) { - for (String rule : ((AdventureCard) input).getSpellCard().getRules(game)) { + if (input instanceof CardWithSpellOption) { + for (String rule : ((CardWithSpellOption) input).getSpellCard().getRules(game)) { if (rule.toLowerCase(Locale.ENGLISH).contains(token)) { found = true; break; diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 17238d3c262..7ff0e1610a7 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -334,8 +334,8 @@ public abstract class GameImpl implements Game { Card rightCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); rightCard.setOwnerId(ownerId); addCardToState(rightCard); - } else if (card instanceof AdventureCard) { - Card spellCard = ((AdventureCard) card).getSpellCard(); + } else if (card instanceof CardWithSpellOption) { + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); spellCard.setOwnerId(ownerId); addCardToState(spellCard); } else if (card.isTransformable() && card.getSecondCardFace() != null) { diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index e0c3d41d452..3f13573471d 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -1639,14 +1639,14 @@ public class GameState implements Serializable, Copyable { copiedParts.add(rightCopied); // sync parts ((ModalDoubleFacedCard) copiedCard).setParts(leftCopied, rightCopied); - } else if (copiedCard instanceof AdventureCard) { + } else if (copiedCard instanceof CardWithSpellOption) { // right - AdventureCardSpell rightOriginal = ((AdventureCard) copiedCard).getSpellCard(); - AdventureCardSpell rightCopied = rightOriginal.copy(); + SpellOptionCard rightOriginal = ((CardWithSpellOption) copiedCard).getSpellCard(); + SpellOptionCard rightCopied = rightOriginal.copy(); prepareCardForCopy(rightOriginal, rightCopied, newController); copiedParts.add(rightCopied); // sync parts - ((AdventureCard) copiedCard).setParts(rightCopied); + ((CardWithSpellOption) copiedCard).setParts(rightCopied); } // main part prepare (must be called after other parts cause it change ids for all) diff --git a/Mage/src/main/java/mage/game/permanent/token/AlienRhinoToken.java b/Mage/src/main/java/mage/game/permanent/token/AlienRhinoToken.java new file mode 100644 index 00000000000..43b6fbd5da4 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/AlienRhinoToken.java @@ -0,0 +1,34 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.PreventDamageToSourceEffect; +import mage.abilities.keyword.VanishingAbility; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; + +/** + * @author padfoothelix + */ +public final class AlienRhinoToken extends TokenImpl { + + public AlienRhinoToken() { + super("Alien Rhino Token", "4/4 white Alien Rhino creature token"); + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.ALIEN); + subtype.add(SubType.RHINO); + power = new MageInt(4); + toughness = new MageInt(4); + } + + private AlienRhinoToken(final AlienRhinoToken token) { + super(token); + } + + @Override + public AlienRhinoToken copy() { + return new AlienRhinoToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/Human11WithWard2Token.java b/Mage/src/main/java/mage/game/permanent/token/Human11WithWard2Token.java new file mode 100644 index 00000000000..a39848fcb5c --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/Human11WithWard2Token.java @@ -0,0 +1,33 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.keyword.WardAbility; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; + +/** + * @author padfoothelix + */ +public final class Human11WithWard2Token extends TokenImpl { + + public Human11WithWard2Token() { + super("Human Token", "1/1 white Human creature token with ward {2}"); + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.HUMAN); + power = new MageInt(1); + toughness = new MageInt(1); + this.addAbility(new WardAbility(new GenericManaCost(2))); + } + + private Human11WithWard2Token(final Human11WithWard2Token token) { + super(token); + } + + @Override + public Human11WithWard2Token copy() { + return new Human11WithWard2Token(this); + } +} diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index c234e901643..149d8a6e92b 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -209,10 +209,11 @@ public class Spell extends StackObjectImpl implements Card { + " using " + this.getSpellAbility().getSpellAbilityCastMode(); } - if (card instanceof AdventureCardSpell) { - AdventureCard adventureCard = ((AdventureCardSpell) card).getParentCard(); - return GameLog.replaceNameByColoredName(card, getSpellAbility().toString(), adventureCard) - + " as Adventure spell of " + GameLog.getColoredObjectIdName(adventureCard); + if (card instanceof SpellOptionCard) { + CardWithSpellOption parentCard = ((SpellOptionCard) card).getParentCard(); + String type = ((SpellOptionCard) card).getSpellType(); + return GameLog.replaceNameByColoredName(card, getSpellAbility().toString(), parentCard) + + " as " + type + " spell of " + GameLog.getColoredObjectIdName(parentCard); } if (card instanceof ModalDoubleFacedCardHalf) { @@ -539,8 +540,8 @@ public class Spell extends StackObjectImpl implements Card { public String getIdName() { String idName; if (card != null) { - if (card instanceof AdventureCardSpell) { - idName = ((AdventureCardSpell) card).getParentCard().getId().toString().substring(0, 3); + if (card instanceof SpellOptionCard) { + idName = ((SpellOptionCard) card).getParentCard().getId().toString().substring(0, 3); } else { idName = card.getId().toString().substring(0, 3); } diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index 4f9244234ba..34fb4333ab9 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -426,6 +426,16 @@ public class StackAbility extends StackObjectImpl implements Ability { // Do nothing } + @Override + public void setVariableCostsMinMax(int min, int max) { + ability.setVariableCostsMinMax(min, max); + } + + @Override + public void setVariableCostsValue(int xValue) { + ability.setVariableCostsValue(xValue); + } + @Override public Map getCostsTagMap() { return ability.getCostsTagMap(); @@ -778,14 +788,23 @@ public class StackAbility extends StackObjectImpl implements Ability { } @Override - public CostAdjuster getCostAdjuster() { - return costAdjuster; + public void adjustX(Game game) { + if (costAdjuster != null) { + costAdjuster.prepareX(this, game); + } } @Override - public void adjustCosts(Game game) { + public void adjustCostsPrepare(Game game) { if (costAdjuster != null) { - costAdjuster.adjustCosts(this, game); + costAdjuster.prepareCost(this, game); + } + } + + @Override + public void adjustCostsModify(Game game, CostModificationType costModificationType) { + if (costAdjuster != null) { + costAdjuster.modifyCost(this, game, costModificationType); } } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index c6c7d14039c..987b47e37f8 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -743,10 +743,14 @@ public interface Player extends MageItem, Copyable { boolean shuffleCardsToLibrary(Card card, Game game, Ability source); - // set the value for X mana spells and abilities + /** + * Set the value for X mana spells and abilities + */ int announceXMana(int min, int max, String message, Game game, Ability ability); - // set the value for non mana X costs + /** + * Set the value for non mana X costs + */ int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost); // TODO: rework to use pair's list of effect + ability instead string's map diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index a6f3ac1dd88..88e36b7c3fb 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3679,8 +3679,11 @@ public abstract class PlayerImpl implements Player, Serializable { if (!copy.canActivate(playerId, game).canActivate()) { return false; } + + // apply dynamic costs and cost modification + copy.adjustX(game); if (availableMana != null) { - copy.adjustCosts(game); + // TODO: need research, why it look at availableMana here - can delete condition? game.getContinuousEffects().costModification(copy, game); } boolean canBeCastRegularly = true; @@ -3891,7 +3894,8 @@ public abstract class PlayerImpl implements Player, Serializable { copyAbility = ability.copy(); copyAbility.clearManaCostsToPay(); copyAbility.addManaCostsToPay(manaCosts.copy()); - copyAbility.adjustCosts(game); + // apply dynamic costs and cost modification + copyAbility.adjustX(game); game.getContinuousEffects().costModification(copyAbility, game); // reduced all cost @@ -3963,12 +3967,9 @@ public abstract class PlayerImpl implements Player, Serializable { // alternative cost reduce copyAbility = ability.copy(); copyAbility.clearManaCostsToPay(); - // TODO: IDE warning: - // Unchecked assignment: 'mage.abilities.costs.mana.ManaCosts' to - // 'java.util.Collection'. - // Reason: 'manaCosts' has raw type, so result of copy is erased copyAbility.addManaCostsToPay(manaCosts.copy()); - copyAbility.adjustCosts(game); + // apply dynamic costs and cost modification + copyAbility.adjustX(game); game.getContinuousEffects().costModification(copyAbility, game); // reduced all cost @@ -4068,11 +4069,11 @@ public abstract class PlayerImpl implements Player, Serializable { getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output); getPlayableFromObjectSingle(game, fromZone, mainCard.getRightHalfCard(), mainCard.getRightHalfCard().getAbilities(game), availableMana, output); getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output); - } else if (object instanceof AdventureCard) { + } else if (object instanceof CardWithSpellOption) { // adventure must use different card characteristics for different spells (main or adventure) - AdventureCard adventureCard = (AdventureCard) object; - getPlayableFromObjectSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(game), availableMana, output); - getPlayableFromObjectSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(game), availableMana, output); + CardWithSpellOption cardWithSpellOption = (CardWithSpellOption) object; + getPlayableFromObjectSingle(game, fromZone, cardWithSpellOption.getSpellCard(), cardWithSpellOption.getSpellCard().getAbilities(game), availableMana, output); + getPlayableFromObjectSingle(game, fromZone, cardWithSpellOption, cardWithSpellOption.getSharedAbilities(game), availableMana, output); } else if (object instanceof Card) { getPlayableFromObjectSingle(game, fromZone, object, ((Card) object).getAbilities(game), availableMana, output); } else if (object instanceof StackObject) { diff --git a/Mage/src/main/java/mage/target/targetadjustment/TargetsCountAdjuster.java b/Mage/src/main/java/mage/target/targetadjustment/TargetsCountAdjuster.java index c84fbd949d5..c8ffbea7271 100644 --- a/Mage/src/main/java/mage/target/targetadjustment/TargetsCountAdjuster.java +++ b/Mage/src/main/java/mage/target/targetadjustment/TargetsCountAdjuster.java @@ -25,8 +25,13 @@ public class TargetsCountAdjuster extends GenericTargetAdjuster { @Override public void adjustTargets(Ability ability, Game game) { - Target newTarget = blueprintTarget.copy(); int count = dynamicValue.calculate(game, ability, ability.getEffects().get(0)); + ability.getTargets().clear(); + if (count <= 0) { + return; + } + + Target newTarget = blueprintTarget.copy(); newTarget.setMaxNumberOfTargets(count); Filter filter = newTarget.getFilter(); if (blueprintTarget.getMinNumberOfTargets() != 0) { @@ -35,7 +40,6 @@ public class TargetsCountAdjuster extends GenericTargetAdjuster { } else { newTarget.withTargetName(filter.getMessage() + " (up to " + count + " targets)"); } - ability.getTargets().clear(); ability.addTarget(newTarget); } } diff --git a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java index af3d80dbb31..1a3b3d926bd 100644 --- a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java +++ b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java @@ -22,14 +22,7 @@ public class FixedTargets extends TargetPointerImpl { final ArrayList targets = new ArrayList<>(); - public FixedTargets(List objects, Game game) { - this(objects - .stream() - .map(o -> new MageObjectReference(o.getId(), game)) - .collect(Collectors.toList())); - } - - public FixedTargets(Set objects, Game game) { + public FixedTargets(Collection objects, Game game) { this(objects .stream() .map(o -> new MageObjectReference(o.getId(), game)) @@ -50,7 +43,7 @@ public class FixedTargets extends TargetPointerImpl { .collect(Collectors.toList()), game); } - public FixedTargets(List morList) { + public FixedTargets(Collection morList) { super(); targets.addAll(morList); this.setInitialized(); // no need dynamic init diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 7ba17e4036f..eb2a2e8d513 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1079,6 +1079,53 @@ public final class CardUtil { .collect(Collectors.toSet()); } + public static Set getAllPossibleTargets(Cost cost, Game game, Ability source) { + return cost.getTargets() + .stream() + .map(t -> t.possibleTargets(source.getControllerId(), source, game)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } + + /** + * Distribute values between min and max and make sure that the values will be evenly distributed + * Use it to limit possible values list like mana options + */ + public static List distributeValues(int count, int min, int max) { + List res = new ArrayList<>(); + if (count <= 0 || min > max) { + return res; + } + + if (min == max) { + res.add(min); + return res; + } + + int range = max - min + 1; + + // low possible amount + if (range <= count) { + for (int i = 0; i < range; i++) { + res.add(min + i); + } + return res; + } + + // big possible amount, so skip some values + double step = (double) (max - min) / (count - 1); + for (int i = 0; i < count; i++) { + res.add(min + (int) Math.round(i * step)); + } + // make sure first and last elements are good + res.set(0, min); + if (res.size() > 1) { + res.set(res.size() - 1, max); + } + + return res; + } + /** * For finding the spell or ability on the stack for "becomes the target" triggers. * @@ -1086,13 +1133,22 @@ public final class CardUtil { * @param game the Game from checkTrigger() or watch() * @return the StackObject which targeted the source, or null if not found */ - public static StackObject getTargetingStackObject(GameEvent event, Game game) { + public static StackObject getTargetingStackObject(String checkingReference, GameEvent event, Game game) { // In case of multiple simultaneous triggered abilities from the same source, // need to get the actual one that targeted, see #8026, #8378 // Also avoids triggering on cancelled selections, see #8802 + String stateKey = "targetedMap" + checkingReference; + Map> targetMap = (Map>) game.getState().getValue(stateKey); + // targetMap: key - targetId; value - Set of stackObject Ids + if (targetMap == null) { + targetMap = new HashMap<>(); + } else { + targetMap = new HashMap<>(targetMap); // must have new object reference if saved back to game state + } + Set targetingObjects = targetMap.computeIfAbsent(event.getTargetId(), k -> new HashSet<>()); for (StackObject stackObject : game.getStack()) { Ability stackAbility = stackObject.getStackAbility(); - if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId())) { + if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId()) || targetingObjects.contains(stackObject.getId())) { continue; } if (CardUtil.getAllSelectedTargets(stackAbility, game).contains(event.getTargetId())) { @@ -1263,7 +1319,7 @@ public final class CardUtil { Card permCard; if (card instanceof SplitCard) { permCard = card; - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { permCard = card; } else if (card instanceof ModalDoubleFacedCard) { permCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); @@ -1451,9 +1507,9 @@ public final class CardUtil { if (cardToCast instanceof CardWithHalves) { cards.add(((CardWithHalves) cardToCast).getLeftHalfCard()); cards.add(((CardWithHalves) cardToCast).getRightHalfCard()); - } else if (cardToCast instanceof AdventureCard) { + } else if (cardToCast instanceof CardWithSpellOption) { cards.add(cardToCast); - cards.add(((AdventureCard) cardToCast).getSpellCard()); + cards.add(((CardWithSpellOption) cardToCast).getSpellCard()); } else { cards.add(cardToCast); } @@ -1642,9 +1698,9 @@ public final class CardUtil { } // handle adventure cards - if (card instanceof AdventureCard) { + if (card instanceof CardWithSpellOption) { Card creatureCard = card.getMainCard(); - Card spellCard = ((AdventureCard) card).getSpellCard(); + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); if (manaCost != null) { // get additional cost if any Costs additionalCostsCreature = creatureCard.getSpellAbility().getCosts(); @@ -1682,9 +1738,9 @@ public final class CardUtil { game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null); game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null); } - if (card instanceof AdventureCard) { + if (card instanceof CardWithSpellOption) { Card creatureCard = card.getMainCard(); - Card spellCard = ((AdventureCard) card).getSpellCard(); + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); game.getState().setValue("PlayFromNotOwnHandZone" + creatureCard.getId(), null); game.getState().setValue("PlayFromNotOwnHandZone" + spellCard.getId(), null); } @@ -1899,6 +1955,10 @@ public final class CardUtil { return defaultValue; } + public static int getSourceCostsTagX(Game game, Ability source, int defaultValue) { + return getSourceCostsTag(game, source, "X", defaultValue); + } + public static String addCostVerb(String text) { if (costWords.stream().anyMatch(text.toLowerCase(Locale.ENGLISH)::startsWith)) { return text; @@ -2069,8 +2129,8 @@ public final class CardUtil { res.add(mainCard); res.add(mainCard.getLeftHalfCard()); res.add(mainCard.getRightHalfCard()); - } else if (object instanceof AdventureCard || object instanceof AdventureCardSpell) { - AdventureCard mainCard = (AdventureCard) ((Card) object).getMainCard(); + } else if (object instanceof CardWithSpellOption || object instanceof SpellOptionCard) { + CardWithSpellOption mainCard = (CardWithSpellOption) ((Card) object).getMainCard(); res.add(mainCard); res.add(mainCard.getSpellCard()); } else if (object instanceof Spell) { @@ -2176,6 +2236,19 @@ public final class CardUtil { return sb.toString(); } + public static T getEffectValueFromAbility(Ability ability, String key, Class clazz) { + return getEffectValueFromAbility(ability, key, clazz, null); + } + + public static T getEffectValueFromAbility(Ability ability, String key, Class clazz, T defaultValue) { + return castStream( + ability.getAllEffects() + .stream() + .map(effect -> effect.getValue(key)), + clazz + ).findFirst().orElse(defaultValue); + } + public static Stream castStream(Collection collection, Class clazz) { return castStream(collection.stream(), clazz); } diff --git a/Mage/src/main/java/mage/util/CircularList.java b/Mage/src/main/java/mage/util/CircularList.java index 5fb6a09c6d6..45890ad68f1 100644 --- a/Mage/src/main/java/mage/util/CircularList.java +++ b/Mage/src/main/java/mage/util/CircularList.java @@ -82,8 +82,8 @@ public class CircularList implements List, Iterable, Serializable { */ @Override public E get(int index) { - if (list.size() > this.index) { - return list.get(this.index); + if (list.size() > index) { + return list.get(index); } return null; } diff --git a/Mage/src/main/java/mage/util/ManaUtil.java b/Mage/src/main/java/mage/util/ManaUtil.java index 08b478d6e23..816353e13e9 100644 --- a/Mage/src/main/java/mage/util/ManaUtil.java +++ b/Mage/src/main/java/mage/util/ManaUtil.java @@ -13,10 +13,7 @@ import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.Effect; import mage.abilities.mana.*; -import mage.cards.AdventureCard; -import mage.cards.Card; -import mage.cards.ModalDoubleFacedCard; -import mage.cards.SplitCard; +import mage.cards.*; import mage.choices.Choice; import mage.constants.ColoredManaSymbol; import mage.constants.ManaType; @@ -644,8 +641,8 @@ public final class ManaUtil { Card secondSide; if (card instanceof SplitCard) { secondSide = ((SplitCard) card).getRightHalfCard(); - } else if (card instanceof AdventureCard) { - secondSide = ((AdventureCard) card).getSpellCard(); + } else if (card instanceof CardWithSpellOption) { + secondSide = ((CardWithSpellOption) card).getSpellCard(); } else if (card instanceof ModalDoubleFacedCard) { secondSide = ((ModalDoubleFacedCard) card).getRightHalfCard(); } else { diff --git a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java index c97cf9436f3..accc6b4628c 100644 --- a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java @@ -29,7 +29,7 @@ public class NumberOfTimesPermanentTargetedATurnWatcher extends Watcher { if (event.getType() != GameEvent.EventType.TARGETED) { return; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getKey(), event, game); if (targetingObject == null || CardUtil.checkTargetedEventAlreadyUsed(this.getKey(), targetingObject, event, game)) { return; } diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index 0b77e47469c..8dc1ecf4e63 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -2235,6 +2235,7 @@ # WHO |Generate|TOK:WHO|Alien|||AlienToken| |Generate|TOK:WHO|Alien Insect|||AlienInsectToken| +|Generate|TOK:WHO|Alien Rhino|||AlienRhinoToken| |Generate|TOK:WHO|Alien Salamander|||AlienSalamanderToken| |Generate|TOK:WHO|Alien Warrior|||AlienWarriorToken| |Generate|TOK:WHO|Beast|||BeastToken| @@ -2248,7 +2249,8 @@ |Generate|TOK:WHO|Food|2||FoodToken| |Generate|TOK:WHO|Food|3||FoodToken| |Generate|TOK:WHO|Horse|||TheGirlInTheFireplaceHorseToken| -|Generate|TOK:WHO|Human|||TheEleventhHourToken| +|Generate|TOK:WHO|Human|1||Human11WithWard2Token| +|Generate|TOK:WHO|Human|2||TheEleventhHourToken| |Generate|TOK:WHO|Human Noble|||TheGirlInTheFireplaceHumanNobleToken| |Generate|TOK:WHO|Mark of the Rani|||MarkOfTheRaniToken| |Generate|TOK:WHO|Soldier|||SoldierToken| diff --git a/Utils/keywords.txt b/Utils/keywords.txt index a3eea683be2..fb6f00141ca 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -25,6 +25,7 @@ Cycling|cost| Dash|manaString| Daybound|new| Deathtouch|instance| +Decayed|new| Demonstrate|new| Delve|new| Dethrone|new| diff --git a/pom.xml b/pom.xml index a9f8e251668..f61deeb6eff 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.mage mage-root - 1.4.56 + 1.4.57 pom Mage Root Mage Root POM @@ -16,7 +16,7 @@ ${project.basedir} - 1.4.56 + 1.4.57 -Dfile.encoding=UTF-8 UTF-8 yyyy-MM-dd'T'HH:mm:ss'Z'