diff --git a/Mage.Client/src/main/java/mage/client/cards/CardArea.java b/Mage.Client/src/main/java/mage/client/cards/CardArea.java index d49afeb9b03..82583d73d94 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardArea.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardArea.java @@ -18,10 +18,8 @@ import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.Comparator; +import java.util.*; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; /** @@ -258,7 +256,7 @@ public class CardArea extends JPanel implements CardEventProducer { this.reloaded = false; } - public void selectCards(List selected) { + public void selectCards(Set selected) { for (Component component : cardArea.getComponents()) { if (component instanceof MageCard) { MageCard mageCard = (MageCard) component; @@ -269,7 +267,7 @@ public class CardArea extends JPanel implements CardEventProducer { } } - public void markCards(List marked) { + public void markCards(Set marked) { for (Component component : cardArea.getComponents()) { if (component instanceof MageCard) { MageCard mageCard = (MageCard) component; diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckImportClipboardDialog.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckImportClipboardDialog.java index f08bdbd4366..11347eb59ce 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckImportClipboardDialog.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckImportClipboardDialog.java @@ -1,5 +1,6 @@ package mage.client.deckeditor; +import mage.cards.decks.importer.MtgaImporter; import mage.client.MageFrame; import mage.client.dialog.MageDialog; import mage.util.DeckUtil; @@ -20,11 +21,18 @@ import java.util.Optional; public class DeckImportClipboardDialog extends MageDialog { private static final String FORMAT_TEXT = - "// Example:\n" + - "//1 Library of Congress\n" + - "//1 Cryptic Gateway\n" + - "//1 Azami, Lady of Scrolls\n" + - "// NB: This is slow as, and will lock your screen :)\n" + + "// MTGO format example:\n" + + "// 1 Library of Congress\n" + + "// 3 Cryptic Gateway\n" + + "//\n" + + "// MTGA, moxfield, archidekt format example:\n" + + "// Deck\n" + + "// 4 Accumulated Knowledge (A25) 40\n" + + "// 2 Adarkar Wastes (EOC) 147\n" + + "// Commander\n" + + "// 1 Grizzly Bears\n" + + "//\n" + + "// Importing deck can take some time to finish\n" + "\n" + "// Your current clipboard:\n" + "\n"; @@ -68,17 +76,19 @@ public class DeckImportClipboardDialog extends MageDialog { } private void onOK() { - String decklist = editData.getText(); - decklist = decklist.replace(FORMAT_TEXT, ""); + String importData = editData.getText(); + importData = importData.replace(FORMAT_TEXT, "").trim(); + // find possible data format String tempDeckPath; - // This dialog also accepts a paste in .mtga format - if (decklist.startsWith("Deck\n")) { // An .mtga list always starts with the first line being "Deck". This kind of paste is processed as .mtga - tempDeckPath = DeckUtil.writeTextToTempFile("cbimportdeck", ".mtga", decklist); + if (MtgaImporter.isMTGA(importData)) { + // MTGA or Moxfield + tempDeckPath = DeckUtil.writeTextToTempFile("cbimportdeck", ".mtga", importData); } else { - // If the paste is not .mtga format, it's processed as plaintext - tempDeckPath = DeckUtil.writeTextToTempFile(decklist); + // text + tempDeckPath = DeckUtil.writeTextToTempFile(importData); } + if (this.callback != null) { callback.onImportDone(tempDeckPath); } diff --git a/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java b/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java index 34090e0ff8c..0b366daa334 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java @@ -100,11 +100,11 @@ cardArea.loadCards(showCards, bigCard, gameId); if (options != null) { if (options.containsKey("chosenTargets")) { - java.util.List chosenCards = (java.util.List) options.get("chosenTargets"); + java.util.Set chosenCards = (java.util.Set) options.get("chosenTargets"); cardArea.selectCards(chosenCards); } if (options.containsKey("possibleTargets")) { - java.util.List choosableCards = (java.util.List) options.get("possibleTargets"); + java.util.Set choosableCards = (java.util.Set) options.get("possibleTargets"); cardArea.markCards(choosableCards); } if (options.containsKey("queryType") && options.get("queryType") == QueryType.PICK_ABILITY) { diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 6b687a3c305..5c778dac428 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -216,17 +216,9 @@ public final class GamePanel extends javax.swing.JPanel { }); } - public List getPossibleTargets() { - if (options != null && options.containsKey("possibleTargets")) { - return (List) options.get("possibleTargets"); - } else { - return Collections.emptyList(); - } - } - public Set getChosenTargets() { if (options != null && options.containsKey("chosenTargets")) { - return new HashSet<>((List) options.get("chosenTargets")); + return (Set) options.get("chosenTargets"); } else { return Collections.emptySet(); } diff --git a/Mage.Client/src/main/java/mage/client/remote/XmageURLConnection.java b/Mage.Client/src/main/java/mage/client/remote/XmageURLConnection.java index ef4e38fba03..6b2ebad8a66 100644 --- a/Mage.Client/src/main/java/mage/client/remote/XmageURLConnection.java +++ b/Mage.Client/src/main/java/mage/client/remote/XmageURLConnection.java @@ -10,10 +10,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.URL; +import java.net.*; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; @@ -93,7 +90,9 @@ public class XmageURLConnection { initDefaultProxy(); try { - URL url = new URL(this.url); + // convert utf8 url to ascii format (e.g. url encode) + URI uri = new URI(this.url); + URL url = new URL(uri.toASCIIString()); // proxy settings if (this.proxy != null) { @@ -107,7 +106,7 @@ public class XmageURLConnection { this.connection.setReadTimeout(CONNECTION_READING_TIMEOUT_MS); initDefaultHeaders(); - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { this.connection = null; } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java index 18de4e36e2d..522ae5632af 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java @@ -107,7 +107,7 @@ public class GathererSets implements Iterable { "JMP", "2XM", "ZNR", "KLR", "CMR", "KHC", "KHM", "TSR", "STX", "STA", "C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID", "NEC", "YNEO", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "40K", "GN3", - "UNF", "BRO", "BRC", "BOT", "J22", "DMR", "ONE", "ONC", + "UNF", "BRO", "BRC", "BOT", "J22", "DMR", "ONE", "ONC", "SCH", "MOM", "MOC", "MUL", "MAT", "LTR", "CMM", "WOE", "WHO", "RVR", "WOT", "WOC", "SPG", "LCI", "LCC", "REX", "PIP", "MKM", "MKC", "CLU", "OTJ", "OTC", "OTP", "BIG", "MH3", "M3C", "ACR", "BLB", "BLC", "DSK", "DSC", diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java index 29b20a0d26b..5fc2c4b0f2b 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java @@ -28,6 +28,8 @@ public class ScryfallImageSupportCards { add("2ED"); // Unlimited Edition //add("CEI"); // Intl. Collectors’ Edition //add("CED"); // Collectors’ Edition + add("RIN"); // Rinascimento + add("REN"); // Renaissance add("ARN"); // Arabian Nights add("ATQ"); // Antiquities //add("FBB"); // Foreign Black Border @@ -465,6 +467,7 @@ public class ScryfallImageSupportCards { // add("MD1"); // Modern Event Deck // add("DD3"); // Duel Decks Anthology // add("PZ1"); // Legendary Cube + add("PLG20"); // Love Your LGS 2020 add("IKO"); // Ikoria: Lair of Behemoths add("C20"); // Commander 2020 add("M21"); // Core Set 2021 @@ -514,10 +517,13 @@ public class ScryfallImageSupportCards { add("NCC"); // New Capenna Commander add("SLX"); // Universes Within add("CLB"); // Commander Legends: Battle for Baldur's Gate + add("PLG22"); // Love Your LGS 2022 add("2X2"); // Double Masters 2022 + add("SCH"); // Store Championships add("DMU"); // Dominaria United add("DMC"); // Dominaria United Commander add("YDMU"); // Alchemy: Dominaria + add("PRCQ"); // Regional Championship Qualifiers 2022 add("40K"); // Warhammer 40,000 Commander add("UNF"); // Unfinity add("GN3"); // Game Night: Free-for-All @@ -545,9 +551,11 @@ public class ScryfallImageSupportCards { add("30A"); // 30th Anniversary Edition add("P30A"); // 30th Anniversary Play Promos add("P30M"); // 30th Anniversary Misc Promos + add("P30H"); // 30th Anniversary History Promos add("PEWK"); // Eternal Weekend add("LTR"); // The Lord of the Rings: Tales of Middle-Earth add("LTC"); // Tales of Middle-Earth Commander + add("PF23"); // MagicFest 2023 add("CMM"); // Commander Masters add("WHO"); // Doctor Who add("WOE"); // Wilds of Eldraine @@ -558,10 +566,13 @@ public class ScryfallImageSupportCards { add("REX"); // Jurassic World Collection add("SPG"); // Special Guests add("PW24"); // Wizards Play Network 2024 + add("PF24"); // MagicFest 2024 add("RVR"); // Ravnica Remastered + add("PL24"); // Year of the Dragon 2024 add("PIP"); // Fallout add("MKM"); // Murders at Karlov Manor add("MKC"); // Murders at Karlov Manor Commander + add("PSS4"); // MKM Standard Showdown add("CLU"); // Ravnica: Clue Edition add("OTJ"); // Outlaws of Thunder Junction add("OTC"); // Outlaws of Thunder Junction Commander @@ -569,9 +580,12 @@ public class ScryfallImageSupportCards { add("BIG"); // The Big Score add("MH3"); // Modern Horizons 3 add("M3C"); // Modern Horizons 3 Commander + add("H2R"); // Modern Horizons 2 Timeshifts add("ACR"); // Assassin's Creed add("BLB"); // Bloomburrow add("BLC"); // Bloomburrow Commander + add("PLG24"); // Love Your LGS 2024 + add("PCBB"); // Cowboy Bebop add("MB2"); // Mystery Booster 2 add("DSK"); // Duskmourn: House of Horror add("DSC"); // Duskmourn: House of Horror Commander @@ -579,21 +593,26 @@ public class ScryfallImageSupportCards { add("J25"); // Foundations Jumpstart add("PIO"); // Pioneer Masters add("PW25"); // Wizards Play Network 2025 + add("PSPL"); // Spotlight Series add("INR"); // Innistrad Remastered add("PF25"); // MagicFest 2025 + add("PL25"); // Year of the Snake 2025 add("DFT"); // Aetherdrift add("DRC"); // Aetherdrift Commander + add("PLG25"); // Love Your LGS 2025 add("TDM"); // Tarkir: Dragonstorm add("TDC"); // Tarkir: Dragonstorm Commander add("FIN"); // Final Fantasy add("FIC"); // Final Fantasy Commander add("FCA"); // Final Fantasy: Through the Ages + add("PSS5"); // FIN Standard Showdown add("EOE"); // Edge of Eternities add("EOC"); // Edge of Eternities Commander add("EOS"); // Edge of Eternities: Stellar Sights add("SPM"); // Marvel's Spider-Man add("SPE"); // Marvel's Spider-Man Eternal add("TLA"); // Avatar: The Last Airbender + add("TLE"); // Avatar: The Last Airbender Eternal // Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks add("CALC"); // Custom Alchemized versions of existing cards 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 1d07e69f7af..96db19de51a 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 @@ -1,10 +1,10 @@ package org.mage.plugins.card.dl.sources; -import mage.cards.repository.TokenRepository; - import java.util.HashMap; import java.util.Map; +import mage.cards.repository.TokenRepository; + /** * @author JayDi85 */ @@ -837,6 +837,8 @@ public class ScryfallImageSupportTokens { put("SLD/Hydra", "https://api.scryfall.com/cards/sld/1334?format=image"); put("SLD/Icingdeath, Frost Tongue", "https://api.scryfall.com/cards/sld/1018?format=image"); put("SLD/Marit Lage", "https://api.scryfall.com/cards/sld/1681?format=image"); + put("SLD/Mechtitan/1", "https://api.scryfall.com/cards/sld/1969?format=image"); + put("SLD/Mechtitan/2", "https://api.scryfall.com/cards/sld/1969/en?format=image&face=back"); put("SLD/Mechtitan", "https://api.scryfall.com/cards/sld/1969?format=image"); put("SLD/Myr", "https://api.scryfall.com/cards/sld/2101?format=image"); put("SLD/Saproling", "https://api.scryfall.com/cards/sld/1139?format=image"); @@ -2787,6 +2789,7 @@ public class ScryfallImageSupportTokens { // EOE put("EOE/Drone", "https://api.scryfall.com/cards/teoe/3?format=image"); + put("EOE/Emblem Tezzeret", "https://api.scryfall.com/cards/teoe/11?format=image"); put("EOE/Human Soldier", "https://api.scryfall.com/cards/teoe/2?format=image"); put("EOE/Lander/1", "https://api.scryfall.com/cards/teoe/4?format=image"); put("EOE/Lander/2", "https://api.scryfall.com/cards/teoe/5?format=image"); @@ -2928,6 +2931,12 @@ public class ScryfallImageSupportTokens { // PL23 put("PL23/Food", "https://api.scryfall.com/cards/pl23/2?format=image"); + // PL24 + put("PL24/Dragon", "https://api.scryfall.com/cards/pl24/3?format=image"); + + // PL25 + put("PL25/Snake", "https://api.scryfall.com/cards/pl25/2?format=image"); + // generate supported sets supportedSets.clear(); for (String cardName : this.keySet()) { diff --git a/Mage.Client/src/test/java/mage/client/util/DownloaderTest.java b/Mage.Client/src/test/java/mage/client/util/DownloaderTest.java index f90f7e8df0a..d81812d92b8 100644 --- a/Mage.Client/src/test/java/mage/client/util/DownloaderTest.java +++ b/Mage.Client/src/test/java/mage/client/util/DownloaderTest.java @@ -43,6 +43,12 @@ public class DownloaderTest { Assert.assertTrue("must have text data (redirect to login page)", s.contains("Sign in to GitHub")); } + @Test + public void test_DownloadText_ScryfallUtf8() { + String s = XmageURLConnection.downloadText("https://api.scryfall.com/cards/sld/379★/en"); + Assert.assertTrue("must have text data (utf8 url must work)", s.contains("Zndrsplt, Eye of Wisdom")); + } + @Test public void test_DownloadFile_ByHttp() throws IOException { // use any public image here diff --git a/Mage.Common/src/main/java/mage/utils/SystemUtil.java b/Mage.Common/src/main/java/mage/utils/SystemUtil.java index 408e0b0de29..c8144e49def 100644 --- a/Mage.Common/src/main/java/mage/utils/SystemUtil.java +++ b/Mage.Common/src/main/java/mage/utils/SystemUtil.java @@ -785,7 +785,7 @@ public final class SystemUtil { * @return added cards list */ private static Set addNewCardsToGame(Game game, String cardName, String setCode, int amount, Player owner) { - CardInfo cardInfo = CardLookup.instance.lookupCardInfo(cardName, setCode).orElse(null); + CardInfo cardInfo = CardLookup.instance.lookupCardInfo(cardName, setCode, null); if (cardInfo == null || amount <= 0) { return null; } diff --git a/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java index 2ae8d4cae79..27c89d98863 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java +++ b/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java @@ -7,6 +7,7 @@ import mage.game.Game; import mage.players.Player; import mage.target.Target; import mage.target.TargetPermanent; +import mage.target.TargetPlayer; import mage.target.common.TargetPermanentOrPlayer; /** @@ -76,6 +77,10 @@ abstract class BaseTestableDialog implements TestableDialog { return createAnyTarget(min, max, false); } + static Target createPlayerTarget(int min, int max, boolean notTarget) { + return new TargetPlayer(min, max, notTarget); + } + private static Target createAnyTarget(int min, int max, boolean notTarget) { return new TargetPermanentOrPlayer(min, max).withNotTarget(notTarget); } diff --git a/Mage.Common/src/main/java/mage/utils/testers/ChooseTargetTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/ChooseTargetTestableDialog.java index 017aa055b27..3a949ee5350 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/ChooseTargetTestableDialog.java +++ b/Mage.Common/src/main/java/mage/utils/testers/ChooseTargetTestableDialog.java @@ -126,6 +126,19 @@ class ChooseTargetTestableDialog extends BaseTestableDialog { runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1-3", createImpossibleTarget(1, 3)).aiMustChoose(false, 0)); runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 2-3", createImpossibleTarget(2, 3)).aiMustChoose(false, 0)); runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible max", createImpossibleTarget(0, Integer.MAX_VALUE)).aiMustChoose(false, 0)); + // + // additional tests for 2 possible options limitation + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0", createPlayerTarget(0, 0, notTarget)).aiMustChoose(false, 0)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0-1", createPlayerTarget(0, 1, notTarget)).aiMustChoose(true, 1)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0-2", createPlayerTarget(0, 2, notTarget)).aiMustChoose(true, 1)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0-5", createPlayerTarget(0, 5, notTarget)).aiMustChoose(true, 1)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 1", createPlayerTarget(1, 1, notTarget)).aiMustChoose(true, 1)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 1-2", createPlayerTarget(1, 2, notTarget)).aiMustChoose(true, 1)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 1-5", createPlayerTarget(1, 5, notTarget)).aiMustChoose(true, 1)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 2", createPlayerTarget(2, 2, notTarget)).aiMustChoose(true, 2)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 2-5", createPlayerTarget(2, 5, notTarget)).aiMustChoose(true, 2)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 3", createPlayerTarget(3, 3, notTarget)).aiMustChoose(false, 0)); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 3-5", createPlayerTarget(3, 5, notTarget)).aiMustChoose(false, 0)); } } } diff --git a/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java b/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java index 0970d8ecc94..f4805f2184e 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java +++ b/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java @@ -8,10 +8,7 @@ import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; /** @@ -120,10 +117,8 @@ public class TestableDialogsRunner { choice = prepareSelectDialogChoice(needGroup); player.choose(Outcome.Benefit, choice, game); if (choice.getChoiceKey() != null) { - int needIndex = Integer.parseInt(choice.getChoiceKey()); - if (needIndex < this.dialogs.size()) { - needDialog = this.dialogs.get(needIndex); - } + int needRegNumber = Integer.parseInt(choice.getChoiceKey()); + needDialog = this.dialogs.getOrDefault(needRegNumber, null); } } if (needDialog == null) { @@ -144,15 +139,20 @@ public class TestableDialogsRunner { Choice choice = new ChoiceImpl(false); choice.setMessage("Choose dialogs group to run"); + // use min reg number for groups + Map groupNumber = new HashMap<>(); + this.dialogs.values().forEach(dialog -> { + groupNumber.put(dialog.getGroup(), Math.min(groupNumber.getOrDefault(dialog.getGroup(), Integer.MAX_VALUE), dialog.getRegNumber())); + }); + // main groups - int recNumber = 0; for (int i = 0; i < groups.size(); i++) { - recNumber++; String group = groups.get(i); + Integer groupMinNumber = groupNumber.getOrDefault(group, 0); choice.withItem( String.valueOf(i), - String.format("%02d. %s", recNumber, group), - recNumber, + String.format("%02d. %s", groupMinNumber, group), + groupMinNumber, ChoiceHintType.TEXT, String.join("
", group) ); @@ -186,18 +186,15 @@ public class TestableDialogsRunner { private Choice prepareSelectDialogChoice(String needGroup) { Choice choice = new ChoiceImpl(false); choice.setMessage("Choose game dialog to run from " + needGroup); - int recNumber = 0; - for (int i = 0; i < this.dialogs.size(); i++) { - TestableDialog dialog = this.dialogs.get(i); + for (TestableDialog dialog : this.dialogs.values()) { if (!dialog.getGroup().equals(needGroup)) { continue; } - recNumber++; String info = String.format("%s - %s - %s", dialog.getGroup(), dialog.getName(), dialog.getDescription()); choice.withItem( - String.valueOf(i), - String.format("%02d. %s", recNumber, info), - recNumber, + String.valueOf(dialog.getRegNumber()), + String.format("%02d. %s", dialog.getRegNumber(), info), + dialog.getRegNumber(), ChoiceHintType.TEXT, String.join("
", info) ); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java index 697de25bcef..cc4e1c45827 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java @@ -13,17 +13,20 @@ public class DuelCommander extends Commander { banned.add("Balance"); banned.add("Bazaar of Baghdad"); banned.add("Black Lotus"); + banned.add("Blood Moon"); banned.add("Capture of Jingzhou"); banned.add("Cavern of Souls"); banned.add("Channel"); banned.add("Chrome Mox"); banned.add("Comet, Stellar Pup"); + banned.add("Dark Ritual"); banned.add("Deadly Rollick"); banned.add("Deflecting Swat"); banned.add("Dig Through Time"); banned.add("Emrakul, the Aeons Torn"); banned.add("Entomb"); banned.add("Fastbond"); + banned.add("Force of Will"); banned.add("Field of the Dead"); banned.add("Fierce Guardianship"); banned.add("Flawless Maneuver"); @@ -82,6 +85,7 @@ public class DuelCommander extends Commander { banned.add("Tolarian Academy"); banned.add("Trazyn The Infinite"); banned.add("Treasure Cruise"); + banned.add("Underworld Breach"); banned.add("Uro, Titan of Nature's Wrath"); banned.add("Vampiric Tutor"); banned.add("Wasteland"); diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index f25152f9a90..24f62353adf 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -219,6 +219,7 @@ public class ComputerPlayer6 extends ComputerPlayer { } // Condition to stop deeper simulation if (SimulationNode2.nodeCount > MAX_SIMULATED_NODES_PER_ERROR) { + // how-to fix: make sure you are disabled debug mode by COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = false throw new IllegalStateException("AI ERROR: too much nodes (possible actions)"); } if (depth <= 0 @@ -398,20 +399,23 @@ public class ComputerPlayer6 extends ComputerPlayer { } protected void resolve(SimulationNode2 node, int depth, Game game) { - StackObject stackObject = game.getStack().getFirst(); + StackObject stackObject = game.getStack().getFirstOrNull(); + if (stackObject == null) { + throw new IllegalStateException("Catch empty stack on resolve (something wrong with sim code)"); + } if (stackObject instanceof StackAbility) { // AI hint for search effects (calc all possible cards for best score) SearchEffect effect = getSearchEffect((StackAbility) stackObject); if (effect != null && stackObject.getControllerId().equals(playerId)) { Target target = effect.getTarget(); - if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game)) { + if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game, null)) { for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) { Game sim = game.createSimulationForAI(); StackAbility newAbility = (StackAbility) stackObject.copy(); SearchEffect newEffect = getSearchEffect(newAbility); newEffect.getTarget().addTarget(targetId, newAbility, sim); - sim.getStack().push(newAbility); + sim.getStack().push(sim, newAbility); SimulationNode2 newNode = new SimulationNode2(node, sim, depth, stackObject.getControllerId()); node.children.add(newNode); newNode.getTargets().add(targetId); @@ -498,7 +502,7 @@ public class ComputerPlayer6 extends ComputerPlayer { } logger.warn("Possible freeze chain:"); if (root != null && chain.isEmpty()) { - logger.warn(" - unknown use case"); // maybe can't finish any calc, maybe related to target options, I don't know + logger.warn(" - unknown use case (too many possible targets?)"); // maybe can't finish any calc, maybe related to target options } chain.forEach(s -> { logger.warn(" - " + s); @@ -639,7 +643,7 @@ public class ComputerPlayer6 extends ComputerPlayer { return "unknown"; }) .collect(Collectors.joining(", ")); - logger.info(String.format("Sim Prio [%d] -> with choices (TODO): [%d] (%s)", + logger.info(String.format("Sim Prio [%d] -> with possible choices: [%d] (%s)", depth, currentNode.getDepth(), printDiffScore(currentScore - prevScore), @@ -648,14 +652,18 @@ public class ComputerPlayer6 extends ComputerPlayer { } else if (!currentNode.getChoices().isEmpty()) { // ON CHOICES String choicesInfo = String.join(", ", currentNode.getChoices()); - logger.info(String.format("Sim Prio [%d] -> with choices (TODO): [%d] (%s)", + logger.info(String.format("Sim Prio [%d] -> with possible choices (must not see that code): [%d] (%s)", depth, currentNode.getDepth(), printDiffScore(currentScore - prevScore), choicesInfo) ); } else { - throw new IllegalStateException("AI CALC ERROR: unknown calculation result (no abilities, no targets, no choices)"); + logger.info(String.format("Sim Prio [%d] -> with do nothing: [%d]", + depth, + currentNode.getDepth(), + printDiffScore(currentScore - prevScore)) + ); } } } @@ -886,10 +894,10 @@ public class ComputerPlayer6 extends ComputerPlayer { } UUID abilityControllerId = target.getAffectedAbilityControllerId(getId()); - if (!target.isChoiceCompleted(abilityControllerId, source, game)) { + if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) { for (UUID targetId : targets) { target.addTarget(targetId, source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) { targets.clear(); return true; } @@ -906,10 +914,10 @@ public class ComputerPlayer6 extends ComputerPlayer { } UUID abilityControllerId = target.getAffectedAbilityControllerId(getId()); - if (!target.isChoiceCompleted(abilityControllerId, source, game)) { + if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) { for (UUID targetId : targets) { target.add(targetId, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) { targets.clear(); return true; } diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java index 4452e3e682e..9d7347f7d2b 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java @@ -105,7 +105,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer { return list; } - protected void simulateOptions(Game game) { + private void simulateOptions(Game game) { List playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer); for (ActivatedAbility ability : playables) { if (ability.isManaAbility()) { @@ -176,6 +176,10 @@ public final class SimulatedPlayer2 extends ComputerPlayer { return options; } + // remove invalid targets + // TODO: is it useless cause it already filtered before? + options.removeIf(option -> !option.getTargets().isChosen(game)); + if (AI_SIMULATE_ALL_BAD_AND_GOOD_TARGETS) { return options; } @@ -314,14 +318,17 @@ public final class SimulatedPlayer2 extends ComputerPlayer { Ability ability = source.copy(); List options = getPlayableOptions(ability, game); if (options.isEmpty()) { + // no options - activate as is logger.debug("simulating -- triggered ability:" + ability); - game.getStack().push(new StackAbility(ability, playerId)); + game.getStack().push(game, new StackAbility(ability, playerId)); if (ability.activate(game, false) && ability.isUsesStack()) { game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId())); } game.applyEffects(); game.getPlayers().resetPassed(); } else { + // many options - activate and add to sims tree + // TODO: AI run all sims, but do not use best option for triggers yet SimulationNode2 parent = (SimulationNode2) game.getCustomData(); int depth = parent.getDepth() - 1; if (depth == 0) { @@ -337,16 +344,16 @@ public final class SimulatedPlayer2 extends ComputerPlayer { protected void addAbilityNode(SimulationNode2 parent, Ability ability, int depth, Game game) { Game sim = game.createSimulationForAI(); - sim.getStack().push(new StackAbility(ability, playerId)); + sim.getStack().push(sim, new StackAbility(ability, playerId)); if (ability.activate(sim, false) && ability.isUsesStack()) { - game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId())); + sim.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId())); } sim.applyEffects(); SimulationNode2 newNode = new SimulationNode2(parent, sim, depth, playerId); logger.debug("simulating -- node #:" + SimulationNode2.getCount() + " triggered ability option"); for (Target target : ability.getTargets()) { for (UUID targetId : target.getTargets()) { - newNode.getTargets().add(targetId); // save for info only (real targets in newNode.ability already) + newNode.getTargets().add(targetId); // save for info only (real targets in newNode.game.stack already) } } parent.children.add(newNode); 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 20a8114e4f2..441a45bbfb4 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 @@ -142,17 +142,27 @@ public class ComputerPlayer extends PlayerImpl { UUID abilityControllerId = target.getAffectedAbilityControllerId(getId()); // nothing to choose, e.g. X=0 - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, fromCards)) { return false; } - // default logic for any targets PossibleTargetsSelector possibleTargetsSelector = new PossibleTargetsSelector(outcome, target, abilityControllerId, source, game); possibleTargetsSelector.findNewTargets(fromCards); + + // nothing to choose, e.g. no valid targets + if (!possibleTargetsSelector.hasAnyTargets()) { + return false; + } + + // can't choose + if (!possibleTargetsSelector.hasMinNumberOfTargets()) { + return false; + } + // good targets -- choose as much as possible for (MageItem item : possibleTargetsSelector.getGoodTargets()) { target.add(item.getId(), game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, fromCards)) { return true; } } @@ -226,7 +236,7 @@ public class ComputerPlayer extends PlayerImpl { UUID abilityControllerId = target.getAffectedAbilityControllerId(getId()); // nothing to choose, e.g. X=0 - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return false; } @@ -238,6 +248,11 @@ public class ComputerPlayer extends PlayerImpl { return false; } + // can't choose + if (!possibleTargetsSelector.hasMinNumberOfTargets()) { + return false; + } + // KILL PRIORITY if (outcome == Outcome.Damage) { // opponent first @@ -251,7 +266,7 @@ public class ComputerPlayer extends PlayerImpl { int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game); if (leftLife > 0 && leftLife <= target.getAmountRemaining()) { target.addTarget(item.getId(), leftLife, source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return true; } } @@ -268,7 +283,7 @@ public class ComputerPlayer extends PlayerImpl { int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game); if (leftLife > 0 && leftLife <= target.getAmountRemaining()) { target.addTarget(item.getId(), leftLife, source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return true; } } @@ -283,7 +298,7 @@ public class ComputerPlayer extends PlayerImpl { continue; } target.addTarget(item.getId(), target.getAmountRemaining(), source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return true; } } @@ -303,7 +318,7 @@ public class ComputerPlayer extends PlayerImpl { int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game); if (leftLife > 1) { target.addTarget(item.getId(), Math.min(leftLife - 1, target.getAmountRemaining()), source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return true; } } @@ -322,7 +337,7 @@ public class ComputerPlayer extends PlayerImpl { return !target.getTargets().isEmpty(); } target.addTarget(item.getId(), target.getAmountRemaining(), source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return true; } } @@ -339,7 +354,7 @@ public class ComputerPlayer extends PlayerImpl { continue; } target.addTarget(item.getId(), target.getAmountRemaining(), source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return true; } } @@ -355,7 +370,7 @@ public class ComputerPlayer extends PlayerImpl { return !target.getTargets().isEmpty(); } target.addTarget(item.getId(), target.getAmountRemaining(), source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return true; } } diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/PossibleTargetsSelector.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/PossibleTargetsSelector.java index c3d9f25ea22..cd3184b2c55 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/PossibleTargetsSelector.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/PossibleTargetsSelector.java @@ -47,7 +47,6 @@ public class PossibleTargetsSelector { // collect new valid targets List found = target.possibleTargets(abilityControllerId, source, game, fromTargetsList).stream() .filter(id -> !target.contains(id)) - .filter(id -> target.canTarget(abilityControllerId, id, source, game)) .map(id -> { Player player = game.getPlayer(id); if (player != null) { @@ -137,6 +136,10 @@ public class PossibleTargetsSelector { } } + public List getAny() { + return this.any; + } + public static boolean isMyItem(UUID abilityControllerId, MageItem item) { if (item instanceof Player) { return item.getId().equals(abilityControllerId); @@ -181,7 +184,12 @@ public class PossibleTargetsSelector { return false; } - boolean hasAnyTargets() { + public boolean hasAnyTargets() { return !this.any.isEmpty(); } + + public boolean hasMinNumberOfTargets() { + return this.target.getMinNumberOfTargets() == 0 + || this.any.size() >= this.target.getMinNumberOfTargets(); + } } \ No newline at end of file diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java index f3dd92bb7be..0649b72732e 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java @@ -121,7 +121,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer { } } if (ability.isUsesStack()) { - game.getStack().push(new StackAbility(ability, playerId)); + game.getStack().push(game, new StackAbility(ability, playerId)); if (ability.activate(game, false)) { game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId())); actionCount++; @@ -187,8 +187,8 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer { abort = true; } - protected boolean chooseRandom(Target target, Game game) { - Set possibleTargets = target.possibleTargets(playerId, game); + private boolean chooseRandom(Target target, Ability source, Game game) { + Set possibleTargets = target.possibleTargets(playerId, source, game); if (possibleTargets.isEmpty()) { return false; } @@ -233,7 +233,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer { @Override public boolean choose(Outcome outcome, Target target, Ability source, Game game) { if (this.isHuman()) { - return chooseRandom(target, game); + return chooseRandom(target, source, game); } return super.choose(outcome, target, source, game); } @@ -241,7 +241,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer { @Override public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map options) { if (this.isHuman()) { - return chooseRandom(target, game); + return chooseRandom(target, source, game); } return super.choose(outcome, target, source, game, options); } @@ -252,7 +252,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer { if (cards.isEmpty()) { return false; } - Set possibleTargets = target.possibleTargets(playerId, cards, source, game); + Set possibleTargets = target.possibleTargets(playerId, source, game, cards); if (possibleTargets.isEmpty()) { return false; } 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 cd7b6569cf8..0b3e78aa195 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 @@ -3,7 +3,6 @@ package mage.player.human; import mage.MageIdentifier; import mage.MageObject; import mage.abilities.*; -import mage.abilities.costs.VariableCost; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCost; @@ -639,8 +638,8 @@ public class HumanPlayer extends PlayerImpl { // Check check if the spell being paid for cares about the color of mana being paid // See: https://github.com/magefree/mage/issues/9070 boolean caresAboutManaColor = false; - if (!game.getStack().isEmpty() && game.getStack().getFirst() instanceof Spell) { - Spell spellBeingCast = (Spell) game.getStack().getFirst(); + if (game.getStack().getFirstOrNull() instanceof Spell) { + Spell spellBeingCast = (Spell) game.getStack().getFirstOrNull(); if (!spellBeingCast.isResolving() && spellBeingCast.getControllerId().equals(this.getId())) { CardImpl card = (CardImpl) game.getCard(spellBeingCast.getSourceId()); caresAboutManaColor = card.caresAboutManaColor(game); @@ -697,7 +696,7 @@ public class HumanPlayer extends PlayerImpl { } // stop on completed, e.g. X=0 - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return false; } @@ -729,7 +728,7 @@ public class HumanPlayer extends PlayerImpl { // continue to next target (example: auto-choose must fill min/max = 2 from 2 possible cards) } else { // manual choose - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); prepareForResponse(game); if (!isExecutingMacro()) { @@ -747,9 +746,9 @@ public class HumanPlayer extends PlayerImpl { continue; } - if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, game)) { + if (possibleTargets.contains(responseId)) { target.add(responseId, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { break; } } @@ -794,7 +793,7 @@ public class HumanPlayer extends PlayerImpl { // manual choice if (responseId == null) { - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); prepareForResponse(game); if (!isExecutingMacro()) { @@ -814,11 +813,9 @@ public class HumanPlayer extends PlayerImpl { // add new target if (possibleTargets.contains(responseId)) { - if (target.canTarget(abilityControllerId, responseId, source, game)) { - target.addTarget(responseId, source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { - return true; - } + target.addTarget(responseId, source, game); + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { + return true; } } } else { @@ -864,13 +861,7 @@ public class HumanPlayer extends PlayerImpl { UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId()); while (canRespond()) { - - List possibleTargets = new ArrayList<>(); - for (UUID cardId : cards) { - if (target.canTarget(abilityControllerId, cardId, source, cards, game)) { - possibleTargets.add(cardId); - } - } + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards); boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); int count = cards.count(target.getFilter(), abilityControllerId, source, game); @@ -880,7 +871,7 @@ public class HumanPlayer extends PlayerImpl { } // if nothing to choose then show dialog (user must see non selectable items and click on any of them) - // TODO: need research - is it used? + // TODO: need research - is it used (test player and AI player don't see empty dialogs)? if (required && possibleTargets.isEmpty()) { required = false; } @@ -894,7 +885,7 @@ public class HumanPlayer extends PlayerImpl { } else { // manual choose Map options = getOptions(target, null); - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); if (!possibleTargets.isEmpty()) { options.put("possibleTargets", (Serializable) possibleTargets); } @@ -916,9 +907,9 @@ public class HumanPlayer extends PlayerImpl { continue; } - if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, cards, game)) { + if (possibleTargets.contains(responseId)) { target.add(responseId, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) { return true; } } @@ -962,12 +953,8 @@ public class HumanPlayer extends PlayerImpl { required = false; } - List possibleTargets = new ArrayList<>(); - for (UUID cardId : cards) { - if (target.canTarget(abilityControllerId, cardId, source, cards, game)) { - possibleTargets.add(cardId); - } - } + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards); + // if nothing to choose then show dialog (user must see non-selectable items and click on any of them) if (possibleTargets.isEmpty()) { required = false; @@ -977,7 +964,7 @@ public class HumanPlayer extends PlayerImpl { if (responseId == null) { Map options = getOptions(target, null); - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); if (!possibleTargets.isEmpty()) { options.put("possibleTargets", (Serializable) possibleTargets); @@ -995,9 +982,9 @@ public class HumanPlayer extends PlayerImpl { if (responseId != null) { if (target.contains(responseId)) { // if already included remove it target.remove(responseId); - } else if (target.canTarget(abilityControllerId, responseId, source, cards, game)) { + } else if (possibleTargets.contains(responseId)) { target.addTarget(responseId, source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) { return true; } } @@ -1054,9 +1041,9 @@ public class HumanPlayer extends PlayerImpl { // 1. Select targets // TODO: rework to use existing chooseTarget instead custom select? while (canRespond()) { - Set possibleTargetIds = target.possibleTargets(abilityControllerId, source, game); + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game); boolean required = target.isRequired(source.getSourceId(), game); - if (possibleTargetIds.isEmpty() + if (possibleTargets.isEmpty() || target.getSize() >= target.getMinNumberOfTargets()) { required = false; } @@ -1065,12 +1052,6 @@ public class HumanPlayer extends PlayerImpl { // responseId is null if a choice couldn't be automatically made if (responseId == null) { - List possibleTargets = new ArrayList<>(); - for (UUID targetId : possibleTargetIds) { - if (target.canTarget(abilityControllerId, targetId, source, game)) { - possibleTargets.add(targetId); - } - } // if nothing to choose then show dialog (user must see non selectable items and click on any of them) if (required && possibleTargets.isEmpty()) { required = false; @@ -1078,7 +1059,7 @@ public class HumanPlayer extends PlayerImpl { // selected Map options = getOptions(target, null); - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); if (!possibleTargets.isEmpty()) { options.put("possibleTargets", (Serializable) possibleTargets); } @@ -1087,7 +1068,7 @@ public class HumanPlayer extends PlayerImpl { if (!isExecutingMacro()) { String multiType = multiAmountType == MultiAmountType.DAMAGE ? " to divide %d damage" : " to distribute %d counters"; String message = target.getMessage(game) + String.format(multiType, amountTotal); - game.fireSelectTargetEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), possibleTargetIds, required, options); + game.fireSelectTargetEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), possibleTargets, required, options); } waitForResponse(game); @@ -1098,9 +1079,7 @@ public class HumanPlayer extends PlayerImpl { if (target.contains(responseId)) { // unselect target.remove(responseId); - } else if (possibleTargetIds.contains(responseId) - && target.canTarget(abilityControllerId, responseId, source, game) - && target.getSize() < amountTotal) { + } else if (possibleTargets.contains(responseId) && target.getSize() < amountTotal) { // select target.addTarget(responseId, source, game); } @@ -2094,32 +2073,33 @@ public class HumanPlayer extends PlayerImpl { if (!canCallFeedback(game)) { return; } - TargetAttackingCreature target = new TargetAttackingCreature(); - // TODO: add canRespond cycle? + // no need in cycle, cause parent selectBlockers used it already if (!canRespond()) { return; } UUID responseId = null; - prepareForResponse(game); if (!isExecutingMacro()) { // possible attackers to block - Set attackers = target.possibleTargets(playerId, null, game); + TargetAttackingCreature target = new TargetAttackingCreature(); Permanent blocker = game.getPermanent(blockerId); - Set possibleTargets = new HashSet<>(); - for (UUID attackerId : attackers) { + Set allAttackers = target.possibleTargets(playerId, null, game); + Set possibleAttackersToBlock = new HashSet<>(); + for (UUID attackerId : allAttackers) { CombatGroup group = game.getCombat().findGroup(attackerId); if (group != null && blocker != null && group.canBlock(blocker, game)) { - possibleTargets.add(attackerId); + possibleAttackersToBlock.add(attackerId); } } - if (possibleTargets.size() == 1) { - responseId = possibleTargets.stream().iterator().next(); + if (possibleAttackersToBlock.size() == 1) { + // auto-choice + responseId = possibleAttackersToBlock.stream().iterator().next(); } else { + prepareForResponse(game); game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, game)), - possibleTargets, false, getOptions(target, null)); + possibleAttackersToBlock, false, getOptions(target, null)); waitForResponse(game); } } @@ -2174,7 +2154,7 @@ public class HumanPlayer extends PlayerImpl { break; } - return xValue; + return xValue; } @Override diff --git a/Mage.Sets/src/mage/cards/a/AangAirNomad.java b/Mage.Sets/src/mage/cards/a/AangAirNomad.java new file mode 100644 index 00000000000..ff064854e72 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AangAirNomad.java @@ -0,0 +1,54 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AangAirNomad extends CardImpl { + + public AangAirNomad(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Other creatures you control have vigilance. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + VigilanceAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_CREATURES, true + ))); + } + + private AangAirNomad(final AangAirNomad card) { + super(card); + } + + @Override + public AangAirNomad copy() { + return new AangAirNomad(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AangAirbendingMaster.java b/Mage.Sets/src/mage/cards/a/AangAirbendingMaster.java new file mode 100644 index 00000000000..6f1ac6a3f2b --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AangAirbendingMaster.java @@ -0,0 +1,67 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.OneOrMoreLeaveWithoutDyingTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersControllerCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.AddCountersPlayersEffect; +import mage.abilities.effects.keyword.AirbendTargetEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.AllyToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AangAirbendingMaster extends CardImpl { + + private static final DynamicValue xValue = new CountersControllerCount(CounterType.EXPERIENCE); + + public AangAirbendingMaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Aang enters, airbend another target creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new AirbendTargetEffect()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE)); + this.addAbility(ability); + + // Whenever one or more creatures you control leave the battlefield without dying, you get an experience counter. + this.addAbility(new OneOrMoreLeaveWithoutDyingTriggeredAbility( + new AddCountersPlayersEffect(CounterType.EXPERIENCE.createInstance(), TargetController.YOU), + StaticFilters.FILTER_CONTROLLED_CREATURES + )); + + // At the beginning of your upkeep, create a 1/1 white Ally creature token for each experience counter you have. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new CreateTokenEffect(new AllyToken(), xValue) + .setText("create a 1/1 white Ally creature token for each experience counter you have"))); + } + + private AangAirbendingMaster(final AangAirbendingMaster card) { + super(card); + } + + @Override + public AangAirbendingMaster copy() { + return new AangAirbendingMaster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AangMasterOfElements.java b/Mage.Sets/src/mage/cards/a/AangMasterOfElements.java new file mode 100644 index 00000000000..951e3c91215 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AangMasterOfElements.java @@ -0,0 +1,112 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.game.Game; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AangMasterOfElements extends CardImpl { + + private static final FilterCard filter = new FilterCard("spells"); + + public AangMasterOfElements(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Spells you cast cost {W}{U}{B}{R}{G} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect( + filter, new ManaCostsImpl<>("{W}{U}{B}{R}{G}"), StaticValue.get(1), true + ))); + + // At the beginning of each upkeep, you may transform Aang, Master of Elements. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + TargetController.ANY, + new DoIfCostPaid(new GainLifeEffect(4), new AangMasterOfElementsCost()) + .addEffect(new DrawCardSourceControllerEffect(4).concatBy(",")) + .addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(4)) + .setText(", put four +1/+1 counters on him")) + .addEffect(new DamagePlayersEffect(4, TargetController.OPPONENT) + .setText(", and he deals 4 damage to each opponent")), + false + )); + } + + private AangMasterOfElements(final AangMasterOfElements card) { + super(card); + } + + @Override + public AangMasterOfElements copy() { + return new AangMasterOfElements(this); + } +} + +class AangMasterOfElementsCost extends CostImpl { + + AangMasterOfElementsCost() { + super(); + text = "transform {this}"; + } + + private AangMasterOfElementsCost(final AangMasterOfElementsCost cost) { + super(cost); + } + + @Override + public AangMasterOfElementsCost copy() { + return new AangMasterOfElementsCost(this); + } + + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + return Optional + .ofNullable(source.getSourcePermanentIfItStillExists(game)) + .filter(Card::isTransformable) + .isPresent(); + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + paid = Optional + .ofNullable(source.getSourcePermanentIfItStillExists(game)) + .filter(permanent -> permanent.transform(source, game)) + .isPresent(); + return paid; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AangTheLastAirbender.java b/Mage.Sets/src/mage/cards/a/AangTheLastAirbender.java new file mode 100644 index 00000000000..8bfcd1d7928 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AangTheLastAirbender.java @@ -0,0 +1,70 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.keyword.AirbendTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.FilterSpell; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AangTheLastAirbender extends CardImpl { + + private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanent"); + private static final FilterSpell filter2 = new FilterSpell("a Lesson spell"); + + static { + filter.add(AnotherPredicate.instance); + filter2.add(SubType.LESSON.getPredicate()); + } + + public AangTheLastAirbender(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.AVATAR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Aang enters, airbend up to one other target nonland permanent. + Ability ability = new EntersBattlefieldTriggeredAbility(new AirbendTargetEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + + // Whenever you cast a Lesson spell, Aang gains lifelink until end of turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new GainAbilitySourceEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn), filter2, false + )); + } + + private AangTheLastAirbender(final AangTheLastAirbender card) { + super(card); + } + + @Override + public AangTheLastAirbender copy() { + return new AangTheLastAirbender(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AangsDefense.java b/Mage.Sets/src/mage/cards/a/AangsDefense.java new file mode 100644 index 00000000000..9ab76ccdcf0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AangsDefense.java @@ -0,0 +1,45 @@ +package mage.cards.a; + +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.BlockingPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AangsDefense extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("blocking creature you control"); + + static { + filter.add(BlockingPredicate.instance); + } + + public AangsDefense(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Target blocking creature you control gets +2/+2 until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(2, 2)); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + + // Draw a card. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); + } + + private AangsDefense(final AangsDefense card) { + super(card); + } + + @Override + public AangsDefense copy() { + return new AangsDefense(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AangsIceberg.java b/Mage.Sets/src/mage/cards/a/AangsIceberg.java new file mode 100644 index 00000000000..3b37939c473 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AangsIceberg.java @@ -0,0 +1,58 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AangsIceberg extends CardImpl { + + private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanent"); + + static { + filter.add(AnotherPredicate.instance); + } + + public AangsIceberg(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When this enchantment enters, exile up to one other target nonland permanent until this enchantment leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + + // Waterbend {3}: Sacrifice this enchantment. If you do, scry 2. + this.addAbility(new SimpleActivatedAbility(new DoIfCostPaid( + new ScryEffect(2), new SacrificeSourceCost(), null, false + ), new WaterbendCost(3))); + } + + private AangsIceberg(final AangsIceberg card) { + super(card); + } + + @Override + public AangsIceberg copy() { + return new AangsIceberg(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AangsJourney.java b/Mage.Sets/src/mage/cards/a/AangsJourney.java new file mode 100644 index 00000000000..c43bd803446 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AangsJourney.java @@ -0,0 +1,64 @@ +package mage.cards.a; + +import mage.abilities.condition.common.KickedCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.KickerAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardAndOrCardInLibrary; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AangsJourney extends CardImpl { + + private static final Predicate predicate = Predicates.and( + SuperType.BASIC.getPredicate(), + CardType.LAND.getPredicate() + ); + + public AangsJourney(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}"); + + this.subtype.add(SubType.LESSON); + + // Kicker {2} + this.addAbility(new KickerAbility("{2}")); + + // Search your library for a basic land card. If this spell was kicked, instead search your library for a basic land card and a Shrine card. Reveal those cards, put them into your hand, then shuffle. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SearchLibraryPutInHandEffect(new TargetCardAndOrCardInLibrary( + predicate, SubType.SHRINE.getPredicate(), + "a basic land card and/or a Shrine card" + ), true), + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true), + KickedCondition.ONCE, "search your library for a basic land card. If this spell was kicked, " + + "instead search your library for a basic land card and a Shrine card. " + + "Reveal those cards, put them into your hand, then shuffle" + )); + + // You gain 2 life. + this.getSpellAbility().addEffect(new GainLifeEffect(2).concatBy("
")); + } + + private AangsJourney(final AangsJourney card) { + super(card); + } + + @Override + public AangsJourney copy() { + return new AangsJourney(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AardvarkSloth.java b/Mage.Sets/src/mage/cards/a/AardvarkSloth.java new file mode 100644 index 00000000000..bbda1b20a66 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AardvarkSloth.java @@ -0,0 +1,37 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AardvarkSloth extends CardImpl { + + public AardvarkSloth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.SLOTH); + this.subtype.add(SubType.BEAST); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + } + + private AardvarkSloth(final AardvarkSloth card) { + super(card); + } + + @Override + public AardvarkSloth copy() { + return new AardvarkSloth(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AbandonAttachments.java b/Mage.Sets/src/mage/cards/a/AbandonAttachments.java new file mode 100644 index 00000000000..d3a447913d0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AbandonAttachments.java @@ -0,0 +1,35 @@ +package mage.cards.a; + +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AbandonAttachments extends CardImpl { + + public AbandonAttachments(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U/R}"); + + this.subtype.add(SubType.LESSON); + + // You may discard a card. If you do, draw two cards. + this.getSpellAbility().addEffect(new DoIfCostPaid(new DrawCardSourceControllerEffect(2), new DiscardCardCost())); + } + + private AbandonAttachments(final AbandonAttachments card) { + super(card); + } + + @Override + public AbandonAttachments copy() { + return new AbandonAttachments(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AccursedWitch.java b/Mage.Sets/src/mage/cards/a/AccursedWitch.java index 242b3f8c0df..c214b37dd02 100644 --- a/Mage.Sets/src/mage/cards/a/AccursedWitch.java +++ b/Mage.Sets/src/mage/cards/a/AccursedWitch.java @@ -23,6 +23,8 @@ import java.util.UUID; */ public final class AccursedWitch extends CardImpl { + private static final FilterCard filter = new FilterCard("spells"); + public AccursedWitch(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); this.subtype.add(SubType.HUMAN); @@ -34,7 +36,7 @@ public final class AccursedWitch extends CardImpl { // Spells your opponents cast that target Accursed Witch cost {1} less to cast. this.addAbility(new SimpleStaticAbility( - new SpellsCostModificationThatTargetSourceEffect(-1, new FilterCard("Spells"), TargetController.OPPONENT)) + new SpellsCostModificationThatTargetSourceEffect(-1, filter, TargetController.OPPONENT)) ); // When Accursed Witch dies, return it to the battlefield transformed under your control attached to target opponent. diff --git a/Mage.Sets/src/mage/cards/a/AerialSurveyor.java b/Mage.Sets/src/mage/cards/a/AerialSurveyor.java index b8199c1a008..3313a035a90 100644 --- a/Mage.Sets/src/mage/cards/a/AerialSurveyor.java +++ b/Mage.Sets/src/mage/cards/a/AerialSurveyor.java @@ -13,10 +13,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.filter.common.FilterLandPermanent; import mage.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate; import mage.game.Controllable; @@ -32,8 +30,6 @@ import java.util.stream.Collectors; */ public final class AerialSurveyor extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard(SubType.PLAINS); - public AerialSurveyor(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}"); @@ -45,7 +41,7 @@ public final class AerialSurveyor extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever Aerial Surveyor attacks, if defending player controls more lands than you, search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle. - this.addAbility(new AttacksTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true)) + this.addAbility(new AttacksTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true)) .withInterveningIf(AerialSurveyorCondition.instance) .addHint(LandsYouControlHint.instance) .addHint(AerialSurveyorHint.instance)); diff --git a/Mage.Sets/src/mage/cards/a/AgentVenom.java b/Mage.Sets/src/mage/cards/a/AgentVenom.java new file mode 100644 index 00000000000..9217071f4a2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AgentVenom.java @@ -0,0 +1,66 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.TokenPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AgentVenom extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another nontoken creature you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TokenPredicate.FALSE); + } + + public AgentVenom(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SYMBIOTE); + this.subtype.add(SubType.SOLDIER); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever another nontoken creature you control dies, you draw a card and lose 1 life. + Ability ability = new DiesCreatureTriggeredAbility( + new DrawCardSourceControllerEffect(1, true), false, filter + ); + ability.addEffect(new LoseLifeSourceControllerEffect(1).setText("and lose 1 life")); + this.addAbility(ability); + } + + private AgentVenom(final AgentVenom card) { + super(card); + } + + @Override + public AgentVenom copy() { + return new AgentVenom(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AirbendingLesson.java b/Mage.Sets/src/mage/cards/a/AirbendingLesson.java new file mode 100644 index 00000000000..d86ccfd133e --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AirbendingLesson.java @@ -0,0 +1,39 @@ +package mage.cards.a; + +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.keyword.AirbendTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AirbendingLesson extends CardImpl { + + public AirbendingLesson(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}"); + + this.subtype.add(SubType.LESSON); + + // Airbend target nonland permanent. + this.getSpellAbility().addEffect(new AirbendTargetEffect()); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + + // Draw a card. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); + } + + private AirbendingLesson(final AirbendingLesson card) { + super(card); + } + + @Override + public AirbendingLesson copy() { + return new AirbendingLesson(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AjanisChosen.java b/Mage.Sets/src/mage/cards/a/AjanisChosen.java index c84c947d8ee..534097ade4c 100644 --- a/Mage.Sets/src/mage/cards/a/AjanisChosen.java +++ b/Mage.Sets/src/mage/cards/a/AjanisChosen.java @@ -77,7 +77,7 @@ class AjanisChosenEffect extends OneShotEffect { Permanent oldCreature = game.getPermanent(enchantment.getAttachedTo()); if (oldCreature != null) { boolean canAttach = enchantment.getSpellAbility() == null - || (!enchantment.getSpellAbility().getTargets().isEmpty() && enchantment.getSpellAbility().getTargets().get(0).canTarget(tokenPermanent.getId(), game)); + || (!enchantment.getSpellAbility().getTargets().isEmpty() && enchantment.getSpellAbility().getTargets().get(0).canTarget(tokenPermanent.getId(), source, game)); if (canAttach && controller.chooseUse(Outcome.Neutral, "Attach " + enchantment.getName() + " to the token ?", source, game)) { if (oldCreature.removeAttachment(enchantment.getId(), source, game)) { tokenPermanent.addAttachment(enchantment.getId(), source, game); diff --git a/Mage.Sets/src/mage/cards/a/AlienSymbiosis.java b/Mage.Sets/src/mage/cards/a/AlienSymbiosis.java new file mode 100644 index 00000000000..99b3985117a --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AlienSymbiosis.java @@ -0,0 +1,101 @@ +package mage.cards.a; + +import mage.MageIdentifier; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class AlienSymbiosis extends CardImpl { + + public AlienSymbiosis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature gets +1/+1, has menace, and is a Symbiote in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEnchantedEffect(1, 1)); + ability.addEffect(new GainAbilityAttachedEffect(new MenaceAbility(), null).concatBy(",")); + ability.addEffect(new AddCardSubtypeAttachedEffect(SubType.SYMBIOTE, AttachmentType.AURA).concatBy(",").setText("and is a Symbiote in addition to its other types")); + this.addAbility(ability); + + // You may cast this card from your graveyard by discarding a card in addition to paying its other costs. + this.addAbility(new SimpleStaticAbility(Zone.GRAVEYARD, new AlienSymbiosisGraveyardEffect())); + } + + private AlienSymbiosis(final AlienSymbiosis card) { + super(card); + } + + @Override + public AlienSymbiosis copy() { + return new AlienSymbiosis(this); + } +} +class AlienSymbiosisGraveyardEffect extends AsThoughEffectImpl { + + AlienSymbiosisGraveyardEffect() { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.PutCreatureInPlay); + this.staticText = "You may cast this card from your graveyard by discarding a card in addition to paying its other costs."; + } + + private AlienSymbiosisGraveyardEffect(final AlienSymbiosisGraveyardEffect effect) { + super(effect); + } + + @Override + public AlienSymbiosisGraveyardEffect copy() { + return new AlienSymbiosisGraveyardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!objectId.equals(source.getSourceId()) || !source.isControlledBy(affectedControllerId) + || game.getState().getZone(source.getSourceId()) != Zone.GRAVEYARD) { + return false; + } + Player controller = game.getPlayer(affectedControllerId); + if (controller != null) { + Costs costs = new CostsImpl<>(); + costs.add(new DiscardCardCost()); + controller.setCastSourceIdWithAlternateMana(objectId, new ManaCostsImpl<>("{1}{B}"), costs, + MageIdentifier.AlienSymbiosisAlternateCast); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AllWillBeOne.java b/Mage.Sets/src/mage/cards/a/AllWillBeOne.java index 6ee9aa8e73b..13c0266e07e 100644 --- a/Mage.Sets/src/mage/cards/a/AllWillBeOne.java +++ b/Mage.Sets/src/mage/cards/a/AllWillBeOne.java @@ -12,6 +12,8 @@ import mage.filter.common.FilterCreaturePlayerOrPlaneswalker; import mage.filter.common.FilterPermanentOrPlayer; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; import mage.target.common.TargetPermanentOrPlayer; import java.util.UUID; @@ -68,6 +70,16 @@ class AllWillBeOneTriggeredAbility extends TriggeredAbilityImpl { if (!isControlledBy(event.getPlayerId())) { return false; } + Player player = game.getPlayer(event.getTargetId()); + if (player == null) { + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null) { + permanent = game.getPermanentEntering(event.getTargetId()); + if (permanent == null) { + return false; + } + } + } getEffects().setValue("damage", event.getAmount()); return true; } diff --git a/Mage.Sets/src/mage/cards/a/AlliedTeamwork.java b/Mage.Sets/src/mage/cards/a/AlliedTeamwork.java new file mode 100644 index 00000000000..0c51d4ab1fa --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AlliedTeamwork.java @@ -0,0 +1,43 @@ +package mage.cards.a; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.permanent.token.AllyToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AlliedTeamwork extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent(SubType.ALLY, "Allies"); + + public AlliedTeamwork(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + // When this enchantment enters, create a 1/1 white Ally creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new AllyToken()))); + + // Allies you control get +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, filter))); + } + + private AlliedTeamwork(final AlliedTeamwork card) { + super(card); + } + + @Override + public AlliedTeamwork copy() { + return new AlliedTeamwork(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AlpineGuide.java b/Mage.Sets/src/mage/cards/a/AlpineGuide.java index 7ec0ffa6ed3..13ae8cd915a 100644 --- a/Mage.Sets/src/mage/cards/a/AlpineGuide.java +++ b/Mage.Sets/src/mage/cards/a/AlpineGuide.java @@ -13,7 +13,6 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterBySubtypeCard; import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCardInLibrary; @@ -24,7 +23,7 @@ import java.util.UUID; */ public final class AlpineGuide extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.MOUNTAIN); + private static final FilterCard filter = new FilterCard(SubType.MOUNTAIN); private static final FilterPermanent filter2 = new FilterControlledPermanent(SubType.MOUNTAIN, "Mountain"); public AlpineGuide(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/a/AlpineMoon.java b/Mage.Sets/src/mage/cards/a/AlpineMoon.java index da13a2bff44..6c4ab3c4649 100644 --- a/Mage.Sets/src/mage/cards/a/AlpineMoon.java +++ b/Mage.Sets/src/mage/cards/a/AlpineMoon.java @@ -57,6 +57,12 @@ class AlpineMoonEffect extends ContinuousEffectImpl { this.staticText = "lands your opponents control with the chosen name " + "lose all land types and abilities, " + "and they gain \"{T}: Add one mana of any color.\""; + addDependedToType(DependencyType.BecomeMountain); + addDependedToType(DependencyType.BecomeForest); + addDependedToType(DependencyType.BecomeIsland); + addDependedToType(DependencyType.BecomeSwamp); + addDependedToType(DependencyType.BecomePlains); + addDependedToType(DependencyType.BecomeNonbasicLand); } private AlpineMoonEffect(final AlpineMoonEffect effect) { @@ -84,9 +90,6 @@ class AlpineMoonEffect extends ContinuousEffectImpl { for (Permanent land : game.getBattlefield().getActivePermanents(filter2, source.getControllerId(), game)) { switch (layer) { case TypeChangingEffects_4: - // 305.7 Note that this doesn't remove any abilities that were granted to the land by other effects - // So the ability removing has to be done before Layer 6 - land.removeAllAbilities(source.getSourceId(), game); land.removeAllSubTypes(game, SubTypeSet.NonBasicLandType); break; case AbilityAddingRemovingEffects_6: diff --git a/Mage.Sets/src/mage/cards/a/AmazingAcrobatics.java b/Mage.Sets/src/mage/cards/a/AmazingAcrobatics.java new file mode 100644 index 00000000000..b843c829900 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AmazingAcrobatics.java @@ -0,0 +1,47 @@ +package mage.cards.a; + +import mage.abilities.Mode; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class AmazingAcrobatics extends CardImpl { + + public AmazingAcrobatics(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}{U}"); + + + // Choose one or both -- + this.getSpellAbility().getModes().setMinModes(1); + this.getSpellAbility().getModes().setMaxModes(2); + + // * Counter target spell. + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addTarget(new TargetSpell()); + + // * Tap one or two target creatures. + Mode mode = new Mode(new TapTargetEffect()); + mode.addTarget(new TargetPermanent(1, 2, StaticFilters.FILTER_PERMANENT_CREATURES)); + this.getSpellAbility().getModes().addMode(mode); + } + + private AmazingAcrobatics(final AmazingAcrobatics card) { + super(card); + } + + @Override + public AmazingAcrobatics copy() { + return new AmazingAcrobatics(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AmazingAlliance.java b/Mage.Sets/src/mage/cards/a/AmazingAlliance.java new file mode 100644 index 00000000000..9cbaa9e1a94 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AmazingAlliance.java @@ -0,0 +1,46 @@ +package mage.cards.a; + +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.EffectKeyValue; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AmazingAlliance extends CardImpl { + + private static final DynamicValue xValue = new EffectKeyValue( + AttacksWithCreaturesTriggeredAbility.VALUEKEY_NUMBER_ATTACKERS, "that much" + ); + + public AmazingAlliance(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{W}"); + + // Creatures you control get +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield))); + + // Whenever you attack with one or more legendary creatures, you gain that much life. + this.addAbility(new AttacksWithCreaturesTriggeredAbility( + new GainLifeEffect(xValue), 1, StaticFilters.FILTER_CREATURES_LEGENDARY + )); + } + + private AmazingAlliance(final AmazingAlliance card) { + super(card); + } + + @Override + public AmazingAlliance copy() { + return new AmazingAlliance(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java b/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java index cf911245785..b92a884539b 100644 --- a/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java +++ b/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java @@ -14,8 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -25,13 +24,6 @@ import java.util.UUID; */ public final class AmbitiousFarmhand extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - public AmbitiousFarmhand(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); @@ -43,7 +35,7 @@ public final class AmbitiousFarmhand extends CardImpl { // When Ambitious Farmhand enters the battlefield, you may search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), true + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true), true )); // Coven—{1}{W}{W}: Transform Ambitious Farmhand. Activate only if you control three or more creatures with different powers. diff --git a/Mage.Sets/src/mage/cards/a/AmbushCommander.java b/Mage.Sets/src/mage/cards/a/AmbushCommander.java index 036e316e46f..69d8b124cc3 100644 --- a/Mage.Sets/src/mage/cards/a/AmbushCommander.java +++ b/Mage.Sets/src/mage/cards/a/AmbushCommander.java @@ -14,7 +14,6 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.custom.CreatureToken; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -42,7 +41,11 @@ public final class AmbushCommander extends CardImpl { ContinuousEffect effect = new BecomesCreatureAllEffect( new CreatureToken(1, 1, "1/1 green Elf creatures").withColor("G").withSubType(SubType.ELF), "lands", filter2, Duration.WhileOnBattlefield, true); - effect.getDependencyTypes().add(DependencyType.BecomeForest); + effect.addDependedToType(DependencyType.BecomeForest); + effect.addDependedToType(DependencyType.BecomeIsland); + effect.addDependedToType(DependencyType.BecomeMountain); + effect.addDependedToType(DependencyType.BecomePlains); + effect.addDependedToType(DependencyType.BecomeSwamp); this.addAbility(new SimpleStaticAbility(effect)); // {1}{G}, Sacrifice an Elf: Target creature gets +3/+3 until end of turn. diff --git a/Mage.Sets/src/mage/cards/a/AnHavvaConstable.java b/Mage.Sets/src/mage/cards/a/AnHavvaConstable.java index f077d739041..8de16885a89 100644 --- a/Mage.Sets/src/mage/cards/a/AnHavvaConstable.java +++ b/Mage.Sets/src/mage/cards/a/AnHavvaConstable.java @@ -1,7 +1,6 @@ package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.ObjectColor; @@ -16,6 +15,8 @@ import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** * * @author fireshoes @@ -29,7 +30,7 @@ public final class AnHavvaConstable extends CardImpl { this.toughness = new MageInt(1); // An-Havva Constable's toughness is equal to 1 plus the number of green creatures on the battlefield. - this.addAbility(new SimpleStaticAbility(new AnHavvaConstableEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AnHavvaConstableEffect())); } private AnHavvaConstable(final AnHavvaConstable card) { @@ -72,7 +73,7 @@ class AnHavvaConstableEffect extends ContinuousEffectImpl { FilterCreaturePermanent filter = new FilterCreaturePermanent("green creatures"); filter.add(new ColorPredicate(ObjectColor.GREEN)); - int numberOfGreenCreatures = game.getBattlefield().count(filter, source.getSourceId(), source, game); + int numberOfGreenCreatures = game.getBattlefield().count(filter, source.getControllerId(), source, game); mageObject.getToughness().setModifiedBaseValue(1 + numberOfGreenCreatures); diff --git a/Mage.Sets/src/mage/cards/a/AncestorsEmbrace.java b/Mage.Sets/src/mage/cards/a/AncestorsEmbrace.java index 8929484e38f..ce046048700 100644 --- a/Mage.Sets/src/mage/cards/a/AncestorsEmbrace.java +++ b/Mage.Sets/src/mage/cards/a/AncestorsEmbrace.java @@ -1,11 +1,9 @@ package mage.cards.a; -import mage.abilities.Ability; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.LifelinkAbility; import mage.cards.CardImpl; @@ -32,8 +30,7 @@ public final class AncestorsEmbrace extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature has lifelink. this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect( @@ -41,7 +38,7 @@ public final class AncestorsEmbrace extends CardImpl { ))); // If Ancestor's Embrace would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private AncestorsEmbrace(final AncestorsEmbrace card) { diff --git a/Mage.Sets/src/mage/cards/a/AncientBrassDragon.java b/Mage.Sets/src/mage/cards/a/AncientBrassDragon.java index 4e748752c45..ca2c720f483 100644 --- a/Mage.Sets/src/mage/cards/a/AncientBrassDragon.java +++ b/Mage.Sets/src/mage/cards/a/AncientBrassDragon.java @@ -108,8 +108,8 @@ class AncientBrassDragonTarget extends TargetCardInGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, xValue, game); } diff --git a/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java b/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java index bf0df6ed71b..3e97627ad2c 100644 --- a/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java +++ b/Mage.Sets/src/mage/cards/a/AngelOfDestiny.java @@ -114,7 +114,7 @@ class AngelOfDestinyLoseEffect extends OneShotEffect { return false; } Set playerSet = watcher.getPlayers(new MageObjectReference( - source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game + source.getSourceId(), source.getStackMomentSourceZCC(), game )); if (playerSet == null) { return false; diff --git a/Mage.Sets/src/mage/cards/a/AngelOfJubilation.java b/Mage.Sets/src/mage/cards/a/AngelOfJubilation.java index 535752c85f4..8fb4bcc2cca 100644 --- a/Mage.Sets/src/mage/cards/a/AngelOfJubilation.java +++ b/Mage.Sets/src/mage/cards/a/AngelOfJubilation.java @@ -1,23 +1,16 @@ package mage.cards.a; import mage.MageInt; -import mage.abilities.Ability; +import mage.abilities.common.CantPayLifeOrSacrificeAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.continuous.BoostControlledEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.Filter; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.players.Player; import java.util.UUID; @@ -39,9 +32,7 @@ public final class AngelOfJubilation extends CardImpl { this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURES_NON_BLACK, true))); // Players can't pay life or sacrifice creatures to cast spells or activate abilities. - Ability ability = new SimpleStaticAbility(new AngelOfJubilationEffect()); - ability.addEffect(new AngelOfJubilationSacrificeFilterEffect()); - this.addAbility(ability); + this.addAbility(new CantPayLifeOrSacrificeAbility(StaticFilters.FILTER_PERMANENT_CREATURES)); } private AngelOfJubilation(final AngelOfJubilation card) { @@ -53,66 +44,3 @@ public final class AngelOfJubilation extends CardImpl { return new AngelOfJubilation(this); } } - -class AngelOfJubilationEffect extends ContinuousEffectImpl { - - AngelOfJubilationEffect() { - super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment); - staticText = "Players can't pay life or sacrifice creatures to cast spells"; - } - - private AngelOfJubilationEffect(final AngelOfJubilationEffect effect) { - super(effect); - } - - @Override - public AngelOfJubilationEffect copy() { - return new AngelOfJubilationEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { - Player player = game.getPlayer(playerId); - player.setPayLifeCostLevel(Player.PayLifeCostLevel.nonSpellnonActivatedAbilities); - player.setCanPaySacrificeCostFilter(new FilterCreaturePermanent()); - } - return true; - } -} - -class AngelOfJubilationSacrificeFilterEffect extends CostModificationEffectImpl { - - AngelOfJubilationSacrificeFilterEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment, CostModificationType.SET_COST); - staticText = "or activate abilities"; - } - - protected AngelOfJubilationSacrificeFilterEffect(AngelOfJubilationSacrificeFilterEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - for (Cost cost : abilityToModify.getCosts()) { - if (cost instanceof SacrificeTargetCost) { - SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost; - Filter filter = sacrificeCost.getTargets().get(0).getFilter(); - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - } - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return (abilityToModify.isActivatedAbility() || abilityToModify.getAbilityType() == AbilityType.SPELL) - && game.getState().getPlayersInRange(source.getControllerId(), game).contains(abilityToModify.getControllerId()); - } - - @Override - public AngelOfJubilationSacrificeFilterEffect copy() { - return new AngelOfJubilationSacrificeFilterEffect(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/a/AntiVenomHorrifyingHealer.java b/Mage.Sets/src/mage/cards/a/AntiVenomHorrifyingHealer.java new file mode 100644 index 00000000000..f8dbc3c8602 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AntiVenomHorrifyingHealer.java @@ -0,0 +1,89 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CastFromEverywhereSourceCondition; +import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AntiVenomHorrifyingHealer extends CardImpl { + + public AntiVenomHorrifyingHealer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{W}{W}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SYMBIOTE); + this.subtype.add(SubType.HERO); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // When Anti-Venom enters, if he was cast, return target creature card from your graveyard to the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()) + .withInterveningIf(CastFromEverywhereSourceCondition.instance); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.addAbility(ability); + + // If damage would be dealt to Anti-Venom, prevent that damage and put that many +1/+1 counters on him. + this.addAbility(new SimpleStaticAbility(new AntiVenomHorrifyingHealerEffect())); + } + + private AntiVenomHorrifyingHealer(final AntiVenomHorrifyingHealer card) { + super(card); + } + + @Override + public AntiVenomHorrifyingHealer copy() { + return new AntiVenomHorrifyingHealer(this); + } +} + +class AntiVenomHorrifyingHealerEffect extends PreventionEffectImpl { + + AntiVenomHorrifyingHealerEffect() { + super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false); + staticText = "if damage would be dealt to {this}, prevent that damage and put that many +1/+1 counters on him"; + } + + private AntiVenomHorrifyingHealerEffect(final AntiVenomHorrifyingHealerEffect effect) { + super(effect); + } + + @Override + public AntiVenomHorrifyingHealerEffect copy() { + return new AntiVenomHorrifyingHealerEffect(this); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return super.applies(event, source, game) + && event.getTargetId().equals(source.getSourceId()); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + permanent.addCounters(CounterType.P1P1.createInstance(event.getAmount()), source.getControllerId(), source, game); + } + return super.replaceEvent(event, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AnuridScavenger.java b/Mage.Sets/src/mage/cards/a/AnuridScavenger.java index 9cbf5efe14d..9fbf8962508 100644 --- a/Mage.Sets/src/mage/cards/a/AnuridScavenger.java +++ b/Mage.Sets/src/mage/cards/a/AnuridScavenger.java @@ -84,7 +84,7 @@ class AnuridScavengerCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/a/AoTheDawnSky.java b/Mage.Sets/src/mage/cards/a/AoTheDawnSky.java index 5813da126bd..6c577cd75e5 100644 --- a/Mage.Sets/src/mage/cards/a/AoTheDawnSky.java +++ b/Mage.Sets/src/mage/cards/a/AoTheDawnSky.java @@ -134,8 +134,8 @@ class AoTheDawnSkyTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 4, game); } diff --git a/Mage.Sets/src/mage/cards/a/AppaAangsCompanion.java b/Mage.Sets/src/mage/cards/a/AppaAangsCompanion.java new file mode 100644 index 00000000000..e95d762bb8c --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AppaAangsCompanion.java @@ -0,0 +1,62 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +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.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.AttackingPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AppaAangsCompanion extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another target attacking creature without flying"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(AttackingPredicate.instance); + filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); + } + + public AppaAangsCompanion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BISON); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Appa attacks, another target attacking creature without flying gains flying until until end of turn. + Ability ability = new AttacksTriggeredAbility(new GainAbilityTargetEffect(FlyingAbility.getInstance())); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private AppaAangsCompanion(final AppaAangsCompanion card) { + super(card); + } + + @Override + public AppaAangsCompanion copy() { + return new AppaAangsCompanion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AppaSteadfastGuardian.java b/Mage.Sets/src/mage/cards/a/AppaSteadfastGuardian.java new file mode 100644 index 00000000000..e9bd0a841ee --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AppaSteadfastGuardian.java @@ -0,0 +1,70 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.keyword.AirbendTargetEffect; +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.StaticFilters; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.permanent.token.AllyToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AppaSteadfastGuardian extends CardImpl { + + private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanents you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public AppaSteadfastGuardian(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BISON); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Appa enters, airbend any number of other target nonland permanents you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new AirbendTargetEffect()); + ability.addTarget(new TargetPermanent(0, Integer.MAX_VALUE, filter)); + this.addAbility(ability); + + // Whenever you cast a spell from exile, create a 1/1 white Ally creature token. + this.addAbility(new SpellCastControllerTriggeredAbility( + Zone.BATTLEFIELD, new CreateTokenEffect(new AllyToken()), StaticFilters.FILTER_SPELL_A, + false, SetTargetPointer.NONE, Zone.EXILED + )); + } + + private AppaSteadfastGuardian(final AppaSteadfastGuardian card) { + super(card); + } + + @Override + public AppaSteadfastGuardian copy() { + return new AppaSteadfastGuardian(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArachnePsionicWeaver.java b/Mage.Sets/src/mage/cards/a/ArachnePsionicWeaver.java new file mode 100644 index 00000000000..d80351d2164 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArachnePsionicWeaver.java @@ -0,0 +1,70 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.ChooseCardTypeEffect; +import mage.abilities.effects.common.LookAtTargetPlayerHandEffect; +import mage.abilities.effects.common.cost.SpellsCostIncreasingAllEffect; +import mage.abilities.keyword.WebSlingingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.ChosenCardTypePredicate; +import mage.target.common.TargetOpponent; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * + * @author Jmlundeen + */ +public final class ArachnePsionicWeaver extends CardImpl { + + private static final FilterCard filter = new FilterCard("spells of the chosen type"); + + static { + filter.add(ChosenCardTypePredicate.TRUE); + } + + public ArachnePsionicWeaver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Web-slinging {W} + this.addAbility(new WebSlingingAbility(this, "{W}")); + + // As Arachne enters, look at target opponent's hand, then choose a noncreature card type. + List types = Arrays.stream(CardType.values()).filter(cardType -> cardType != CardType.CREATURE) + .collect(Collectors.toList()); + Ability ability = new AsEntersBattlefieldAbility(new LookAtTargetPlayerHandEffect()); + ability.addEffect(new ChooseCardTypeEffect(Outcome.Benefit, types) + .setText("choose a noncreature card type") + .concatBy(", then")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // Spells of the chosen type cost {1} more to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostIncreasingAllEffect(1, filter, TargetController.ANY))); + } + + private ArachnePsionicWeaver(final ArachnePsionicWeaver card) { + super(card); + } + + @Override + public ArachnePsionicWeaver copy() { + return new ArachnePsionicWeaver(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AranaHeartOfTheSpider.java b/Mage.Sets/src/mage/cards/a/AranaHeartOfTheSpider.java new file mode 100644 index 00000000000..cf4d01e8367 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AranaHeartOfTheSpider.java @@ -0,0 +1,63 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.ModifiedPredicate; +import mage.target.common.TargetAttackingCreature; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AranaHeartOfTheSpider extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("modified creature you control"); + + static { + filter.add(ModifiedPredicate.instance); + } + + public AranaHeartOfTheSpider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever you attack, put a +1/+1 counter on target attacking creature. + Ability ability = new AttacksWithCreaturesTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), 1 + ); + ability.addTarget(new TargetAttackingCreature()); + this.addAbility(ability); + + // Whenever a modified creature you control deals combat damage to a player, exile the top card of your library. You may play that card this turn. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn), + filter, false, SetTargetPointer.NONE, true + )); + } + + private AranaHeartOfTheSpider(final AranaHeartOfTheSpider card) { + super(card); + } + + @Override + public AranaHeartOfTheSpider copy() { + return new AranaHeartOfTheSpider(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArchaeomancersMap.java b/Mage.Sets/src/mage/cards/a/ArchaeomancersMap.java index 27cfcac06b0..221acf4cf58 100644 --- a/Mage.Sets/src/mage/cards/a/ArchaeomancersMap.java +++ b/Mage.Sets/src/mage/cards/a/ArchaeomancersMap.java @@ -9,10 +9,7 @@ import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.constants.TargetController; -import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterLandPermanent; @@ -26,13 +23,10 @@ import java.util.UUID; */ public final class ArchaeomancersMap extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Plains cards"); - private static final FilterPermanent filter2 = new FilterLandPermanent("a land an opponent controls"); + private static final FilterPermanent filter = new FilterLandPermanent("a land an opponent controls"); static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); - filter2.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(TargetController.OPPONENT.getControllerPredicate()); } public ArchaeomancersMap(UUID ownerId, CardSetInfo setInfo) { @@ -40,12 +34,12 @@ public final class ArchaeomancersMap extends CardImpl { // When Archaeomancer's Map enters the battlefield, search your library for up to two basic Plains cards, reveal them, put them into your hand, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 2, filter), true) + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_PLAINS), true) )); // Whenever a land enters the battlefield under an opponent's control, if that player controls more lands than you, you may put a land card from your hand onto the battlefield. this.addAbility(new EntersBattlefieldAllTriggeredAbility( - new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A), filter2 + new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A), filter ).withInterveningIf(ArchaeomancersMapCondition.instance)); } diff --git a/Mage.Sets/src/mage/cards/a/ArchdemonOfGreed.java b/Mage.Sets/src/mage/cards/a/ArchdemonOfGreed.java index b4c8c7a0518..6698dcde482 100644 --- a/Mage.Sets/src/mage/cards/a/ArchdemonOfGreed.java +++ b/Mage.Sets/src/mage/cards/a/ArchdemonOfGreed.java @@ -1,21 +1,18 @@ package mage.cards.a; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DamageControllerEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.TapSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TrampleAbility; 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.filter.common.FilterControlledPermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.common.TargetSacrifice; import java.util.UUID; @@ -24,11 +21,7 @@ import java.util.UUID; */ public final class ArchdemonOfGreed extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("Human"); - - static { - filter.add(SubType.HUMAN.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.HUMAN, "Human"); public ArchdemonOfGreed(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); @@ -44,7 +37,10 @@ public final class ArchdemonOfGreed extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // At the beginning of your upkeep, sacrifice a Human. If you can't, tap Archdemon of Greed and it deals 9 damage to you. - this.addAbility(new BeginningOfUpkeepTriggeredAbility(new ArchdemonOfGreedEffect())); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DoIfCostPaid( + null, new TapSourceEffect(), new SacrificeTargetCost(filter), false + ).addOtherwiseEffect(new DamageControllerEffect(9)) + .setText("sacrifice a Human. If you can't, tap {this} and it deals 9 damage to you"))); } private ArchdemonOfGreed(final ArchdemonOfGreed card) { @@ -55,48 +51,4 @@ public final class ArchdemonOfGreed extends CardImpl { public ArchdemonOfGreed copy() { return new ArchdemonOfGreed(this); } - - static class ArchdemonOfGreedEffect extends OneShotEffect { - - public ArchdemonOfGreedEffect() { - super(Outcome.Damage); - this.staticText = "sacrifice a Human. If you can't, tap {this} and it deals 9 damage to you."; - } - - private ArchdemonOfGreedEffect(final ArchdemonOfGreedEffect effect) { - super(effect); - } - - @Override - public ArchdemonOfGreedEffect copy() { - return new ArchdemonOfGreedEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - - if (permanent != null) { - // create cost for sacrificing a human - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - TargetSacrifice target = new TargetSacrifice(filter); - // if they can pay the cost, then they must pay - if (target.canChoose(player.getId(), source, game)) { - player.choose(Outcome.Sacrifice, target, source, game); - Permanent humanSacrifice = game.getPermanent(target.getFirstTarget()); - if (humanSacrifice != null) { - // sacrifice the chosen card - return humanSacrifice.sacrifice(source, game); - } - } else { - permanent.tap(source, game); - player.damage(9, source.getSourceId(), source, game); - } - } - return true; - } - return false; - } - } } diff --git a/Mage.Sets/src/mage/cards/a/ArchiveHaunt.java b/Mage.Sets/src/mage/cards/a/ArchiveHaunt.java index 7516cde5dc8..b4fac23a008 100644 --- a/Mage.Sets/src/mage/cards/a/ArchiveHaunt.java +++ b/Mage.Sets/src/mage/cards/a/ArchiveHaunt.java @@ -2,9 +2,8 @@ package mage.cards.a; import mage.MageInt; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.effects.common.DrawDiscardControllerEffect; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -35,7 +34,7 @@ public final class ArchiveHaunt extends CardImpl { this.addAbility(new AttacksTriggeredAbility(new DrawDiscardControllerEffect(1, 1))); // If Archive Haunt would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private ArchiveHaunt(final ArchiveHaunt card) { diff --git a/Mage.Sets/src/mage/cards/a/ArdentDustspeaker.java b/Mage.Sets/src/mage/cards/a/ArdentDustspeaker.java index 4b4ace434dc..1c2b4b95b6b 100644 --- a/Mage.Sets/src/mage/cards/a/ArdentDustspeaker.java +++ b/Mage.Sets/src/mage/cards/a/ArdentDustspeaker.java @@ -79,7 +79,7 @@ class ArdentDustspeakerCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/a/ArlinnEmbracedByTheMoon.java b/Mage.Sets/src/mage/cards/a/ArlinnEmbracedByTheMoon.java index 8e470214cdd..8a6b011c2e0 100644 --- a/Mage.Sets/src/mage/cards/a/ArlinnEmbracedByTheMoon.java +++ b/Mage.Sets/src/mage/cards/a/ArlinnEmbracedByTheMoon.java @@ -1,9 +1,7 @@ - package mage.cards.a; -import java.util.UUID; +import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.GetEmblemEffect; import mage.abilities.effects.common.TransformSourceEffect; @@ -21,8 +19,9 @@ import mage.filter.StaticFilters; import mage.game.command.emblems.ArlinnEmbracedByTheMoonEmblem; import mage.target.common.TargetAnyTarget; +import java.util.UUID; + /** - * * @author fireshoes */ public final class ArlinnEmbracedByTheMoon extends CardImpl { @@ -37,12 +36,14 @@ public final class ArlinnEmbracedByTheMoon extends CardImpl { this.nightCard = true; // +1: Creatures you control get +1/+1 and gain trample until end of turn. - Effect effect = new BoostControlledEffect(1, 1, Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE); - effect.setText("Creatures you control get +1/+1"); - LoyaltyAbility ability = new LoyaltyAbility(effect, 1); - effect = new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE); - effect.setText("and gain trample until end of turn"); - ability.addEffect(effect); + Ability ability = new LoyaltyAbility(new BoostControlledEffect( + 1, 1, Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("Creatures you control get +1/+1"), 1); + ability.addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("and gain trample until end of turn")); this.addAbility(ability); // -1: Arlinn, Embraced by the Moon deals 3 damage to any target. Transform Arlinn, Embraced by the Moon. diff --git a/Mage.Sets/src/mage/cards/a/ArlinnKord.java b/Mage.Sets/src/mage/cards/a/ArlinnKord.java index 6a61cf53703..dbb9593deb6 100644 --- a/Mage.Sets/src/mage/cards/a/ArlinnKord.java +++ b/Mage.Sets/src/mage/cards/a/ArlinnKord.java @@ -1,9 +1,7 @@ - package mage.cards.a; -import java.util.UUID; +import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; @@ -20,14 +18,15 @@ import mage.constants.SuperType; import mage.game.permanent.token.WolfToken; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author fireshoes */ public final class ArlinnKord extends CardImpl { public ArlinnKord(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.PLANESWALKER},"{2}{R}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{G}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.ARLINN); @@ -36,15 +35,15 @@ public final class ArlinnKord extends CardImpl { this.setStartingLoyalty(3); // +1: Until end of turn, up to one target creature gets +2/+2 and gains vigilance and haste. - Effect effect = new BoostTargetEffect(2, 2, Duration.EndOfTurn); - effect.setText("Until end of turn, up to one target creature gets +2/+2"); - LoyaltyAbility ability = new LoyaltyAbility(effect, 1); - effect = new GainAbilityTargetEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn); - effect.setText("and gains vigilance"); - ability.addEffect(effect); - effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); - effect.setText("and haste"); - ability.addEffect(effect); + Ability ability = new LoyaltyAbility(new BoostTargetEffect( + 2, 2, Duration.EndOfTurn + ).setText("until end of turn, up to one target creature gets +2/+2"), 1); + ability.addEffect(new GainAbilityTargetEffect( + VigilanceAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains vigilance")); + ability.addEffect(new GainAbilityTargetEffect( + HasteAbility.getInstance(), Duration.EndOfTurn + ).setText("and haste")); ability.addTarget(new TargetCreaturePermanent(0, 1)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java b/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java index ca6455ad195..8c698857b28 100644 --- a/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java +++ b/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java @@ -18,7 +18,7 @@ import mage.cards.CardSetInfo; import mage.constants.AttachmentType; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.game.Game; import mage.util.CardUtil; @@ -30,6 +30,8 @@ import java.util.UUID; */ public final class ArmMountedAnchor extends CardImpl { + private static final FilterCard filter = new FilterCard(SubType.PIRATE); + public ArmMountedAnchor(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); this.subtype.add(SubType.EQUIPMENT); @@ -42,11 +44,12 @@ public final class ArmMountedAnchor extends CardImpl { this.addAbility(firstAbility); // Whenever equipped creature deals combat damage to a player, draw two cards. Then discard two cards unless you discard a Pirate card. - Ability drawAbility = new DealsDamageToAPlayerAttachedTriggeredAbility(new DrawCardSourceControllerEffect(2), "equipped creature", false); - DiscardCardCost cost = new DiscardCardCost(new FilterBySubtypeCard(SubType.PIRATE)); - cost.setText("Discard a Pirate card instead of discarding two cards"); + Ability drawAbility = new DealsDamageToAPlayerAttachedTriggeredAbility( + new DrawCardSourceControllerEffect(2), "equipped creature", false + ); drawAbility.addEffect(new DoIfCostPaid( - null, new DiscardControllerEffect(2), cost + null, new DiscardControllerEffect(2), + new DiscardCardCost(filter).setText("Discard a Pirate card instead of discarding two cards") ).setText("Then discard two cards unless you discard a Pirate card")); this.addAbility(drawAbility); diff --git a/Mage.Sets/src/mage/cards/a/ArtifactWard.java b/Mage.Sets/src/mage/cards/a/ArtifactWard.java index 919dbc77724..8f4bdfae93c 100644 --- a/Mage.Sets/src/mage/cards/a/ArtifactWard.java +++ b/Mage.Sets/src/mage/cards/a/ArtifactWard.java @@ -1,7 +1,6 @@ package mage.cards.a; -import java.util.UUID; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; @@ -9,11 +8,16 @@ import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.ProtectionAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.filter.common.FilterArtifactCard; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author MarcoMarin @@ -33,7 +37,7 @@ public final class ArtifactWard extends CardImpl { // Enchanted creature can't be blocked by artifact creatures. // Prevent all damage that would be dealt to enchanted creature by artifact sources. // Enchanted creature can't be the target of abilities from artifact sources. - this.addAbility(new SimpleStaticAbility( + this.addAbility(new SimpleStaticAbility( // TODO: Implement as separate abilities, this isn't quite the same as "Enchanted creature gains protection from artifacts" new GainAbilityAttachedEffect(new ProtectionAbility(new FilterArtifactCard("artifacts")), AttachmentType.AURA))); } diff --git a/Mage.Sets/src/mage/cards/a/AshayaSoulOfTheWild.java b/Mage.Sets/src/mage/cards/a/AshayaSoulOfTheWild.java index 8f726adaa2a..a0c80d4df31 100644 --- a/Mage.Sets/src/mage/cards/a/AshayaSoulOfTheWild.java +++ b/Mage.Sets/src/mage/cards/a/AshayaSoulOfTheWild.java @@ -64,6 +64,7 @@ class AshayaSoulOfTheWildEffect extends ContinuousEffectImpl { staticText = "Nontoken creatures you control are Forest lands in addition to their other types"; this.dependendToTypes.add(DependencyType.BecomeCreature); this.dependencyTypes.add(DependencyType.BecomeForest); + this.dependencyTypes.add(DependencyType.BecomeNonbasicLand); } private AshayaSoulOfTheWildEffect(final AshayaSoulOfTheWildEffect effect) { diff --git a/Mage.Sets/src/mage/cards/a/AshioksErasure.java b/Mage.Sets/src/mage/cards/a/AshioksErasure.java index a8a4273cb13..4033aad9e33 100644 --- a/Mage.Sets/src/mage/cards/a/AshioksErasure.java +++ b/Mage.Sets/src/mage/cards/a/AshioksErasure.java @@ -90,7 +90,7 @@ class AshioksErasureExileEffect extends OneShotEffect { || spell == null) { return false; } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); return controller.moveCardsToExile(spell, source, game, true, exileId, sourceObject.getIdName()); } } @@ -126,7 +126,7 @@ class AshioksErasureReplacementEffect extends ContinuousRuleModifyingEffectImpl || card == null) { return false; } - UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exile = game.getExile().getExileZone(exileZone); if (exile == null) { diff --git a/Mage.Sets/src/mage/cards/a/AshmouthBlade.java b/Mage.Sets/src/mage/cards/a/AshmouthBlade.java index 7ada6ce0445..4fe8866988e 100644 --- a/Mage.Sets/src/mage/cards/a/AshmouthBlade.java +++ b/Mage.Sets/src/mage/cards/a/AshmouthBlade.java @@ -1,44 +1,40 @@ - package mage.cards.a; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; /** - * * @author fireshoes */ public final class AshmouthBlade extends CardImpl { public AshmouthBlade(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},""); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); this.subtype.add(SubType.EQUIPMENT); // this card is the second face of double-faced card this.nightCard = true; - // Equipped creature gets +3/+3 + // Equipped creature gets +3/+3 and has first strike. Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(3, 3)); + ability.addEffect(new GainAbilityAttachedEffect( + FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText("and has first strike")); this.addAbility(ability); - // and has first strike. - Effect effect = new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT); - effect.setText("and has first strike"); - ability.addEffect(effect); - // Equip {3} - this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(3), new TargetControlledCreaturePermanent(), false)); + this.addAbility(new EquipAbility(3, false)); } private AshmouthBlade(final AshmouthBlade card) { diff --git a/Mage.Sets/src/mage/cards/a/AstralDrift.java b/Mage.Sets/src/mage/cards/a/AstralDrift.java index e181fee3284..72c5956fbdd 100644 --- a/Mage.Sets/src/mage/cards/a/AstralDrift.java +++ b/Mage.Sets/src/mage/cards/a/AstralDrift.java @@ -68,10 +68,7 @@ class AstralDriftTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (game.getState().getStack().isEmpty()) { - return false; - } - StackObject item = game.getState().getStack().getFirst(); + StackObject item = game.getState().getStack().getFirstOrNull(); if (!(item instanceof StackAbility && item.getStackAbility() instanceof CyclingAbility)) { return false; diff --git a/Mage.Sets/src/mage/cards/a/AuratouchedMage.java b/Mage.Sets/src/mage/cards/a/AuratouchedMage.java index 1a06f66a7e8..17c958cf8b0 100644 --- a/Mage.Sets/src/mage/cards/a/AuratouchedMage.java +++ b/Mage.Sets/src/mage/cards/a/AuratouchedMage.java @@ -73,7 +73,7 @@ class AuratouchedMageEffect extends OneShotEffect { Card aura = game.getCard(target.getFirstTarget()); Permanent auratouchedMage = source.getSourcePermanentIfItStillExists(game); if (aura != null && auratouchedMage != null - && game.getState().getZoneChangeCounter(source.getSourceId()) == source.getSourceObjectZoneChangeCounter()) { + && game.getState().getZoneChangeCounter(source.getSourceId()) == source.getStackMomentSourceZCC()) { game.getState().setValue("attachTo:" + aura.getId(), auratouchedMage); if (controller.moveCards(aura, Zone.BATTLEFIELD, source, game)) { auratouchedMage.addAttachment(aura.getId(), source, game); diff --git a/Mage.Sets/src/mage/cards/a/AutumnalGloom.java b/Mage.Sets/src/mage/cards/a/AutumnalGloom.java index 684727c24dd..b7d0f232ddf 100644 --- a/Mage.Sets/src/mage/cards/a/AutumnalGloom.java +++ b/Mage.Sets/src/mage/cards/a/AutumnalGloom.java @@ -1,22 +1,21 @@ package mage.cards.a; -import java.util.UUID; - -import mage.abilities.Ability; -import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.condition.common.DeliriumCondition; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount; import mage.abilities.effects.common.MillCardsControllerEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.TargetController; +import java.util.UUID; + /** * @author LevelX2 */ @@ -31,10 +30,9 @@ public final class AutumnalGloom extends CardImpl { // Delirium — At the beginning of your end step, if there are four or more card types among cards in your graveyard, transform Autumnal Gloom. this.addAbility(new TransformAbility()); - Ability ability = new BeginningOfEndStepTriggeredAbility(TargetController.YOU, new TransformSourceEffect(), false, DeliriumCondition.instance); - ability.setAbilityWord(AbilityWord.DELIRIUM); - ability.addHint(CardTypesInGraveyardCount.YOU.getHint()); - this.addAbility(ability); + this.addAbility(new BeginningOfEndStepTriggeredAbility( + TargetController.YOU, new TransformSourceEffect(), false, DeliriumCondition.instance + ).setAbilityWord(AbilityWord.DELIRIUM).addHint(CardTypesInGraveyardCount.YOU.getHint())); } private AutumnalGloom(final AutumnalGloom card) { diff --git a/Mage.Sets/src/mage/cards/a/AvabruckCaretaker.java b/Mage.Sets/src/mage/cards/a/AvabruckCaretaker.java index b34392a4b94..eaec30e14aa 100644 --- a/Mage.Sets/src/mage/cards/a/AvabruckCaretaker.java +++ b/Mage.Sets/src/mage/cards/a/AvabruckCaretaker.java @@ -2,10 +2,10 @@ package mage.cards.a; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.keyword.DayboundAbility; import mage.abilities.keyword.HexproofAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -35,9 +35,7 @@ public final class AvabruckCaretaker extends CardImpl { // At the beginning of combat on your turn, put two +1/+1 counters on another target creature you control. Ability ability = new BeginningOfCombatTriggeredAbility( - new AddCountersTargetEffect( - CounterType.P1P1.createInstance(2) - ) + new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)) ); ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/a/AvatarAang.java b/Mage.Sets/src/mage/cards/a/AvatarAang.java new file mode 100644 index 00000000000..24be5ad614e --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AvatarAang.java @@ -0,0 +1,156 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.FirebendingAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AvatarAang extends CardImpl { + + public AvatarAang(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{G}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + this.secondSideCardClazz = mage.cards.a.AangMasterOfElements.class; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Firebending 2 + this.addAbility(new FirebendingAbility(2)); + + // Whenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform Avatar Aang. + this.addAbility(new AvatarAangTriggeredAbility()); + } + + private AvatarAang(final AvatarAang card) { + super(card); + } + + @Override + public AvatarAang copy() { + return new AvatarAang(this); + } +} + +class AvatarAangTriggeredAbility extends TriggeredAbilityImpl { + + private enum AvatarAangCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return AvatarAangWatcher.checkPlayer(game, source); + } + } + + AvatarAangTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); + this.addEffect(new ConditionalOneShotEffect( + new TransformSourceEffect(), AvatarAangCondition.instance, + "Then if you've done all four this turn, transform {this}" + )); + this.setTriggerPhrase("Whenever you waterbend, earthbend, firebend, or airbend, "); + this.addWatcher(new AvatarAangWatcher()); + } + + private AvatarAangTriggeredAbility(final AvatarAangTriggeredAbility ability) { + super(ability); + } + + @Override + public AvatarAangTriggeredAbility copy() { + return new AvatarAangTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + switch (event.getType()) { + case EARTHBENDED: + case AIRBENDED: + case FIREBENDED: + case WATERBENDED: + return true; + default: + return false; + } + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return isControlledBy(event.getPlayerId()); + } +} + +class AvatarAangWatcher extends Watcher { + + private final Set earthSet = new HashSet<>(); + private final Set airSet = new HashSet<>(); + private final Set fireSet = new HashSet<>(); + private final Set waterSet = new HashSet<>(); + + AvatarAangWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case EARTHBENDED: + earthSet.add(event.getPlayerId()); + return; + case AIRBENDED: + airSet.add(event.getPlayerId()); + return; + case FIREBENDED: + fireSet.add(event.getPlayerId()); + return; + case WATERBENDED: + waterSet.add(event.getPlayerId()); + } + } + + @Override + public void reset() { + super.reset(); + earthSet.clear(); + airSet.clear(); + fireSet.clear(); + earthSet.clear(); + } + + private boolean checkPlayer(UUID playerId) { + return earthSet.contains(playerId) + && airSet.contains(playerId) + && fireSet.contains(playerId) + && earthSet.contains(playerId); + } + + static boolean checkPlayer(Game game, Ability source) { + return game.getState().getWatcher(AvatarAangWatcher.class).checkPlayer(source.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AvatarEnthusiasts.java b/Mage.Sets/src/mage/cards/a/AvatarEnthusiasts.java new file mode 100644 index 00000000000..bb1179c66c5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AvatarEnthusiasts.java @@ -0,0 +1,51 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +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.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AvatarEnthusiasts extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ALLY, "another Ally you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + public AvatarEnthusiasts(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever another Ally you control enters, put a +1/+1 counter on this creature. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter + )); + } + + private AvatarEnthusiasts(final AvatarEnthusiasts card) { + super(card); + } + + @Override + public AvatarEnthusiasts copy() { + return new AvatarEnthusiasts(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AzorsGateway.java b/Mage.Sets/src/mage/cards/a/AzorsGateway.java index 6ac44610cf8..4a6f94a4a39 100644 --- a/Mage.Sets/src/mage/cards/a/AzorsGateway.java +++ b/Mage.Sets/src/mage/cards/a/AzorsGateway.java @@ -1,12 +1,15 @@ - package mage.cards.a; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.UntapSourceEffect; import mage.abilities.keyword.TransformAbility; @@ -19,11 +22,10 @@ import mage.constants.SuperType; import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; +import mage.target.TargetCard; import mage.target.common.TargetCardInHand; import mage.util.CardUtil; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -41,8 +43,13 @@ public final class AzorsGateway extends CardImpl { // If cards with five or more different converted mana costs are exiled with Azor's Gateway, // you gain 5 life, untap Azor's Gateway, and transform it. this.addAbility(new TransformAbility()); - Ability ability = new SimpleActivatedAbility(new AzorsGatewayEffect(), new GenericManaCost(1)); + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new GenericManaCost(1)); ability.addCost(new TapSourceCost()); + ability.addEffect(new AzorsGatewayEffect()); + ability.addEffect(new ConditionalOneShotEffect( + new GainLifeEffect(5), AzorsGatewayCondition.instance, "If cards with five or more " + + "different mana values are exiled with {this}, you gain 5 life, untap {this}, and transform it." + ).addEffect(new UntapSourceEffect()).addEffect(new TransformSourceEffect())); this.addAbility(ability); } @@ -56,13 +63,28 @@ public final class AzorsGateway extends CardImpl { } } +enum AzorsGatewayCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + return exileZone != null + && exileZone + .getCards(game) + .stream() + .map(MageObject::getManaValue) + .distinct() + .mapToInt(x -> 1) + .sum() >= 5; + } +} + class AzorsGatewayEffect extends OneShotEffect { AzorsGatewayEffect() { super(Outcome.Benefit); - this.staticText = "Draw a card, then exile a card from your hand. " + - "If cards with five or more different mana values are exiled with {this}, " + - "you gain 5 life, untap {this}, and transform it"; + this.staticText = ", then exile a card from your hand"; } private AzorsGatewayEffect(final AzorsGatewayEffect effect) { @@ -76,37 +98,18 @@ class AzorsGatewayEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getHand().isEmpty()) { return false; } - - MageObject sourceObject = source.getSourceObject(game); - if (sourceObject == null) { - return false; - } - - UUID exileId = CardUtil.getCardExileZoneId(game, source); - - controller.drawCards(1, source, game); - TargetCardInHand target = new TargetCardInHand(); - controller.choose(outcome, target, source, game); - Card cardToExile = game.getCard(target.getFirstTarget()); - if (cardToExile != null) { - controller.moveCardsToExile(cardToExile, source, game, true, exileId, sourceObject.getIdName()); - } - Set usedCMC = new HashSet<>(); - ExileZone exileZone = game.getExile().getExileZone(exileId); - if (exileZone != null) { - for (Card card : exileZone.getCards(game)) { - usedCMC.add(card.getManaValue()); - } - if (usedCMC.size() > 4) { - controller.gainLife(4, game, source); - new UntapSourceEffect().apply(game, source); - new TransformSourceEffect().apply(game, source); - } - } - return true; + TargetCard target = new TargetCardInHand(); + target.withChooseHint("to exile"); + player.choose(outcome, player.getHand(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + return card != null && player.moveCardsToExile( + card, source, game, true, + CardUtil.getExileZoneId(game, source), + CardUtil.getSourceLogName(game, source) + ); } } diff --git a/Mage.Sets/src/mage/cards/a/AzulaAlwaysLies.java b/Mage.Sets/src/mage/cards/a/AzulaAlwaysLies.java new file mode 100644 index 00000000000..f8d7dbe6f40 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AzulaAlwaysLies.java @@ -0,0 +1,46 @@ +package mage.cards.a; + +import mage.abilities.Mode; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AzulaAlwaysLies extends CardImpl { + + public AzulaAlwaysLies(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); + + this.subtype.add(SubType.LESSON); + + // Choose one or both -- + this.getSpellAbility().getModes().setMinModes(1); + this.getSpellAbility().getModes().setMaxModes(2); + + // * Target creature gets -1/-1 until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(-1, -1)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // * Put a +1/+1 counter on target creature. + this.getSpellAbility().addMode(new Mode(new AddCountersTargetEffect(CounterType.P1P1.createInstance())) + .addTarget(new TargetCreaturePermanent())); + } + + private AzulaAlwaysLies(final AzulaAlwaysLies card) { + super(card); + } + + @Override + public AzulaAlwaysLies copy() { + return new AzulaAlwaysLies(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java b/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java index 7e720d5c3c2..9a905eee13e 100644 --- a/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java +++ b/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java @@ -67,7 +67,7 @@ class BackFromTheBrinkCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/b/Badgermole.java b/Mage.Sets/src/mage/cards/b/Badgermole.java new file mode 100644 index 00000000000..24d46635ab7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/Badgermole.java @@ -0,0 +1,53 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Badgermole extends CardImpl { + + public Badgermole(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.BADGER); + this.subtype.add(SubType.MOLE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When this creature enters, earthbend 2. + Ability ability = new EntersBattlefieldTriggeredAbility(new EarthbendTargetEffect(2)); + ability.addTarget(new TargetControlledLandPermanent()); + this.addAbility(ability); + + // Creatures you control with +1/+1 counters on them have trample. + this.addAbility(new SimpleStaticAbility(new GainAbilityAllEffect( + TrampleAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_CONTROLLED_CREATURES_P1P1 + ))); + } + + private Badgermole(final Badgermole card) { + super(card); + } + + @Override + public Badgermole copy() { + return new Badgermole(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BagOfHolding.java b/Mage.Sets/src/mage/cards/b/BagOfHolding.java index 5b56b21b6fe..8705dc5cdd4 100644 --- a/Mage.Sets/src/mage/cards/b/BagOfHolding.java +++ b/Mage.Sets/src/mage/cards/b/BagOfHolding.java @@ -111,7 +111,7 @@ class BagOfHoldingReturnCardsEffect extends OneShotEffect { return false; } ExileZone exileZone = game.getExile().getExileZone( - CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()) + CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()) ); if (exileZone == null) { return true; diff --git a/Mage.Sets/src/mage/cards/b/BagelAndSchmear.java b/Mage.Sets/src/mage/cards/b/BagelAndSchmear.java new file mode 100644 index 00000000000..329fff0f0c5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BagelAndSchmear.java @@ -0,0 +1,55 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.token.FoodAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BagelAndSchmear extends CardImpl { + + public BagelAndSchmear(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.FOOD); + + // Share -- {W}, {T}, Sacrifice this artifact: Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), new ManaCostsImpl<>("{W}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addEffect(new DrawCardSourceControllerEffect(1)); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability.withFlavorWord("Share")); + + // Nosh -- {2}, {T}, Sacrifice this artifact: You gain 3 life and draw a card. + ability = new FoodAbility(); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability.withFlavorWord("Nosh")); + } + + private BagelAndSchmear(final BagelAndSchmear card) { + super(card); + } + + @Override + public BagelAndSchmear copy() { + return new BagelAndSchmear(this); + } +} +// where's the lox? diff --git a/Mage.Sets/src/mage/cards/b/BallistaWielder.java b/Mage.Sets/src/mage/cards/b/BallistaWielder.java index 72f2f8b952a..81c2dd0ad50 100644 --- a/Mage.Sets/src/mage/cards/b/BallistaWielder.java +++ b/Mage.Sets/src/mage/cards/b/BallistaWielder.java @@ -3,7 +3,6 @@ package mage.cards.b; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.combat.CantBlockTargetEffect; @@ -72,15 +71,15 @@ class BallistaWielderEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { - Player player = game.getPlayer(source.getFirstTarget()); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); return player != null && player.damage(1, source, game) > 0; } - if (permanent.damage(1, source, game) > 0) { - game.addEffect(new CantBlockTargetEffect(Duration.EndOfTurn), source); - return true; + if (permanent.damage(1, source, game) <= 0) { + return false; } - return false; + game.addEffect(new CantBlockTargetEffect(Duration.EndOfTurn), source); + return true; } } diff --git a/Mage.Sets/src/mage/cards/b/BalthierAndFran.java b/Mage.Sets/src/mage/cards/b/BalthierAndFran.java index f3bb72fec8e..30412e58d21 100644 --- a/Mage.Sets/src/mage/cards/b/BalthierAndFran.java +++ b/Mage.Sets/src/mage/cards/b/BalthierAndFran.java @@ -36,7 +36,7 @@ public final class BalthierAndFran extends CardImpl { = new FilterCreaturePermanent(SubType.VEHICLE, "a Vehicle crewed by {this} this turn"); static { - filter.add(BalthierAndFranPredicate.instance); + filter2.add(BalthierAndFranPredicate.instance); } public BalthierAndFran(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/b/BantPanorama.java b/Mage.Sets/src/mage/cards/b/BantPanorama.java index dbc71082e43..87ff6af34b0 100644 --- a/Mage.Sets/src/mage/cards/b/BantPanorama.java +++ b/Mage.Sets/src/mage/cards/b/BantPanorama.java @@ -1,7 +1,5 @@ - package mage.cards.b; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; @@ -11,11 +9,15 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** * @author North */ @@ -24,7 +26,6 @@ public final class BantPanorama extends CardImpl { private static final FilterCard filter = new FilterCard("a basic Forest, Plains, or Island card"); static { - filter.add(CardType.LAND.getPredicate()); filter.add(SuperType.BASIC.getPredicate()); filter.add(Predicates.or( SubType.FOREST.getPredicate(), diff --git a/Mage.Sets/src/mage/cards/b/BarrelsOfBlastingJelly.java b/Mage.Sets/src/mage/cards/b/BarrelsOfBlastingJelly.java new file mode 100644 index 00000000000..9fdd9763dbb --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BarrelsOfBlastingJelly.java @@ -0,0 +1,48 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.mana.AddManaOfAnyColorEffect; +import mage.abilities.mana.LimitedTimesPerTurnActivatedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BarrelsOfBlastingJelly extends CardImpl { + + public BarrelsOfBlastingJelly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + // {1}: Add one mana of any color. Activate only once each turn. + this.addAbility(new LimitedTimesPerTurnActivatedManaAbility( + Zone.BATTLEFIELD, new AddManaOfAnyColorEffect(), new GenericManaCost(1) + )); + + // {5}, {T}, Sacrifice this artifact: It deals 5 damage to target creature. + Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(5, "it"), new GenericManaCost(5)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private BarrelsOfBlastingJelly(final BarrelsOfBlastingJelly card) { + super(card); + } + + @Override + public BarrelsOfBlastingJelly copy() { + return new BarrelsOfBlastingJelly(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BattleForBretagard.java b/Mage.Sets/src/mage/cards/b/BattleForBretagard.java index ac11200470e..b05074878a6 100644 --- a/Mage.Sets/src/mage/cards/b/BattleForBretagard.java +++ b/Mage.Sets/src/mage/cards/b/BattleForBretagard.java @@ -147,6 +147,7 @@ class BattleForBretagardTarget extends TargetPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + Set names = this.getTargets() .stream() .map(game::getPermanent) @@ -159,6 +160,7 @@ class BattleForBretagardTarget extends TargetPermanent { Permanent permanent = game.getPermanent(uuid); return permanent == null || names.contains(permanent.getName()); }); + return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/b/BattlefieldScrounger.java b/Mage.Sets/src/mage/cards/b/BattlefieldScrounger.java index 5a914df491f..f03656b0c32 100644 --- a/Mage.Sets/src/mage/cards/b/BattlefieldScrounger.java +++ b/Mage.Sets/src/mage/cards/b/BattlefieldScrounger.java @@ -79,7 +79,7 @@ class BattlefieldScroungerCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/b/BeamsplitterMage.java b/Mage.Sets/src/mage/cards/b/BeamsplitterMage.java index 1bdb93421cf..e1282b210d6 100644 --- a/Mage.Sets/src/mage/cards/b/BeamsplitterMage.java +++ b/Mage.Sets/src/mage/cards/b/BeamsplitterMage.java @@ -121,11 +121,11 @@ class BeamsplitterMageTriggeredAbility extends TriggeredAbilityImpl { private boolean checkNotSource(Permanent permanent, Game game) { // workaround for zcc not being set before first intervening if check - if (this.getSourceObjectZoneChangeCounter() == 0) { + if (this.getStackMomentSourceZCC() == 0) { return !permanent.getId().equals(this.getSourceId()); } return !permanent.getId().equals(this.getSourceId()) - || permanent.getZoneChangeCounter(game) != this.getSourceObjectZoneChangeCounter(); + || permanent.getZoneChangeCounter(game) != this.getStackMomentSourceZCC(); } @Override diff --git a/Mage.Sets/src/mage/cards/b/BeetleHeadedMerchants.java b/Mage.Sets/src/mage/cards/b/BeetleHeadedMerchants.java new file mode 100644 index 00000000000..78eb80306ae --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BeetleHeadedMerchants.java @@ -0,0 +1,46 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DoIfCostPaid; +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.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BeetleHeadedMerchants extends CardImpl { + + public BeetleHeadedMerchants(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Whenever this creature attacks, you may sacrifice another creature or artifact. If you do, draw a card and put a +1/+1 counter on this creature. + this.addAbility(new AttacksTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), + new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT) + ).addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()).concatBy("and")))); + } + + private BeetleHeadedMerchants(final BeetleHeadedMerchants card) { + super(card); + } + + @Override + public BeetleHeadedMerchants copy() { + return new BeetleHeadedMerchants(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BeetleLegacyCriminal.java b/Mage.Sets/src/mage/cards/b/BeetleLegacyCriminal.java index fdb86fd1b9f..233f3b7981e 100644 --- a/Mage.Sets/src/mage/cards/b/BeetleLegacyCriminal.java +++ b/Mage.Sets/src/mage/cards/b/BeetleLegacyCriminal.java @@ -44,7 +44,7 @@ public final class BeetleLegacyCriminal extends CardImpl { ability.addCost(new ExileSourceFromGraveCost()); ability.addTarget(new TargetCreaturePermanent()); ability.addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance()) - .setText("Put a +1/+1 counter on target creature")); + .setText("It gains flying until end of turn")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/b/BeguilerOfWills.java b/Mage.Sets/src/mage/cards/b/BeguilerOfWills.java index eb43b1be046..c8d88660c1b 100644 --- a/Mage.Sets/src/mage/cards/b/BeguilerOfWills.java +++ b/Mage.Sets/src/mage/cards/b/BeguilerOfWills.java @@ -66,12 +66,12 @@ class BeguilerOfWillsTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); int count = game.getBattlefield().countAll(this.filter, source.getControllerId(), game); if (permanent != null && permanent.getPower().getValue() <= count) { - return super.canTarget(controllerId, id, source, game); + return super.canTarget(playerId, id, source, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/b/BeholdTheSinisterSix.java b/Mage.Sets/src/mage/cards/b/BeholdTheSinisterSix.java new file mode 100644 index 00000000000..a37ca39c4bb --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BeholdTheSinisterSix.java @@ -0,0 +1,91 @@ +package mage.cards.b; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.game.Game; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class BeholdTheSinisterSix extends CardImpl { + + public BeholdTheSinisterSix(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{6}{B}"); + + // Return up to six target creature cards with different names from your graveyard to the battlefield. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); + this.getSpellAbility().addTarget(new BeholdTheSinisterSixTarget()); + } + + private BeholdTheSinisterSix(final BeholdTheSinisterSix card) { + super(card); + } + + @Override + public BeholdTheSinisterSix copy() { + return new BeholdTheSinisterSix(this); + } +} + +class BeholdTheSinisterSixTarget extends TargetCardInYourGraveyard { + + private static final FilterCard filter = new FilterPermanentCard("creature cards with different names"); + + BeholdTheSinisterSixTarget() { + super(0, 6, filter, false); + } + + private BeholdTheSinisterSixTarget(final BeholdTheSinisterSixTarget target) { + super(target); + } + + @Override + public BeholdTheSinisterSixTarget copy() { + return new BeholdTheSinisterSixTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability ability, Game game) { + if (!super.canTarget(playerId, id, ability, game)) { + return false; + } + Set names = this.getTargets() + .stream() + .map(game::getCard) + .map(MageObject::getName) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Card card = game.getCard(id); + return card != null && !names.contains(card.getName()); + } + + + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + Set names = this.getTargets() + .stream() + .map(game::getCard) + .map(MageObject::getName) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + possibleTargets.removeIf(uuid -> { + Card card = game.getCard(uuid); + return card != null && names.contains(card.getName()); + }); + return possibleTargets; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BendersWaterskin.java b/Mage.Sets/src/mage/cards/b/BendersWaterskin.java new file mode 100644 index 00000000000..9d28a9147ea --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BendersWaterskin.java @@ -0,0 +1,35 @@ +package mage.cards.b; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.UntapSourceDuringEachOtherPlayersUntapStepEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BendersWaterskin extends CardImpl { + + public BendersWaterskin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // Untap this artifact during each other player's untap step. + this.addAbility(new SimpleStaticAbility(new UntapSourceDuringEachOtherPlayersUntapStepEffect())); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + } + + private BendersWaterskin(final BendersWaterskin card) { + super(card); + } + + @Override + public BendersWaterskin copy() { + return new BendersWaterskin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BenevolentGeist.java b/Mage.Sets/src/mage/cards/b/BenevolentGeist.java index d74cbeb9eb5..69c570a5580 100644 --- a/Mage.Sets/src/mage/cards/b/BenevolentGeist.java +++ b/Mage.Sets/src/mage/cards/b/BenevolentGeist.java @@ -1,10 +1,9 @@ package mage.cards.b; import mage.MageInt; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.CantBeCounteredControlledEffect; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -39,7 +38,7 @@ public final class BenevolentGeist extends CardImpl { ))); // If Benevolent Geist would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private BenevolentGeist(final BenevolentGeist card) { diff --git a/Mage.Sets/src/mage/cards/b/BindingTheOldGods.java b/Mage.Sets/src/mage/cards/b/BindingTheOldGods.java index 966e3bdc186..3d655a721f4 100644 --- a/Mage.Sets/src/mage/cards/b/BindingTheOldGods.java +++ b/Mage.Sets/src/mage/cards/b/BindingTheOldGods.java @@ -13,7 +13,6 @@ import mage.constants.SagaChapter; import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBySubtypeCard; import mage.target.TargetPermanent; import mage.target.common.TargetCardInLibrary; @@ -24,7 +23,7 @@ import java.util.UUID; */ public final class BindingTheOldGods extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.FOREST); + private static final FilterCard filter = new FilterCard(SubType.FOREST); public BindingTheOldGods(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{G}"); @@ -33,15 +32,18 @@ public final class BindingTheOldGods extends CardImpl { // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) SagaAbility sagaAbility = new SagaAbility(this); + // I — Destroy target nonland permanent an opponent controls. sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_I, new DestroyTargetEffect(), new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND) ); + // II — Search your library for a Forest card, put it onto the battlefield tapped, then shuffle your library. sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true) ); + // III — Creatures you control gain deathtouch until end of turn. sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new GainAbilityControlledEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn, diff --git a/Mage.Sets/src/mage/cards/b/BiorganicCarapace.java b/Mage.Sets/src/mage/cards/b/BiorganicCarapace.java new file mode 100644 index 00000000000..42a052657e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BiorganicCarapace.java @@ -0,0 +1,64 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.ModifiedPredicate; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class BiorganicCarapace extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("each modified creature you control"); + + static { + filter.add(ModifiedPredicate.instance); + } + + public BiorganicCarapace(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}{U}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When this Equipment enters, attach it to target creature you control. + Ability ability = new EntersBattlefieldAbility(new AttachEffect(Outcome.BoostCreature)); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + + // Equipped creature gets +2/+2 and has "Whenever this creature deals combat damage to a player, draw a card for each modified creature you control." + Ability gainedAbility = new DealsCombatDamageTriggeredAbility(new DrawCardSourceControllerEffect(new PermanentsOnBattlefieldCount(filter)), false); + Ability equipAbility = new SimpleStaticAbility(new BoostEquippedEffect(2, 2)); + equipAbility.addEffect(new GainAbilityAttachedEffect(gainedAbility, null) + .concatBy("and")); + this.addAbility(equipAbility); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private BiorganicCarapace(final BiorganicCarapace card) { + super(card); + } + + @Override + public BiorganicCarapace copy() { + return new BiorganicCarapace(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BishopOfBinding.java b/Mage.Sets/src/mage/cards/b/BishopOfBinding.java index cdfe4492213..df6167235e0 100644 --- a/Mage.Sets/src/mage/cards/b/BishopOfBinding.java +++ b/Mage.Sets/src/mage/cards/b/BishopOfBinding.java @@ -18,13 +18,11 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; -import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.game.ExileZone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE; @@ -87,7 +85,7 @@ class BishopOfBindingExileEffect extends OneShotEffect { // the target creature won't be exiled. if (permanent != null) { new ExileTargetEffect( - CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), permanent.getIdName() + CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()), permanent.getIdName() ).apply(game, source); game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source); return true; @@ -101,7 +99,7 @@ enum BishopOfBindingValue implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, sourceAbility.getSourceId(), sourceAbility.getSourceObjectZoneChangeCounter())); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, sourceAbility.getSourceId(), sourceAbility.getStackMomentSourceZCC())); if (exileZone != null) { Card exiledCard = exileZone.getRandom(game); if (exiledCard != null) { diff --git a/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java b/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java index 067cabe2f2a..3daa5a7fd14 100644 --- a/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java +++ b/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java @@ -12,11 +12,9 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.StaticFilters; import mage.game.Game; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; import java.util.UUID; @@ -65,7 +63,7 @@ enum BiteDownOnCrimeAdjuster implements CostAdjuster { @Override public void reduceCost(Ability ability, Game game) { if (CollectedEvidenceCondition.instance.apply(game, ability) - || (game.inCheckPlayableState() && collectEvidenceCost.canPay(ability, null, ability.getControllerId(), game))) { + || (game.inCheckPlayableState() && collectEvidenceCost.canPay(ability, ability, ability.getControllerId(), game))) { CardUtil.reduceCost(ability, 2); } } diff --git a/Mage.Sets/src/mage/cards/b/BlackCatCunningThief.java b/Mage.Sets/src/mage/cards/b/BlackCatCunningThief.java new file mode 100644 index 00000000000..3560d34dc26 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BlackCatCunningThief.java @@ -0,0 +1,91 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * + * @author Jmlundeen + */ +public final class BlackCatCunningThief extends CardImpl { + + public BlackCatCunningThief(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When Black Cat enters, look at the top nine cards of target opponent's library, exile two of them face down, then put the rest on the bottom of their library in a random order. You may play the exiled cards for as long as they remain exiled. Mana of any type can be spent to cast spells this way. + Ability ability = new EntersBattlefieldTriggeredAbility(new BlackCatCunningThiefEffect()); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private BlackCatCunningThief(final BlackCatCunningThief card) { + super(card); + } + + @Override + public BlackCatCunningThief copy() { + return new BlackCatCunningThief(this); + } +} +class BlackCatCunningThiefEffect extends OneShotEffect { + + BlackCatCunningThiefEffect() { + super(Outcome.Benefit); + this.staticText = "look at the top nine cards of target opponent's library, exile two of them face down, then put the rest on the bottom of their library in a random order. You may play the exiled cards for as long as they remain exiled. Mana of any type can be spent to cast spells this way"; + } + + private BlackCatCunningThiefEffect(final BlackCatCunningThiefEffect effect) { + super(effect); + } + + @Override + public BlackCatCunningThiefEffect copy() { + return new BlackCatCunningThiefEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || opponent == null || sourceObject == null) { + return false; + } + Cards topCards = new CardsImpl(); + topCards.addAllCards(opponent.getLibrary().getTopCards(game, 9)); + TargetCard target = new TargetCard(2, 2, Zone.LIBRARY, new FilterCard("card to exile")); + controller.choose(outcome, topCards, target, source, game); + Cards exiledCards = new CardsImpl(target.getTargets().stream() + .map(game::getCard) + .collect(Collectors.toList())); + new ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect(false, CastManaAdjustment.AS_THOUGH_ANY_MANA_TYPE) + .setTargetPointer(new FixedTargets(exiledCards, game)) + .apply(game, source); + topCards.retainZone(Zone.LIBRARY, game); + // then put the rest on the bottom of that library in a random order + controller.putCardsOnBottomOfLibrary(topCards, game, source, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BlazingEffigy.java b/Mage.Sets/src/mage/cards/b/BlazingEffigy.java index c64d4e9826f..4723a25efcc 100644 --- a/Mage.Sets/src/mage/cards/b/BlazingEffigy.java +++ b/Mage.Sets/src/mage/cards/b/BlazingEffigy.java @@ -59,7 +59,7 @@ enum BlazingEffigyCount implements DynamicValue { if (watcher == null) { return 3; } - int effigyDamage = watcher.damageDoneTo(sourceAbility.getSourceId(), sourceAbility.getSourceObjectZoneChangeCounter() - 1, game); + int effigyDamage = watcher.damageDoneTo(sourceAbility.getSourceId(), sourceAbility.getStackMomentSourceZCC() - 1, game); return CardUtil.overflowInc(3, effigyDamage); } diff --git a/Mage.Sets/src/mage/cards/b/BlazingHope.java b/Mage.Sets/src/mage/cards/b/BlazingHope.java index 4fc35abe721..06f8e7cec1b 100644 --- a/Mage.Sets/src/mage/cards/b/BlazingHope.java +++ b/Mage.Sets/src/mage/cards/b/BlazingHope.java @@ -49,18 +49,18 @@ class BlazingHopeTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); if (permanent != null) { if (!isNotTarget()) { - if (!permanent.canBeTargetedBy(game.getObject(source.getId()), controllerId, source, game) - || !permanent.canBeTargetedBy(game.getObject(source), controllerId, source, game)) { + if (!permanent.canBeTargetedBy(game.getObject(source.getId()), playerId, source, game) + || !permanent.canBeTargetedBy(game.getObject(source), playerId, source, game)) { return false; } } Player controller = game.getPlayer(source.getControllerId()); if (controller != null && permanent.getPower().getValue() >= controller.getLife()) { - return filter.match(permanent, controllerId, source, game); + return filter.match(permanent, playerId, source, game); } } return false; diff --git a/Mage.Sets/src/mage/cards/b/Blink.java b/Mage.Sets/src/mage/cards/b/Blink.java index 3189879ab0a..98940ff92fc 100644 --- a/Mage.Sets/src/mage/cards/b/Blink.java +++ b/Mage.Sets/src/mage/cards/b/Blink.java @@ -51,7 +51,7 @@ public final class Blink extends CardImpl { this, SagaChapter.CHAPTER_IV, new CreateTokenEffect(new AlienAngelToken()) ); - this.addAbility(sagaAbility); + this.addAbility(sagaAbility); //TODO: These should be a single AddChapterEffect, but currently XMage does not support noncontiguous Saga chapters } private Blink(final Blink card) { diff --git a/Mage.Sets/src/mage/cards/b/BloodsoakedReveler.java b/Mage.Sets/src/mage/cards/b/BloodsoakedReveler.java index bd266074285..1c5d37419f5 100644 --- a/Mage.Sets/src/mage/cards/b/BloodsoakedReveler.java +++ b/Mage.Sets/src/mage/cards/b/BloodsoakedReveler.java @@ -2,7 +2,6 @@ package mage.cards.b; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.common.YouGainedLifeCondition; @@ -12,10 +11,14 @@ import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; import mage.abilities.hint.ConditionHint; import mage.abilities.hint.Hint; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; import mage.game.permanent.token.BloodToken; +import mage.watchers.common.PlayerGainedLifeWatcher; import java.util.UUID; @@ -40,7 +43,7 @@ public final class BloodsoakedReveler extends CardImpl { this.addAbility(new BeginningOfEndStepTriggeredAbility( TargetController.YOU, new CreateTokenEffect(new BloodToken()), false, condition - ).addHint(hint)); + ).addHint(hint), new PlayerGainedLifeWatcher()); // {4}{B}: Each opponent loses 2 life and you gain 2 life. Ability ability = new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/b/BloomvineRegent.java b/Mage.Sets/src/mage/cards/b/BloomvineRegent.java index 88f7e8cb72d..1b1e0b1ed7c 100644 --- a/Mage.Sets/src/mage/cards/b/BloomvineRegent.java +++ b/Mage.Sets/src/mage/cards/b/BloomvineRegent.java @@ -1,40 +1,31 @@ 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.cards.OmenCard; import mage.constants.CardType; -import mage.constants.SuperType; +import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterBasicCard; import mage.filter.common.FilterCreaturePermanent; -import mage.target.Target; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @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()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST, "basic Forest cards"); 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); diff --git a/Mage.Sets/src/mage/cards/b/BogardanPhoenix.java b/Mage.Sets/src/mage/cards/b/BogardanPhoenix.java index 4e7eb4308e6..7bd42f29c23 100644 --- a/Mage.Sets/src/mage/cards/b/BogardanPhoenix.java +++ b/Mage.Sets/src/mage/cards/b/BogardanPhoenix.java @@ -74,11 +74,11 @@ class BogardanPhoenixEffect extends OneShotEffect { if (permanent == null || controller == null || permanent.getZoneChangeCounter(game) + 1 - != source.getSourceObjectZoneChangeCounter()) { + != source.getStackMomentSourceZCC()) { return false; } Card card = game.getCard(permanent.getId()); - if (card == null || card.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter()) { + if (card == null || card.getZoneChangeCounter(game) != source.getStackMomentSourceZCC()) { return false; } if (permanent.getCounters(game).containsKey(CounterType.DEATH)) { diff --git a/Mage.Sets/src/mage/cards/b/BohnBeguilingBalladeer.java b/Mage.Sets/src/mage/cards/b/BohnBeguilingBalladeer.java index 2bae253674d..825caef29d8 100644 --- a/Mage.Sets/src/mage/cards/b/BohnBeguilingBalladeer.java +++ b/Mage.Sets/src/mage/cards/b/BohnBeguilingBalladeer.java @@ -4,18 +4,14 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.CastSecondSpellTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.combat.GoadTargetEffect; import mage.abilities.keyword.ForetellAbility; -import mage.cards.*; -import mage.constants.*; -import mage.filter.common.FilterNonlandCard; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.game.Game; -import mage.players.Player; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.target.common.TargetOpponentsCreaturePermanent; -import mage.util.CardUtil; import java.util.UUID; @@ -34,7 +30,7 @@ public final class BohnBeguilingBalladeer extends CardImpl { this.toughness = new MageInt(3); // Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by {2}. - this.addAbility(new SimpleStaticAbility(new EdginLarcenousLutenistEffect())); + this.addAbility(new SimpleStaticAbility(ForetellAbility.makeAddForetellEffect())); // Whenever you cast your second spell each turn, goad target creature an opponent controls. Ability ability = new CastSecondSpellTriggeredAbility(new GoadTargetEffect()); @@ -51,69 +47,3 @@ public final class BohnBeguilingBalladeer extends CardImpl { return new BohnBeguilingBalladeer(this); } } - -class EdginLarcenousLutenistEffect extends ContinuousEffectImpl { - - private static final FilterNonlandCard filter = new FilterNonlandCard(); - - static { - filter.add(Predicates.not(new AbilityPredicate(ForetellAbility.class))); - } - - EdginLarcenousLutenistEffect() { - super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.staticText = "Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by {2}"; - } - - private EdginLarcenousLutenistEffect(final EdginLarcenousLutenistEffect effect) { - super(effect); - } - - @Override - public EdginLarcenousLutenistEffect copy() { - return new EdginLarcenousLutenistEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - for (Card card : controller.getHand().getCards(filter, game)) { - ForetellAbility foretellAbility = null; - if (card instanceof SplitCard) { - String leftHalfCost = CardUtil.reduceCost(((SplitCard) card).getLeftHalfCard().getManaCost(), 2).getText(); - String rightHalfCost = CardUtil.reduceCost(((SplitCard) card).getRightHalfCard().getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); - } else if (card instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - // If front side of MDFC is land, do nothing as Dream Devourer does not apply to lands - // MDFC cards in hand are considered lands if front side is land - if (!leftHalfCard.isLand(game)) { - String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), 2).getText(); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); - if (rightHalfCard.isLand(game)) { - foretellAbility = new ForetellAbility(card, leftHalfCost); - } else { - String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); - } - } - } else if (card instanceof CardWithSpellOption) { - String creatureCost = CardUtil.reduceCost(card.getMainCard().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(); - foretellAbility = new ForetellAbility(card, costText); - } - if (foretellAbility != null) { - foretellAbility.setSourceId(card.getId()); - foretellAbility.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card, foretellAbility); - } - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BoseijuReachesSkyward.java b/Mage.Sets/src/mage/cards/b/BoseijuReachesSkyward.java index a7d623109af..120cf07a3ab 100644 --- a/Mage.Sets/src/mage/cards/b/BoseijuReachesSkyward.java +++ b/Mage.Sets/src/mage/cards/b/BoseijuReachesSkyward.java @@ -10,9 +10,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SagaChapter; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; -import mage.filter.common.FilterLandCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInYourGraveyard; @@ -23,13 +23,7 @@ import java.util.UUID; */ public final class BoseijuReachesSkyward extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest cards"); - private static final FilterCard filter2 = new FilterLandCard("land card from your graveyard"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST, "basic Forest cards"); public BoseijuReachesSkyward(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); @@ -52,7 +46,7 @@ public final class BoseijuReachesSkyward extends CardImpl { sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_II, new PutOnLibraryTargetEffect(true), - new TargetCardInYourGraveyard(0, 1, filter2) + new TargetCardInYourGraveyard(0, 1, StaticFilters.FILTER_CARD_LAND_FROM_YOUR_GRAVEYARD) ); // III — Exile this Saga, then return it to the battlefield transformed under your control. diff --git a/Mage.Sets/src/mage/cards/b/BottleCapBlast.java b/Mage.Sets/src/mage/cards/b/BottleCapBlast.java index 6d9d536f495..1b19927a649 100644 --- a/Mage.Sets/src/mage/cards/b/BottleCapBlast.java +++ b/Mage.Sets/src/mage/cards/b/BottleCapBlast.java @@ -62,8 +62,8 @@ class BottleCapBlastEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - UUID target = getTargetPointer().getFirst(game, source); - Player player = game.getPlayer(target); + UUID targetId = getTargetPointer().getFirst(game, source); + Player player = game.getPlayer(targetId); if (player != null) { player.damage(5, source, game); return true; @@ -72,11 +72,10 @@ class BottleCapBlastEffect extends OneShotEffect { if (permanent == null) { return false; } - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), 5); - permanent.damage(5, source.getSourceId(), source, game); - if (lethal < 5) { + int excess = permanent.damageWithExcess(5, source, game); + if (excess > 0) { new TreasureToken().putOntoBattlefield( - 5 - lethal, game, source, source.getControllerId(), true, false + excess, game, source, source.getControllerId(), true, false ); } return true; diff --git a/Mage.Sets/src/mage/cards/b/BountifulLandscape.java b/Mage.Sets/src/mage/cards/b/BountifulLandscape.java index fd792e43046..b456c364b9e 100644 --- a/Mage.Sets/src/mage/cards/b/BountifulLandscape.java +++ b/Mage.Sets/src/mage/cards/b/BountifulLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class BountifulLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Forest, Island, or Mountain card"); + private static final FilterCard filter = new FilterBasicCard("a basic Forest, Island, or Mountain card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/b/BreakingOfTheFellowship.java b/Mage.Sets/src/mage/cards/b/BreakingOfTheFellowship.java index 6b1449b7d2e..6f0d8f01653 100644 --- a/Mage.Sets/src/mage/cards/b/BreakingOfTheFellowship.java +++ b/Mage.Sets/src/mage/cards/b/BreakingOfTheFellowship.java @@ -1,6 +1,5 @@ package mage.cards.b; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.keyword.TheRingTemptsYouEffect; @@ -8,20 +7,20 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; +import java.util.Set; import java.util.UUID; /** + * TODO: combine with Mutiny + * * @author Susucr */ public final class BreakingOfTheFellowship extends CardImpl { @@ -31,8 +30,8 @@ public final class BreakingOfTheFellowship extends CardImpl { // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. this.getSpellAbility().addEffect(new BreakingOfTheFellowshipEffect()); - this.getSpellAbility().addTarget(new BreakingOfTheFellowshipFirstTarget()); - this.getSpellAbility().addTarget(new TargetPermanent(new FilterCreaturePermanent("another target creature that player controls"))); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.getSpellAbility().addTarget(new BreakingOfTheFellowshipSecondTarget()); // The Ring tempts you. this.getSpellAbility().addEffect(new TheRingTemptsYouEffect()); @@ -76,74 +75,45 @@ class BreakingOfTheFellowshipEffect extends OneShotEffect { } return true; } - } -class BreakingOfTheFellowshipFirstTarget extends TargetPermanent { +class BreakingOfTheFellowshipSecondTarget extends TargetPermanent { - public BreakingOfTheFellowshipFirstTarget() { - super(1, 1, StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, false); + public BreakingOfTheFellowshipSecondTarget() { + super(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE); } - private BreakingOfTheFellowshipFirstTarget(final BreakingOfTheFellowshipFirstTarget target) { + private BreakingOfTheFellowshipSecondTarget(final BreakingOfTheFellowshipSecondTarget target) { super(target); } @Override - public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) { - super.addTarget(id, source, game, skipEvent); - // Update the second target - UUID firstController = game.getControllerId(id); - if (firstController != null && source.getTargets().size() > 1) { - Player controllingPlayer = game.getPlayer(firstController); - TargetCreaturePermanent targetCreaturePermanent = (TargetCreaturePermanent) source.getTargets().get(1); - // Set a new filter to the second target with the needed restrictions - FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature that player " + controllingPlayer.getName() + " controls"); - filter.add(new ControllerIdPredicate(firstController)); - filter.add(Predicates.not(new PermanentIdPredicate(id))); - targetCreaturePermanent.replaceFilter(filter); - } - } + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { - // can only target, if the controller has at least two targetable creatures - UUID controllingPlayerId = game.getControllerId(id); - int possibleTargets = 0; - MageObject sourceObject = game.getObject(source.getId()); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllingPlayerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, controllerId, source, game)) { - possibleTargets++; - } + Permanent firstTarget = game.getPermanent(source.getFirstTarget()); + if (firstTarget == null) { + // playable or first target not yet selected + // use all + if (possibleTargets.size() == 1) { + // workaround to make 1 target invalid + possibleTargets.clear(); } - return possibleTargets > 1; + } else { + // real + // filter by same player + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null || !permanent.isControlledBy(firstTarget.getControllerId()); + }); } - return false; + possibleTargets.removeIf(id -> firstTarget != null && firstTarget.getId().equals(id)); + + return possibleTargets; } @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (super.canChoose(sourceControllerId, source, game)) { - UUID controllingPlayerId = game.getControllerId(source.getSourceId()); - for (UUID playerId : game.getOpponents(controllingPlayerId)) { - int possibleTargets = 0; - MageObject sourceObject = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, playerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, controllingPlayerId, source, game)) { - possibleTargets++; - } - } - if (possibleTargets > 1) { - return true; - } - } - } - return false; - } - - @Override - public BreakingOfTheFellowshipFirstTarget copy() { - return new BreakingOfTheFellowshipFirstTarget(this); + public BreakingOfTheFellowshipSecondTarget copy() { + return new BreakingOfTheFellowshipSecondTarget(this); } } diff --git a/Mage.Sets/src/mage/cards/b/BrineboundGift.java b/Mage.Sets/src/mage/cards/b/BrineboundGift.java index 03d465a3ff0..254ed7f3601 100644 --- a/Mage.Sets/src/mage/cards/b/BrineboundGift.java +++ b/Mage.Sets/src/mage/cards/b/BrineboundGift.java @@ -1,12 +1,10 @@ package mage.cards.b; -import mage.abilities.Ability; import mage.abilities.common.BecomesTargetAttachedTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.CreateTokenEffect; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; import mage.abilities.meta.OrTriggeredAbility; import mage.cards.CardImpl; @@ -43,8 +41,7 @@ public final class BrineboundGift extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Whenever Brinebound Gift enters the battlefield or enchanted creature becomes the target of an Aura spell, create a 1/1 white Spirit creature token with flying. this.addAbility(new OrTriggeredAbility(Zone.ALL, new CreateTokenEffect(new SpiritWhiteToken()), false, @@ -53,7 +50,7 @@ public final class BrineboundGift extends CardImpl { new BecomesTargetAttachedTriggeredAbility(null, filter, SetTargetPointer.NONE, false))); // If Brinebound Gift would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private BrineboundGift(final BrineboundGift card) { diff --git a/Mage.Sets/src/mage/cards/b/BumiEclecticEarthbender.java b/Mage.Sets/src/mage/cards/b/BumiEclecticEarthbender.java new file mode 100644 index 00000000000..6fc1617a324 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BumiEclecticEarthbender.java @@ -0,0 +1,59 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BumiEclecticEarthbender extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("land creature you control"); + + static { + filter.add(CardType.LAND.getPredicate()); + } + + public BumiEclecticEarthbender(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Bumi enters, earthbend 1. + Ability ability = new EntersBattlefieldTriggeredAbility(new EarthbendTargetEffect(1)); + ability.addTarget(new TargetControlledLandPermanent()); + this.addAbility(ability); + + // Whenever Bumi attacks, put two +1/+1 counters on each land creature you control. + this.addAbility(new AttacksTriggeredAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(2), filter))); + } + + private BumiEclecticEarthbender(final BumiEclecticEarthbender card) { + super(card); + } + + @Override + public BumiEclecticEarthbender copy() { + return new BumiEclecticEarthbender(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BuzzardWaspColony.java b/Mage.Sets/src/mage/cards/b/BuzzardWaspColony.java new file mode 100644 index 00000000000..25eef058a85 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BuzzardWaspColony.java @@ -0,0 +1,99 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.Counter; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.CounterAnyPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BuzzardWaspColony extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another creature you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(CounterAnyPredicate.instance); + } + + public BuzzardWaspColony(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.INSECT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, you may sacrifice an artifact or creature. If you do, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), + new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE) + ))); + + // Whenever another creature you control dies, if it had counters on it, put its counters on this creature. + this.addAbility(new DiesCreatureTriggeredAbility(new BuzzardWaspColonyEffect(), false, filter)); + } + + private BuzzardWaspColony(final BuzzardWaspColony card) { + super(card); + } + + @Override + public BuzzardWaspColony copy() { + return new BuzzardWaspColony(this); + } +} + +class BuzzardWaspColonyEffect extends OneShotEffect { + + BuzzardWaspColonyEffect() { + super(Outcome.Benefit); + staticText = "if it had counters on it, put its counters on {this}"; + } + + private BuzzardWaspColonyEffect(final BuzzardWaspColonyEffect effect) { + super(effect); + } + + @Override + public BuzzardWaspColonyEffect copy() { + return new BuzzardWaspColonyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + Permanent creature = (Permanent) getValue("creatureDied"); + if (permanent == null || creature == null) { + return false; + } + for (Counter counter : creature.getCounters(game).values()) { + permanent.addCounters(counter.copy(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CabalStronghold.java b/Mage.Sets/src/mage/cards/c/CabalStronghold.java index 2f2d4ccfc52..75c31ac39b4 100644 --- a/Mage.Sets/src/mage/cards/c/CabalStronghold.java +++ b/Mage.Sets/src/mage/cards/c/CabalStronghold.java @@ -1,13 +1,12 @@ - package mage.cards.c; -import java.util.UUID; - import mage.Mana; import mage.abilities.Ability; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.mana.ColorlessManaAbility; import mage.abilities.mana.DynamicManaAbility; @@ -16,21 +15,26 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.filter.common.FilterControlledLandPermanent; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; + +import java.util.UUID; /** * @author JRHerlehy - * Created on 4/7/18. + * Created on 4/7/18. */ public final class CabalStronghold extends CardImpl { - private static final FilterControlledLandPermanent filter = new FilterControlledLandPermanent("basic Swamp you control"); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SWAMP, "basic Swamp you control"); static { filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.SWAMP.getPredicate()); } + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private static final Hint hint = new ValueHint("Basic Swamps you control", xValue); + public CabalStronghold(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); @@ -38,9 +42,9 @@ public final class CabalStronghold extends CardImpl { this.addAbility(new ColorlessManaAbility()); // {3}, {T}: Add {B} for each basic Swamp you control. - Ability ability = new DynamicManaAbility(Mana.BlackMana(1), new PermanentsOnBattlefieldCount(filter), new GenericManaCost(3)); + Ability ability = new DynamicManaAbility(Mana.BlackMana(1), xValue, new GenericManaCost(3)); ability.addCost(new TapSourceCost()); - this.addAbility(ability.addHint(new ValueHint("Basic Swamps you control", new PermanentsOnBattlefieldCount(filter)))); + this.addAbility(ability.addHint(hint)); } private CabalStronghold(final CabalStronghold card) { diff --git a/Mage.Sets/src/mage/cards/c/CallOfTheDeathDweller.java b/Mage.Sets/src/mage/cards/c/CallOfTheDeathDweller.java index ed40fb64915..e244de72599 100644 --- a/Mage.Sets/src/mage/cards/c/CallOfTheDeathDweller.java +++ b/Mage.Sets/src/mage/cards/c/CallOfTheDeathDweller.java @@ -141,8 +141,8 @@ class CallOfTheDeathDwellerTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 3, game); } diff --git a/Mage.Sets/src/mage/cards/c/CanoptekWraith.java b/Mage.Sets/src/mage/cards/c/CanoptekWraith.java index 16e238996e4..b0579a1a5e1 100644 --- a/Mage.Sets/src/mage/cards/c/CanoptekWraith.java +++ b/Mage.Sets/src/mage/cards/c/CanoptekWraith.java @@ -12,13 +12,10 @@ import mage.abilities.keyword.CantBeBlockedSourceAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterLandCard; import mage.filter.predicate.Predicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -101,7 +98,8 @@ class CanoptekWraithEffect extends OneShotEffect { if (permanent == null) { return false; } - FilterCard filter = new FilterBasicLandCard("basic land cards with the same name as the chosen land"); + FilterCard filter = new FilterLandCard("basic land cards with the same name as the chosen land"); + filter.add(SuperType.BASIC.getPredicate()); filter.add(new CanoptekWraithPredicate(permanent)); TargetCardInLibrary targetCard = new TargetCardInLibrary(0, 2, filter); player.searchLibrary(targetCard, source, game); @@ -129,4 +127,4 @@ class CanoptekWraithPredicate implements Predicate { public boolean apply(Card input, Game game) { return CardUtil.haveSameNames(permanent, input); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/c/CapitalGuard.java b/Mage.Sets/src/mage/cards/c/CapitalGuard.java new file mode 100644 index 00000000000..3614b01388c --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CapitalGuard.java @@ -0,0 +1,33 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CapitalGuard extends CardImpl { + + public CapitalGuard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + } + + private CapitalGuard(final CapitalGuard card) { + super(card); + } + + @Override + public CapitalGuard copy() { + return new CapitalGuard(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CarnageCrimsonChaos.java b/Mage.Sets/src/mage/cards/c/CarnageCrimsonChaos.java new file mode 100644 index 00000000000..12860badb5b --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CarnageCrimsonChaos.java @@ -0,0 +1,116 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksEachCombatStaticAbility; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.MayhemAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class CarnageCrimsonChaos extends CardImpl { + + private static final FilterCard filter = new FilterCard("creature card with mana value 3 or less"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 3)); + } + + public CarnageCrimsonChaos(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SYMBIOTE); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Carnage enters, return target creature card with mana value 3 or less from your graveyard to the battlefield. It gains "This creature attacks each combat if able" and "When this creature deals combat damage to a player, sacrifice it." + Ability ability = new EntersBattlefieldTriggeredAbility(new CarnageCrimsonChaosReturnEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + + // Mayhem {B}{R} + this.addAbility(new MayhemAbility(this, "{B}{R}")); + + } + + private CarnageCrimsonChaos(final CarnageCrimsonChaos card) { + super(card); + } + + @Override + public CarnageCrimsonChaos copy() { + return new CarnageCrimsonChaos(this); + } +} + +class CarnageCrimsonChaosReturnEffect extends OneShotEffect { + + CarnageCrimsonChaosReturnEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "return target creature card with mana value 3 or less from your graveyard to the battlefield. It gains \"This creature attacks each combat if able\" and \"When this creature deals combat damage to a player, sacrifice it.\""; + } + + protected CarnageCrimsonChaosReturnEffect(final CarnageCrimsonChaosReturnEffect effect) { + super(effect); + } + + @Override + public CarnageCrimsonChaosReturnEffect copy() { + return new CarnageCrimsonChaosReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + Card card = game.getCard(source.getFirstTarget()); + if (card == null) { + return false; + } + controller.moveCards(card, Zone.BATTLEFIELD, source, game); + Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game); + if (permanent == null) { + return false; + } + Ability attacksEachTurnAbility = new AttacksEachCombatStaticAbility(); + Ability damageTriggerAbility = new DealsCombatDamageToAPlayerTriggeredAbility(new SacrificeSourceEffect()); + ContinuousEffect effectOne = new GainAbilityTargetEffect(attacksEachTurnAbility, Duration.WhileOnBattlefield); + effectOne.setTargetPointer(new FixedTarget(permanent, game)); + ContinuousEffect effectTwo = new GainAbilityTargetEffect(damageTriggerAbility, Duration.WhileOnBattlefield); + effectTwo.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effectOne, source); + game.addEffect(effectTwo, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CatGator.java b/Mage.Sets/src/mage/cards/c/CatGator.java new file mode 100644 index 00000000000..e53c110a7d5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CatGator.java @@ -0,0 +1,57 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CatGator extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount( + new FilterControlledPermanent(SubType.SWAMP, "Swamps you control") + ); + private static final Hint hint = new ValueHint("Swamps you control", xValue); + + public CatGator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{B}"); + + this.subtype.add(SubType.FISH); + this.subtype.add(SubType.CROCODILE); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When this creature enters, it deals damage equal to the number of Swamps you control to any target. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(xValue) + .setText("it deals damage equal to the number of Swamps you control to any target")); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability.addHint(hint)); + } + + private CatGator(final CatGator card) { + super(card); + } + + @Override + public CatGator copy() { + return new CatGator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CatOwl.java b/Mage.Sets/src/mage/cards/c/CatOwl.java new file mode 100644 index 00000000000..74b806921ab --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CatOwl.java @@ -0,0 +1,47 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CatOwl extends CardImpl { + + public CatOwl(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W/U}"); + + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.BIRD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever this creature attacks, untap target artifact or creature. + Ability ability = new AttacksTriggeredAbility(new UntapTargetEffect()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); + this.addAbility(ability); + } + + private CatOwl(final CatOwl card) { + super(card); + } + + @Override + public CatOwl copy() { + return new CatOwl(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CatlikeCuriosity.java b/Mage.Sets/src/mage/cards/c/CatlikeCuriosity.java index 932763c5175..c741013939f 100644 --- a/Mage.Sets/src/mage/cards/c/CatlikeCuriosity.java +++ b/Mage.Sets/src/mage/cards/c/CatlikeCuriosity.java @@ -1,13 +1,11 @@ package mage.cards.c; -import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -36,8 +34,7 @@ public final class CatlikeCuriosity extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature has "Whenever this creature deals combat damage to a player, draw a card." this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect( @@ -47,7 +44,7 @@ public final class CatlikeCuriosity extends CardImpl { ))); // If Catlike Curiosity would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private CatlikeCuriosity(final CatlikeCuriosity card) { diff --git a/Mage.Sets/src/mage/cards/c/CelestialDawn.java b/Mage.Sets/src/mage/cards/c/CelestialDawn.java index 397cf682d29..123db6fa2e1 100644 --- a/Mage.Sets/src/mage/cards/c/CelestialDawn.java +++ b/Mage.Sets/src/mage/cards/c/CelestialDawn.java @@ -63,6 +63,8 @@ class CelestialDawnToPlainsEffect extends ContinuousEffectImpl { CelestialDawnToPlainsEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); this.staticText = "Lands you control are Plains"; + this.dependendToTypes.add(DependencyType.BecomeNonbasicLand); + this.dependencyTypes.add(DependencyType.BecomePlains); } private CelestialDawnToPlainsEffect(final CelestialDawnToPlainsEffect effect) { @@ -126,7 +128,7 @@ class CelestialDawnToWhiteEffect extends ContinuousEffectImpl { } } // Exile - for (Card card : game.getExile().getAllCards(game)) { + for (Card card : game.getExile().getAllCardsByRange(game, controller.getId())) { if (card.isOwnedBy(controller.getId())) { setColor(card.getColor(game), game); } diff --git a/Mage.Sets/src/mage/cards/c/CemeteryGatekeeper.java b/Mage.Sets/src/mage/cards/c/CemeteryGatekeeper.java index cfc09b66f3c..9ec04a37b8b 100644 --- a/Mage.Sets/src/mage/cards/c/CemeteryGatekeeper.java +++ b/Mage.Sets/src/mage/cards/c/CemeteryGatekeeper.java @@ -86,7 +86,7 @@ class CemeteryGatekeeperEffect extends OneShotEffect { controller.choose(outcome, target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); return controller.moveCardsToExile(card, source, game, true, exileId, exileName); diff --git a/Mage.Sets/src/mage/cards/c/CemeteryIlluminator.java b/Mage.Sets/src/mage/cards/c/CemeteryIlluminator.java index 31ffac3f823..550529caeb5 100644 --- a/Mage.Sets/src/mage/cards/c/CemeteryIlluminator.java +++ b/Mage.Sets/src/mage/cards/c/CemeteryIlluminator.java @@ -92,7 +92,7 @@ class CemeteryIlluminatorExileEffect extends OneShotEffect { controller.choose(outcome, target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); return controller.moveCardsToExile(card, source, game, true, exileId, exileName); diff --git a/Mage.Sets/src/mage/cards/c/CemeteryProtector.java b/Mage.Sets/src/mage/cards/c/CemeteryProtector.java index 199dd0cf567..8c7cb4a13d1 100644 --- a/Mage.Sets/src/mage/cards/c/CemeteryProtector.java +++ b/Mage.Sets/src/mage/cards/c/CemeteryProtector.java @@ -87,7 +87,7 @@ class CemeteryProtectorEffect extends OneShotEffect { controller.choose(outcome, target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); return controller.moveCardsToExile(card, source, game, true, exileId, exileName); diff --git a/Mage.Sets/src/mage/cards/c/CemeteryProwler.java b/Mage.Sets/src/mage/cards/c/CemeteryProwler.java index 835e90b1c21..5df6f0900e6 100644 --- a/Mage.Sets/src/mage/cards/c/CemeteryProwler.java +++ b/Mage.Sets/src/mage/cards/c/CemeteryProwler.java @@ -79,7 +79,7 @@ class CemeteryProwlerExileEffect extends OneShotEffect { controller.choose(outcome, target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); return controller.moveCardsToExile(card, source, game, true, exileId, exileName); diff --git a/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java b/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java index 95111180226..9a31aa06474 100644 --- a/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java +++ b/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java @@ -141,14 +141,14 @@ class ChainerNightmareAdeptWatcher extends Watcher { return false; } MageObjectReference mor = new MageObjectReference( - source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game + source.getSourceId(), source.getStackMomentSourceZCC(), game ); return morMap.computeIfAbsent(mor, m -> new HashMap<>()).getOrDefault(playerId, 0) > 0; } void addPlayable(Ability source, Game game) { MageObjectReference mor = new MageObjectReference( - source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game + source.getSourceId(), source.getStackMomentSourceZCC(), game ); morMap.computeIfAbsent(mor, m -> new HashMap<>()) .compute(source.getControllerId(), CardUtil::setOrIncrementValue); diff --git a/Mage.Sets/src/mage/cards/c/ChameleonMasterOfDisguise.java b/Mage.Sets/src/mage/cards/c/ChameleonMasterOfDisguise.java new file mode 100644 index 00000000000..e4aa44f5842 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChameleonMasterOfDisguise.java @@ -0,0 +1,65 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.common.CopyPermanentEffect; +import mage.abilities.keyword.MayhemAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.util.functions.CopyApplier; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChameleonMasterOfDisguise extends CardImpl { + + private static final CopyApplier applier = new CopyApplier() { + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID copyToObjectId) { + blueprint.setName("Chameleon, Master of Disguise"); + return true; + } + + @Override + public String getText() { + return ", except his name is Chameleon, Master of Disguise"; + } + }; + + public ChameleonMasterOfDisguise(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SHAPESHIFTER); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // You may have Chameleon enter as a copy of a creature you control, except his name is Chameleon, Master of Disguise. + this.addAbility(new EntersBattlefieldAbility(new CopyPermanentEffect( + StaticFilters.FILTER_CONTROLLED_CREATURE, applier + ), true)); + + // Mayhem {2}{U} + this.addAbility(new MayhemAbility(this, "{2}{U}")); + } + + private ChameleonMasterOfDisguise(final ChameleonMasterOfDisguise card) { + super(card); + } + + @Override + public ChameleonMasterOfDisguise copy() { + return new ChameleonMasterOfDisguise(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChandraDressedToKill.java b/Mage.Sets/src/mage/cards/c/ChandraDressedToKill.java index ed2fd8e518e..a80ea00f467 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraDressedToKill.java +++ b/Mage.Sets/src/mage/cards/c/ChandraDressedToKill.java @@ -97,7 +97,7 @@ class ChandraDressedToKillExile1Effect extends OneShotEffect { if (card == null) { return false; } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); controller.moveCardsToExile(card, source, game, true, exileId, exileName); @@ -135,7 +135,7 @@ class ChandraDressedToKillExile5Effect extends OneShotEffect { if (cards.isEmpty()) { return false; } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); controller.moveCardsToExile(cards, source, game, true, exileId, exileName); diff --git a/Mage.Sets/src/mage/cards/c/ChandraFireOfKaladesh.java b/Mage.Sets/src/mage/cards/c/ChandraFireOfKaladesh.java index 10885efa17e..cbe8871a1aa 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraFireOfKaladesh.java +++ b/Mage.Sets/src/mage/cards/c/ChandraFireOfKaladesh.java @@ -1,13 +1,12 @@ package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.ObjectColor; import mage.abilities.Ability; -import mage.constants.Pronoun; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.Condition; import mage.abilities.condition.common.SourceDealtDamageCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalOneShotEffect; @@ -23,8 +22,9 @@ import mage.filter.predicate.mageobject.ColorPredicate; import mage.target.common.TargetPlayerOrPlaneswalker; import mage.watchers.common.DamageDoneWatcher; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ChandraFireOfKaladesh extends CardImpl { @@ -35,6 +35,8 @@ public final class ChandraFireOfKaladesh extends CardImpl { filter.add(new ColorPredicate(ObjectColor.RED)); } + private static final Condition condition = new SourceDealtDamageCondition(3); + public ChandraFireOfKaladesh(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{R}"); this.supertype.add(SuperType.LEGENDARY); @@ -51,10 +53,11 @@ public final class ChandraFireOfKaladesh extends CardImpl { // {T}: Chandra, Fire of Kaladesh deals 1 damage to target player. If Chandra has dealt 3 or more damage this turn, exile her, then return her to the battlefield transformed under her owner's control. this.addAbility(new TransformAbility()); Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(1), new TapSourceCost()); - ability.addEffect(new ConditionalOneShotEffect(new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED,Pronoun.SHE), new SourceDealtDamageCondition(3))); + ability.addEffect(new ConditionalOneShotEffect( + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED, Pronoun.SHE), condition + )); ability.addTarget(new TargetPlayerOrPlaneswalker()); this.addAbility(ability, new DamageDoneWatcher()); - } private ChandraFireOfKaladesh(final ChandraFireOfKaladesh card) { diff --git a/Mage.Sets/src/mage/cards/c/ChandraPyromaster.java b/Mage.Sets/src/mage/cards/c/ChandraPyromaster.java index d219c7239ff..035f31d6a6d 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraPyromaster.java +++ b/Mage.Sets/src/mage/cards/c/ChandraPyromaster.java @@ -1,8 +1,5 @@ package mage.cards.c; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.ApprovingObject; import mage.MageObject; import mage.abilities.Ability; @@ -17,7 +14,6 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterInstantOrSorceryCard; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.target.TargetCard; @@ -25,6 +21,9 @@ import mage.target.TargetPermanent; import mage.target.common.TargetPlayerOrPlaneswalker; import mage.target.targetpointer.FixedTarget; +import java.util.Set; +import java.util.UUID; + /** * @author jeffwadsworth */ @@ -111,47 +110,23 @@ class ChandraPyromasterTarget extends TargetPermanent { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Player player = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); - if (player == null) { - return false; - } - UUID firstTarget = player.getId(); - Permanent permanent = game.getPermanent(id); - if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) { - return super.canTarget(id, source, game); - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (StackObject item : game.getState().getStack()) { - if (item.getId().equals(source.getSourceId())) { - object = item; - } - if (item.getSourceId().equals(source.getSourceId())) { - object = item; - } + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - Player player = game.getPlayerOrPlaneswalkerController(playerId); - if (player != null) { - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.isControlledBy(player.getId())) { - possibleTargets.add(targetId); - } - } - } - } return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/c/ChandraRoaringFlame.java b/Mage.Sets/src/mage/cards/c/ChandraRoaringFlame.java index f1cec24e163..9759367aa57 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraRoaringFlame.java +++ b/Mage.Sets/src/mage/cards/c/ChandraRoaringFlame.java @@ -47,7 +47,6 @@ public final class ChandraRoaringFlame extends CardImpl { //-7: Chandra, Roaring Flame deals 6 damage to each opponent. Each player dealt damage this way gets an emblem with "At the beginning of your upkeep, this emblem deals 3 damage to you." this.addAbility(new LoyaltyAbility(new ChandraRoaringFlameEmblemEffect(), -7)); - } private ChandraRoaringFlame(final ChandraRoaringFlame card) { @@ -79,20 +78,19 @@ class ChandraRoaringFlameEmblemEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - List opponentsEmblem = new ArrayList<>(); - for (UUID playerId : game.getOpponents(controller.getId())) { - Player opponent = game.getPlayer(playerId); - if (opponent != null) { - if (opponent.damage(6, source.getSourceId(), source, game) > 0) { - opponentsEmblem.add(opponent); - } - } - } - for (Player opponent : opponentsEmblem) { - game.addEmblem(new ChandraRoaringFlameEmblem(), source.getSourceObject(game), opponent.getId()); + if (controller == null) { + return false; + } + List opponentsEmblem = new ArrayList<>(); + for (UUID playerId : game.getOpponents(controller.getId())) { + Player opponent = game.getPlayer(playerId); + if (opponent != null && opponent.damage(6, source, game) > 0) { + opponentsEmblem.add(opponent); } } + for (Player opponent : opponentsEmblem) { + game.addEmblem(new ChandraRoaringFlameEmblem(), source.getSourceObject(game), opponent.getId()); + } return false; } } diff --git a/Mage.Sets/src/mage/cards/c/ChangeOfPlans.java b/Mage.Sets/src/mage/cards/c/ChangeOfPlans.java index 0d0b27b6f5b..e4b236b1c97 100644 --- a/Mage.Sets/src/mage/cards/c/ChangeOfPlans.java +++ b/Mage.Sets/src/mage/cards/c/ChangeOfPlans.java @@ -2,7 +2,7 @@ package mage.cards.c; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.keyword.ConniveSourceEffect; +import mage.abilities.effects.keyword.ConniveTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -30,6 +30,7 @@ public final class ChangeOfPlans extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{1}{U}"); // Each of X target creatures you control connive. You may have any number of them phase out. + this.getSpellAbility().addEffect(new ConniveTargetEffect().setText("each of X target creatures you control connive")); this.getSpellAbility().addEffect(new ChangeOfPlansEffect()); this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); this.getSpellAbility().setTargetAdjuster(new XTargetsCountAdjuster()); @@ -49,7 +50,7 @@ class ChangeOfPlansEffect extends OneShotEffect { ChangeOfPlansEffect() { super(Outcome.Benefit); - staticText = "each of X target creatures you control connive. You may have any number of them phase out"; + staticText = "You may have any number of them phase out"; } private ChangeOfPlansEffect(final ChangeOfPlansEffect effect) { @@ -73,9 +74,6 @@ class ChangeOfPlansEffect extends OneShotEffect { if (permanents.isEmpty()) { return false; } - for (Permanent permanent : permanents) { - ConniveSourceEffect.connive(permanent, 1, source, game); - } Player player = game.getPlayer(source.getControllerId()); if (player == null) { return true; diff --git a/Mage.Sets/src/mage/cards/c/ChaosMutation.java b/Mage.Sets/src/mage/cards/c/ChaosMutation.java index fb4b4cfbc41..bbb0639611e 100644 --- a/Mage.Sets/src/mage/cards/c/ChaosMutation.java +++ b/Mage.Sets/src/mage/cards/c/ChaosMutation.java @@ -124,8 +124,8 @@ class ChaosMutationTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/c/ChapelShieldgeist.java b/Mage.Sets/src/mage/cards/c/ChapelShieldgeist.java index 5c366277c10..eb0d21c98ca 100644 --- a/Mage.Sets/src/mage/cards/c/ChapelShieldgeist.java +++ b/Mage.Sets/src/mage/cards/c/ChapelShieldgeist.java @@ -1,11 +1,10 @@ package mage.cards.c; import mage.MageInt; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.WardAbility; @@ -48,7 +47,7 @@ public final class ChapelShieldgeist extends CardImpl { "counter it unless that player pays 1.)"))); // If Chapel Shieldgeist would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private ChapelShieldgeist(final ChapelShieldgeist card) { diff --git a/Mage.Sets/src/mage/cards/c/CharitableLevy.java b/Mage.Sets/src/mage/cards/c/CharitableLevy.java index bbc6d4a8e7a..a95a84a5a4a 100644 --- a/Mage.Sets/src/mage/cards/c/CharitableLevy.java +++ b/Mage.Sets/src/mage/cards/c/CharitableLevy.java @@ -20,7 +20,6 @@ import mage.constants.TargetController; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBySubtypeCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -39,7 +38,7 @@ public final class CharitableLevy extends CardImpl { private static final Condition condition = new SourceHasCounterCondition(CounterType.COLLECTION, 3); - private static final FilterCard filterPlains = new FilterBySubtypeCard(SubType.PLAINS); + private static final FilterCard filterPlains = new FilterCard(SubType.PLAINS); public CharitableLevy(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); diff --git a/Mage.Sets/src/mage/cards/c/CheeringCrowd.java b/Mage.Sets/src/mage/cards/c/CheeringCrowd.java new file mode 100644 index 00000000000..efeb7790ea7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CheeringCrowd.java @@ -0,0 +1,100 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.costs.common.PutCountersSourceCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.triggers.BeginningOfFirstMainTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class CheeringCrowd extends CardImpl { + + public CheeringCrowd(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R/G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // At the beginning of each player's first main phase, that player may put a +1/+1 counter on this creature. If they do, they add {C} for each counter on it. + this.addAbility(new BeginningOfFirstMainTriggeredAbility(TargetController.EACH_PLAYER, new CheeringCrowdDoIfPaidEffect(), false)); + } + + private CheeringCrowd(final CheeringCrowd card) { + super(card); + } + + @Override + public CheeringCrowd copy() { + return new CheeringCrowd(this); + } +} +class CheeringCrowdDoIfPaidEffect extends DoIfCostPaid { + + CheeringCrowdDoIfPaidEffect() { + super(new CheeringCrowdEffect(), new PutCountersSourceCost(CounterType.P1P1.createInstance()), + "Put a +1/+1 counter on Cheering Crowd and add {C} for each counter on it?", true); + staticText = "that player may put a +1/+1 counter on this creature. If they do, they add {C} for each counter on it"; + } + + private CheeringCrowdDoIfPaidEffect(final CheeringCrowdDoIfPaidEffect effect) { + super(effect); + } + + @Override + public CheeringCrowdDoIfPaidEffect copy() { + return new CheeringCrowdDoIfPaidEffect(this); + } + + @Override + protected Player getPayingPlayer(Game game, Ability source) { + return game.getPlayer(getTargetPointer().getFirst(game, source)); + } +} + +class CheeringCrowdEffect extends OneShotEffect { + + public CheeringCrowdEffect() { + super(Outcome.PutManaInPool); + staticText = "they add {C} for each counter on it"; + } + + protected CheeringCrowdEffect(final CheeringCrowdEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (player == null || permanent == null) { + return false; + } + int counterCounter = permanent.getCounters(game).getCount(CounterType.P1P1); + player.getManaPool().addMana(Mana.ColorlessMana(counterCounter), game, source); + return true; + } + + @Override + public CheeringCrowdEffect copy() { + return new CheeringCrowdEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChromaticLantern.java b/Mage.Sets/src/mage/cards/c/ChromaticLantern.java index db50ca986ef..1a8a7a561d2 100644 --- a/Mage.Sets/src/mage/cards/c/ChromaticLantern.java +++ b/Mage.Sets/src/mage/cards/c/ChromaticLantern.java @@ -3,13 +3,14 @@ package mage.cards.c; import java.util.UUID; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.mana.AnyColorManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.DependencyType; import mage.constants.Duration; -import mage.constants.Zone; import mage.filter.StaticFilters; /** @@ -21,7 +22,10 @@ public final class ChromaticLantern extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); // Lands you control have "{T}: Add one mana of any color." - this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(new AnyColorManaAbility(), Duration.WhileOnBattlefield, StaticFilters.FILTER_LANDS, false))); + ContinuousEffect effect = new GainAbilityControlledEffect(new AnyColorManaAbility(), Duration.WhileOnBattlefield, StaticFilters.FILTER_LANDS, false); + effect.getDependedToTypes().add(DependencyType.BecomeNonbasicLand); + this.addAbility(new SimpleStaticAbility(effect)); + // {T}: Add one mana of any color. this.addAbility(new AnyColorManaAbility()); diff --git a/Mage.Sets/src/mage/cards/c/CityPigeon.java b/Mage.Sets/src/mage/cards/c/CityPigeon.java new file mode 100644 index 00000000000..ad9305069b4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CityPigeon.java @@ -0,0 +1,42 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.FoodToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CityPigeon extends CardImpl { + + public CityPigeon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.BIRD); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature leaves the battlefield, create a Food token. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new CreateTokenEffect(new FoodToken()))); + } + + private CityPigeon(final CityPigeon card) { + super(card); + } + + @Override + public CityPigeon copy() { + return new CityPigeon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CivilizedScholar.java b/Mage.Sets/src/mage/cards/c/CivilizedScholar.java index 8baaa504abc..9d9315204fe 100644 --- a/Mage.Sets/src/mage/cards/c/CivilizedScholar.java +++ b/Mage.Sets/src/mage/cards/c/CivilizedScholar.java @@ -1,24 +1,24 @@ package mage.cards.c; -import java.util.UUID; - import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.SubType; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.Optional; +import java.util.UUID; + /** * @author nantuko */ @@ -35,8 +35,10 @@ public final class CivilizedScholar extends CardImpl { this.toughness = new MageInt(1); // {tap}: Draw a card, then discard a card. If a creature card is discarded this way, untap Civilized Scholar, then transform it. - this.addAbility(new SimpleActivatedAbility(new CivilizedScholarEffect(), new TapSourceCost())); + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new TapSourceCost()); + ability.addEffect(new CivilizedScholarEffect()); this.addAbility(new TransformAbility()); + this.addAbility(ability); } private CivilizedScholar(final CivilizedScholar card) { @@ -50,12 +52,11 @@ public final class CivilizedScholar extends CardImpl { } - class CivilizedScholarEffect extends OneShotEffect { CivilizedScholarEffect() { super(Outcome.DrawCard); - staticText = "Draw a card, then discard a card. If a creature card is discarded this way, untap {this}, then transform it"; + staticText = ", then discard a card. If a creature card is discarded this way, untap {this}, then transform it"; } private CivilizedScholarEffect(final CivilizedScholarEffect effect) { @@ -70,18 +71,18 @@ class CivilizedScholarEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.drawCards(1, source, game); - Card card = player.discardOne(false, false, source, game); - if (card != null && card.isCreature(game)) { - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { - permanent.untap(game); - permanent.transform(source, game); - } - } + if (player == null) { + return false; + } + Card card = player.discardOne(false, false, source, game); + if (card == null || !card.isCreature(game)) { return true; } - return false; + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(permanent -> { + permanent.untap(game); + permanent.transform(source, game); + }); + return true; } } diff --git a/Mage.Sets/src/mage/cards/c/ClayFiredBricks.java b/Mage.Sets/src/mage/cards/c/ClayFiredBricks.java index a6dd2fed8c7..06cc8042fe6 100644 --- a/Mage.Sets/src/mage/cards/c/ClayFiredBricks.java +++ b/Mage.Sets/src/mage/cards/c/ClayFiredBricks.java @@ -8,9 +8,7 @@ import mage.abilities.keyword.CraftAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; -import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -20,15 +18,13 @@ import java.util.UUID; */ public final class ClayFiredBricks extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard(SubType.PLAINS); - public ClayFiredBricks(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); this.secondSideCardClazz = mage.cards.c.CosmiumKiln.class; // When Clay-Fired Bricks enters the battlefield, search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. You gain 2 life. Ability ability = new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true) ); ability.addEffect(new GainLifeEffect(2)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/CleverDistraction.java b/Mage.Sets/src/mage/cards/c/CleverDistraction.java index b38b7721651..751b419f195 100644 --- a/Mage.Sets/src/mage/cards/c/CleverDistraction.java +++ b/Mage.Sets/src/mage/cards/c/CleverDistraction.java @@ -2,12 +2,11 @@ package mage.cards.c; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -46,17 +45,16 @@ public final class CleverDistraction extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature has "Whenever this creature attacks, tap target creature defending player controls." - ability = new AttacksTriggeredAbility(new TapTargetEffect()) + Ability ability = new AttacksTriggeredAbility(new TapTargetEffect()) .setTriggerPhrase("Whenever this creature attacks, "); ability.addTarget(new TargetPermanent(filter)); this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect(ability, AttachmentType.AURA))); // If Clever Distracting would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private CleverDistraction(final CleverDistraction card) { diff --git a/Mage.Sets/src/mage/cards/c/ClockworkBeetle.java b/Mage.Sets/src/mage/cards/c/ClockworkBeetle.java index e513d181cb4..54c08ad4fb0 100644 --- a/Mage.Sets/src/mage/cards/c/ClockworkBeetle.java +++ b/Mage.Sets/src/mage/cards/c/ClockworkBeetle.java @@ -66,7 +66,7 @@ class ClockworkBeetleEffect extends OneShotEffect { Permanent permanent = game.getPermanent(source.getSourceId()); if (permanent != null) { Effect effect = new RemoveCounterTargetEffect(CounterType.P1P1.createInstance()); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getSourceObjectZoneChangeCounter())); + effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getStackMomentSourceZCC())); game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility(effect), source); return true; } diff --git a/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java b/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java index 91b5eea84fd..fa6dcd2fa80 100644 --- a/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java +++ b/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java @@ -85,8 +85,8 @@ class CloudsLimitBreakTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/c/CobraTrap.java b/Mage.Sets/src/mage/cards/c/CobraTrap.java index 90ec0240ab8..99cd7e9cd7f 100644 --- a/Mage.Sets/src/mage/cards/c/CobraTrap.java +++ b/Mage.Sets/src/mage/cards/c/CobraTrap.java @@ -78,11 +78,9 @@ class CobraTrapWatcher extends Watcher { if (event.getType() == GameEvent.EventType.DESTROYED_PERMANENT) { Permanent perm = game.getPermanentOrLKIBattlefield(event.getTargetId()); // can regenerate or be indestructible if (perm != null && !perm.isCreature(game)) { - if (!game.getStack().isEmpty()) { - StackObject spell = game.getStack().getStackObject(event.getSourceId()); - if (spell != null && game.getOpponents(perm.getControllerId()).contains(spell.getControllerId())) { - players.add(perm.getControllerId()); - } + StackObject spell = game.getStack().getStackObject(event.getSourceId()); + if (spell != null && game.getOpponents(perm.getControllerId()).contains(spell.getControllerId())) { + players.add(perm.getControllerId()); } } } diff --git a/Mage.Sets/src/mage/cards/c/CommonCrook.java b/Mage.Sets/src/mage/cards/c/CommonCrook.java new file mode 100644 index 00000000000..25d18e58d38 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CommonCrook.java @@ -0,0 +1,40 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CommonCrook extends CardImpl { + + public CommonCrook(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When this creature dies, create a Treasure token. + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new TreasureToken()))); + } + + private CommonCrook(final CommonCrook card) { + super(card); + } + + @Override + public CommonCrook copy() { + return new CommonCrook(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ConduitOfEmrakul.java b/Mage.Sets/src/mage/cards/c/ConduitOfEmrakul.java index ce9612de660..ec2a8fc337e 100644 --- a/Mage.Sets/src/mage/cards/c/ConduitOfEmrakul.java +++ b/Mage.Sets/src/mage/cards/c/ConduitOfEmrakul.java @@ -1,28 +1,26 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.Mana; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.delayed.AtTheBeginOfMainPhaseDelayedTriggeredAbility; -import mage.abilities.effects.Effect; -import mage.abilities.effects.mana.AddManaToManaPoolSourceControllerEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.mana.AddManaToManaPoolSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.TargetController; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ConduitOfEmrakul extends CardImpl { public ConduitOfEmrakul(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},""); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); this.subtype.add(SubType.ELDRAZI); this.subtype.add(SubType.WEREWOLF); this.power = new MageInt(5); @@ -32,11 +30,12 @@ public final class ConduitOfEmrakul extends CardImpl { this.nightCard = true; // Whenever Conduit of Emrakul attacks, add {C}{C} at the beginning of your next main phase this turn. - Effect effect = new CreateDelayedTriggeredAbilityEffect( + this.addAbility(new AttacksTriggeredAbility(new CreateDelayedTriggeredAbilityEffect( new AtTheBeginOfMainPhaseDelayedTriggeredAbility( - new AddManaToManaPoolSourceControllerEffect(Mana.GenericMana(2)), false, TargetController.YOU, AtTheBeginOfMainPhaseDelayedTriggeredAbility.PhaseSelection.NEXT_MAIN_THIS_TURN)); - effect.setText("add {C}{C} at the beginning of your next main phase this turn"); - this.addAbility(new AttacksTriggeredAbility(effect, false)); + new AddManaToManaPoolSourceControllerEffect(Mana.GenericMana(2)), false, + TargetController.YOU, AtTheBeginOfMainPhaseDelayedTriggeredAbility.PhaseSelection.NEXT_MAIN_THIS_TURN + ) + ).setText("add {C}{C} at the beginning of your next main phase this turn"), false)); } private ConduitOfEmrakul(final ConduitOfEmrakul card) { diff --git a/Mage.Sets/src/mage/cards/c/ConduitOfStorms.java b/Mage.Sets/src/mage/cards/c/ConduitOfStorms.java index c0e8f602938..0fdf2d7098f 100644 --- a/Mage.Sets/src/mage/cards/c/ConduitOfStorms.java +++ b/Mage.Sets/src/mage/cards/c/ConduitOfStorms.java @@ -1,33 +1,30 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.Mana; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.delayed.AtTheBeginOfMainPhaseDelayedTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; -import mage.abilities.effects.mana.AddManaToManaPoolSourceControllerEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.mana.AddManaToManaPoolSourceControllerEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.TargetController; -import mage.constants.Zone; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class ConduitOfStorms extends CardImpl { public ConduitOfStorms(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); this.subtype.add(SubType.WEREWOLF); this.subtype.add(SubType.HORROR); this.power = new MageInt(2); @@ -36,11 +33,13 @@ public final class ConduitOfStorms extends CardImpl { this.secondSideCardClazz = mage.cards.c.ConduitOfEmrakul.class; // Whenever Conduit of Storms attacks, add {R} at the beginning of your next main phase this turn. - Effect effect = new CreateDelayedTriggeredAbilityEffect( + this.addAbility(new AttacksTriggeredAbility(new CreateDelayedTriggeredAbilityEffect( new AtTheBeginOfMainPhaseDelayedTriggeredAbility( - new AddManaToManaPoolSourceControllerEffect(Mana.RedMana(1)), false, TargetController.YOU, AtTheBeginOfMainPhaseDelayedTriggeredAbility.PhaseSelection.NEXT_MAIN_THIS_TURN)); - effect.setText("add {R} at the beginning of your next main phase this turn"); - this.addAbility(new AttacksTriggeredAbility(effect, false)); + new AddManaToManaPoolSourceControllerEffect(Mana.RedMana(1)), false, + TargetController.YOU, AtTheBeginOfMainPhaseDelayedTriggeredAbility.PhaseSelection.NEXT_MAIN_THIS_TURN + ) + ).setText("add {R} at the beginning of your next main phase this turn"), false)); + // {3}{R}{R}: Transform Conduit of Storms. this.addAbility(new TransformAbility()); this.addAbility(new SimpleActivatedAbility(new TransformSourceEffect(), new ManaCostsImpl<>("{3}{R}{R}"))); diff --git a/Mage.Sets/src/mage/cards/c/ConsignToMemory.java b/Mage.Sets/src/mage/cards/c/ConsignToMemory.java index 0e28b8b29a2..eb8e853108a 100644 --- a/Mage.Sets/src/mage/cards/c/ConsignToMemory.java +++ b/Mage.Sets/src/mage/cards/c/ConsignToMemory.java @@ -6,11 +6,10 @@ import mage.abilities.keyword.ReplicateAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterSpell; import mage.filter.FilterStackObject; import mage.filter.predicate.Predicate; -import mage.filter.predicate.mageobject.ColorlessPredicate; import mage.game.Game; +import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.target.TargetStackObject; @@ -31,7 +30,6 @@ public final class ConsignToMemory extends CardImpl { public ConsignToMemory(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); - // Replicate {1} this.addAbility(new ReplicateAbility("{1}")); @@ -53,18 +51,11 @@ public final class ConsignToMemory extends CardImpl { enum ConsignToMemoryPredicate implements Predicate { instance; - private static final FilterSpell filterSpell = new FilterSpell("colorless spell"); - - static { - filterSpell.add(ColorlessPredicate.instance); - } - @Override public boolean apply(StackObject input, Game game) { - if (input instanceof Ability) { - Ability ability = (Ability) input; - return ability.getAbilityType().isTriggeredAbility(); + if (input instanceof Spell) { + return input.getColor(game).isColorless(); } - return filterSpell.match(input, game); + return input instanceof Ability && ((Ability) input).isTriggeredAbility(); } } diff --git a/Mage.Sets/src/mage/cards/c/ConspiracyTheorist.java b/Mage.Sets/src/mage/cards/c/ConspiracyTheorist.java index dbd41c58ddd..d767e99db1d 100644 --- a/Mage.Sets/src/mage/cards/c/ConspiracyTheorist.java +++ b/Mage.Sets/src/mage/cards/c/ConspiracyTheorist.java @@ -119,8 +119,7 @@ class ConspiracyTheoristEffect extends OneShotEffect { if (controller != null) { CardsImpl cards = new CardsImpl(discardedCards); TargetCard target = new TargetCard(Zone.GRAVEYARD, new FilterCard("card to exile")); - boolean validTarget = cards.stream() - .anyMatch(card -> target.canTarget(card, game)); + boolean validTarget = cards.stream().anyMatch(card -> target.canTarget(card, source, game)); if (validTarget && controller.chooseUse(Outcome.Benefit, "Exile a card?", source, game)) { if (controller.choose(Outcome.Benefit, cards, target, source, game)) { Card card = cards.get(target.getFirstTarget(), game); diff --git a/Mage.Sets/src/mage/cards/c/ConsultTheStarCharts.java b/Mage.Sets/src/mage/cards/c/ConsultTheStarCharts.java index 6a2cfcbbe25..1363162cccc 100644 --- a/Mage.Sets/src/mage/cards/c/ConsultTheStarCharts.java +++ b/Mage.Sets/src/mage/cards/c/ConsultTheStarCharts.java @@ -2,7 +2,7 @@ package mage.cards.c; import mage.abilities.condition.common.KickedCondition; import mage.abilities.decorator.ConditionalOneShotEffect; -import mage.abilities.dynamicvalue.common.ManaSpentToCastCount; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; import mage.abilities.keyword.KickerAbility; import mage.cards.CardImpl; @@ -25,8 +25,8 @@ public final class ConsultTheStarCharts extends CardImpl { // Look at the top X cards of your library, where X is the number of lands you control. Put one of those cards into your hand. If this spell was kicked, put two of those cards into your hand instead. Put the rest on the bottom of your library in a random order. this.getSpellAbility().addEffect(new ConditionalOneShotEffect( - new LookLibraryAndPickControllerEffect(ManaSpentToCastCount.instance, 2, PutCards.HAND, PutCards.BOTTOM_RANDOM), - new LookLibraryAndPickControllerEffect(ManaSpentToCastCount.instance, 1, PutCards.HAND, PutCards.BOTTOM_RANDOM), + new LookLibraryAndPickControllerEffect(LandsYouControlCount.instance, 2, PutCards.HAND, PutCards.BOTTOM_RANDOM), + new LookLibraryAndPickControllerEffect(LandsYouControlCount.instance, 1, PutCards.HAND, PutCards.BOTTOM_RANDOM), KickedCondition.ONCE, "look at the top X cards of your library, where X is the number of lands you control. " + "Put one of those cards into your hand. If this spell was kicked, put two of those cards into your hand instead. " + "Put the rest on the bottom of your library in a random order" diff --git a/Mage.Sets/src/mage/cards/c/ContaminatedLandscape.java b/Mage.Sets/src/mage/cards/c/ContaminatedLandscape.java index 33befbbf714..441fc40438e 100644 --- a/Mage.Sets/src/mage/cards/c/ContaminatedLandscape.java +++ b/Mage.Sets/src/mage/cards/c/ContaminatedLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class ContaminatedLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Plains, Island, or Swamp card"); + private static final FilterCard filter = new FilterBasicCard("a basic Plains, Island, or Swamp card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/c/ContestOfClaws.java b/Mage.Sets/src/mage/cards/c/ContestOfClaws.java index 530b4a9847e..afa5f2dc6d4 100644 --- a/Mage.Sets/src/mage/cards/c/ContestOfClaws.java +++ b/Mage.Sets/src/mage/cards/c/ContestOfClaws.java @@ -1,7 +1,5 @@ package mage.cards.c; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.keyword.DiscoverEffect; @@ -9,34 +7,29 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.StaticFilters; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.Target; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; import static mage.filter.StaticFilters.FILTER_ANOTHER_CREATURE_TARGET_2; /** - * * @author jimga150 */ public final class ContestOfClaws extends CardImpl { public ContestOfClaws(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); - // Target creature you control deals damage equal to its power to another target creature. If excess damage was dealt this way, discover X, where X is that excess damage. this.getSpellAbility().addEffect(new ContestOfClawsDamageEffect()); - Target target = new TargetControlledCreaturePermanent().setTargetTag(1); - this.getSpellAbility().addTarget(target); - - Target target2 = new TargetPermanent(FILTER_ANOTHER_CREATURE_TARGET_2).setTargetTag(2); - this.getSpellAbility().addTarget(target2); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent().setTargetTag(1)); + this.getSpellAbility().addTarget(new TargetPermanent(FILTER_ANOTHER_CREATURE_TARGET_2).setTargetTag(2)); } private ContestOfClaws(final ContestOfClaws card) { @@ -74,22 +67,13 @@ class ContestOfClawsDamageEffect extends OneShotEffect { if (ownCreature == null || targetCreature == null) { return false; } - int damage = ownCreature.getPower().getValue(); - int lethalDamage = targetCreature.getLethalDamage(source.getSourceId(), game); - targetCreature.damage(damage, ownCreature.getId(), source, game, false, true); - - if (damage < lethalDamage){ - return true; + int excess = targetCreature.damageWithExcess( ownCreature.getPower().getValue(), ownCreature.getId(), source, game); + if (excess > 0) { + Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> DiscoverEffect.doDiscover(player, excess, game, source)); } - int discoverValue = damage - lethalDamage; - Player player = game.getPlayer(source.getControllerId()); - - if (player == null){ - // If somehow this case is hit, the damage still technically happened, so i guess it applied? - return true; - } - DiscoverEffect.doDiscover(player, discoverValue, game, source); - return true; } } diff --git a/Mage.Sets/src/mage/cards/c/Conversion.java b/Mage.Sets/src/mage/cards/c/Conversion.java index 73afd85c63a..41cedbc0a28 100644 --- a/Mage.Sets/src/mage/cards/c/Conversion.java +++ b/Mage.Sets/src/mage/cards/c/Conversion.java @@ -54,6 +54,12 @@ public final class Conversion extends CardImpl { ConversionEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); this.staticText = "All Mountains are Plains"; + this.dependendToTypes.add(DependencyType.BecomeForest); + this.dependendToTypes.add(DependencyType.BecomeIsland); + this.dependendToTypes.add(DependencyType.BecomeMountain); + this.dependendToTypes.add(DependencyType.BecomePlains); + this.dependendToTypes.add(DependencyType.BecomeSwamp); + this.dependencyTypes.add(DependencyType.BecomePlains); } private ConversionEffect(final ConversionEffect effect) { diff --git a/Mage.Sets/src/mage/cards/c/CorrosiveOoze.java b/Mage.Sets/src/mage/cards/c/CorrosiveOoze.java index 2979160128e..d06923b0a46 100644 --- a/Mage.Sets/src/mage/cards/c/CorrosiveOoze.java +++ b/Mage.Sets/src/mage/cards/c/CorrosiveOoze.java @@ -79,7 +79,7 @@ class CorrosiveOozeEffect extends OneShotEffect { if (watcher == null) { return false; } - MageObjectReference sourceMor = new MageObjectReference(source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game); + MageObjectReference sourceMor = new MageObjectReference(source.getSourceId(), source.getStackMomentSourceZCC(), game); List equipments = watcher.getEquipmentsToDestroy(sourceMor) .stream() .map(mor -> mor.getPermanent(game)) diff --git a/Mage.Sets/src/mage/cards/c/CosmicSpiderMan.java b/Mage.Sets/src/mage/cards/c/CosmicSpiderMan.java new file mode 100644 index 00000000000..dcddeabb67a --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CosmicSpiderMan.java @@ -0,0 +1,77 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.*; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CosmicSpiderMan extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.SPIDER, "Spiders"); + + public CosmicSpiderMan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}{B}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // At the beginning of combat on your turn, other Spiders you control gain flying, first strike, trample, lifelink, and haste until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.EndOfTurn, filter, true + ).setText("other Spiders you control gain flying")); + ability.addEffect(new GainAbilityControlledEffect( + FirstStrikeAbility.getInstance(), Duration.EndOfTurn, filter, true + ).setText(", first strike")); + ability.addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, filter, true + ).setText(", trample")); + ability.addEffect(new GainAbilityControlledEffect( + LifelinkAbility.getInstance(), Duration.EndOfTurn, filter, true + ).setText(", lifelink")); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.EndOfTurn, filter, true + ).setText(", and haste until end of turn")); + this.addAbility(ability); + } + + private CosmicSpiderMan(final CosmicSpiderMan card) { + super(card); + } + + @Override + public CosmicSpiderMan copy() { + return new CosmicSpiderMan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CostumeCloset.java b/Mage.Sets/src/mage/cards/c/CostumeCloset.java new file mode 100644 index 00000000000..4d152009b7e --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CostumeCloset.java @@ -0,0 +1,62 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.MoveCountersFromSourceToTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.ModifiedPredicate; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CostumeCloset extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("a modified creature you control"); + + static { + filter.add(ModifiedPredicate.instance); + } + + public CostumeCloset(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); + + // This artifact enters with two +1/+1 counters on it. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + "with two +1/+1 counters on it" + )); + + // {T}: Move a +1/+1 counter from this artifact onto target creature you control. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new MoveCountersFromSourceToTargetEffect(), new TapSourceCost() + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + + // Whenever a modified creature you control leaves the battlefield, put a +1/+1 counter on this artifact. + this.addAbility(new LeavesBattlefieldAllTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter + )); + } + + private CostumeCloset(final CostumeCloset card) { + super(card); + } + + @Override + public CostumeCloset copy() { + return new CostumeCloset(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CovetousGeist.java b/Mage.Sets/src/mage/cards/c/CovetousGeist.java index fbc4f1faec1..7dd65c1bb4f 100644 --- a/Mage.Sets/src/mage/cards/c/CovetousGeist.java +++ b/Mage.Sets/src/mage/cards/c/CovetousGeist.java @@ -1,9 +1,8 @@ package mage.cards.c; import mage.MageInt; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -34,7 +33,7 @@ public final class CovetousGeist extends CardImpl { this.addAbility(DeathtouchAbility.getInstance()); // If Covetous Geist would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private CovetousGeist(final CovetousGeist card) { diff --git a/Mage.Sets/src/mage/cards/c/CustodyBattle.java b/Mage.Sets/src/mage/cards/c/CustodyBattle.java index 8ff892631d4..4b2a3f30eb1 100644 --- a/Mage.Sets/src/mage/cards/c/CustodyBattle.java +++ b/Mage.Sets/src/mage/cards/c/CustodyBattle.java @@ -131,7 +131,7 @@ class CustodyBattleUnlessPaysEffect extends OneShotEffect { return true; } } - if (source.getSourceObjectZoneChangeCounter() == game.getState().getZoneChangeCounter(source.getSourceId()) + if (source.getStackMomentSourceZCC() == game.getState().getZoneChangeCounter(source.getSourceId()) && game.getState().getZone(source.getSourceId()) == Zone.BATTLEFIELD) { ContinuousEffect effect = new GiveControlEffect(); effect.setTargetPointer(new FixedTarget(source.getFirstTarget(), game)); diff --git a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java index 2de29c123d3..ad96ccb040e 100644 --- a/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java +++ b/Mage.Sets/src/mage/cards/c/CyclopeanTomb.java @@ -147,7 +147,7 @@ class CyclopeanTombEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - MageObjectReference mor = new MageObjectReference(source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game); + MageObjectReference mor = new MageObjectReference(source.getSourceId(), source.getStackMomentSourceZCC(), game); CyclopeanTombCounterWatcher watcher = game.getState().getWatcher(CyclopeanTombCounterWatcher.class); if (controller != null && watcher != null) { diff --git a/Mage.Sets/src/mage/cards/d/DaiLiIndoctrination.java b/Mage.Sets/src/mage/cards/d/DaiLiIndoctrination.java new file mode 100644 index 00000000000..04a7b59fc45 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DaiLiIndoctrination.java @@ -0,0 +1,52 @@ +package mage.cards.d; + +import mage.abilities.Mode; +import mage.abilities.effects.common.discard.DiscardCardYouChooseTargetEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetControlledLandPermanent; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DaiLiIndoctrination extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard("a nonland permanent card"); + + static { + filter.add(Predicates.not(CardType.LAND.getPredicate())); + } + + public DaiLiIndoctrination(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + this.subtype.add(SubType.LESSON); + + // Choose one -- + // * Target opponent reveals their hand. You choose a nonland permanent card from it. That player discards that card. + this.getSpellAbility().addEffect(new DiscardCardYouChooseTargetEffect(filter)); + this.getSpellAbility().addTarget(new TargetOpponent()); + + // * Earthbend 2. + this.getSpellAbility().addMode(new Mode(new EarthbendTargetEffect(2)) + .addTarget(new TargetControlledLandPermanent())); + } + + private DaiLiIndoctrination(final DaiLiIndoctrination card) { + super(card); + } + + @Override + public DaiLiIndoctrination copy() { + return new DaiLiIndoctrination(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DailyBugleBuilding.java b/Mage.Sets/src/mage/cards/d/DailyBugleBuilding.java new file mode 100644 index 00000000000..79491405489 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DailyBugleBuilding.java @@ -0,0 +1,52 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.mana.AnyColorManaAbility; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DailyBugleBuilding extends CardImpl { + + public DailyBugleBuilding(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {1}, {T}: Add one mana of any color. + Ability ability = new AnyColorManaAbility(new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + + // Smear Campaign -- {1}, {T}: Target legendary creature gains menace until end of turn. Activate only as a sorcery. + ability = new ActivateAsSorceryActivatedAbility( + new GainAbilityTargetEffect(new MenaceAbility(false)), new GenericManaCost(1) + ); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_LEGENDARY)); + this.addAbility(ability.withFlavorWord("Smear Campaign")); + } + + private DailyBugleBuilding(final DailyBugleBuilding card) { + super(card); + } + + @Override + public DailyBugleBuilding copy() { + return new DailyBugleBuilding(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DamageControlCrew.java b/Mage.Sets/src/mage/cards/d/DamageControlCrew.java new file mode 100644 index 00000000000..d4ff8c8d47d --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DamageControlCrew.java @@ -0,0 +1,62 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DamageControlCrew extends CardImpl { + + private static final FilterCard filter = new FilterCard("card with mana value 4 or greater from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.MORE_THAN, 3)); + } + + public DamageControlCrew(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When this creature enters, choose one -- + // * Repair -- Return target card with mana value 4 or greater from your graveyard to your hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + ability.withFirstModeFlavorWord("Repair"); + + // * Impound -- Exile target artifact or enchantment. + ability.addMode(new Mode(new ExileTargetEffect()) + .addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)) + .withFlavorWord("Impound")); + this.addAbility(ability); + } + + private DamageControlCrew(final DamageControlCrew card) { + super(card); + } + + @Override + public DamageControlCrew copy() { + return new DamageControlCrew(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java b/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java index effdfb78226..cbcaf214e65 100644 --- a/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java +++ b/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java @@ -1,31 +1,29 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.IntPlusDynamicValue; -import mage.abilities.dynamicvalue.MultipliedValue; -import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; -import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInHandCondition; +import mage.abilities.effects.common.DrawCardsEqualToDifferenceEffect; import mage.abilities.effects.common.SkipDrawStepEffect; import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ComparisonType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.TargetController; -import mage.game.Game; -import mage.players.Player; + +import java.util.UUID; /** - * * @author emerald000 */ public final class DamiaSageOfStone extends CardImpl { + private static final Condition condition = new CardsInHandCondition(ComparisonType.FEWER_THAN, 7); + public DamiaSageOfStone(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{G}{U}"); this.supertype.add(SuperType.LEGENDARY); @@ -42,7 +40,7 @@ public final class DamiaSageOfStone extends CardImpl { this.addAbility(new SimpleStaticAbility(new SkipDrawStepEffect())); // At the beginning of your upkeep, if you have fewer than seven cards in hand, draw cards equal to the difference. - this.addAbility(new DamiaSageOfStoneTriggeredAbility()); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DrawCardsEqualToDifferenceEffect(7)).withInterveningIf(condition)); } private DamiaSageOfStone(final DamiaSageOfStone card) { @@ -54,33 +52,3 @@ public final class DamiaSageOfStone extends CardImpl { return new DamiaSageOfStone(this); } } - -class DamiaSageOfStoneTriggeredAbility extends BeginningOfUpkeepTriggeredAbility { - - DamiaSageOfStoneTriggeredAbility() { - super(TargetController.YOU, new DrawCardSourceControllerEffect(new IntPlusDynamicValue(7, new MultipliedValue(CardsInControllerHandCount.ANY, -1))), false); - } - - private DamiaSageOfStoneTriggeredAbility(final DamiaSageOfStoneTriggeredAbility ability) { - super(ability); - } - - @Override - public DamiaSageOfStoneTriggeredAbility copy() { - return new DamiaSageOfStoneTriggeredAbility(this); - } - - @Override - public boolean checkInterveningIfClause(Game game) { - Player player = game.getPlayer(this.getControllerId()); - if (player != null) { - return player.getHand().size() < 7; - } - return false; - } - - @Override - public String getRule() { - return "At the beginning of your upkeep, if you have fewer than seven cards in hand, draw cards equal to the difference."; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DampingEngine.java b/Mage.Sets/src/mage/cards/d/DampingEngine.java index d298afd4fd0..20d92803bfb 100644 --- a/Mage.Sets/src/mage/cards/d/DampingEngine.java +++ b/Mage.Sets/src/mage/cards/d/DampingEngine.java @@ -78,7 +78,7 @@ public class DampingEngine extends CardImpl { return "dampingEngine_" + playerId + "_" + source.getSourceId() + "_" - + source.getSourceObjectZoneChangeCounter() + "_" + + source.getStackMomentSourceZCC() + "_" + game.getTurnNum(); } diff --git a/Mage.Sets/src/mage/cards/d/DaringThief.java b/Mage.Sets/src/mage/cards/d/DaringThief.java index 44566227ba6..668d2eaf7ca 100644 --- a/Mage.Sets/src/mage/cards/d/DaringThief.java +++ b/Mage.Sets/src/mage/cards/d/DaringThief.java @@ -9,6 +9,7 @@ import mage.abilities.keyword.InspiredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; @@ -35,8 +36,12 @@ public final class DaringThief extends CardImpl { this.toughness = new MageInt(3); // Inspired - Whenever Daring Thief becomes untapped, you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it. - Ability ability = new InspiredAbility(new ExchangeControlTargetEffect(Duration.EndOfGame, - "you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it", false, true), true); + Ability ability = new InspiredAbility(new ExchangeControlTargetEffect( + Duration.EndOfGame, + "you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it", + false, + true + ), true); ability.addTarget(new TargetControlledPermanentSharingOpponentPermanentCardType()); ability.addTarget(new DaringThiefSecondTarget()); this.addAbility(ability); @@ -66,9 +71,10 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { - Set cardTypes = getOpponentPermanentCardTypes(controllerId, game); + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + Set cardTypes = getOpponentPermanentCardTypes(playerId, game); + + if (super.canTarget(playerId, id, source, game)) { Permanent permanent = game.getPermanent(id); for (CardType type : permanent.getCardType(game)) { if (cardTypes.contains(type)) { @@ -81,23 +87,21 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - // get all cardtypes from opponents permanents Set cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game); + Set possibleTargets = new HashSet<>(); MageObject targetSource = game.getObject(source); if (targetSource != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - for (CardType type : permanent.getCardType(game)) { - if (cardTypes.contains(type)) { - possibleTargets.add(permanent.getId()); - break; - } + for (CardType type : permanent.getCardType(game)) { + if (cardTypes.contains(type)) { + possibleTargets.add(permanent.getId()); + break; } } } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -119,58 +123,54 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo } } - class DaringThiefSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public DaringThiefSecondTarget() { - super(); - this.filter = this.filter.copy(); - filter.add(TargetController.OPPONENT.getControllerPredicate()); + super(StaticFilters.FILTER_OPPONENTS_PERMANENT); withTargetName("permanent an opponent controls that shares a card type with it"); } private DaringThiefSecondTarget(final DaringThiefSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } - @Override public boolean canTarget(UUID id, Ability source, Game game) { - if (super.canTarget(id, source, game)) { - Permanent target1 = game.getPermanent(source.getFirstTarget()); - Permanent opponentPermanent = game.getPermanent(id); - if (target1 != null && opponentPermanent != null) { - return target1.shareTypes(opponentPermanent, game); - } + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + Permanent possiblePermanent = game.getPermanent(id); + if (ownPermanent == null || possiblePermanent == null) { + return false; } - return false; + return super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (permanent.shareTypes(firstTarget, game)) { - possibleTargets.add(permanent.getId()); - } - } + + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (ownPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by shared type + if (permanent.shareTypes(ownPermanent, game)) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); - return super.chooseTarget(Outcome.Damage, playerId, source, game); + // AI hint with better outcome + return super.chooseTarget(Outcome.GainControl, playerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DarkImpostor.java b/Mage.Sets/src/mage/cards/d/DarkImpostor.java index 34ca02b5c1d..cc24ed51e4e 100644 --- a/Mage.Sets/src/mage/cards/d/DarkImpostor.java +++ b/Mage.Sets/src/mage/cards/d/DarkImpostor.java @@ -102,7 +102,7 @@ class DarkImpostorContinuousEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { Permanent permanent = source.getSourcePermanentIfItStillExists(game); - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source, 1)); if (permanent == null || exileZone == null || exileZone.isEmpty()) { return false; } diff --git a/Mage.Sets/src/mage/cards/d/DayOfTheDragons.java b/Mage.Sets/src/mage/cards/d/DayOfTheDragons.java index 660d41a4655..791ea5f5ba7 100644 --- a/Mage.Sets/src/mage/cards/d/DayOfTheDragons.java +++ b/Mage.Sets/src/mage/cards/d/DayOfTheDragons.java @@ -78,7 +78,7 @@ class DayOfTheDragonsEntersEffect extends OneShotEffect { Set toExile = new HashSet<>(); toExile.addAll(game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)); if (!toExile.isEmpty()) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); controller.moveCardsToExile(toExile, source, game, true, exileId, sourceObject.getIdName()); DragonToken2 token = new DragonToken2(); token.putOntoBattlefield(toExile.size(), game, source, source.getControllerId()); @@ -110,7 +110,7 @@ class DayOfTheDragonsLeavesEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (controller != null) { - int zoneChangeCounter = source.getSourceObjectZoneChangeCounter(); + int zoneChangeCounter = source.getStackMomentSourceZCC(); if (zoneChangeCounter > 0 && !(sourceObject instanceof PermanentToken)) { zoneChangeCounter--; } diff --git a/Mage.Sets/src/mage/cards/d/DayOfTheMoon.java b/Mage.Sets/src/mage/cards/d/DayOfTheMoon.java index a5d100572d2..f5e39a65e3e 100644 --- a/Mage.Sets/src/mage/cards/d/DayOfTheMoon.java +++ b/Mage.Sets/src/mage/cards/d/DayOfTheMoon.java @@ -101,7 +101,7 @@ class DayOfTheMoonEffect extends OneShotEffect { } private static List getOrSetValue(Game game, Ability source) { - String key = "DayOfTheMoon_" + source.getControllerId() + '_' + source.getSourceObjectZoneChangeCounter(); + String key = "DayOfTheMoon_" + source.getControllerId() + '_' + source.getStackMomentSourceZCC(); List list = (List) game.getState().getValue(key); if (list != null) { return list; diff --git a/Mage.Sets/src/mage/cards/d/DeathbonnetHulk.java b/Mage.Sets/src/mage/cards/d/DeathbonnetHulk.java index 5dabd16cdb4..a796956ec84 100644 --- a/Mage.Sets/src/mage/cards/d/DeathbonnetHulk.java +++ b/Mage.Sets/src/mage/cards/d/DeathbonnetHulk.java @@ -2,12 +2,15 @@ package mage.cards.d; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; @@ -33,9 +36,7 @@ public final class DeathbonnetHulk extends CardImpl { this.nightCard = true; // At the beginning of your upkeep, you may exile a card from a graveyard. If a creature card was exiled this way, put a +1/+1 counter on Deathbonnet Hulk. - this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new DeathbonnetHulkEffect() - )); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DeathbonnetHulkEffect())); } private DeathbonnetHulk(final DeathbonnetHulk card) { diff --git a/Mage.Sets/src/mage/cards/d/DeathriteShaman.java b/Mage.Sets/src/mage/cards/d/DeathriteShaman.java index efa2186a081..88b91646ca8 100644 --- a/Mage.Sets/src/mage/cards/d/DeathriteShaman.java +++ b/Mage.Sets/src/mage/cards/d/DeathriteShaman.java @@ -1,6 +1,5 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -16,14 +15,14 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterLandCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInGraveyard; +import java.util.UUID; + /** * * @author LevelX2 diff --git a/Mage.Sets/src/mage/cards/d/DeceptiveLandscape.java b/Mage.Sets/src/mage/cards/d/DeceptiveLandscape.java index a795b780bd0..2b8bfcc9640 100644 --- a/Mage.Sets/src/mage/cards/d/DeceptiveLandscape.java +++ b/Mage.Sets/src/mage/cards/d/DeceptiveLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class DeceptiveLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Plains, Swamp, or Forest card"); + private static final FilterCard filter = new FilterBasicCard("a basic Plains, Swamp, or Forest card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/d/DeepCavernBat.java b/Mage.Sets/src/mage/cards/d/DeepCavernBat.java index 96b1573da43..67564ae1bd3 100644 --- a/Mage.Sets/src/mage/cards/d/DeepCavernBat.java +++ b/Mage.Sets/src/mage/cards/d/DeepCavernBat.java @@ -91,9 +91,7 @@ class DeepCaverBatEffect extends OneShotEffect { return true; } - TargetCard target = new TargetCardInHand( - 0, 1, StaticFilters.FILTER_CARD_A_NON_LAND - ); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); controller.choose(outcome, opponent.getHand(), target, source, game); Card card = opponent.getHand().get(target.getFirstTarget(), game); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/d/DefenseOfTheHeart.java b/Mage.Sets/src/mage/cards/d/DefenseOfTheHeart.java index 7278452a172..31e5db66543 100644 --- a/Mage.Sets/src/mage/cards/d/DefenseOfTheHeart.java +++ b/Mage.Sets/src/mage/cards/d/DefenseOfTheHeart.java @@ -2,35 +2,32 @@ package mage.cards.d; import mage.abilities.Ability; import mage.abilities.condition.Condition; -import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.effects.common.SacrificeSourceEffect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.ComparisonType; import mage.filter.StaticFilters; -import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.game.Controllable; +import mage.game.Game; import mage.target.common.TargetCardInLibrary; import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; /** * @author Plopman */ public final class DefenseOfTheHeart extends CardImpl { - private static final Condition condition = new PermanentsOnTheBattlefieldCondition( - new FilterOpponentsCreaturePermanent("an opponent controls three or more creatures"), - ComparisonType.MORE_THAN, 2 - ); - public DefenseOfTheHeart(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); // At the beginning of your upkeep, if an opponent controls three or more creatures, sacrifice Defense of the Heart, search your library for up to two creature cards, and put those cards onto the battlefield. Then shuffle your library. - Ability ability = new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceEffect()).withInterveningIf(condition); + Ability ability = new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceEffect()) + .withInterveningIf(DefenseOfTheHeartCondition.instance); ability.addEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary( 0, 2, StaticFilters.FILTER_CARD_CREATURES )).concatBy(",")); @@ -46,3 +43,28 @@ public final class DefenseOfTheHeart extends CardImpl { return new DefenseOfTheHeart(this); } } + +enum DefenseOfTheHeartCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, + source.getControllerId(), source, game + ) + .stream() + .map(Controllable::getControllerId) + .collect(Collectors.toMap(Function.identity(), x -> 1, Integer::sum)) + .values() + .stream() + .anyMatch(x -> x >= 3); + } + + @Override + public String toString() { + return "an opponent controls three or more creatures"; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DelverOfSecrets.java b/Mage.Sets/src/mage/cards/d/DelverOfSecrets.java index ec29df45be6..0b89a031e26 100644 --- a/Mage.Sets/src/mage/cards/d/DelverOfSecrets.java +++ b/Mage.Sets/src/mage/cards/d/DelverOfSecrets.java @@ -1,23 +1,20 @@ - package mage.cards.d; -import java.util.UUID; - import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.*; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; -import mage.filter.FilterCard; -import mage.filter.common.FilterInstantOrSorceryCard; +import mage.constants.SubType; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; /** * @author Alvin @@ -51,11 +48,10 @@ public final class DelverOfSecrets extends CardImpl { class DelverOfSecretsEffect extends OneShotEffect { - private static final FilterCard filter = new FilterInstantOrSorceryCard(); - public DelverOfSecretsEffect() { super(Outcome.Benefit); - this.staticText = "look at the top card of your library. You may reveal that card. If an instant or sorcery card is revealed this way, transform {this}"; + this.staticText = "look at the top card of your library. You may reveal that card. " + + "If an instant or sorcery card is revealed this way, transform {this}"; } private DelverOfSecretsEffect(final DelverOfSecretsEffect effect) { @@ -70,25 +66,22 @@ class DelverOfSecretsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); - if (player == null || sourcePermanent == null) { + if (player == null || !player.getLibrary().hasCards()) { return false; } - if (player.getLibrary().hasCards()) { - Card card = player.getLibrary().getFromTop(game); - if(card == null){ - return false; - } - Cards cards = new CardsImpl(); - cards.add(card); - player.lookAtCards(sourcePermanent.getName(), cards, game); - if (player.chooseUse(Outcome.DrawCard, "Reveal the top card of your library?", source, game)) { - player.revealCards(sourcePermanent.getName(), cards, game); - if (filter.match(card, game)) { - return new TransformSourceEffect().apply(game, source); - } - } - + Card card = player.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + Cards cards = new CardsImpl(card); + player.lookAtCards(CardUtil.getSourceLogName(game, source), cards, game); + if (!player.chooseUse(Outcome.DrawCard, "Reveal the top card of your library?", source, game)) { + return false; + } + player.revealCards(source, cards, game); + if (card.isInstantOrSorcery(game)) { + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(permanent -> permanent.transform(source, game)); } return true; } diff --git a/Mage.Sets/src/mage/cards/d/DennickPiousApparition.java b/Mage.Sets/src/mage/cards/d/DennickPiousApparition.java index 5559ba3a766..ce39618f468 100644 --- a/Mage.Sets/src/mage/cards/d/DennickPiousApparition.java +++ b/Mage.Sets/src/mage/cards/d/DennickPiousApparition.java @@ -2,9 +2,8 @@ package mage.cards.d; import mage.MageInt; import mage.abilities.common.PutCardIntoGraveFromAnywhereAllTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -43,7 +42,7 @@ public final class DennickPiousApparition extends CardImpl { ).setTriggersLimitEachTurn(1).setTriggerPhrase("Whenever one or more creature cards are put into graveyards from anywhere, ")); // If Dennick, Pious Apparition would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private DennickPiousApparition(final DennickPiousApparition card) { diff --git a/Mage.Sets/src/mage/cards/d/DenyEntry.java b/Mage.Sets/src/mage/cards/d/DenyEntry.java new file mode 100644 index 00000000000..027b7c9f74d --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DenyEntry.java @@ -0,0 +1,35 @@ +package mage.cards.d; + +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DenyEntry extends CardImpl { + + public DenyEntry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}"); + + // Counter target creature spell. Draw a card, then discard a card. + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addTarget(new TargetSpell(StaticFilters.FILTER_SPELL_CREATURE)); + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(1, 1)); + } + + private DenyEntry(final DenyEntry card) { + super(card); + } + + @Override + public DenyEntry copy() { + return new DenyEntry(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DepartedSoulkeeper.java b/Mage.Sets/src/mage/cards/d/DepartedSoulkeeper.java index c937f9d2991..2503f1601ba 100644 --- a/Mage.Sets/src/mage/cards/d/DepartedSoulkeeper.java +++ b/Mage.Sets/src/mage/cards/d/DepartedSoulkeeper.java @@ -2,8 +2,7 @@ package mage.cards.d; import mage.MageInt; import mage.abilities.common.CanBlockOnlyFlyingAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -34,7 +33,7 @@ public final class DepartedSoulkeeper extends CardImpl { this.addAbility(new CanBlockOnlyFlyingAbility()); // If Departed Soulkeeper would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private DepartedSoulkeeper(final DepartedSoulkeeper card) { diff --git a/Mage.Sets/src/mage/cards/d/DesertersDisciple.java b/Mage.Sets/src/mage/cards/d/DesertersDisciple.java new file mode 100644 index 00000000000..68f3a9142c6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DesertersDisciple.java @@ -0,0 +1,57 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DesertersDisciple extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another target creature you control with power 2 or less"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 3)); + } + + public DesertersDisciple(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.REBEL); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {T}: Another target creature you control with power 2 or less can't be blocked this turn. + Ability ability = new SimpleActivatedAbility(new CantBeBlockedTargetEffect(Duration.EndOfTurn), new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private DesertersDisciple(final DesertersDisciple card) { + super(card); + } + + @Override + public DesertersDisciple copy() { + return new DesertersDisciple(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DesperateGambit.java b/Mage.Sets/src/mage/cards/d/DesperateGambit.java index a33067ba290..719077d465c 100644 --- a/Mage.Sets/src/mage/cards/d/DesperateGambit.java +++ b/Mage.Sets/src/mage/cards/d/DesperateGambit.java @@ -137,49 +137,12 @@ class TargetControlledSource extends TargetSource { } @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - int count = 0; - for (StackObject stackObject : game.getStack()) { - if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) - && Objects.equals(stackObject.getControllerId(), sourceControllerId)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) { - if (Objects.equals(permanent.getControllerId(), sourceControllerId)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Player player : game.getPlayers().values()) { - if (Objects.equals(player, game.getPlayer(sourceControllerId))) { - for (Card card : player.getGraveyard().getCards(game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - // 108.4a If anything asks for the controller of a card that doesn't have one (because it's not a permanent or spell), use its owner instead. - for (Card card : game.getExile().getAllCards(game)) { - if (Objects.equals(card.getOwnerId(), sourceControllerId)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - } - } - return false; + public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); for (StackObject stackObject : game.getStack()) { if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) @@ -205,7 +168,7 @@ class TargetControlledSource extends TargetSource { } } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DetentionSphere.java b/Mage.Sets/src/mage/cards/d/DetentionSphere.java index c16dc102058..4238b79c43e 100644 --- a/Mage.Sets/src/mage/cards/d/DetentionSphere.java +++ b/Mage.Sets/src/mage/cards/d/DetentionSphere.java @@ -74,7 +74,7 @@ class DetentionSphereEntersEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source); diff --git a/Mage.Sets/src/mage/cards/d/DevotedGrafkeeper.java b/Mage.Sets/src/mage/cards/d/DevotedGrafkeeper.java index b54e5ad7092..e16068a08a7 100644 --- a/Mage.Sets/src/mage/cards/d/DevotedGrafkeeper.java +++ b/Mage.Sets/src/mage/cards/d/DevotedGrafkeeper.java @@ -1,20 +1,19 @@ package mage.cards.d; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.MillCardsControllerEffect; import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.keyword.DisturbAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.events.GameEvent; import mage.target.TargetPermanent; import java.util.UUID; @@ -37,7 +36,12 @@ public final class DevotedGrafkeeper extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(2))); // Whenever you cast a spell from your graveyard, tap target creature you don't control. - this.addAbility(new DevotedGrafkeeperTriggeredAbility()); + Ability ability = new SpellCastControllerTriggeredAbility( + Zone.BATTLEFIELD, new TapTargetEffect(), StaticFilters.FILTER_SPELL_A, + false, SetTargetPointer.NONE, Zone.GRAVEYARD + ); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); + this.addAbility(ability); // Disturb {1}{W}{U} this.addAbility(new DisturbAbility(this, "{1}{W}{U}")); @@ -52,35 +56,3 @@ public final class DevotedGrafkeeper extends CardImpl { return new DevotedGrafkeeper(this); } } - -class DevotedGrafkeeperTriggeredAbility extends TriggeredAbilityImpl { - - DevotedGrafkeeperTriggeredAbility() { - super(Zone.BATTLEFIELD, new TapTargetEffect(), false); - this.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); - } - - private DevotedGrafkeeperTriggeredAbility(DevotedGrafkeeperTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.SPELL_CAST; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return isControlledBy(event.getPlayerId()) && event.getZone() == Zone.GRAVEYARD; - } - - @Override - public DevotedGrafkeeperTriggeredAbility copy() { - return new DevotedGrafkeeperTriggeredAbility(this); - } - - @Override - public String getRule() { - return "Whenever you cast a spell from your graveyard, tap target creature you don't control."; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DistendedMindbender.java b/Mage.Sets/src/mage/cards/d/DistendedMindbender.java index d12bec48bd7..fad22da9f7e 100644 --- a/Mage.Sets/src/mage/cards/d/DistendedMindbender.java +++ b/Mage.Sets/src/mage/cards/d/DistendedMindbender.java @@ -90,10 +90,10 @@ class DistendedMindbenderEffect extends OneShotEffect { TargetCard targetThreeOrLess = new TargetCard(1, Zone.HAND, filterThreeOrLess); TargetCard targetFourOrGreater = new TargetCard(1, Zone.HAND, filterFourOrGreater); Cards toDiscard = new CardsImpl(); - if (controller.chooseTarget(Outcome.Benefit, opponent.getHand(), targetThreeOrLess, source, game)) { + if (controller.choose(Outcome.Benefit, opponent.getHand(), targetThreeOrLess, source, game)) { toDiscard.addAll(targetThreeOrLess.getTargets()); } - if (controller.chooseTarget(Outcome.Benefit, opponent.getHand(), targetFourOrGreater, source, game)) { + if (controller.choose(Outcome.Benefit, opponent.getHand(), targetFourOrGreater, source, game)) { toDiscard.addAll(targetFourOrGreater.getTargets()); } opponent.discard(toDiscard, false, source, game); diff --git a/Mage.Sets/src/mage/cards/d/DocOckSinisterScientist.java b/Mage.Sets/src/mage/cards/d/DocOckSinisterScientist.java new file mode 100644 index 00000000000..675412e19cf --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DocOckSinisterScientist.java @@ -0,0 +1,69 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DocOckSinisterScientist extends CardImpl { + + private static final Condition condition = new CardsInControllerGraveyardCondition(8); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.VILLAIN); + + static { + filter.add(AnotherPredicate.instance); + } + + private static final Condition condition2 = new PermanentsOnTheBattlefieldCondition(filter); + + public DocOckSinisterScientist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCIENTIST); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // As long as there are eight or more cards in your graveyard, Doc Ock has base power and toughness 8/8. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new SetBasePowerToughnessSourceEffect(8, 8, Duration.WhileOnBattlefield), condition, + "as long as there are eight or more cards in your graveyard, {this} has base power and toughness 8/8" + ))); + + // As long as you control another Villain, Doc Ock has hexproof. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(HexproofAbility.getInstance()), condition2, + "as long as you control another Villain, {this} has hexproof" + ))); + } + + private DocOckSinisterScientist(final DocOckSinisterScientist card) { + super(card); + } + + @Override + public DocOckSinisterScientist copy() { + return new DocOckSinisterScientist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DocOcksTentacles.java b/Mage.Sets/src/mage/cards/d/DocOcksTentacles.java new file mode 100644 index 00000000000..33d12582124 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DocOcksTentacles.java @@ -0,0 +1,57 @@ +package mage.cards.d; + +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.ManaValuePredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DocOcksTentacles extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("a creature you control with mana value 5 or greater"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.MORE_THAN, 4)); + } + + public DocOcksTentacles(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Whenever a creature you control with mana value 5 or greater enters, you may attach this Equipment to it. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new AttachEffect(Outcome.BoostCreature, "attach {this} to it"), filter, true + )); + + // Equipped creature gets +4/+4. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(4, 4))); + + // Equip {5} + this.addAbility(new EquipAbility(5)); + } + + private DocOcksTentacles(final DocOcksTentacles card) { + super(card); + } + + @Override + public DocOcksTentacles copy() { + return new DocOcksTentacles(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DocentOfPerfection.java b/Mage.Sets/src/mage/cards/d/DocentOfPerfection.java index db808cdc156..129e98d02db 100644 --- a/Mage.Sets/src/mage/cards/d/DocentOfPerfection.java +++ b/Mage.Sets/src/mage/cards/d/DocentOfPerfection.java @@ -1,42 +1,40 @@ - package mage.cards.d; -import java.util.UUID; - import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SpellCastControllerTriggeredAbility; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; +import mage.constants.ComparisonType; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.filter.FilterPermanent; -import mage.filter.FilterSpell; -import mage.filter.predicate.Predicates; -import mage.game.Game; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; import mage.game.permanent.token.HumanWizardToken; -import mage.players.Player; + +import java.util.UUID; /** * @author fireshoes */ public final class DocentOfPerfection extends CardImpl { - private static final FilterSpell filterSpell = new FilterSpell("an instant or sorcery spell"); - - static { - filterSpell.add(Predicates.or( - CardType.INSTANT.getPredicate(), - CardType.SORCERY.getPredicate())); - } + private static final Condition condition = new PermanentsOnTheBattlefieldCondition( + new FilterControlledPermanent(SubType.WIZARD), ComparisonType.MORE_THAN, 2 + ); + private static final Hint hint = new ValueHint( + "Wizards you control", new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.WIZARD)) + ); public DocentOfPerfection(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); @@ -53,10 +51,15 @@ public final class DocentOfPerfection extends CardImpl { // Whenever you cast an instant or sorcery spell, create a 1/1 blue Human Wizard creature token. // Then if you control three or more Wizards, transform Docent of Perfection. this.addAbility(new TransformAbility()); - Effect effect = new DocentOfPerfectionEffect(); - Ability ability = new SpellCastControllerTriggeredAbility(new CreateTokenEffect(new HumanWizardToken()), filterSpell, false); - ability.addEffect(effect); - this.addAbility(ability); + Ability ability = new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new HumanWizardToken()), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false + ); + ability.addEffect(new ConditionalOneShotEffect( + new TransformSourceEffect(), condition, + "Then if you control three or more Wizards, transform {this}" + )); + this.addAbility(ability.addHint(hint)); } private DocentOfPerfection(final DocentOfPerfection card) { @@ -68,38 +71,3 @@ public final class DocentOfPerfection extends CardImpl { return new DocentOfPerfection(this); } } - -class DocentOfPerfectionEffect extends OneShotEffect { - - private static final FilterPermanent filter = new FilterPermanent("Wizards"); - - static { - filter.add(SubType.WIZARD.getPredicate()); - filter.add(TargetController.YOU.getControllerPredicate()); - } - - public DocentOfPerfectionEffect() { - super(Outcome.Benefit); - staticText = "Then if you control three or more Wizards, transform {this}"; - } - - private DocentOfPerfectionEffect(final DocentOfPerfectionEffect effect) { - super(effect); - } - - @Override - public DocentOfPerfectionEffect copy() { - return new DocentOfPerfectionEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - if (game.getBattlefield().count(filter, source.getControllerId(), source, game) >= 3) { - return new TransformSourceEffect().apply(game, source); - } - } - return false; - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DoctorOctopusMasterPlanner.java b/Mage.Sets/src/mage/cards/d/DoctorOctopusMasterPlanner.java new file mode 100644 index 00000000000..72ae12cb57c --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DoctorOctopusMasterPlanner.java @@ -0,0 +1,58 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInHandCondition; +import mage.abilities.effects.common.DrawCardsEqualToDifferenceEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DoctorOctopusMasterPlanner extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.VILLAIN, "Villains"); + private static final Condition condition = new CardsInHandCondition(ComparisonType.FEWER_THAN, 8); + + public DoctorOctopusMasterPlanner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCIENTIST); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(4); + this.toughness = new MageInt(8); + + // Other Villains you control get +2/+2. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + 2, 2, Duration.WhileOnBattlefield, filter, true + ))); + + // Your maximum hand size is eight. + this.addAbility(new SimpleStaticAbility(new MaximumHandSizeControllerEffect( + 8, Duration.WhileOnBattlefield, MaximumHandSizeControllerEffect.HandSizeModification.SET + ))); + + // At the beginning of your end step, if you have fewer than eight cards in hand, draw cards equal to the difference. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new DrawCardsEqualToDifferenceEffect(8)).withInterveningIf(condition)); + } + + private DoctorOctopusMasterPlanner(final DoctorOctopusMasterPlanner card) { + super(card); + } + + @Override + public DoctorOctopusMasterPlanner copy() { + return new DoctorOctopusMasterPlanner(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DorotheasRetribution.java b/Mage.Sets/src/mage/cards/d/DorotheasRetribution.java index f884b920053..688994337d7 100644 --- a/Mage.Sets/src/mage/cards/d/DorotheasRetribution.java +++ b/Mage.Sets/src/mage/cards/d/DorotheasRetribution.java @@ -2,14 +2,13 @@ package mage.cards.d; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.SacrificeTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -43,8 +42,7 @@ public final class DorotheasRetribution extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature has "Whenever this creature attacks, create a 4/4 white Spirit creature token with flying that's tapped and attacking. Sacrifice that token at end of combat." this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect( @@ -54,7 +52,7 @@ public final class DorotheasRetribution extends CardImpl { ))); // If Dorothea's Retribution would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private DorotheasRetribution(final DorotheasRetribution card) { diff --git a/Mage.Sets/src/mage/cards/d/DoubleTrouble.java b/Mage.Sets/src/mage/cards/d/DoubleTrouble.java new file mode 100644 index 00000000000..3dd0b2a5ba1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DoubleTrouble.java @@ -0,0 +1,68 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +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.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DoubleTrouble extends CardImpl { + + public DoubleTrouble(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}"); + + // Double the power of each creature you control until end of turn. + this.getSpellAbility().addEffect(new DoubleTroubleEffect()); + } + + private DoubleTrouble(final DoubleTrouble card) { + super(card); + } + + @Override + public DoubleTrouble copy() { + return new DoubleTrouble(this); + } +} + +class DoubleTroubleEffect extends OneShotEffect { + + DoubleTroubleEffect() { + super(Outcome.Benefit); + staticText = "double the power of each creature you control until end of turn"; + } + + private DoubleTroubleEffect(final DoubleTroubleEffect effect) { + super(effect); + } + + @Override + public DoubleTroubleEffect copy() { + return new DoubleTroubleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, source.getControllerId(), source, game + )) { + int power = permanent.getPower().getValue(); + if (power != 0) { + game.addEffect(new BoostTargetEffect(power, 0) + .setTargetPointer(new FixedTarget(permanent, game)), source); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DourPortMage.java b/Mage.Sets/src/mage/cards/d/DourPortMage.java index 34cf369b0b7..070db06b979 100644 --- a/Mage.Sets/src/mage/cards/d/DourPortMage.java +++ b/Mage.Sets/src/mage/cards/d/DourPortMage.java @@ -2,8 +2,7 @@ package mage.cards.d; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.BatchTriggeredAbility; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.OneOrMoreLeaveWithoutDyingTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; @@ -13,13 +12,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeBatchEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import java.util.UUID; @@ -38,7 +31,9 @@ public final class DourPortMage extends CardImpl { this.toughness = new MageInt(3); // Whenever one or more other creatures you control leave the battlefield without dying, draw a card. - this.addAbility(new DourPortMageTriggeredAbility()); + this.addAbility(new OneOrMoreLeaveWithoutDyingTriggeredAbility( + new DrawCardSourceControllerEffect(1), StaticFilters.FILTER_OTHER_CONTROLLED_CREATURES + )); // {1}{U}, {T}: Return another target creature you control to its owner's hand. Ability ability = new SimpleActivatedAbility(new ReturnToHandTargetEffect(), new ManaCostsImpl<>("{1}{U}")); @@ -56,39 +51,3 @@ public final class DourPortMage extends CardImpl { return new DourPortMage(this); } } - -class DourPortMageTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { - - DourPortMageTriggeredAbility() { - super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); - setTriggerPhrase("Whenever one or more other creatures you control leave the battlefield without dying, "); - } - - private DourPortMageTriggeredAbility(final DourPortMageTriggeredAbility ability) { - super(ability); - } - - @Override - public DourPortMageTriggeredAbility copy() { - return new DourPortMageTriggeredAbility(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.BATTLEFIELD || event.getToZone() == Zone.GRAVEYARD || event.getTargetId().equals(getSourceId())) { - return false; - } - Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); - return permanent != null && permanent.isCreature(game) && permanent.isControlledBy(getControllerId()); - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return !getFilteredEvents((ZoneChangeBatchEvent) event, game).isEmpty(); - } -} diff --git a/Mage.Sets/src/mage/cards/d/DowsingDagger.java b/Mage.Sets/src/mage/cards/d/DowsingDagger.java index 8a2f5db3934..a5b02e21193 100644 --- a/Mage.Sets/src/mage/cards/d/DowsingDagger.java +++ b/Mage.Sets/src/mage/cards/d/DowsingDagger.java @@ -1,8 +1,6 @@ package mage.cards.d; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.common.DealsDamageToAPlayerAttachedTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -13,16 +11,16 @@ import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.TransformAbility; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.SubType; import mage.game.permanent.token.DefenderPlantToken; -import mage.target.Target; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * @author TheElk801 */ @@ -37,8 +35,7 @@ public final class DowsingDagger extends CardImpl { // When Dowsing Dagger enters the battlefield, target opponent creates two 0/2 green Plant creature tokens with defender. Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenTargetEffect(new DefenderPlantToken(), 2), false); - Target target = new TargetOpponent(); - ability.addTarget(target); + ability.addTarget(new TargetOpponent()); this.addAbility(ability); // Equipped creature gets +2/+1. diff --git a/Mage.Sets/src/mage/cards/d/DrafnasRestoration.java b/Mage.Sets/src/mage/cards/d/DrafnasRestoration.java index 1641294bdf5..47486d9fbe5 100644 --- a/Mage.Sets/src/mage/cards/d/DrafnasRestoration.java +++ b/Mage.Sets/src/mage/cards/d/DrafnasRestoration.java @@ -1,25 +1,26 @@ package mage.cards.d; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.cards.*; +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.filter.common.FilterArtifactCard; import mage.game.Game; -import mage.game.events.TargetEvent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPlayer; import mage.target.common.TargetCardInGraveyard; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + /** - * * @author emerald000 */ public final class DrafnasRestoration extends CardImpl { @@ -53,27 +54,33 @@ class DrafnasRestorationTarget extends TargetCardInGraveyard { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Player targetPlayer = game.getPlayer(source.getFirstTarget()); - return targetPlayer != null && targetPlayer.getGraveyard().contains(id) && super.canTarget(id, source, game); - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); - if (object instanceof StackObject) { - Player targetPlayer = game.getPlayer(((StackObject) object).getStackAbility().getFirstTarget()); - if (targetPlayer != null) { - for (Card card : targetPlayer.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets.add(card.getId()); - } - } - } + + Player controller = game.getPlayer(sourceControllerId); + if (controller == null) { + return possibleTargets; } - return possibleTargets; + + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + game.getState().getPlayersInRange(sourceControllerId, game, true).stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .flatMap(player -> player.getGraveyard().getCards(filter, sourceControllerId, source, game).stream()) + .forEach(card -> { + if (targetPlayer == null) { + // playable or not selected - use any + possibleTargets.add(card.getId()); + } else { + // selected, filter by player + if (targetPlayer.getId().equals(card.getControllerOrOwnerId())) { + possibleTargets.add(card.getId()); + } + } + }); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DragonMoose.java b/Mage.Sets/src/mage/cards/d/DragonMoose.java new file mode 100644 index 00000000000..9a2fe5bb4ff --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DragonMoose.java @@ -0,0 +1,37 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.keyword.HasteAbility; +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 DragonMoose extends CardImpl { + + public DragonMoose(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.DRAGON); + this.subtype.add(SubType.ELK); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Haste + this.addAbility(HasteAbility.getInstance()); + } + + private DragonMoose(final DragonMoose card) { + super(card); + } + + @Override + public DragonMoose copy() { + return new DragonMoose(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DragonWhelp.java b/Mage.Sets/src/mage/cards/d/DragonWhelp.java index 3d721f6e9c9..f30efb7ff6a 100644 --- a/Mage.Sets/src/mage/cards/d/DragonWhelp.java +++ b/Mage.Sets/src/mage/cards/d/DragonWhelp.java @@ -19,7 +19,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; import mage.game.Game; /** @@ -74,7 +73,7 @@ class DragonWhelpEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - ActivationInfo activationInfo = ActivationInfo.getInstance(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + ActivationInfo activationInfo = ActivationInfo.getInstance(game, source.getSourceId(), source.getStackMomentSourceZCC()); activationInfo.addActivation(game); if (activationInfo.getActivationCounter() >= 4) { DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new SacrificeSourceEffect()); diff --git a/Mage.Sets/src/mage/cards/d/DragonsFire.java b/Mage.Sets/src/mage/cards/d/DragonsFire.java index 3492742a074..acb2a20cc50 100644 --- a/Mage.Sets/src/mage/cards/d/DragonsFire.java +++ b/Mage.Sets/src/mage/cards/d/DragonsFire.java @@ -18,8 +18,8 @@ import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.TargetPermanent; import mage.target.common.TargetCardInHand; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreatureOrPlaneswalker; /** @@ -54,11 +54,10 @@ class DragonsFireCost extends CostImpl { public enum DragonZone { HAND, - BATTLEFIELD, - NONE + BATTLEFIELD } - private DragonZone dragonZone = DragonZone.NONE; + private DragonZone dragonZone = null; private UUID selectedCardId = null; private static final FilterCard handFilter = new FilterCard("Dragon card from your hand"); @@ -90,14 +89,13 @@ class DragonsFireCost extends CostImpl { @Override public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { - this.getTargets().clear(); - dragonZone = DragonZone.NONE; + dragonZone = null; selectedCardId = null; Player controller = game.getPlayer(controllerId); if (controller != null) { boolean dragonInHand = false; boolean dragonOnBattlefield = false; - DragonZone chosenZone = DragonZone.NONE; + DragonZone chosenZone = null; for (UUID cardId : controller.getHand()) { Card card = game.getCard(cardId); if (card != null && card.hasSubtype(SubType.DRAGON, game)) { @@ -132,23 +130,25 @@ class DragonsFireCost extends CostImpl { } switch (chosenZone) { case HAND: - this.getTargets().add(new TargetCardInHand(handFilter)); - if (this.getTargets().choose(Outcome.Benefit, controllerId, source.getSourceId(), source, game)) { - Card card = game.getCard(this.getTargets().getFirstTarget()); + TargetCardInHand handTarget = new TargetCardInHand(handFilter); + handTarget.withNotTarget(true); + if (controller.choose(Outcome.Benefit, handTarget, source, game)) { + Card card = game.getCard(handTarget.getFirstTarget()); if (card != null) { dragonZone = DragonZone.HAND; - selectedCardId = this.getTargets().getFirstTarget(); + selectedCardId = handTarget.getFirstTarget(); controller.revealCards(source, new CardsImpl(card), game); } } break; case BATTLEFIELD: - this.getTargets().add(new TargetControlledPermanent(battlefieldFilter)); - if (this.getTargets().choose(Outcome.Benefit, controllerId, source.getSourceId(), source, game)) { - Permanent permanent = game.getPermanent(this.getTargets().getFirstTarget()); + TargetPermanent battlefieldTarget = new TargetPermanent(battlefieldFilter); + battlefieldTarget.withNotTarget(true); + if (controller.choose(Outcome.Benefit, battlefieldTarget, source, game)) { + Permanent permanent = game.getPermanent(battlefieldTarget.getFirstTarget()); if (permanent != null) { dragonZone = DragonZone.BATTLEFIELD; - selectedCardId = this.getTargets().getFirstTarget(); + selectedCardId = battlefieldTarget.getFirstTarget(); game.informPlayers(controller.getLogName() + " chooses " + permanent.getLogName()); } } @@ -190,7 +190,7 @@ class DragonsFireEffect extends OneShotEffect { if (targetedPermanent == null) { return false; } - DragonsFireCost.DragonZone dragonZone = DragonsFireCost.DragonZone.NONE; + DragonsFireCost.DragonZone dragonZone = null; UUID selectedCardId = null; int damage = 3; for (Cost cost : source.getCosts()) { @@ -201,21 +201,23 @@ class DragonsFireEffect extends OneShotEffect { break; } } - switch (dragonZone) { - case HAND: - Card card = game.getCard(selectedCardId); - if (card != null) { - damage = card.getPower().getValue(); - } - break; - case BATTLEFIELD: - Permanent dragon = game.getPermanentOrLKIBattlefield(selectedCardId); - if (dragon != null) { - damage = dragon.getPower().getValue(); - } - break; + if (dragonZone != null) { + switch (dragonZone) { + case HAND: + Card card = game.getCard(selectedCardId); + if (card != null) { + damage = card.getPower().getValue(); + } + break; + case BATTLEFIELD: + Permanent dragon = game.getPermanentOrLKIBattlefield(selectedCardId); + if (dragon != null) { + damage = dragon.getPower().getValue(); + } + break; + } } targetedPermanent.damage(damage, source.getSourceId(), source, game); return true; } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DralnusPet.java b/Mage.Sets/src/mage/cards/d/DralnusPet.java index 20979c4cffa..d3daea9aa08 100644 --- a/Mage.Sets/src/mage/cards/d/DralnusPet.java +++ b/Mage.Sets/src/mage/cards/d/DralnusPet.java @@ -92,7 +92,7 @@ class DralnusPetEffect extends OneShotEffect { SpellAbility spellAbility = (SpellAbility) getValue(EntersBattlefieldEffect.SOURCE_CAST_SPELL_ABILITY); if (spellAbility != null && spellAbility.getSourceId().equals(source.getSourceId()) - && permanent.getZoneChangeCounter(game) == spellAbility.getSourceObjectZoneChangeCounter()) { + && permanent.getZoneChangeCounter(game) == spellAbility.getStackMomentSourceZCC()) { int cmc = 0; for (Cost cost : spellAbility.getCosts()) { if (cost instanceof DiscardCardCost && !((DiscardCardCost) cost).getCards().isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/d/DreamDevourer.java b/Mage.Sets/src/mage/cards/d/DreamDevourer.java index 0ed63bc1e30..688b87cf66d 100644 --- a/Mage.Sets/src/mage/cards/d/DreamDevourer.java +++ b/Mage.Sets/src/mage/cards/d/DreamDevourer.java @@ -1,27 +1,17 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.ForetellSourceControllerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.keyword.ForetellAbility; -import mage.cards.*; -import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; -import mage.filter.common.FilterNonlandCard; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.game.Game; -import mage.players.Player; -import mage.util.CardUtil; +import mage.constants.SubType; + +import java.util.UUID; /** * @@ -38,7 +28,7 @@ public final class DreamDevourer extends CardImpl { this.toughness = new MageInt(3); // Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by 2. - this.addAbility(new SimpleStaticAbility(new DreamDevourerAddAbilityEffect())); + this.addAbility(new SimpleStaticAbility(ForetellAbility.makeAddForetellEffect())); // Whenever you foretell a card, Dream Devourer gets +2/+0 until end of turn. this.addAbility(new ForetellSourceControllerTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn))); @@ -54,69 +44,3 @@ public final class DreamDevourer extends CardImpl { return new DreamDevourer(this); } } - -class DreamDevourerAddAbilityEffect extends ContinuousEffectImpl { - - private static final FilterNonlandCard filter = new FilterNonlandCard(); - - static { - filter.add(Predicates.not(new AbilityPredicate(ForetellAbility.class))); - } - - DreamDevourerAddAbilityEffect() { - super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.staticText = "Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by {2}"; - } - - private DreamDevourerAddAbilityEffect(final DreamDevourerAddAbilityEffect effect) { - super(effect); - } - - @Override - public DreamDevourerAddAbilityEffect copy() { - return new DreamDevourerAddAbilityEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - for (Card card : controller.getHand().getCards(filter, game)) { - ForetellAbility foretellAbility = null; - if (card instanceof SplitCard) { - String leftHalfCost = CardUtil.reduceCost(((SplitCard) card).getLeftHalfCard().getManaCost(), 2).getText(); - String rightHalfCost = CardUtil.reduceCost(((SplitCard) card).getRightHalfCard().getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); - } else if (card instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - // If front side of MDFC is land, do nothing as Dream Devourer does not apply to lands - // MDFC cards in hand are considered lands if front side is land - if (!leftHalfCard.isLand(game)) { - String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), 2).getText(); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); - if (rightHalfCard.isLand(game)) { - foretellAbility = new ForetellAbility(card, leftHalfCost); - } else { - String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); - } - } - } else if (card instanceof CardWithSpellOption) { - String creatureCost = CardUtil.reduceCost(card.getMainCard().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(); - foretellAbility = new ForetellAbility(card, costText); - } - if (foretellAbility != null) { - foretellAbility.setSourceId(card.getId()); - foretellAbility.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card, foretellAbility); - } - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DreamsOfSteelAndOil.java b/Mage.Sets/src/mage/cards/d/DreamsOfSteelAndOil.java index 3c569c5ea9d..b3d7f51fdf7 100644 --- a/Mage.Sets/src/mage/cards/d/DreamsOfSteelAndOil.java +++ b/Mage.Sets/src/mage/cards/d/DreamsOfSteelAndOil.java @@ -72,7 +72,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect { filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(), CardType.CREATURE.getPredicate())); TargetCard target = new TargetCard(Zone.HAND, filter); target.withNotTarget(true); - controller.chooseTarget(Outcome.Discard, opponent.getHand(), target, source, game); + controller.choose(Outcome.Discard, opponent.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { toExile.add(card); @@ -81,7 +81,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect { filter.setMessage("artifact or creature card from " + opponent.getName() + "'s graveyard"); target = new TargetCard(Zone.GRAVEYARD, filter); target.withNotTarget(true); - controller.chooseTarget(Outcome.Exile, opponent.getGraveyard(), target, source, game); + controller.choose(Outcome.Exile, opponent.getGraveyard(), target, source, game); card = game.getCard(target.getFirstTarget()); if (card != null) { toExile.add(card); diff --git a/Mage.Sets/src/mage/cards/d/DrogskolArmaments.java b/Mage.Sets/src/mage/cards/d/DrogskolArmaments.java index e071b99cdb0..dd67d25ed9d 100644 --- a/Mage.Sets/src/mage/cards/d/DrogskolArmaments.java +++ b/Mage.Sets/src/mage/cards/d/DrogskolArmaments.java @@ -1,11 +1,9 @@ package mage.cards.d; -import mage.abilities.Ability; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -33,14 +31,13 @@ public final class DrogskolArmaments extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature gets +2/+2. this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(2, 2))); // If Drogskol Armaments would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private DrogskolArmaments(final DrogskolArmaments card) { diff --git a/Mage.Sets/src/mage/cards/d/DruidicRitual.java b/Mage.Sets/src/mage/cards/d/DruidicRitual.java index 83df3b1514f..f5986ca4c9d 100644 --- a/Mage.Sets/src/mage/cards/d/DruidicRitual.java +++ b/Mage.Sets/src/mage/cards/d/DruidicRitual.java @@ -105,11 +105,10 @@ class RevivalExperimentTarget extends TargetCardInYourGraveyard { return cardTypeAssigner.getRoleCount(cards, game) >= cards.size(); } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/d/DuneChanter.java b/Mage.Sets/src/mage/cards/d/DuneChanter.java index 23a1c23af93..2cd68f54296 100644 --- a/Mage.Sets/src/mage/cards/d/DuneChanter.java +++ b/Mage.Sets/src/mage/cards/d/DuneChanter.java @@ -5,6 +5,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; @@ -45,10 +46,12 @@ public final class DuneChanter extends CardImpl { this.addAbility(new SimpleStaticAbility(new DuneChanterContinuousEffect())); // Lands you control have "{T}: Add one mana of any color." - this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + ContinuousEffect effect = new GainAbilityControlledEffect( new AnyColorManaAbility(), Duration.WhileOnBattlefield, StaticFilters.FILTER_LANDS, false - ))); + ); + effect.getDependedToTypes().add(DependencyType.BecomeNonbasicLand); + this.addAbility(new SimpleStaticAbility(effect)); // {T}: Mill two cards. You gain 1 life for each land card milled this way. this.addAbility(new SimpleActivatedAbility(new DuneChanterEffect(), new TapSourceCost())); @@ -76,6 +79,7 @@ class DuneChanterContinuousEffect extends ContinuousEffectImpl { public DuneChanterContinuousEffect() { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); staticText = "Lands you control and land cards you own that aren't on the battlefield are Deserts in addition to their other types"; + dependendToTypes.add(DependencyType.BecomeNonbasicLand); } private DuneChanterContinuousEffect(final DuneChanterContinuousEffect effect) { diff --git a/Mage.Sets/src/mage/cards/d/DurkwoodTracker.java b/Mage.Sets/src/mage/cards/d/DurkwoodTracker.java index 83f1f8de7db..1fdac9d4f5c 100644 --- a/Mage.Sets/src/mage/cards/d/DurkwoodTracker.java +++ b/Mage.Sets/src/mage/cards/d/DurkwoodTracker.java @@ -70,7 +70,7 @@ class DurkwoodTrackerEffect extends OneShotEffect { Permanent permanent = game.getPermanent(source.getSourceId()); if (permanent == null || permanent.getZoneChangeCounter(game) - != source.getSourceObjectZoneChangeCounter()) { + != source.getStackMomentSourceZCC()) { return false; } Permanent targeted = game.getPermanent(source.getFirstTarget()); diff --git a/Mage.Sets/src/mage/cards/e/EarthKingdomSoldier.java b/Mage.Sets/src/mage/cards/e/EarthKingdomSoldier.java new file mode 100644 index 00000000000..58b1e7fb78b --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EarthKingdomSoldier.java @@ -0,0 +1,47 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +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.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EarthKingdomSoldier extends CardImpl { + + public EarthKingdomSoldier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G/W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When this creature enters, put a +1/+1 counter on each of up to two target creatures you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetControlledCreaturePermanent(0, 2)); + this.addAbility(ability); + } + + private EarthKingdomSoldier(final EarthKingdomSoldier card) { + super(card); + } + + @Override + public EarthKingdomSoldier copy() { + return new EarthKingdomSoldier(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EarthRumble.java b/Mage.Sets/src/mage/cards/e/EarthRumble.java new file mode 100644 index 00000000000..9a90e31c967 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EarthRumble.java @@ -0,0 +1,67 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.FightTargetsEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetControlledLandPermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EarthRumble extends CardImpl { + + public EarthRumble(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); + + // Earthbend 2. When you do, up to one target creature you control fights target creature an opponent controls. + this.getSpellAbility().addEffect(new EarthbendTargetEffect(2)); + this.getSpellAbility().addTarget(new TargetControlledLandPermanent()); + this.getSpellAbility().addEffect(new EarthRumbleEffect()); + } + + private EarthRumble(final EarthRumble card) { + super(card); + } + + @Override + public EarthRumble copy() { + return new EarthRumble(this); + } +} + +class EarthRumbleEffect extends OneShotEffect { + + EarthRumbleEffect() { + super(Outcome.Benefit); + staticText = "When you do, up to one target creature you control fights target creature an opponent controls"; + } + + private EarthRumbleEffect(final EarthRumbleEffect effect) { + super(effect); + } + + @Override + public EarthRumbleEffect copy() { + return new EarthRumbleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new FightTargetsEffect(), false); + ability.addTarget(new TargetControlledCreaturePermanent(0, 1)); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EarthVillageRuffians.java b/Mage.Sets/src/mage/cards/e/EarthVillageRuffians.java new file mode 100644 index 00000000000..25245ce79be --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EarthVillageRuffians.java @@ -0,0 +1,43 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EarthVillageRuffians extends CardImpl { + + public EarthVillageRuffians(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B/G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // When this creature dies, earthbend 2. + Ability ability = new DiesSourceTriggeredAbility(new EarthbendTargetEffect(2)); + ability.addTarget(new TargetControlledLandPermanent()); + this.addAbility(ability); + } + + private EarthVillageRuffians(final EarthVillageRuffians card) { + super(card); + } + + @Override + public EarthVillageRuffians copy() { + return new EarthVillageRuffians(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EarthbendingLesson.java b/Mage.Sets/src/mage/cards/e/EarthbendingLesson.java new file mode 100644 index 00000000000..61962ad7d52 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EarthbendingLesson.java @@ -0,0 +1,35 @@ +package mage.cards.e; + +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EarthbendingLesson extends CardImpl { + + public EarthbendingLesson(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); + + this.subtype.add(SubType.LESSON); + + // Earthbend 4. + this.getSpellAbility().addEffect(new EarthbendTargetEffect(4)); + this.getSpellAbility().addTarget(new TargetControlledLandPermanent()); + } + + private EarthbendingLesson(final EarthbendingLesson card) { + super(card); + } + + @Override + public EarthbendingLesson copy() { + return new EarthbendingLesson(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EarthbendingStudent.java b/Mage.Sets/src/mage/cards/e/EarthbendingStudent.java new file mode 100644 index 00000000000..462f2ef3e18 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EarthbendingStudent.java @@ -0,0 +1,60 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EarthbendingStudent extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("land creatures"); + + static { + filter.add(CardType.LAND.getPredicate()); + } + + public EarthbendingStudent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // When this creature enters, earthbend 2. + Ability ability = new EntersBattlefieldTriggeredAbility(new EarthbendTargetEffect(2)); + ability.addTarget(new TargetControlledLandPermanent()); + this.addAbility(ability); + + // Land creatures you control have vigilance. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + VigilanceAbility.getInstance(), Duration.WhileOnBattlefield, filter + ))); + } + + private EarthbendingStudent(final EarthbendingStudent card) { + super(card); + } + + @Override + public EarthbendingStudent copy() { + return new EarthbendingStudent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EddieBrock.java b/Mage.Sets/src/mage/cards/e/EddieBrock.java new file mode 100644 index 00000000000..cc84afc2c29 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EddieBrock.java @@ -0,0 +1,130 @@ +package mage.cards.e; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardSetInfo; +import mage.cards.ModalDoubleFacedCard; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EddieBrock extends ModalDoubleFacedCard { + + private static final FilterCard filter = new FilterCreatureCard("creature card with mana value 1 or less"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 2)); + } + + public EddieBrock(UUID ownerId, CardSetInfo setInfo) { + super( + ownerId, setInfo, + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.HERO, SubType.VILLAIN}, "{2}{B}", + "Venom, Lethal Protector", + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.SYMBIOTE, SubType.HERO, SubType.VILLAIN}, "{3}{B}{R}{G}" + ); + this.getLeftHalfCard().setPT(5, 5); + this.getRightHalfCard().setPT(5, 5); + + // When Eddie Brock enters, return target creature card with mana value 1 or less from your graveyard to the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.getLeftHalfCard().addAbility(ability); + + // {3}{B}{R}{G}: Transform Eddie Brock. Activate only as a sorcery. + this.getLeftHalfCard().addAbility(new ActivateAsSorceryActivatedAbility( + new TransformSourceEffect(), new ManaCostsImpl<>("{3}{B}{R}{G}") + )); + + // Venom, Lethal Protector + // Menace + this.getRightHalfCard().addAbility(new MenaceAbility()); + + // Trample + this.getRightHalfCard().addAbility(TrampleAbility.getInstance()); + + // Haste + this.getRightHalfCard().addAbility(HasteAbility.getInstance()); + + // Whenever Venom attacks, you may sacrifice another creature. If you do, draw X cards, then you may put a permanent card with mana value X or less from your hand onto the battlefield, where X is the sacrificed creature's mana value. + this.getRightHalfCard().addAbility(new AttacksTriggeredAbility(new VenomLethalProtectorEffect())); + } + + private EddieBrock(final EddieBrock card) { + super(card); + } + + @Override + public EddieBrock copy() { + return new EddieBrock(this); + } +} + +class VenomLethalProtectorEffect extends OneShotEffect { + + VenomLethalProtectorEffect() { + super(Outcome.Benefit); + staticText = "you may sacrifice another creature. If you do, draw X cards, " + + "then you may put a permanent card with mana value X or less from your hand onto the battlefield, " + + "where X is the sacrificed creature's mana value"; + } + + private VenomLethalProtectorEffect(final VenomLethalProtectorEffect effect) { + super(effect); + } + + @Override + public VenomLethalProtectorEffect copy() { + return new VenomLethalProtectorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + SacrificeTargetCost cost = new SacrificeTargetCost(StaticFilters.FILTER_ANOTHER_CREATURE); + if (!cost.canPay(source, source, source.getControllerId(), game) + || !player.chooseUse(Outcome.Sacrifice, "Sacrifice another creature?", source, game) + || !cost.pay(source, game, source, source.getControllerId(), true)) { + return false; + } + int amount = cost + .getPermanents() + .stream() + .filter(Objects::nonNull) + .mapToInt(MageObject::getManaValue) + .sum(); + player.drawCards(amount, source, game); + game.processAction(); + FilterCard filter = new FilterPermanentCard("permanent card with mana value " + amount + " or less"); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, amount + 1)); + new PutCardFromHandOntoBattlefieldEffect(filter).apply(game, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EdgarMarkovsCoffin.java b/Mage.Sets/src/mage/cards/e/EdgarMarkovsCoffin.java index 2b1d0310f07..36ac4be28bd 100644 --- a/Mage.Sets/src/mage/cards/e/EdgarMarkovsCoffin.java +++ b/Mage.Sets/src/mage/cards/e/EdgarMarkovsCoffin.java @@ -1,18 +1,19 @@ package mage.cards.e; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.RemoveAllCountersSourceEffect; +import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SuperType; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.game.permanent.token.EdgarMarkovsCoffinVampireToken; import java.util.UUID; @@ -22,6 +23,8 @@ import java.util.UUID; */ public final class EdgarMarkovsCoffin extends CardImpl { + private static final Condition condition = new SourceHasCounterCondition(CounterType.BLOODLINE, 3); + public EdgarMarkovsCoffin(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); @@ -31,13 +34,12 @@ public final class EdgarMarkovsCoffin extends CardImpl { this.nightCard = true; // At the beginning of your upkeep, create a 1/1 white and black Vampire creature token with lifelink and put a bloodline counter on Edgar Markov's Coffin. Then if there are three or more bloodline counters on it, remove those counters and transform it. - Ability ability = new BeginningOfUpkeepTriggeredAbility( - new CreateTokenEffect(new EdgarMarkovsCoffinVampireToken()) - ); - ability.addEffect(new AddCountersSourceEffect( - CounterType.BLOODLINE.createInstance() - ).concatBy("and")); - ability.addEffect(new EdgarMarkovsCoffinEffect()); + Ability ability = new BeginningOfUpkeepTriggeredAbility(new CreateTokenEffect(new EdgarMarkovsCoffinVampireToken())); + ability.addEffect(new AddCountersSourceEffect(CounterType.BLOODLINE.createInstance()).concatBy("and")); + ability.addEffect(new ConditionalOneShotEffect( + new RemoveAllCountersSourceEffect(CounterType.BLOODLINE), condition, + "Then if there are three or more bloodline counters on it, remove those counters and transform it" + ).addEffect(new TransformSourceEffect())); this.addAbility(ability); } @@ -50,35 +52,3 @@ public final class EdgarMarkovsCoffin extends CardImpl { return new EdgarMarkovsCoffin(this); } } - -class EdgarMarkovsCoffinEffect extends OneShotEffect { - - EdgarMarkovsCoffinEffect() { - super(Outcome.Benefit); - staticText = "Then if there are three or more bloodline counters on it, remove those counters and transform it"; - } - - private EdgarMarkovsCoffinEffect(final EdgarMarkovsCoffinEffect effect) { - super(effect); - } - - @Override - public EdgarMarkovsCoffinEffect copy() { - return new EdgarMarkovsCoffinEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent == null) { - return false; - } - int counters = permanent.getCounters(game).getCount(CounterType.BLOODLINE); - if (counters < 3) { - return false; - } - permanent.removeCounters(CounterType.BLOODLINE.createInstance(counters), source, game); - permanent.transform(source, game); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/e/EelHounds.java b/Mage.Sets/src/mage/cards/e/EelHounds.java new file mode 100644 index 00000000000..5e313e6c556 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EelHounds.java @@ -0,0 +1,49 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EelHounds extends CardImpl { + + public EelHounds(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.FISH); + this.subtype.add(SubType.DOG); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever this creature attacks, another target creature you control gets +2/+2 and gains trample until end of turn. + Ability ability = new AttacksTriggeredAbility(new BoostTargetEffect(2, 2).setText("another target creature you control gets +2/+2")); + ability.addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance()).setText("and gains trample until end of turn")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + } + + private EelHounds(final EelHounds card) { + super(card); + } + + @Override + public EelHounds copy() { + return new EelHounds(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElbrusTheBindingBlade.java b/Mage.Sets/src/mage/cards/e/ElbrusTheBindingBlade.java index 3b3e67f5c3b..8612d8b1dfa 100644 --- a/Mage.Sets/src/mage/cards/e/ElbrusTheBindingBlade.java +++ b/Mage.Sets/src/mage/cards/e/ElbrusTheBindingBlade.java @@ -1,25 +1,23 @@ package mage.cards.e; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.common.DealsDamageToAPlayerAttachedTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.Zone; import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; /** * @author BetaSteward @@ -36,10 +34,16 @@ public final class ElbrusTheBindingBlade extends CardImpl { // Equipped creature gets +1/+0. this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(1, 0))); + // When equipped creature deals combat damage to a player, unattach Elbrus, the Binding Blade, then transform it. - this.addAbility(new DealsDamageToAPlayerAttachedTriggeredAbility(new ElbrusTheBindingBladeEffect(), "equipped", false)); + Ability ability = new DealsDamageToAPlayerAttachedTriggeredAbility( + new ElbrusTheBindingBladeEffect(), "equipped", false + ); + ability.addEffect(new TransformSourceEffect(true).concatBy(", then")); + this.addAbility(ability); + // Equip {1} - this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(1), new TargetControlledCreaturePermanent(), false)); + this.addAbility(new EquipAbility(1, false)); } private ElbrusTheBindingBlade(final ElbrusTheBindingBlade card) { @@ -64,20 +68,12 @@ class ElbrusTheBindingBladeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent equipment = game.getPermanent(source.getSourceId()); - if (equipment != null && equipment.getAttachedTo() != null) { - Permanent attachedTo = game.getPermanent(equipment.getAttachedTo()); - if (attachedTo != null) { - attachedTo.removeAttachment(equipment.getId(), source, game); - equipment.transform(source, game); - } - } - return false; + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)).ifPresent(permanent -> permanent.unattach(game)); + return true; } @Override public ElbrusTheBindingBladeEffect copy() { return new ElbrusTheBindingBladeEffect(this); } - } diff --git a/Mage.Sets/src/mage/cards/e/ElderfangRitualist.java b/Mage.Sets/src/mage/cards/e/ElderfangRitualist.java index 5d2cfdf3f0c..fa99c9a98ce 100644 --- a/Mage.Sets/src/mage/cards/e/ElderfangRitualist.java +++ b/Mage.Sets/src/mage/cards/e/ElderfangRitualist.java @@ -1,29 +1,28 @@ package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class ElderfangRitualist extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.ELF); + private static final FilterCard filter = new FilterCard(SubType.ELF, "another target Elf card from your graveyard"); + static { filter.add(AnotherPredicate.instance); - filter.setMessage("another target Elf card from your graveyard"); } public ElderfangRitualist(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/e/ElectroAssaultingBattery.java b/Mage.Sets/src/mage/cards/e/ElectroAssaultingBattery.java new file mode 100644 index 00000000000..34b05ea2ee8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElectroAssaultingBattery.java @@ -0,0 +1,93 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.YouDontLoseManaEffect; +import mage.abilities.effects.mana.AddManaToManaPoolSourceControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.util.ManaUtil; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class ElectroAssaultingBattery extends CardImpl { + + public ElectroAssaultingBattery(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // You don't lose unspent red mana as steps and phases end. + this.addAbility(new SimpleStaticAbility(new YouDontLoseManaEffect(ManaType.RED))); + + // Whenever you cast an instant or sorcery spell, add {R}. + this.addAbility(new SpellCastControllerTriggeredAbility(new AddManaToManaPoolSourceControllerEffect(Mana.RedMana(1)), false)); + + // When Electro leaves the battlefield, you may pay x. When you do, he deals X damage to target player. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ElectroAssaultingBatteryEffect())); + } + + private ElectroAssaultingBattery(final ElectroAssaultingBattery card) { + super(card); + } + + @Override + public ElectroAssaultingBattery copy() { + return new ElectroAssaultingBattery(this); + } +} +class ElectroAssaultingBatteryEffect extends OneShotEffect { + + ElectroAssaultingBatteryEffect() { + super(Outcome.Damage); + staticText = "you may pay x. When you do, he deals X damage to target player"; + } + + private ElectroAssaultingBatteryEffect(final ElectroAssaultingBatteryEffect effect) { + super(effect); + } + + @Override + public ElectroAssaultingBatteryEffect copy() { + return new ElectroAssaultingBatteryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + if (controller.chooseUse(outcome, "Pay x mana to deal x damage to target player?", source, game)) { + TargetPlayer target = new TargetPlayer(); + target.chooseTarget(outcome, controller.getId(), source, game); + int amount = ManaUtil.playerPaysXGenericMana(false, "Electro, Assaulting Battery", controller, source, game); + Player targetOpponent = game.getPlayer(target.getFirstTarget()); + if (targetOpponent != null) { + targetOpponent.damage(amount, source, game); + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/e/ElectrosBolt.java b/Mage.Sets/src/mage/cards/e/ElectrosBolt.java new file mode 100644 index 00000000000..0db11cffee2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElectrosBolt.java @@ -0,0 +1,36 @@ +package mage.cards.e; + +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.MayhemAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ElectrosBolt extends CardImpl { + + public ElectrosBolt(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); + + // Electro's Bolt deals 4 damage to target creature. + this.getSpellAbility().addEffect(new DamageTargetEffect(4)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // Mayhem {1}{R} + this.addAbility(new MayhemAbility(this, "{1}{R}")); + } + + private ElectrosBolt(final ElectrosBolt card) { + super(card); + } + + @Override + public ElectrosBolt copy() { + return new ElectrosBolt(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElephantRat.java b/Mage.Sets/src/mage/cards/e/ElephantRat.java new file mode 100644 index 00000000000..0698a4082c9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElephantRat.java @@ -0,0 +1,37 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.keyword.MenaceAbility; +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 ElephantRat extends CardImpl { + + public ElephantRat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.ELEPHANT); + this.subtype.add(SubType.RAT); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + } + + private ElephantRat(final ElephantRat card) { + super(card); + } + + @Override + public ElephantRat copy() { + return new ElephantRat(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EliteSpellbinder.java b/Mage.Sets/src/mage/cards/e/EliteSpellbinder.java index caefc4cb1c2..f7fc09fffcc 100644 --- a/Mage.Sets/src/mage/cards/e/EliteSpellbinder.java +++ b/Mage.Sets/src/mage/cards/e/EliteSpellbinder.java @@ -79,9 +79,7 @@ class EliteSpellbinderEffect extends OneShotEffect { if (controller == null || opponent == null || opponent.getHand().isEmpty()) { return false; } - TargetCard target = new TargetCardInHand( - 0, 1, StaticFilters.FILTER_CARD_A_NON_LAND - ); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); controller.choose(outcome, opponent.getHand(), target, source, game); Card card = opponent.getHand().get(target.getFirstTarget(), game); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/e/EmberwildeDjinn.java b/Mage.Sets/src/mage/cards/e/EmberwildeDjinn.java index 027f7e47f62..981cd9ed20b 100644 --- a/Mage.Sets/src/mage/cards/e/EmberwildeDjinn.java +++ b/Mage.Sets/src/mage/cards/e/EmberwildeDjinn.java @@ -82,7 +82,7 @@ class EmberwildeDjinnEffect extends OneShotEffect { if (player.chooseUse(Outcome.GainControl, "Gain control of " + sourceObject.getLogName() + "?", source, game)) { if (cost.pay(source, game, source, player.getId(), false)) { ContinuousEffect effect = new GainControlTargetEffect(Duration.Custom, false, player.getId()); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getSourceObjectZoneChangeCounter())); + effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getStackMomentSourceZCC())); game.addEffect(effect, source); player.resetStoredBookmark(game); } diff --git a/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java b/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java index 113e9841794..8ba2936186f 100644 --- a/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java +++ b/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java @@ -129,7 +129,7 @@ class EnchantmentAlterationEffect extends OneShotEffect { if (oldPermanent != null && !oldPermanent.equals(permanentToBeAttachedTo)) { Target auraTarget = aura.getSpellAbility().getTargets().get(0); - if (!auraTarget.canTarget(permanentToBeAttachedTo.getId(), game)) { + if (!auraTarget.canTarget(permanentToBeAttachedTo.getId(), source, game)) { game.informPlayers(aura.getLogName() + " was not attched to " + permanentToBeAttachedTo.getLogName() + " because it's no legal target for the aura"); } else if (oldPermanent.removeAttachment(aura.getId(), source, game)) { game.informPlayers(aura.getLogName() + " was unattached from " + oldPermanent.getLogName() + " and attached to " + permanentToBeAttachedTo.getLogName()); diff --git a/Mage.Sets/src/mage/cards/e/EncroachingMycosynth.java b/Mage.Sets/src/mage/cards/e/EncroachingMycosynth.java index 5270c509724..0e88261963a 100644 --- a/Mage.Sets/src/mage/cards/e/EncroachingMycosynth.java +++ b/Mage.Sets/src/mage/cards/e/EncroachingMycosynth.java @@ -45,6 +45,7 @@ class EncroachingMycosynthEffect extends ContinuousEffectImpl { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); staticText = "Nonland permanents you control are artifacts in addition to their other types. " + "The same is true for permanent spells you control and nonland permanent cards you own that aren't on the battlefield"; + this.dependendToTypes.add(DependencyType.BecomeNonbasicLand); this.dependencyTypes.add(DependencyType.ArtifactAddingRemoving); // March of the Machines } diff --git a/Mage.Sets/src/mage/cards/e/EntishRestoration.java b/Mage.Sets/src/mage/cards/e/EntishRestoration.java index 6df9fc8f39e..a330ef60b17 100644 --- a/Mage.Sets/src/mage/cards/e/EntishRestoration.java +++ b/Mage.Sets/src/mage/cards/e/EntishRestoration.java @@ -1,7 +1,5 @@ package mage.cards.e; -import java.util.UUID; - import mage.abilities.condition.common.FerociousCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.SacrificeControllerEffect; @@ -9,23 +7,20 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author Susucr */ public final class EntishRestoration extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("basic land cards"); - private static final String rule = "Search your library for up to two basic land cards, " + - "put them onto the battlefield tapped, then shuffle. " + - "If " + FerociousCondition.instance.toString() + ", instead search your library for up " + - "to three basic land cards, put them onto the battlefield tapped, then shuffle."; + "put them onto the battlefield tapped, then shuffle. " + + "If " + FerociousCondition.instance + ", instead search your library for up " + + "to three basic land cards, put them onto the battlefield tapped, then shuffle."; public EntishRestoration(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); @@ -35,14 +30,14 @@ public final class EntishRestoration extends CardImpl { // creature with power 4 or greater, instead search your library for up // to three basic land cards, put them onto the battlefield tapped, then shuffle. this.getSpellAbility().addEffect(new SacrificeControllerEffect( - StaticFilters.FILTER_LAND, 1, null + StaticFilters.FILTER_LAND, 1, null ).setText("Sacrifice a land.")); this.getSpellAbility().addEffect( - new ConditionalOneShotEffect( - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 3, filter), true), - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 2, filter), true), - FerociousCondition.instance, rule - ) + new ConditionalOneShotEffect( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 3, StaticFilters.FILTER_CARD_BASIC_LANDS), true), + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS), true), + FerociousCondition.instance, rule + ) ); } diff --git a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java index b9f8d113128..5425c44aba2 100644 --- a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java +++ b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java @@ -1,23 +1,22 @@ package mage.cards.e; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ForetellAbility; -import mage.cards.*; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.GameEvent; import mage.players.Player; import mage.target.common.TargetCardInHand; -import mage.util.CardUtil; +import mage.watchers.common.ForetoldWatcher; import java.util.UUID; @@ -38,7 +37,7 @@ public final class EtherealValkyrie extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever Ethereal Valkyrie enters the battlefield or attacks, draw a card, then exile a card from your hand face down. It becomes foretold. Its foretell cost is its mana cost reduced by {2}. - this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new EtherealValkyrieEffect())); + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new EtherealValkyrieEffect()), new ForetoldWatcher()); } private EtherealValkyrie(final EtherealValkyrie card) { @@ -75,77 +74,15 @@ class EtherealValkyrieEffect extends OneShotEffect { if (controller == null) { return false; } - controller.drawCards(1, source, game); - TargetCardInHand targetCard = new TargetCardInHand(new FilterCard("card to exile face down. It becomes foretold.")); + TargetCardInHand targetCard = new TargetCardInHand(StaticFilters.FILTER_CARD).withChooseHint("to exile face down; it becomes foretold"); if (!controller.chooseTarget(Outcome.Benefit, targetCard, source, game)) { return false; } - - Card exileCard = game.getCard(targetCard.getFirstTarget()); - if (exileCard == null) { + Card card = game.getCard(targetCard.getFirstTarget()); + if (card == null) { return false; } - - // process Split, MDFC, and Adventure cards first - // note that 'Foretell Cost' refers to the main card (left) and 'Foretell Split Cost' refers to the (right) card if it exists - ForetellAbility foretellAbility = null; - if (exileCard instanceof SplitCard) { - String leftHalfCost = CardUtil.reduceCost(((SplitCard) exileCard).getLeftHalfCard().getManaCost(), 2).getText(); - String rightHalfCost = CardUtil.reduceCost(((SplitCard) exileCard).getRightHalfCard().getManaCost(), 2).getText(); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Cost", leftHalfCost); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Split Cost", rightHalfCost); - foretellAbility = new ForetellAbility(exileCard, leftHalfCost, rightHalfCost); - } else if (exileCard instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) exileCard).getLeftHalfCard(); - if (!leftHalfCard.isLand(game)) { // Only MDFC cards with a left side a land have a land on the right side too - String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), 2).getText(); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Cost", leftHalfCost); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) exileCard).getRightHalfCard(); - if (rightHalfCard.isLand(game)) { - foretellAbility = new ForetellAbility(exileCard, leftHalfCost); - } else { - String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), 2).getText(); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Split Cost", rightHalfCost); - foretellAbility = new ForetellAbility(exileCard, leftHalfCost, rightHalfCost); - } - } - } else if (exileCard instanceof CardWithSpellOption) { - String creatureCost = CardUtil.reduceCost(exileCard.getMainCard().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); - } else if (!exileCard.isLand(game)) { - // normal card - String costText = CardUtil.reduceCost(exileCard.getManaCost(), 2).getText(); - game.getState().setValue(exileCard.getId().toString() + "Foretell Cost", costText); - foretellAbility = new ForetellAbility(exileCard, costText); - } - - // All card types (including lands) must be exiled - UUID exileId = CardUtil.getExileZoneId(exileCard.getMainCard().getId().toString() + "foretellAbility", game); - controller.moveCardsToExile(exileCard, source, game, true, exileId, " Foretell Turn Number: " + game.getTurnNum()); - exileCard.setFaceDown(true, game); - - // all done pre-processing so stick the foretell cost effect onto the main card - // note that the card is not foretell'd into exile, it is put into exile and made foretold - // If the card is a non-land, it will not be exiled. - if (foretellAbility != null) { - // copy source and use it for the foretold effect on the exiled card - // bug #8673 - Ability copiedSource = source.copy(); - copiedSource.newId(); - copiedSource.setSourceId(exileCard.getId()); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Turn Number", game.getTurnNum()); - foretellAbility.setSourceId(exileCard.getId()); - foretellAbility.setControllerId(exileCard.getOwnerId()); - game.getState().addOtherAbility(exileCard, foretellAbility); - foretellAbility.activate(game, true); - ContinuousEffect effect = new ForetellAbility.ForetellAddCostEffect(new MageObjectReference(exileCard, game)); - game.addEffect(effect, copiedSource); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.FORETOLD, exileCard.getId(), null, null)); - } - return true; + return ForetellAbility.doExileBecomesForetold(card, game, source, 2); } } diff --git a/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java b/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java index 85e501262a3..bd56aeb01bd 100644 --- a/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java +++ b/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java @@ -58,7 +58,7 @@ enum EverythingComesToDustPredicate implements ObjectSourcePlayerPredicate set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>(0)); + HashSet set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>()); for (MageObjectReference mor : set){ Permanent convoked = game.getPermanentOrLKIBattlefield(mor); if (convoked.shareCreatureTypes(game, p)){ diff --git a/Mage.Sets/src/mage/cards/e/EvraHalcyonWitness.java b/Mage.Sets/src/mage/cards/e/EvraHalcyonWitness.java index 09ef0580bc4..bce8344342e 100644 --- a/Mage.Sets/src/mage/cards/e/EvraHalcyonWitness.java +++ b/Mage.Sets/src/mage/cards/e/EvraHalcyonWitness.java @@ -64,7 +64,7 @@ class EvraHalcyonWitnessEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null && player.isLifeTotalCanChange()) { Permanent perm = game.getPermanent(source.getSourceId()); - if (perm != null) { + if (perm != null && perm.isCreature(game)) { int amount = perm.getPower().getValue(); int life = player.getLife(); if (life == amount) { diff --git a/Mage.Sets/src/mage/cards/e/ExplosiveShot.java b/Mage.Sets/src/mage/cards/e/ExplosiveShot.java new file mode 100644 index 00000000000..064c918e829 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExplosiveShot.java @@ -0,0 +1,32 @@ +package mage.cards.e; + +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ExplosiveShot extends CardImpl { + + public ExplosiveShot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); + + // Explosive Shot deals 4 damage to target creature. + this.getSpellAbility().addEffect(new DamageTargetEffect(4)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private ExplosiveShot(final ExplosiveShot card) { + super(card); + } + + @Override + public ExplosiveShot copy() { + return new ExplosiveShot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExtractBrain.java b/Mage.Sets/src/mage/cards/e/ExtractBrain.java index 9f33ad20f40..34d0d0ac6e3 100644 --- a/Mage.Sets/src/mage/cards/e/ExtractBrain.java +++ b/Mage.Sets/src/mage/cards/e/ExtractBrain.java @@ -8,9 +8,11 @@ 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.TargetCard; import mage.target.common.TargetCardInHand; import mage.target.common.TargetOpponent; import mage.util.CardUtil; @@ -65,9 +67,7 @@ class ExtractBrainEffect extends OneShotEffect { if (controller == null || opponent == null || opponent.getHand().isEmpty() || xValue < 1) { return false; } - TargetCardInHand target = new TargetCardInHand( - Math.min(opponent.getHand().size(), xValue), StaticFilters.FILTER_CARD - ); + TargetCard target = new TargetCard(Math.min(opponent.getHand().size(), xValue), Integer.MAX_VALUE, Zone.HAND, StaticFilters.FILTER_CARD); opponent.choose(Outcome.Detriment, opponent.getHand(), target, source, game); Cards cards = new CardsImpl(target.getTargets()); controller.lookAtCards(source, null, cards, game); diff --git a/Mage.Sets/src/mage/cards/e/EzekielSimsSpiderTotem.java b/Mage.Sets/src/mage/cards/e/EzekielSimsSpiderTotem.java new file mode 100644 index 00000000000..4c579649394 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EzekielSimsSpiderTotem.java @@ -0,0 +1,53 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EzekielSimsSpiderTotem extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SPIDER); + + public EzekielSimsSpiderTotem(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // At the beginning of combat on your turn, target Spider you control gets +2/+2 until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility(new BoostTargetEffect(2, 2)); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private EzekielSimsSpiderTotem(final EzekielSimsSpiderTotem card) { + super(card); + } + + @Override + public EzekielSimsSpiderTotem copy() { + return new EzekielSimsSpiderTotem(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FaerieArtisans.java b/Mage.Sets/src/mage/cards/f/FaerieArtisans.java index f5038d7fb81..437a27bfa0c 100644 --- a/Mage.Sets/src/mage/cards/f/FaerieArtisans.java +++ b/Mage.Sets/src/mage/cards/f/FaerieArtisans.java @@ -77,7 +77,7 @@ class FaerieArtisansEffect extends OneShotEffect { CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(null, CardType.ARTIFACT, false); effect.setTargetPointer(new FixedTarget(permanentToCopy, game)); if (effect.apply(game, source)) { - String oldTokens = (String) game.getState().getValue(source.getSourceId().toString() + source.getSourceObjectZoneChangeCounter()); + String oldTokens = (String) game.getState().getValue(source.getSourceId().toString() + source.getStackMomentSourceZCC()); StringBuilder sb = new StringBuilder(); for (Permanent permanent : effect.getAddedPermanents()) { if (sb.length() > 0) { @@ -85,7 +85,7 @@ class FaerieArtisansEffect extends OneShotEffect { } sb.append(permanent.getId()); } - game.getState().setValue(source.getSourceId().toString() + source.getSourceObjectZoneChangeCounter(), sb.toString()); + game.getState().setValue(source.getSourceId().toString() + source.getStackMomentSourceZCC(), sb.toString()); if (oldTokens != null) { Cards cards = new CardsImpl(); diff --git a/Mage.Sets/src/mage/cards/f/FaldornDreadWolfHerald.java b/Mage.Sets/src/mage/cards/f/FaldornDreadWolfHerald.java index 298ec7eb06d..48eff382aa0 100644 --- a/Mage.Sets/src/mage/cards/f/FaldornDreadWolfHerald.java +++ b/Mage.Sets/src/mage/cards/f/FaldornDreadWolfHerald.java @@ -92,9 +92,10 @@ class FaldornDreadWolfHeraldTriggeredAbility extends TriggeredAbilityImpl { return Optional .ofNullable(game.getSpell(event.getTargetId())) .map(Spell::getFromZone) - .orElse(Zone.ALL) - .equals(Zone.EXILED); + .filter(Zone.EXILED::match) + .isPresent(); + default: + return false; } - return false; } } diff --git a/Mage.Sets/src/mage/cards/f/FarrelitePriest.java b/Mage.Sets/src/mage/cards/f/FarrelitePriest.java index e7fb0bd4d6a..3c234f51959 100644 --- a/Mage.Sets/src/mage/cards/f/FarrelitePriest.java +++ b/Mage.Sets/src/mage/cards/f/FarrelitePriest.java @@ -67,7 +67,7 @@ class FarrelitePriestEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - ActivationInfo activationInfo = ActivationInfo.getInstance(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + ActivationInfo activationInfo = ActivationInfo.getInstance(game, source.getSourceId(), source.getStackMomentSourceZCC()); activationInfo.addActivation(game); if (activationInfo.getActivationCounter() == 4) { DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new SacrificeSourceEffect()); diff --git a/Mage.Sets/src/mage/cards/f/FatedFirepower.java b/Mage.Sets/src/mage/cards/f/FatedFirepower.java new file mode 100644 index 00000000000..0461c864193 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FatedFirepower.java @@ -0,0 +1,102 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FatedFirepower extends CardImpl { + + public FatedFirepower(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{X}{R}{R}{R}"); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // This enchantment enters with X fire counters on it. + this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.FIRE.createInstance()))); + + // If a source you control would deal damage to an opponent or a permanent an opponent controls, it deals that much damage plus an amount of damage equal to the number of fire counters on this enchantment instead. + this.addAbility(new SimpleStaticAbility(new FatedFirepowerEffect())); + } + + private FatedFirepower(final FatedFirepower card) { + super(card); + } + + @Override + public FatedFirepower copy() { + return new FatedFirepower(this); + } +} + +class FatedFirepowerEffect extends ReplacementEffectImpl { + + private static final DynamicValue xValue = new CountersSourceCount(CounterType.FIRE); + + FatedFirepowerEffect() { + super(Duration.WhileOnBattlefield, Outcome.Damage); + this.staticText = "if a source you control would deal damage to an opponent or a permanent " + + "an opponent controls, it deals that much damage plus an amount of damage equal to " + + "the number of fire counters on this enchantment instead."; + } + + private FatedFirepowerEffect(final FatedFirepowerEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(CardUtil.overflowInc(event.getAmount(), xValue.calculate(game, source, this))); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGE_PERMANENT: + case DAMAGE_PLAYER: + return true; + default: + return false; + } + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + return controller != null + && controller.hasOpponent(getControllerOrSelf(event.getTargetId(), game), game) + && source.isControlledBy(game.getControllerId(event.getSourceId())) + && event.getAmount() > 0; + } + + private static UUID getControllerOrSelf(UUID id, Game game) { + UUID outId = game.getControllerId(id); + return outId == null ? id : outId; + } + + @Override + public FatedFirepowerEffect copy() { + return new FatedFirepowerEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FieldTrip.java b/Mage.Sets/src/mage/cards/f/FieldTrip.java index 25989562648..c397e88f885 100644 --- a/Mage.Sets/src/mage/cards/f/FieldTrip.java +++ b/Mage.Sets/src/mage/cards/f/FieldTrip.java @@ -7,8 +7,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -18,12 +18,7 @@ import java.util.UUID; */ public final class FieldTrip extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); public FieldTrip(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); diff --git a/Mage.Sets/src/mage/cards/f/FinalIteration.java b/Mage.Sets/src/mage/cards/f/FinalIteration.java index 7ef4c765c72..de5fab8d202 100644 --- a/Mage.Sets/src/mage/cards/f/FinalIteration.java +++ b/Mage.Sets/src/mage/cards/f/FinalIteration.java @@ -1,43 +1,37 @@ - package mage.cards.f; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.FilterSpell; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.Predicates; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.game.permanent.token.HumanWizardToken; +import java.util.UUID; + /** - * * @author fireshoes */ public final class FinalIteration extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Wizards"); - private static final FilterSpell filterSpell = new FilterSpell("an instant or sorcery spell"); + private static final FilterPermanent filter = new FilterPermanent("Wizards"); static { filter.add(SubType.WIZARD.getPredicate()); - filter.add(TargetController.YOU.getControllerPredicate()); - filterSpell.add(Predicates.or( - CardType.INSTANT.getPredicate(), - CardType.SORCERY.getPredicate())); } public FinalIteration(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},""); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); this.subtype.add(SubType.ELDRAZI); this.subtype.add(SubType.INSECT); this.power = new MageInt(6); @@ -50,14 +44,19 @@ public final class FinalIteration extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Wizards you control get +2/+1 and have flying. - Ability ability = new SimpleStaticAbility(new BoostControlledEffect(2, 1, Duration.WhileOnBattlefield, filter, false)); - Effect effect = new GainAbilityAllEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield, filter, false); - effect.setText("and have flying"); - ability.addEffect(effect); + Ability ability = new SimpleStaticAbility(new BoostControlledEffect( + 2, 1, Duration.WhileOnBattlefield, filter, false + )); + ability.addEffect(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.WhileOnBattlefield, filter + ).setText("and have flying")); this.addAbility(ability); // Whenever you cast an instant or sorcery spell, create a 1/1 blue Human Wizard creature token. - this.addAbility(new SpellCastControllerTriggeredAbility(new CreateTokenEffect(new HumanWizardToken()), filterSpell, false)); + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new HumanWizardToken()), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false + )); } private FinalIteration(final FinalIteration card) { diff --git a/Mage.Sets/src/mage/cards/f/FireLordSozin.java b/Mage.Sets/src/mage/cards/f/FireLordSozin.java new file mode 100644 index 00000000000..5074b2310fa --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireLordSozin.java @@ -0,0 +1,158 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.FirebendingAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.card.OwnerIdPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireLordSozin extends CardImpl { + + public FireLordSozin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + this.color.setBlack(true); + this.nightCard = true; + + // Menace + this.addAbility(new MenaceAbility()); + + // Firebending 3 + this.addAbility(new FirebendingAbility(3)); + + // Whenever Fire Lord Sozin deals combat damage to a player, you may pay {X}. When you do, put any number of target creature cards with total mana value X or less from that player's graveyard onto the battlefield under your control. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new FireLordSozinEffect())); + } + + private FireLordSozin(final FireLordSozin card) { + super(card); + } + + @Override + public FireLordSozin copy() { + return new FireLordSozin(this); + } +} + +class FireLordSozinEffect extends OneShotEffect { + + FireLordSozinEffect() { + super(Outcome.Benefit); + staticText = "you may pay {X}. When you do, put any number of target creature cards with " + + "total mana value X or less from that player's graveyard onto the battlefield under your control"; + } + + private FireLordSozinEffect(final FireLordSozinEffect effect) { + super(effect); + } + + @Override + public FireLordSozinEffect copy() { + return new FireLordSozinEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + if (controller == null || !controller.chooseUse(Outcome.BoostCreature, "Pay {X}?", source, game)) { + return false; + } + int xValue = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source, true); + ManaCosts cost = new ManaCostsImpl<>("{X}"); + cost.add(new GenericManaCost(xValue)); + if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { + return false; + } + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), false); + ability.addTarget(new FireLordSozinTarget((UUID) getValue("damagedPlayer"), xValue)); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} + +class FireLordSozinTarget extends TargetCardInGraveyard { + + private final int xValue; + + private static final FilterCard makeFilter(UUID ownerId, int xValue) { + FilterCard filter = new FilterCreatureCard("creature cards with total mana value " + xValue + " or less from that player's graveyard"); + filter.add(new OwnerIdPredicate(ownerId)); + return filter; + } + + FireLordSozinTarget(UUID ownerId, int xValue) { + super(0, Integer.MAX_VALUE, makeFilter(ownerId, xValue), false); + this.xValue = xValue; + } + + private FireLordSozinTarget(final FireLordSozinTarget target) { + super(target); + this.xValue = target.xValue; + } + + @Override + public FireLordSozinTarget copy() { + return new FireLordSozinTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) + && CardUtil.checkCanTargetTotalValueLimit(this.getTargets(), id, MageObject::getManaValue, xValue, game); + } + + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + return CardUtil.checkPossibleTargetsTotalValueLimit( + this.getTargets(), + super.possibleTargets(sourceControllerId, source, game), + MageObject::getManaValue, xValue, game + ); + } + + @Override + public String getMessage(Game game) { + // shows selected total + int selectedValue = this.getTargets().stream() + .map(game::getObject) + .filter(Objects::nonNull) + .mapToInt(MageObject::getManaValue) + .sum(); + return super.getMessage(game) + " (selected total mana value " + selectedValue + ")"; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireLordZuko.java b/Mage.Sets/src/mage/cards/f/FireLordZuko.java new file mode 100644 index 00000000000..bff43f58139 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireLordZuko.java @@ -0,0 +1,96 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.FirebendingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireLordZuko extends CardImpl { + + public FireLordZuko(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Firebending X, where X is Fire Lord Zuko's power. + this.addAbility(new FirebendingAbility(SourcePermanentPowerValue.NOT_NEGATIVE)); + + // Whenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control. + this.addAbility(new FireLordZukoTriggeredAbility()); + } + + private FireLordZuko(final FireLordZuko card) { + super(card); + } + + @Override + public FireLordZuko copy() { + return new FireLordZuko(this); + } +} + +class FireLordZukoTriggeredAbility extends TriggeredAbilityImpl { + + FireLordZukoTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE)); + setTriggerPhrase("Whenever you cast a spell from exile and whenever a permanent you control enters from exile, "); + } + + private FireLordZukoTriggeredAbility(final FireLordZukoTriggeredAbility ability) { + super(ability); + } + + @Override + public FireLordZukoTriggeredAbility copy() { + return new FireLordZukoTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD + || event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!this.isControlledBy(event.getPlayerId())) { + return false; + } + switch (event.getType()) { + case ENTERS_THE_BATTLEFIELD: + return Zone.EXILED.match(((EntersTheBattlefieldEvent) event).getFromZone()); + case SPELL_CAST: + return Optional + .ofNullable(game.getSpell(event.getTargetId())) + .map(Spell::getFromZone) + .filter(Zone.EXILED::match) + .isPresent(); + default: + return false; + } + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireNationAmbushers.java b/Mage.Sets/src/mage/cards/f/FireNationAmbushers.java new file mode 100644 index 00000000000..7f0f2444a60 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireNationAmbushers.java @@ -0,0 +1,37 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireNationAmbushers extends CardImpl { + + public FireNationAmbushers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + } + + private FireNationAmbushers(final FireNationAmbushers card) { + super(card); + } + + @Override + public FireNationAmbushers copy() { + return new FireNationAmbushers(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireNationArchers.java b/Mage.Sets/src/mage/cards/f/FireNationArchers.java new file mode 100644 index 00000000000..bcced837911 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireNationArchers.java @@ -0,0 +1,49 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.game.permanent.token.SoldierRedToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireNationArchers extends CardImpl { + + public FireNationArchers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARCHER); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // {5}: This creature deals 2 damage to each opponent. Create a 2/2 red Soldier creature token. + Ability ability = new SimpleActivatedAbility(new DamagePlayersEffect(2, TargetController.OPPONENT), new GenericManaCost(5)); + ability.addEffect(new CreateTokenEffect(new SoldierRedToken())); + this.addAbility(ability); + } + + private FireNationArchers(final FireNationArchers card) { + super(card); + } + + @Override + public FireNationArchers copy() { + return new FireNationArchers(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireNationAttacks.java b/Mage.Sets/src/mage/cards/f/FireNationAttacks.java new file mode 100644 index 00000000000..c4ffb6c0dd1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireNationAttacks.java @@ -0,0 +1,36 @@ +package mage.cards.f; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.SoldierFirebendingToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireNationAttacks extends CardImpl { + + public FireNationAttacks(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}"); + + // Create two 2/2 red Soldier creature tokens with firebending 1. + this.getSpellAbility().addEffect(new CreateTokenEffect(new SoldierFirebendingToken(), 2)); + + // Flashback {8}{R} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{8}{R}"))); + } + + private FireNationAttacks(final FireNationAttacks card) { + super(card); + } + + @Override + public FireNationAttacks copy() { + return new FireNationAttacks(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireNationEngineer.java b/Mage.Sets/src/mage/cards/f/FireNationEngineer.java new file mode 100644 index 00000000000..93f4c6fd078 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireNationEngineer.java @@ -0,0 +1,63 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.common.RaidCondition; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.RaidHint; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; +import mage.watchers.common.PlayerAttackedWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireNationEngineer extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("another target creature or Vehicle you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + } + + public FireNationEngineer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Raid -- At the beginning of your end step, if you attacked this turn, put a +1/+1 counter on another target creature or Vehicle you control. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()) + ).withInterveningIf(RaidCondition.instance); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability.setAbilityWord(AbilityWord.RAID).addHint(RaidHint.instance), new PlayerAttackedWatcher()); + } + + private FireNationEngineer(final FireNationEngineer card) { + super(card); + } + + @Override + public FireNationEngineer copy() { + return new FireNationEngineer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireNationSentinels.java b/Mage.Sets/src/mage/cards/f/FireNationSentinels.java new file mode 100644 index 00000000000..3cb6c8653b7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireNationSentinels.java @@ -0,0 +1,51 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireNationSentinels extends CardImpl { + + private static final FilterPermanent filter = new FilterOpponentsCreaturePermanent("nontoken creature an opponent controls"); + + static { + filter.add(TokenPredicate.FALSE); + } + + public FireNationSentinels(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Whenever a nontoken creature an opponent controls dies, put a +1/+1 counter on each creature you control. + this.addAbility(new DiesCreatureTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ), false, filter)); + } + + private FireNationSentinels(final FireNationSentinels card) { + super(card); + } + + @Override + public FireNationSentinels copy() { + return new FireNationSentinels(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireNationSoldier.java b/Mage.Sets/src/mage/cards/f/FireNationSoldier.java new file mode 100644 index 00000000000..d267e16f0c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireNationSoldier.java @@ -0,0 +1,37 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.keyword.HasteAbility; +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 FireNationSoldier extends CardImpl { + + public FireNationSoldier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Haste + this.addAbility(HasteAbility.getInstance()); + } + + private FireNationSoldier(final FireNationSoldier card) { + super(card); + } + + @Override + public FireNationSoldier copy() { + return new FireNationSoldier(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireNationsConquest.java b/Mage.Sets/src/mage/cards/f/FireNationsConquest.java new file mode 100644 index 00000000000..c5f9fbc9084 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireNationsConquest.java @@ -0,0 +1,32 @@ +package mage.cards.f; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +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 FireNationsConquest extends CardImpl { + + public FireNationsConquest(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}"); + + // Creatures you control get +1/+0. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 0, Duration.WhileOnBattlefield))); + } + + private FireNationsConquest(final FireNationsConquest card) { + super(card); + } + + @Override + public FireNationsConquest copy() { + return new FireNationsConquest(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireSages.java b/Mage.Sets/src/mage/cards/f/FireSages.java new file mode 100644 index 00000000000..dd7794a3380 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireSages.java @@ -0,0 +1,46 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FirebendingAbility; +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 FireSages extends CardImpl { + + public FireSages(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Firebending 1 + this.addAbility(new FirebendingAbility(1)); + + // {1}{R}{R}: Put a +1/+1 counter on this creature. + this.addAbility(new SimpleActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new ManaCostsImpl<>("{1}{R}{R}") + )); + } + + private FireSages(final FireSages card) { + super(card); + } + + @Override + public FireSages copy() { + return new FireSages(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/Fireball.java b/Mage.Sets/src/mage/cards/f/Fireball.java index b55b90de1b9..17b77fe176b 100644 --- a/Mage.Sets/src/mage/cards/f/Fireball.java +++ b/Mage.Sets/src/mage/cards/f/Fireball.java @@ -134,7 +134,6 @@ class FireballTargetCreatureOrPlayer extends TargetAnyTarget { continue; } - possibleTargets.removeAll(getTargets()); for (UUID targetId : possibleTargets) { TargetAnyTarget target = this.copy(); target.clearChosen(); diff --git a/Mage.Sets/src/mage/cards/f/FirstTimeFlyer.java b/Mage.Sets/src/mage/cards/f/FirstTimeFlyer.java new file mode 100644 index 00000000000..945c34d9b2b --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FirstTimeFlyer.java @@ -0,0 +1,56 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterCard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FirstTimeFlyer extends CardImpl { + + private static final Condition condition = new CardsInControllerGraveyardCondition(1, new FilterCard(SubType.LESSON)); + private static final Hint hint = new ConditionHint(condition, "There's a Lesson card in your graveyard"); + + public FirstTimeFlyer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PILOT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // This creature gets +1/+1 as long as there's a Lesson card in your graveyard. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostSourceEffect(1, 1, Duration.WhileOnBattlefield), + condition, "{this} gets +1/+1 as long as there's a Lesson card in your graveyard" + )).addHint(hint)); + } + + private FirstTimeFlyer(final FirstTimeFlyer card) { + super(card); + } + + @Override + public FirstTimeFlyer copy() { + return new FirstTimeFlyer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FlashThompsonSpiderFan.java b/Mage.Sets/src/mage/cards/f/FlashThompsonSpiderFan.java new file mode 100644 index 00000000000..a4124a17a35 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlashThompsonSpiderFan.java @@ -0,0 +1,59 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FlashThompsonSpiderFan extends CardImpl { + + public FlashThompsonSpiderFan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When Flash Thompson enters, choose one or both-- + // * Heckle -- Tap target creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()); + ability.addTarget(new TargetCreaturePermanent()); + ability.getModes().setMinModes(1); + ability.getModes().setMaxModes(2); + ability.withFirstModeFlavorWord("Heckle"); + + // * Hero Worship -- Untap target creature. + ability.addMode(new Mode(new UntapTargetEffect()) + .addTarget(new TargetCreaturePermanent()) + .withFlavorWord("Hero Worship")); + this.addAbility(ability); + } + + private FlashThompsonSpiderFan(final FlashThompsonSpiderFan card) { + super(card); + } + + @Override + public FlashThompsonSpiderFan copy() { + return new FlashThompsonSpiderFan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FlexibleWaterbender.java b/Mage.Sets/src/mage/cards/f/FlexibleWaterbender.java new file mode 100644 index 00000000000..c315c30d5e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlexibleWaterbender.java @@ -0,0 +1,47 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FlexibleWaterbender extends CardImpl { + + public FlexibleWaterbender(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Waterbend {3}: This creature has base power and toughness 5/2 until end of turn. + this.addAbility(new SimpleActivatedAbility( + new SetBasePowerToughnessSourceEffect(3, 2, Duration.EndOfTurn), new WaterbendCost(3) + )); + } + + private FlexibleWaterbender(final FlexibleWaterbender card) { + super(card); + } + + @Override + public FlexibleWaterbender copy() { + return new FlexibleWaterbender(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FlopsieBumisBuddy.java b/Mage.Sets/src/mage/cards/f/FlopsieBumisBuddy.java new file mode 100644 index 00000000000..993778fe326 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlopsieBumisBuddy.java @@ -0,0 +1,60 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.combat.CantBeBlockedByMoreThanOneAllEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FlopsieBumisBuddy 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 FlopsieBumisBuddy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.APE); + this.subtype.add(SubType.GOAT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Flopsie enters, put a +1/+1 counter on each creature you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ))); + + // Each creature you control with power 4 or greater can't be blocked by more than one creature. + this.addAbility(new SimpleStaticAbility(new CantBeBlockedByMoreThanOneAllEffect(filter))); + } + + private FlopsieBumisBuddy(final FlopsieBumisBuddy card) { + super(card); + } + + @Override + public FlopsieBumisBuddy copy() { + return new FlopsieBumisBuddy(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FlyingDolphinFish.java b/Mage.Sets/src/mage/cards/f/FlyingDolphinFish.java new file mode 100644 index 00000000000..15f2ed50561 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlyingDolphinFish.java @@ -0,0 +1,37 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FlyingDolphinFish extends CardImpl { + + public FlyingDolphinFish(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.WHALE); + this.subtype.add(SubType.FISH); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + } + + private FlyingDolphinFish(final FlyingDolphinFish card) { + super(card); + } + + @Override + public FlyingDolphinFish copy() { + return new FlyingDolphinFish(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForebodingLandscape.java b/Mage.Sets/src/mage/cards/f/ForebodingLandscape.java index fad15d154d7..bc6b5c61f57 100644 --- a/Mage.Sets/src/mage/cards/f/ForebodingLandscape.java +++ b/Mage.Sets/src/mage/cards/f/ForebodingLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class ForebodingLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Swamp, Forest, or Island card"); + private static final FilterCard filter = new FilterBasicCard("a basic Swamp, Forest, or Island card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheCoalition.java b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheCoalition.java index 85110624e88..3d968a4d851 100644 --- a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheCoalition.java +++ b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheCoalition.java @@ -1,32 +1,33 @@ - package mage.cards.f; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.LoseLifeOpponentsEffect; import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author JayDi85 */ public final class ForerunnerOfTheCoalition extends CardImpl { - private static final FilterPermanent filterAnotherPirate = new FilterPermanent(SubType.PIRATE, "another " + SubType.PIRATE.toString()); + private static final FilterCard filter = new FilterCard(SubType.PIRATE); + private static final FilterPermanent filter2 + = new FilterControlledPermanent(SubType.PIRATE, "another Pirate you control"); static { - filterAnotherPirate.add(AnotherPredicate.instance); - filterAnotherPirate.add(TargetController.YOU.getControllerPredicate()); + filter2.add(AnotherPredicate.instance); } public ForerunnerOfTheCoalition(UUID ownerId, CardSetInfo setInfo) { @@ -39,15 +40,13 @@ public final class ForerunnerOfTheCoalition extends CardImpl { // When Forerunner of the Coalition enters the battlefield, you may search your library for a Pirate card, reveal it, then shuffle your library and put that card on top of it. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutOnLibraryEffect( - new TargetCardInLibrary(new FilterBySubtypeCard(SubType.PIRATE)), - true), true)); + new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(filter), true), true + )); // Whenever another Pirate you control enters, each opponent loses 1 life. - Ability ability = new EntersBattlefieldControlledTriggeredAbility( - Zone.BATTLEFIELD, new LoseLifeOpponentsEffect(1), - filterAnotherPirate, false); - this.addAbility(ability); + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new LoseLifeOpponentsEffect(1), filter2 + )); } private ForerunnerOfTheCoalition(final ForerunnerOfTheCoalition card) { diff --git a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheEmpire.java b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheEmpire.java index 1b47ba5a9a8..1029bcbb394 100644 --- a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheEmpire.java +++ b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheEmpire.java @@ -1,9 +1,7 @@ - package mage.cards.f; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; @@ -11,10 +9,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.constants.Zone; -import mage.filter.common.FilterBySubtypeCard; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -24,11 +22,9 @@ import java.util.UUID; */ public final class ForerunnerOfTheEmpire extends CardImpl { - private static final FilterCreaturePermanent filterAnyDinosaur = new FilterCreaturePermanent(SubType.DINOSAUR, "a " + SubType.DINOSAUR.toString()); - - static { - filterAnyDinosaur.add(TargetController.YOU.getControllerPredicate()); - } + private static final FilterCard filter = new FilterCard(SubType.DINOSAUR); + private static final FilterPermanent filter2 + = new FilterControlledPermanent(SubType.DINOSAUR, "Dinosaur you control"); public ForerunnerOfTheEmpire(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); @@ -39,23 +35,16 @@ public final class ForerunnerOfTheEmpire extends CardImpl { this.toughness = new MageInt(3); // When Forerunner of the Empire enters the battlefield, you may search your library for a Dinosaur card, reveal it, then shuffle your library and put that card on top of it. - this.addAbility( - new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutOnLibraryEffect( - new TargetCardInLibrary(new FilterBySubtypeCard(SubType.DINOSAUR)), - true - ), - true - ) - ); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(filter), true), true + )); // Whenever a Dinosaur you control enters, you may have Forerunner of the Empire deal 1 damage to each creature. - Ability ability = new EntersBattlefieldControlledTriggeredAbility( - Zone.BATTLEFIELD, - new DamageAllEffect(1, new FilterCreaturePermanent()).setText("have {this} deal 1 damage to each creature"), - filterAnyDinosaur, - true); - this.addAbility(ability); + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new DamageAllEffect(1, StaticFilters.FILTER_PERMANENT_CREATURE) + .setText("have {this} deal 1 damage to each creature"), + filter2, true + )); } private ForerunnerOfTheEmpire(final ForerunnerOfTheEmpire card) { diff --git a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheHeralds.java b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheHeralds.java index a2acbccc45e..7acdee5b7cc 100644 --- a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheHeralds.java +++ b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheHeralds.java @@ -1,34 +1,35 @@ package mage.cards.f; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.TargetController; +import mage.constants.SubType; import mage.counters.CounterType; +import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author JayDi85 */ public final class ForerunnerOfTheHeralds extends CardImpl { - private static final FilterPermanent filterAnotherMerfolk = new FilterPermanent(SubType.MERFOLK, "another " + SubType.MERFOLK.toString()); + private static final FilterCard filter = new FilterCard(SubType.MERFOLK); + private static final FilterPermanent filter2 + = new FilterControlledPermanent(SubType.MERFOLK, "another Merfolk you control"); + static { - filterAnotherMerfolk.add(AnotherPredicate.instance); - filterAnotherMerfolk.add(TargetController.YOU.getControllerPredicate()); + filter2.add(AnotherPredicate.instance); } public ForerunnerOfTheHeralds(UUID ownerId, CardSetInfo setInfo) { @@ -39,21 +40,15 @@ public final class ForerunnerOfTheHeralds extends CardImpl { this.power = new MageInt(3); this.toughness = new MageInt(2); - // When Forerunner of the Heralds enters the battlefield, you may search your library for a Merfolk card, reveal it, then shuffle your library and put that card on top of it. - this.addAbility( - new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutOnLibraryEffect( - new TargetCardInLibrary(new FilterBySubtypeCard(SubType.MERFOLK)), - true - ), - true - ) - ); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(filter), true), true + )); // Whenever another Merfolk you control enters, put a +1/+1 counter on Forerunner of the Heralds. - Ability ability = new EntersBattlefieldControlledTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filterAnotherMerfolk); - this.addAbility(ability); + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter2 + )); } private ForerunnerOfTheHeralds(final ForerunnerOfTheHeralds card) { diff --git a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheLegion.java b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheLegion.java index 7876f62aff6..fe59885e4d2 100644 --- a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheLegion.java +++ b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheLegion.java @@ -1,32 +1,35 @@ - package mage.cards.f; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author JayDi85 */ public final class ForerunnerOfTheLegion extends CardImpl { - private static final FilterPermanent filterAnotherVampire = new FilterPermanent(SubType.VAMPIRE, "another " + SubType.VAMPIRE.toString()); + private static final FilterCard filter = new FilterCard(SubType.VAMPIRE); + private static final FilterPermanent filter2 + = new FilterControlledPermanent(SubType.VAMPIRE, "another Vampire you control"); + static { - filterAnotherVampire.add(AnotherPredicate.instance); - filterAnotherVampire.add(TargetController.YOU.getControllerPredicate()); + filter2.add(AnotherPredicate.instance); } public ForerunnerOfTheLegion(UUID ownerId, CardSetInfo setInfo) { @@ -38,18 +41,12 @@ public final class ForerunnerOfTheLegion extends CardImpl { this.toughness = new MageInt(2); // When Forerunner of the Legion enters the battlefield, you may search your library for a Vampire card, reveal it, then shuffle your library and put that card on top of it. - this.addAbility( - new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutOnLibraryEffect( - new TargetCardInLibrary(new FilterBySubtypeCard(SubType.VAMPIRE)), - true - ), - true - ) - ); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(filter), true), true + )); // Whenever another Vampire you control enters, target creature gets +1/+1 until end of turn. - Ability ability = new EntersBattlefieldControlledTriggeredAbility(new BoostTargetEffect(1,1, Duration.EndOfTurn), filterAnotherVampire); + Ability ability = new EntersBattlefieldAllTriggeredAbility(new BoostTargetEffect(1, 1), filter2); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/f/ForgersFoundry.java b/Mage.Sets/src/mage/cards/f/ForgersFoundry.java new file mode 100644 index 00000000000..c228dfd1a0a --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/ForgersFoundry.java @@ -0,0 +1,206 @@ +package mage.cards.f; + +import java.util.UUID; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.mana.BlueManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterInstantOrSorcerySpell; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.util.CardUtil; + +/** + * + * @author Grath + */ +public final class ForgersFoundry extends CardImpl { + + private static final FilterInstantOrSorcerySpell filter = new FilterInstantOrSorcerySpell("an instant or sorcery spell with mana value 3 or less"); + static { + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 3)); + } + + public ForgersFoundry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + // {T}: Add {U}. When you spend this mana to cast an instant or sorcery spell with mana value 3 or less, you may exile that spell instead of putting it into its owner's graveyard as it resolves. + Ability ability = new BlueManaAbility(); + this.addAbility(ability); + this.addAbility(new ForgersFoundryTriggeredAbility(ability.getOriginalId())); + + // {3}{U}{U}, {T}: You may cast any number of spells from among cards exiled with Forger's Foundry without paying their mana costs. Activate only as a sorcery. + ability = new SimpleActivatedAbility(new ForgersFoundryCastEffect(), new ManaCostsImpl<>("{3}{U}{U}")); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private ForgersFoundry(final ForgersFoundry card) { + super(card); + } + + @Override + public ForgersFoundry copy() { + return new ForgersFoundry(this); + } +} + +class ForgersFoundryTriggeredAbility extends TriggeredAbilityImpl { + + private static final FilterInstantOrSorcerySpell filter = new FilterInstantOrSorcerySpell("an instant or sorcery spell with mana value 3 or less"); + static { + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 3)); + } + + String abilityOriginalId; + + public ForgersFoundryTriggeredAbility(UUID abilityOriginalId) { + super(Zone.ALL, null, false); + this.abilityOriginalId = abilityOriginalId.toString(); + setTriggerPhrase("When that mana is used to cast an instant or sorcery spell with mana value 3 or less, "); + } + + private ForgersFoundryTriggeredAbility(final ForgersFoundryTriggeredAbility ability) { + super(ability); + this.abilityOriginalId = ability.abilityOriginalId; + } + + @Override + public ForgersFoundryTriggeredAbility copy() { + return new ForgersFoundryTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.MANA_PAID; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getData().equals(abilityOriginalId)) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell != null && filter.match(spell, getControllerId(), this, game)) { + this.getEffects().clear(); + this.addEffect(new ForgersFoundryExileEffect(spell, game)); + return true; + } + } + return false; + } +} + +class ForgersFoundryExileEffect extends ReplacementEffectImpl { + + // we store both Spell and Card to work properly on split cards. + private final MageObjectReference morSpell; + private final MageObjectReference morCard; + + ForgersFoundryExileEffect(Spell spell, Game game) { + super(Duration.OneUse, Outcome.Benefit); + this.morSpell = new MageObjectReference(spell.getCard(), game); + this.morCard = new MageObjectReference(spell.getMainCard(), game); + } + + private ForgersFoundryExileEffect(final ForgersFoundryExileEffect effect) { + super(effect); + this.morSpell = effect.morSpell; + this.morCard = effect.morCard; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Spell sourceSpell = game.getStack().getSpell(morSpell.getSourceId()); + if (sourceSpell == null || sourceSpell.isCopy()) { + return false; + } + Player player = game.getPlayer(sourceSpell.getOwnerId()); + if (player == null) { + return false; + } + player.moveCardsToExile( + sourceSpell, source, game, false, + CardUtil.getExileZoneId(game, source), + CardUtil.getSourceName(game, source) + ); + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Spell sourceSpell = morSpell.getSpell(game); + if (sourceSpell == null || sourceSpell.isCopy()) { + return false; + } + Player player = game.getPlayer(sourceSpell.getOwnerId()); + if (player == null) { + return false; + } + ZoneChangeEvent zEvent = ((ZoneChangeEvent) event); + return Zone.STACK.equals(zEvent.getFromZone()) + && Zone.GRAVEYARD.equals(zEvent.getToZone()) + && morSpell.refersTo(event.getSourceId(), game) // this is how we check that the spell resolved properly (and was not countered or the like) + && morCard.refersTo(event.getTargetId(), game); // this is how we check that the card being moved is the one we want. + } + + @Override + public ForgersFoundryExileEffect copy() { + return new ForgersFoundryExileEffect(this); + } +} + +class ForgersFoundryCastEffect extends OneShotEffect { + + ForgersFoundryCastEffect() { + super(Outcome.Benefit); + staticText = "you may cast any number of spells from among cards exiled with " + + "{this} with total mana value X or less without paying their mana costs"; + } + + private ForgersFoundryCastEffect(final ForgersFoundryCastEffect effect) { + super(effect); + } + + @Override + public ForgersFoundryCastEffect copy() { + return new ForgersFoundryCastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + if (exileZone == null || exileZone.isEmpty()) { + return false; + } + + Cards cards = new CardsImpl(exileZone); + if (player == null || cards.isEmpty()) { + return false; + } + + CardUtil.castMultipleWithAttributeForFree(player, source, game, cards, StaticFilters.FILTER_CARD); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FriendlyNeighborhood.java b/Mage.Sets/src/mage/cards/f/FriendlyNeighborhood.java new file mode 100644 index 00000000000..d6a1c2b4e4f --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FriendlyNeighborhood.java @@ -0,0 +1,65 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.permanent.token.HumanCitizenToken; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetLandPermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class FriendlyNeighborhood extends CardImpl { + + public FriendlyNeighborhood(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant land + TargetPermanent auraTarget = new TargetLandPermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Benefit)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When this Aura enters, create three 1/1 green and white Human Citizen creature tokens. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HumanCitizenToken(), 3))); + + // Enchanted land has "{1}, {T}: Target creature gets +1/+1 until end of turn for each creature you control. Activate only as a sorcery." + Effect boostEffect = new BoostTargetEffect(CreaturesYouControlCount.SINGULAR, CreaturesYouControlCount.SINGULAR) + .setText("Target creature gets +1/+1 until end of turn for each creature you control"); + Ability ability = new ActivateAsSorceryActivatedAbility(boostEffect, + new ManaCostsImpl<>("{1}")); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect(ability, AttachmentType.AURA, Duration.WhileOnBattlefield, null, "land") + .withQuotes(true))); + } + + private FriendlyNeighborhood(final FriendlyNeighborhood card) { + super(card); + } + + @Override + public FriendlyNeighborhood copy() { + return new FriendlyNeighborhood(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FrogSquirrels.java b/Mage.Sets/src/mage/cards/f/FrogSquirrels.java new file mode 100644 index 00000000000..b9696c43088 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrogSquirrels.java @@ -0,0 +1,37 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.keyword.ReachAbility; +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 FrogSquirrels extends CardImpl { + + public FrogSquirrels(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.FROG); + this.subtype.add(SubType.SQUIRREL); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Reach + this.addAbility(ReachAbility.getInstance()); + } + + private FrogSquirrels(final FrogSquirrels card) { + super(card); + } + + @Override + public FrogSquirrels copy() { + return new FrogSquirrels(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FrogkinKidnapper.java b/Mage.Sets/src/mage/cards/f/FrogkinKidnapper.java index 6cd928951b5..1d6dc327fc2 100644 --- a/Mage.Sets/src/mage/cards/f/FrogkinKidnapper.java +++ b/Mage.Sets/src/mage/cards/f/FrogkinKidnapper.java @@ -85,7 +85,7 @@ class FrogkinKidnapperEffect extends OneShotEffect { opponent.revealCards(source, opponent.getHand(), game); TargetCard target = new TargetCard(1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); Cards toRansom = new CardsImpl(); - if (controller.chooseTarget(outcome, opponent.getHand(), target, source, game)) { + if (controller.choose(outcome, opponent.getHand(), target, source, game)) { toRansom.addAll(target.getTargets()); String exileName = "Ransomed (owned by " + opponent.getName() + ")"; diff --git a/Mage.Sets/src/mage/cards/f/FuriousRise.java b/Mage.Sets/src/mage/cards/f/FuriousRise.java index 76265389d70..9143c5fb65e 100644 --- a/Mage.Sets/src/mage/cards/f/FuriousRise.java +++ b/Mage.Sets/src/mage/cards/f/FuriousRise.java @@ -72,7 +72,7 @@ class FuriousRiseEffect extends OneShotEffect { Card cardToExile = controller.getLibrary().getFromTop(game); UUID exileId = CardUtil.getCardExileZoneId(game, source); - controller.moveCardsToExile(cardToExile, source, game, true, exileId, mageObject.getIdName() + " (" + source.getSourceObjectZoneChangeCounter() + ")"); + controller.moveCardsToExile(cardToExile, source, game, true, exileId, mageObject.getIdName() + " (" + source.getStackMomentSourceZCC() + ")"); Card cardToPlay = game.getCard(cardToExile.getId()); endPreviousEffect(game, source); // workaround for Furious Rise @@ -90,7 +90,7 @@ class FuriousRiseEffect extends OneShotEffect { if (effect instanceof FuriousRisePlayEffect) { for (Ability ability : game.getContinuousEffects().getAsThoughEffectsAbility(effect)) { if (ability.getSourceId().equals(source.getSourceId()) - && source.getSourceObjectZoneChangeCounter() == ability.getSourceObjectZoneChangeCounter()) { + && source.getStackMomentSourceZCC() == ability.getStackMomentSourceZCC()) { effect.discard(); return true; } diff --git a/Mage.Sets/src/mage/cards/g/GOTOJAIL.java b/Mage.Sets/src/mage/cards/g/GOTOJAIL.java index 730b6accdfe..78ad6c1b278 100644 --- a/Mage.Sets/src/mage/cards/g/GOTOJAIL.java +++ b/Mage.Sets/src/mage/cards/g/GOTOJAIL.java @@ -13,13 +13,11 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; import java.util.List; @@ -82,7 +80,7 @@ class GoToJailExileEffect extends OneShotEffect { if (controller != null) { game.getState().setValue(permanent.getId() + ChooseOpponentEffect.VALUE_KEY, controller.getId()); new ExileTargetEffect( - CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), permanent.getIdName() + CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()), permanent.getIdName() ).apply(game, source); game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source); return true; diff --git a/Mage.Sets/src/mage/cards/g/GaeasTouch.java b/Mage.Sets/src/mage/cards/g/GaeasTouch.java index c39ad5d322b..fd1a592b044 100644 --- a/Mage.Sets/src/mage/cards/g/GaeasTouch.java +++ b/Mage.Sets/src/mage/cards/g/GaeasTouch.java @@ -9,8 +9,12 @@ import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TimingRule; +import mage.constants.Zone; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import java.util.UUID; @@ -19,12 +23,7 @@ import java.util.UUID; */ public final class GaeasTouch extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Forest card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); public GaeasTouch(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}{G}"); diff --git a/Mage.Sets/src/mage/cards/g/GallantCitizen.java b/Mage.Sets/src/mage/cards/g/GallantCitizen.java new file mode 100644 index 00000000000..9130c4c2a26 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GallantCitizen.java @@ -0,0 +1,38 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GallantCitizen extends CardImpl { + + public GallantCitizen(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G/W}{G/W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When this creature enters, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1))); + } + + private GallantCitizen(final GallantCitizen card) { + super(card); + } + + @Override + public GallantCitizen copy() { + return new GallantCitizen(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GarrukRelentless.java b/Mage.Sets/src/mage/cards/g/GarrukRelentless.java index 5a26e6a9fab..b22ae5c3b93 100644 --- a/Mage.Sets/src/mage/cards/g/GarrukRelentless.java +++ b/Mage.Sets/src/mage/cards/g/GarrukRelentless.java @@ -1,4 +1,3 @@ - package mage.cards.g; import mage.abilities.Ability; @@ -6,6 +5,7 @@ import mage.abilities.LoyaltyAbility; import mage.abilities.StateTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; @@ -39,13 +39,13 @@ public final class GarrukRelentless extends CardImpl { this.addAbility(new GarrukRelentlessStateTrigger()); // 0: Garruk Relentless deals 3 damage to target creature. That creature deals damage equal to its power to him - LoyaltyAbility ability1 = new LoyaltyAbility(new GarrukRelentlessDamageEffect(), 0); - ability1.addTarget(new TargetCreaturePermanent()); - this.addAbility(ability1); + Ability ability = new LoyaltyAbility(new DamageTargetEffect(3), 0); + ability.addEffect(new GarrukRelentlessDamageEffect()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); // 0: Create a 2/2 green Wolf creature token. - LoyaltyAbility ability2 = new LoyaltyAbility(new CreateTokenEffect(new WolfToken()), 0); - this.addAbility(ability2); + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new WolfToken()), 0)); } private GarrukRelentless(final GarrukRelentless card) { @@ -89,7 +89,7 @@ class GarrukRelentlessDamageEffect extends OneShotEffect { GarrukRelentlessDamageEffect() { super(Outcome.Damage); - staticText = "{this} deals 3 damage to target creature. That creature deals damage equal to its power to him"; + staticText = "That creature deals damage equal to its power to him"; } private GarrukRelentlessDamageEffect(final GarrukRelentlessDamageEffect effect) { @@ -98,19 +98,11 @@ class GarrukRelentlessDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (permanent != null) { - int damage = permanent.getPower().getValue(); - permanent.damage(3, source.getSourceId(), source, game, false, true); - if (damage > 0) { - Permanent garruk = game.getPermanent(source.getSourceId()); - if (garruk != null) { - garruk.damage(damage, permanent.getId(), source, game, false, true); - } - } - return true; - } - return false; + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + Permanent creature = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); + return permanent != null + && creature != null + && permanent.damage(creature.getPower().getValue(), creature.getId(), source, game) > 0; } @Override diff --git a/Mage.Sets/src/mage/cards/g/GarthOneEye.java b/Mage.Sets/src/mage/cards/g/GarthOneEye.java index 4a2b385deeb..77fc0c215a2 100644 --- a/Mage.Sets/src/mage/cards/g/GarthOneEye.java +++ b/Mage.Sets/src/mage/cards/g/GarthOneEye.java @@ -140,7 +140,7 @@ class GarthOneEyeEffect extends OneShotEffect { static final String getKey(Ability source) { return source.getSourceId() + "_" - + source.getSourceObjectZoneChangeCounter() + "_" + + source.getStackMomentSourceZCC() + "_" + source.getOriginalId() + "_garth"; } } diff --git a/Mage.Sets/src/mage/cards/g/GateSmasher.java b/Mage.Sets/src/mage/cards/g/GateSmasher.java index 276d48bc734..a5f77b188c3 100644 --- a/Mage.Sets/src/mage/cards/g/GateSmasher.java +++ b/Mage.Sets/src/mage/cards/g/GateSmasher.java @@ -9,13 +9,12 @@ import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.SubType; +import mage.constants.*; import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.ToughnessPredicate; +import mage.target.TargetCard; import mage.target.TargetPermanent; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/g/GauntletsOfChaos.java b/Mage.Sets/src/mage/cards/g/GauntletsOfChaos.java index 049d1ebeb74..9a073fe7d64 100644 --- a/Mage.Sets/src/mage/cards/g/GauntletsOfChaos.java +++ b/Mage.Sets/src/mage/cards/g/GauntletsOfChaos.java @@ -69,9 +69,10 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { - Set cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), controllerId, game); + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + Set cardTypes = getOpponentPermanentCardTypes(playerId, game); + + if (super.canTarget(playerId, id, source, game)) { Permanent permanent = game.getPermanent(id); for (CardType type : permanent.getCardType(game)) { if (cardTypes.contains(type)) { @@ -84,23 +85,21 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - // get all cardtypes from opponents permanents - Set cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), sourceControllerId, game); + Set cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game); + Set possibleTargets = new HashSet<>(); MageObject targetSource = game.getObject(source); if (targetSource != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - for (CardType type : permanent.getCardType(game)) { - if (cardTypes.contains(type)) { - possibleTargets.add(permanent.getId()); - break; - } + for (CardType type : permanent.getCardType(game)) { + if (cardTypes.contains(type)) { + possibleTargets.add(permanent.getId()); + break; } } } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -108,7 +107,7 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent { return new GauntletsOfChaosFirstTarget(this); } - private EnumSet getOpponentPermanentCardTypes(UUID sourceId, UUID sourceControllerId, Game game) { + private EnumSet getOpponentPermanentCardTypes(UUID sourceControllerId, Game game) { Player controller = game.getPlayer(sourceControllerId); EnumSet cardTypes = EnumSet.noneOf(CardType.class); if (controller != null) { @@ -125,8 +124,6 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent { class GauntletsOfChaosSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public GauntletsOfChaosSecondTarget() { super(); this.filter = this.filter.copy(); @@ -136,44 +133,46 @@ class GauntletsOfChaosSecondTarget extends TargetPermanent { private GauntletsOfChaosSecondTarget(final GauntletsOfChaosSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } @Override public boolean canTarget(UUID id, Ability source, Game game) { - if (super.canTarget(id, source, game)) { - Permanent target1 = game.getPermanent(source.getFirstTarget()); - Permanent opponentPermanent = game.getPermanent(id); - if (target1 != null && opponentPermanent != null) { - return target1.shareTypes(opponentPermanent, game); - } + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + Permanent possiblePermanent = game.getPermanent(id); + if (ownPermanent == null || possiblePermanent == null) { + return false; } - return false; + return super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (permanent.shareTypes(firstTarget, game)) { - possibleTargets.add(permanent.getId()); - } - } + + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (ownPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by shared type + if (permanent.shareTypes(ownPermanent, game)) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); - return super.chooseTarget(Outcome.Damage, playerId, source, game); + // AI hint with better outcome + return super.chooseTarget(Outcome.GainControl, playerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/g/GenerousPatron.java b/Mage.Sets/src/mage/cards/g/GenerousPatron.java index 92037434730..7e6e7a9937c 100644 --- a/Mage.Sets/src/mage/cards/g/GenerousPatron.java +++ b/Mage.Sets/src/mage/cards/g/GenerousPatron.java @@ -1,16 +1,14 @@ package mage.cards.g; import mage.MageInt; +import mage.abilities.common.PutCounterOnPermanentTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.keyword.SupportAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.abilities.common.PutCounterOnCreatureTriggeredAbility; +import mage.filter.StaticFilters; import java.util.UUID; @@ -19,13 +17,6 @@ import java.util.UUID; */ public final class GenerousPatron extends CardImpl { - private static final FilterPermanent filter - = new FilterCreaturePermanent("creature you don't control"); - - static { - filter.add(TargetController.NOT_YOU.getControllerPredicate()); - } - public GenerousPatron(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); @@ -38,7 +29,8 @@ public final class GenerousPatron extends CardImpl { this.addAbility(new SupportAbility(this, 2)); // Whenever you put one or more counters on a creature you don't control, draw a card. - this.addAbility(new PutCounterOnCreatureTriggeredAbility(new DrawCardSourceControllerEffect(1), filter)); + this.addAbility(new PutCounterOnPermanentTriggeredAbility(new DrawCardSourceControllerEffect(1), + null, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); } private GenerousPatron(final GenerousPatron card) { diff --git a/Mage.Sets/src/mage/cards/g/GenerousSoul.java b/Mage.Sets/src/mage/cards/g/GenerousSoul.java index d8516e9c39a..d0d85e6ad79 100644 --- a/Mage.Sets/src/mage/cards/g/GenerousSoul.java +++ b/Mage.Sets/src/mage/cards/g/GenerousSoul.java @@ -1,8 +1,7 @@ package mage.cards.g; import mage.MageInt; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; @@ -29,7 +28,7 @@ public final class GenerousSoul extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // If Generous Soul would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private GenerousSoul(final GenerousSoul card) { diff --git a/Mage.Sets/src/mage/cards/g/GeyserLeaper.java b/Mage.Sets/src/mage/cards/g/GeyserLeaper.java new file mode 100644 index 00000000000..ac92da1f133 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GeyserLeaper.java @@ -0,0 +1,46 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GeyserLeaper extends CardImpl { + + public GeyserLeaper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Waterbend {4}: Draw a card, then discard a card. + this.addAbility(new SimpleActivatedAbility( + new DrawDiscardControllerEffect(1, 1), new WaterbendCost(4) + )); + } + + private GeyserLeaper(final GeyserLeaper card) { + super(card); + } + + @Override + public GeyserLeaper copy() { + return new GeyserLeaper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GhastlordOfFugue.java b/Mage.Sets/src/mage/cards/g/GhastlordOfFugue.java index 271c34aaa56..f60ad91dab2 100644 --- a/Mage.Sets/src/mage/cards/g/GhastlordOfFugue.java +++ b/Mage.Sets/src/mage/cards/g/GhastlordOfFugue.java @@ -82,7 +82,7 @@ class GhastlordOfFugueEffect extends OneShotEffect { TargetCard target = new TargetCard(Zone.HAND, new FilterCard()); target.withNotTarget(true); Card chosenCard = null; - if (controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { + if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { chosenCard = game.getCard(target.getFirstTarget()); } if (chosenCard != null) { diff --git a/Mage.Sets/src/mage/cards/g/GhastlyMimicry.java b/Mage.Sets/src/mage/cards/g/GhastlyMimicry.java index aaa96a259bd..85dce72078a 100644 --- a/Mage.Sets/src/mage/cards/g/GhastlyMimicry.java +++ b/Mage.Sets/src/mage/cards/g/GhastlyMimicry.java @@ -1,13 +1,12 @@ package mage.cards.g; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.CreateTokenCopyTargetEffect; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -37,16 +36,13 @@ public final class GhastlyMimicry extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // At the beginning of your upkeep, create a token that's a copy of enchanted creature, except it's a Spirit in addition to its other types. - this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new GhastlyMimicryEffect() - )); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new GhastlyMimicryEffect())); // If Ghastly Mimicry would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private GhastlyMimicry(final GhastlyMimicry card) { diff --git a/Mage.Sets/src/mage/cards/g/GhostSpiderGwenStacy.java b/Mage.Sets/src/mage/cards/g/GhostSpiderGwenStacy.java new file mode 100644 index 00000000000..a22de7d4aaf --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GhostSpiderGwenStacy.java @@ -0,0 +1,58 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GhostSpiderGwenStacy extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_ATTACKING_CREATURE); + private static final Hint hint = new ValueHint("Attacking creatures", xValue); + + public GhostSpiderGwenStacy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever Ghost-Spider attacks, she deals X damage to defending player, where X is the number of attacking creatures. + this.addAbility(new AttacksTriggeredAbility( + new DamageTargetEffect(xValue) + .setText("she deals X damage to defending player, where X is the number of attacking creatures"), + false, null, SetTargetPointer.PLAYER + ).addHint(hint)); + } + + private GhostSpiderGwenStacy(final GhostSpiderGwenStacy card) { + super(card); + } + + @Override + public GhostSpiderGwenStacy copy() { + return new GhostSpiderGwenStacy(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GhostlyCastigator.java b/Mage.Sets/src/mage/cards/g/GhostlyCastigator.java index 44868f2dd40..bb9f5f62e56 100644 --- a/Mage.Sets/src/mage/cards/g/GhostlyCastigator.java +++ b/Mage.Sets/src/mage/cards/g/GhostlyCastigator.java @@ -3,9 +3,8 @@ package mage.cards.g; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.ShuffleIntoLibraryTargetEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -38,7 +37,7 @@ public final class GhostlyCastigator extends CardImpl { this.addAbility(ability); // If Ghostly Castigator would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private GhostlyCastigator(final GhostlyCastigator card) { diff --git a/Mage.Sets/src/mage/cards/g/Ghostway.java b/Mage.Sets/src/mage/cards/g/Ghostway.java index cd3e87eed23..f26f40f3469 100644 --- a/Mage.Sets/src/mage/cards/g/Ghostway.java +++ b/Mage.Sets/src/mage/cards/g/Ghostway.java @@ -67,7 +67,7 @@ class GhostwayEffect extends OneShotEffect { if (sourceObject != null && controller != null) { Set toExile = new HashSet<>(); toExile.addAll(game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)); - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); controller.moveCardsToExile(toExile, source, game, true, exileId, sourceObject.getIdName()); Cards cardsToReturn = new CardsImpl(); diff --git a/Mage.Sets/src/mage/cards/g/GiantKoi.java b/Mage.Sets/src/mage/cards/g/GiantKoi.java new file mode 100644 index 00000000000..1ff553cbd44 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GiantKoi.java @@ -0,0 +1,46 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; +import mage.abilities.keyword.IslandcyclingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GiantKoi extends CardImpl { + + public GiantKoi(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); + + this.subtype.add(SubType.FISH); + this.power = new MageInt(5); + this.toughness = new MageInt(7); + + // Waterbend {3}: This creature can't be blocked this turn. + this.addAbility(new SimpleActivatedAbility( + new CantBeBlockedSourceEffect(Duration.EndOfTurn), new WaterbendCost(3) + )); + + // Islandcycling {2} + this.addAbility(new IslandcyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private GiantKoi(final GiantKoi card) { + super(card); + } + + @Override + public GiantKoi copy() { + return new GiantKoi(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GideonBattleForged.java b/Mage.Sets/src/mage/cards/g/GideonBattleForged.java index a2adb7e3c1a..a1158494564 100644 --- a/Mage.Sets/src/mage/cards/g/GideonBattleForged.java +++ b/Mage.Sets/src/mage/cards/g/GideonBattleForged.java @@ -1,10 +1,8 @@ package mage.cards.g; -import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.common.PreventAllDamageToSourceEffect; import mage.abilities.effects.common.UntapTargetEffect; @@ -14,12 +12,11 @@ import mage.abilities.keyword.IndestructibleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.token.TokenImpl; -import mage.target.TargetPermanent; +import mage.game.permanent.token.custom.CreatureToken; import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; import java.util.UUID; @@ -28,12 +25,6 @@ import java.util.UUID; */ public final class GideonBattleForged extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); - - static { - filter.add(TargetController.OPPONENT.getControllerPredicate()); - } - public GideonBattleForged(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, ""); this.supertype.add(SuperType.LEGENDARY); @@ -46,27 +37,26 @@ public final class GideonBattleForged extends CardImpl { this.setStartingLoyalty(3); // +2: Up to one target creature an opponent controls attacks Gideon, Battle-Forged during its controller's next turn if able. - LoyaltyAbility loyaltyAbility = new LoyaltyAbility(new GideonBattleForgedAttacksIfAbleTargetEffect(Duration.Custom), 2); - loyaltyAbility.addTarget(new TargetPermanent(0, 1, filter)); - this.addAbility(loyaltyAbility); + Ability ability = new LoyaltyAbility(new GideonBattleForgedEffect(), 2); + ability.addTarget(new TargetOpponentsCreaturePermanent(0, 1)); + this.addAbility(ability); // +1: Until your next turn, target creature gains indestructible. Untap that creature. - Effect effect = new GainAbilityTargetEffect(IndestructibleAbility.getInstance(), Duration.UntilYourNextTurn); - effect.setText("Until your next turn, target creature gains indestructible"); - loyaltyAbility = new LoyaltyAbility(effect, 1); - loyaltyAbility.addTarget(new TargetCreaturePermanent()); - effect = new UntapTargetEffect(); - effect.setText("Untap that creature"); - loyaltyAbility.addEffect(effect); - this.addAbility(loyaltyAbility); + ability = new LoyaltyAbility(new GainAbilityTargetEffect( + IndestructibleAbility.getInstance(), Duration.UntilYourNextTurn + ).setText("Until your next turn, target creature gains indestructible"), 1); + ability.addEffect(new UntapTargetEffect().setText("Untap that creature")); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); // 0: Until end of turn, Gideon, Battle-Forged becomes a 4/4 Human Soldier creature with indestructible that's still a planeswalker. Prevent all damage that would be dealt to him this turn. - LoyaltyAbility ability3 = new LoyaltyAbility(new BecomesCreatureSourceEffect(new GideonBattleForgedToken(), CardType.PLANESWALKER, Duration.EndOfTurn), 0); - effect = new PreventAllDamageToSourceEffect(Duration.EndOfTurn); - effect.setText("Prevent all damage that would be dealt to him this turn"); - ability3.addEffect(effect); - this.addAbility(ability3); - + ability = new LoyaltyAbility(new BecomesCreatureSourceEffect(new CreatureToken( + 4, 4, "4/4 Human Soldier creature " + + "with indestructible", SubType.HUMAN, SubType.SOLDIER + ).withAbility(IndestructibleAbility.getInstance()), CardType.PLANESWALKER, Duration.EndOfTurn), 0); + ability.addEffect(new PreventAllDamageToSourceEffect(Duration.EndOfTurn) + .setText("Prevent all damage that would be dealt to him this turn")); + this.addAbility(ability); } private GideonBattleForged(final GideonBattleForged card) { @@ -79,44 +69,23 @@ public final class GideonBattleForged extends CardImpl { } } -class GideonBattleForgedToken extends TokenImpl { - - public GideonBattleForgedToken() { - super("", "4/4 Human Soldier creature with indestructible"); - cardType.add(CardType.CREATURE); - subtype.add(SubType.HUMAN); - subtype.add(SubType.SOLDIER); - power = new MageInt(4); - toughness = new MageInt(4); - this.addAbility(IndestructibleAbility.getInstance()); - } - - private GideonBattleForgedToken(final GideonBattleForgedToken token) { - super(token); - } - - public GideonBattleForgedToken copy() { - return new GideonBattleForgedToken(this); - } -} - -class GideonBattleForgedAttacksIfAbleTargetEffect extends RequirementEffect { +class GideonBattleForgedEffect extends RequirementEffect { protected MageObjectReference targetPermanentReference; - public GideonBattleForgedAttacksIfAbleTargetEffect(Duration duration) { - super(duration); - staticText = "Up to one target creature an opponent controls attacks {this} during its controller's next turn if able"; + GideonBattleForgedEffect() { + super(Duration.Custom); + staticText = "up to one target creature an opponent controls attacks {this} during its controller's next turn if able"; } - private GideonBattleForgedAttacksIfAbleTargetEffect(final GideonBattleForgedAttacksIfAbleTargetEffect effect) { + private GideonBattleForgedEffect(final GideonBattleForgedEffect effect) { super(effect); this.targetPermanentReference = effect.targetPermanentReference; } @Override - public GideonBattleForgedAttacksIfAbleTargetEffect copy() { - return new GideonBattleForgedAttacksIfAbleTargetEffect(this); + public GideonBattleForgedEffect copy() { + return new GideonBattleForgedEffect(this); } @Override @@ -143,17 +112,16 @@ class GideonBattleForgedAttacksIfAbleTargetEffect extends RequirementEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - if (permanent.getId().equals(getTargetPointer().getFirst(game, source))) { - if (game.isActivePlayer(permanent.getControllerId())) { - Permanent planeswalker = game.getPermanent(source.getSourceId()); - if (planeswalker != null) { - return true; - } else { - discard(); - } - } + if (!permanent.getId().equals(getTargetPointer().getFirst(game, source)) + || !game.isActivePlayer(permanent.getControllerId())) { + return false; } - return false; + Permanent planeswalker = source.getSourcePermanentIfItStillExists(game); + if (planeswalker == null) { + discard(); + return false; + } + return true; } @Override @@ -170,5 +138,4 @@ class GideonBattleForgedAttacksIfAbleTargetEffect extends RequirementEffect { public boolean mustBlock(Game game) { return false; } - } diff --git a/Mage.Sets/src/mage/cards/g/Gilacorn.java b/Mage.Sets/src/mage/cards/g/Gilacorn.java new file mode 100644 index 00000000000..b1ae6068c72 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/Gilacorn.java @@ -0,0 +1,36 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Gilacorn extends CardImpl { + + public Gilacorn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.subtype.add(SubType.LIZARD); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + } + + private Gilacorn(final Gilacorn card) { + super(card); + } + + @Override + public Gilacorn copy() { + return new Gilacorn(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GiltspireAvenger.java b/Mage.Sets/src/mage/cards/g/GiltspireAvenger.java index fd51af64dd2..9f538b41201 100644 --- a/Mage.Sets/src/mage/cards/g/GiltspireAvenger.java +++ b/Mage.Sets/src/mage/cards/g/GiltspireAvenger.java @@ -2,7 +2,6 @@ package mage.cards.g; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -12,15 +11,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate; import mage.target.TargetPermanent; -import mage.watchers.common.PlayerDamagedBySourceWatcher; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -28,6 +23,12 @@ import java.util.UUID; */ public final class GiltspireAvenger extends CardImpl { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature that dealt damage to you this turn"); + + static { + filter.add(new DamagedPlayerThisTurnPredicate(TargetController.YOU)); + } + public GiltspireAvenger(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}"); this.subtype.add(SubType.HUMAN); @@ -41,7 +42,7 @@ public final class GiltspireAvenger extends CardImpl { // {T}: Destroy target creature that dealt damage to you this turn. Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new TapSourceCost()); - ability.addTarget(new GiltspireAvengerTarget()); + ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } @@ -55,64 +56,3 @@ public final class GiltspireAvenger extends CardImpl { return new GiltspireAvenger(this); } } - -class GiltspireAvengerTarget extends TargetPermanent { - - public GiltspireAvengerTarget() { - super(1, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false); - targetName = "creature that dealt damage to you this turn"; - } - - private GiltspireAvengerTarget(final GiltspireAvengerTarget target) { - super(target); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, source.getControllerId()); - if (watcher != null && watcher.hasSourceDoneDamage(id, game)) { - return super.canTarget(id, source, game); - } - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId); - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && watcher != null && watcher.hasSourceDoneDamage(targetId, game)) { - possibleTargets.add(targetId); - } - } - return possibleTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int remainingTargets = this.minNumberOfTargets - targets.size(); - if (remainingTargets == 0) { - return true; - } - int count = 0; - MageObject targetSource = game.getObject(source); - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game) - && watcher != null && watcher.hasSourceDoneDamage(permanent.getId(), game)) { - count++; - if (count >= remainingTargets) { - return true; - } - } - } - return false; - } - - @Override - public GiltspireAvengerTarget copy() { - return new GiltspireAvengerTarget(this); - } -} diff --git a/Mage.Sets/src/mage/cards/g/Glaciers.java b/Mage.Sets/src/mage/cards/g/Glaciers.java index d13f9efb471..71d51987e41 100644 --- a/Mage.Sets/src/mage/cards/g/Glaciers.java +++ b/Mage.Sets/src/mage/cards/g/Glaciers.java @@ -51,6 +51,12 @@ public final class Glaciers extends CardImpl { GlaciersEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); this.staticText = "All Mountains are Plains"; + this.dependendToTypes.add(DependencyType.BecomeForest); + this.dependendToTypes.add(DependencyType.BecomeIsland); + this.dependendToTypes.add(DependencyType.BecomeMountain); + this.dependendToTypes.add(DependencyType.BecomePlains); + this.dependendToTypes.add(DependencyType.BecomeSwamp); + this.dependencyTypes.add(DependencyType.BecomePlains); } private GlaciersEffect(final GlaciersEffect effect) { diff --git a/Mage.Sets/src/mage/cards/g/GliderKids.java b/Mage.Sets/src/mage/cards/g/GliderKids.java new file mode 100644 index 00000000000..aa5785f7dae --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GliderKids.java @@ -0,0 +1,43 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GliderKids extends CardImpl { + + public GliderKids(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PILOT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, scry 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(1))); + } + + private GliderKids(final GliderKids card) { + super(card); + } + + @Override + public GliderKids copy() { + return new GliderKids(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GlimpseTheCore.java b/Mage.Sets/src/mage/cards/g/GlimpseTheCore.java index 0445ebb6702..03c1269f250 100644 --- a/Mage.Sets/src/mage/cards/g/GlimpseTheCore.java +++ b/Mage.Sets/src/mage/cards/g/GlimpseTheCore.java @@ -8,7 +8,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInYourGraveyard; @@ -19,7 +19,7 @@ import java.util.UUID; */ public final class GlimpseTheCore extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard(SubType.FOREST); + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); private static final FilterCard filter2 = new FilterCard("Cave card from your graveyard"); static { diff --git a/Mage.Sets/src/mage/cards/g/GloriousProtector.java b/Mage.Sets/src/mage/cards/g/GloriousProtector.java index f667a959a5e..77eadff6410 100644 --- a/Mage.Sets/src/mage/cards/g/GloriousProtector.java +++ b/Mage.Sets/src/mage/cards/g/GloriousProtector.java @@ -100,7 +100,7 @@ class GloriousProtectorEffect extends OneShotEffect { player.moveCardsToExile( new CardsImpl(target.getTargets()).getCards(game), source, game, true, CardUtil.getExileZoneId( - game, source.getSourceId(), source.getSourceObjectZoneChangeCounter() + game, source.getSourceId(), source.getStackMomentSourceZCC() ), sourceObject.getIdName() ); game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source); diff --git a/Mage.Sets/src/mage/cards/g/GloryheathLynx.java b/Mage.Sets/src/mage/cards/g/GloryheathLynx.java index cf551f1ccbf..738cdfd068a 100644 --- a/Mage.Sets/src/mage/cards/g/GloryheathLynx.java +++ b/Mage.Sets/src/mage/cards/g/GloryheathLynx.java @@ -9,8 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -20,13 +19,6 @@ import java.util.UUID; */ public final class GloryheathLynx extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - public GloryheathLynx(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); @@ -40,7 +32,7 @@ public final class GloryheathLynx extends CardImpl { // Whenever this creature attacks while saddled, search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. this.addAbility(new AttacksWhileSaddledTriggeredAbility( - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true) )); // Saddle 2 diff --git a/Mage.Sets/src/mage/cards/g/GlyphOfDelusion.java b/Mage.Sets/src/mage/cards/g/GlyphOfDelusion.java index 4b6b8c25125..06ae97d9e0f 100644 --- a/Mage.Sets/src/mage/cards/g/GlyphOfDelusion.java +++ b/Mage.Sets/src/mage/cards/g/GlyphOfDelusion.java @@ -1,9 +1,7 @@ package mage.cards.g; -import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect; @@ -11,11 +9,14 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DontUntapInControllersUntapStepSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; @@ -59,8 +60,6 @@ public final class GlyphOfDelusion extends CardImpl { class GlyphOfDelusionSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public GlyphOfDelusionSecondTarget() { super(); withTargetName("target creature that target Wall blocked this turn"); @@ -68,33 +67,39 @@ class GlyphOfDelusionSecondTarget extends TargetPermanent { private GlyphOfDelusionSecondTarget(final GlyphOfDelusionSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - BlockedAttackerWatcher watcher = game.getState().getWatcher(BlockedAttackerWatcher.class); - if (watcher != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, source, game)) { - if (!targets.containsKey(creature.getId()) && creature.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), new MageObjectReference(firstTarget, game))) { - possibleTargets.add(creature.getId()); - } - } - } + + BlockedAttackerWatcher watcher = game.getState().getWatcher(BlockedAttackerWatcher.class); + if (watcher == null) { + return possibleTargets; + } + + Permanent targetWall = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (targetWall == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by blocked + if (watcher.creatureHasBlockedAttacker(new MageObjectReference(permanent, game), new MageObjectReference(targetWall, game))) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> targetWall != null && targetWall.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); + // AI hint with better outcome return super.chooseTarget(Outcome.Tap, playerId, source, game); } @@ -133,8 +138,7 @@ class GlyphOfDelusionEffect extends OneShotEffect { effect.setTargetPointer(new FixedTarget(targetPermanent.getId(), game)); game.addEffect(effect, source); - BeginningOfUpkeepTriggeredAbility ability2 = new BeginningOfUpkeepTriggeredAbility(new RemoveCounterSourceEffect(CounterType.GLYPH.createInstance()) - ); + BeginningOfUpkeepTriggeredAbility ability2 = new BeginningOfUpkeepTriggeredAbility(new RemoveCounterSourceEffect(CounterType.GLYPH.createInstance())); GainAbilityTargetEffect effect2 = new GainAbilityTargetEffect(ability2, Duration.Custom); effect2.setTargetPointer(new FixedTarget(targetPermanent.getId(), game)); game.addEffect(effect2, source); diff --git a/Mage.Sets/src/mage/cards/g/GoblinArtisans.java b/Mage.Sets/src/mage/cards/g/GoblinArtisans.java index d134b26b94b..1c4a54413ec 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinArtisans.java +++ b/Mage.Sets/src/mage/cards/g/GoblinArtisans.java @@ -81,8 +81,8 @@ class GoblinArtisansTarget extends TargetSpell { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } MageObjectReference sourceRef = new MageObjectReference(source.getSourceObject(game), game); diff --git a/Mage.Sets/src/mage/cards/g/GoblinNegotiation.java b/Mage.Sets/src/mage/cards/g/GoblinNegotiation.java index 368eb608eab..538d9976e60 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinNegotiation.java +++ b/Mage.Sets/src/mage/cards/g/GoblinNegotiation.java @@ -15,7 +15,6 @@ import mage.util.CardUtil; import java.util.UUID; /** - * * @author ciaccona007 */ public final class GoblinNegotiation extends CardImpl { @@ -62,14 +61,12 @@ class GoblinNegotiationEffect extends OneShotEffect { if (permanent == null) { return false; } - int damage = CardUtil.getSourceCostsTag(game, source, "X", 0); - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), damage); - permanent.damage(damage, source.getSourceId(), source, game); - if (damage > lethal) { - new GoblinToken().putOntoBattlefield( - damage - lethal, game, source, source.getControllerId() - ); + int excess = permanent.damageWithExcess( + CardUtil.getSourceCostsTag(game, source, "X", 0), source, game + ); + if (excess > 0) { + new GoblinToken().putOntoBattlefield(excess, game, source); } return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/g/GoldenGuardian.java b/Mage.Sets/src/mage/cards/g/GoldenGuardian.java index af1a5657715..d30b0a5cf7a 100644 --- a/Mage.Sets/src/mage/cards/g/GoldenGuardian.java +++ b/Mage.Sets/src/mage/cards/g/GoldenGuardian.java @@ -104,17 +104,11 @@ class GoldenGuardianReturnTransformedEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - if (game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { - game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE); - Card card = game.getCard(source.getSourceId()); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game); - } - } - return true; + if (controller == null || game.getState().getZone(source.getSourceId()) != Zone.GRAVEYARD) { + return false; } - return false; + game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE); + Card card = game.getCard(source.getSourceId()); + return card != null && controller.moveCards(card, Zone.BATTLEFIELD, source, game); } - } diff --git a/Mage.Sets/src/mage/cards/g/GontiNightMinister.java b/Mage.Sets/src/mage/cards/g/GontiNightMinister.java index 365c57648ff..af7783794e0 100644 --- a/Mage.Sets/src/mage/cards/g/GontiNightMinister.java +++ b/Mage.Sets/src/mage/cards/g/GontiNightMinister.java @@ -12,7 +12,6 @@ import mage.cards.Card; import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.filter.FilterSpell; import mage.game.Game; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; @@ -152,7 +151,7 @@ class GontiExileEffect extends OneShotEffect { if (card == null) { return false; } - UUID exileZoneId = CardUtil.getExileZoneId(game, controller.getId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZoneId = CardUtil.getExileZoneId(game, controller.getId(), source.getStackMomentSourceZCC()); String exileName = CardUtil.getSourceName(game, source) + " - " + controller.getName(); if (controller.moveCardsToExile(card, source, game, false, exileZoneId, exileName)) { card.setFaceDown(true, game); diff --git a/Mage.Sets/src/mage/cards/g/GorexTheTombshell.java b/Mage.Sets/src/mage/cards/g/GorexTheTombshell.java index 691fac1f249..5f3ccc7a9c1 100644 --- a/Mage.Sets/src/mage/cards/g/GorexTheTombshell.java +++ b/Mage.Sets/src/mage/cards/g/GorexTheTombshell.java @@ -135,11 +135,11 @@ class GorexTheTombshellReturnEffect extends OneShotEffect { // relative zcc depends on object zone (battlefield for attacks trigger, graveyard for dies) // so try both zcc offsets to find zone ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId( - game, source.getSourceId(), source.getSourceObjectZoneChangeCounter() - 1 + game, source.getSourceId(), source.getStackMomentSourceZCC() - 1 )); if (exileZone == null || exileZone.isEmpty()) { exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId( - game, source.getSourceId(), source.getSourceObjectZoneChangeCounter() - 2 + game, source.getSourceId(), source.getStackMomentSourceZCC() - 2 )); if (exileZone == null || exileZone.isEmpty()) { return false; diff --git a/Mage.Sets/src/mage/cards/g/GracefulTakedown.java b/Mage.Sets/src/mage/cards/g/GracefulTakedown.java index 2c946a8ba44..42225c2acb6 100644 --- a/Mage.Sets/src/mage/cards/g/GracefulTakedown.java +++ b/Mage.Sets/src/mage/cards/g/GracefulTakedown.java @@ -61,8 +61,8 @@ class GracefulTakedownTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent permanent = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/g/GreenGoblinRevenant.java b/Mage.Sets/src/mage/cards/g/GreenGoblinRevenant.java new file mode 100644 index 00000000000..994bab4bcbc --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GreenGoblinRevenant.java @@ -0,0 +1,88 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.Game; +import mage.watchers.common.DiscardedCardWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GreenGoblinRevenant extends CardImpl { + + public GreenGoblinRevenant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever Green Goblin attacks, discard a card. Then draw a card for each card you've discarded this turn. + Ability ability = new AttacksTriggeredAbility(new DiscardControllerEffect(1)); + ability.addEffect(new DrawCardSourceControllerEffect(GreenGoblinRevenantValue.instance).concatBy("Then")); + this.addAbility(ability.addHint(GreenGoblinRevenantValue.getHint()), new DiscardedCardWatcher()); + } + + private GreenGoblinRevenant(final GreenGoblinRevenant card) { + super(card); + } + + @Override + public GreenGoblinRevenant copy() { + return new GreenGoblinRevenant(this); + } +} + +enum GreenGoblinRevenantValue implements DynamicValue { + instance; + private static final Hint hint = new ValueHint("Cards you've discarded this turn", instance); + + public static Hint getHint() { + return hint; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return DiscardedCardWatcher.getDiscarded(sourceAbility.getControllerId(), game); + } + + @Override + public GreenGoblinRevenantValue copy() { + return instance; + } + + @Override + public String toString() { + return "1"; + } + + @Override + public String getMessage() { + return "card you've discarded this turn"; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GrimeGorger.java b/Mage.Sets/src/mage/cards/g/GrimeGorger.java index 2f55fa76495..01ab1679c2b 100644 --- a/Mage.Sets/src/mage/cards/g/GrimeGorger.java +++ b/Mage.Sets/src/mage/cards/g/GrimeGorger.java @@ -129,7 +129,7 @@ class GrimeGorgerTarget extends TargetCardInGraveyard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/g/GrimoireThief.java b/Mage.Sets/src/mage/cards/g/GrimoireThief.java index e521250b5e0..18e81ea55b2 100644 --- a/Mage.Sets/src/mage/cards/g/GrimoireThief.java +++ b/Mage.Sets/src/mage/cards/g/GrimoireThief.java @@ -90,7 +90,7 @@ class GrimoireThiefExileEffect extends OneShotEffect { card.setFaceDown(true, game); } UUID exileZoneId = CardUtil.getExileZoneId(game, - source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + source.getSourceId(), source.getStackMomentSourceZCC()); targetOpponent.moveCardsToExile(cards, source, game, false, exileZoneId, sourceObject.getIdName()); for (Card card : cards) { diff --git a/Mage.Sets/src/mage/cards/g/GrislyAnglerfish.java b/Mage.Sets/src/mage/cards/g/GrislyAnglerfish.java index 60a4e8a6058..a8eace0c588 100644 --- a/Mage.Sets/src/mage/cards/g/GrislyAnglerfish.java +++ b/Mage.Sets/src/mage/cards/g/GrislyAnglerfish.java @@ -1,29 +1,25 @@ - package mage.cards.g; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.RequirementEffect; +import mage.abilities.effects.common.combat.AttacksIfAbleAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author fireshoes */ public final class GrislyAnglerfish extends CardImpl { public GrislyAnglerfish(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},""); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); this.subtype.add(SubType.ELDRAZI); this.subtype.add(SubType.FISH); this.power = new MageInt(4); @@ -33,7 +29,9 @@ public final class GrislyAnglerfish extends CardImpl { this.nightCard = true; // {6}: Creatures your opponents control attack this turn if able. - this.addAbility(new SimpleActivatedAbility(new GrislyAnglerfishMustAttackEffect(), new ManaCostsImpl<>("{6}"))); + this.addAbility(new SimpleActivatedAbility(new AttacksIfAbleAllEffect( + StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURES, Duration.EndOfTurn + ), new ManaCostsImpl<>("{6}"))); } private GrislyAnglerfish(final GrislyAnglerfish card) { @@ -45,39 +43,3 @@ public final class GrislyAnglerfish extends CardImpl { return new GrislyAnglerfish(this); } } - -class GrislyAnglerfishMustAttackEffect extends RequirementEffect { - - GrislyAnglerfishMustAttackEffect() { - super(Duration.EndOfTurn); - staticText = "Creatures your opponents control attack this turn if able"; - } - - private GrislyAnglerfishMustAttackEffect(final GrislyAnglerfishMustAttackEffect effect) { - super(effect); - } - - @Override - public GrislyAnglerfishMustAttackEffect copy() { - return new GrislyAnglerfishMustAttackEffect(this); - } - - @Override - public boolean applies(Permanent permanent, Ability source, Game game) { - if (game.getOpponents(source.getControllerId()).contains(permanent.getControllerId())) { - return true; - } - return false; - } - - @Override - public boolean mustAttack(Game game) { - return true; - } - - @Override - public boolean mustBlock(Game game) { - return false; - } - -} diff --git a/Mage.Sets/src/mage/cards/g/GrixisPanorama.java b/Mage.Sets/src/mage/cards/g/GrixisPanorama.java index 56eb1fbc431..5a62f22285a 100644 --- a/Mage.Sets/src/mage/cards/g/GrixisPanorama.java +++ b/Mage.Sets/src/mage/cards/g/GrixisPanorama.java @@ -1,7 +1,5 @@ - package mage.cards.g; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; @@ -11,13 +9,16 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author North */ public final class GrixisPanorama extends CardImpl { @@ -25,7 +26,6 @@ public final class GrixisPanorama extends CardImpl { private static final FilterCard filter = new FilterCard("a basic Island, Swamp, or Mountain card"); static { - filter.add(CardType.LAND.getPredicate()); filter.add(SuperType.BASIC.getPredicate()); filter.add(Predicates.or( SubType.ISLAND.getPredicate(), @@ -34,7 +34,7 @@ public final class GrixisPanorama extends CardImpl { } public GrixisPanorama(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); this.addAbility(new ColorlessManaAbility()); TargetCardInLibrary target = new TargetCardInLibrary(filter); diff --git a/Mage.Sets/src/mage/cards/g/GrizzledAngler.java b/Mage.Sets/src/mage/cards/g/GrizzledAngler.java index 9b31af0fff7..dbe16de1fbc 100644 --- a/Mage.Sets/src/mage/cards/g/GrizzledAngler.java +++ b/Mage.Sets/src/mage/cards/g/GrizzledAngler.java @@ -1,23 +1,22 @@ - package mage.cards.g; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; +import mage.filter.FilterCard; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.mageobject.ColorlessPredicate; -import mage.game.Game; -import mage.players.Player; import java.util.UUID; @@ -26,6 +25,14 @@ import java.util.UUID; */ public final class GrizzledAngler extends CardImpl { + private static final FilterCard filter = new FilterCreatureCard(); + + static { + filter.add(ColorlessPredicate.instance); + } + + private static final Condition condition = new CardsInControllerGraveyardCondition(1, filter); + public GrizzledAngler(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); this.subtype.add(SubType.HUMAN); @@ -36,7 +43,11 @@ public final class GrizzledAngler extends CardImpl { // {T}: Put the top two cards of your library into your graveyard. Then if there is a colorless creature card in your graveyard, transform Grizzled Angler. this.addAbility(new TransformAbility()); - this.addAbility(new SimpleActivatedAbility(new GrizzledAnglerEffect(), new TapSourceCost())); + Ability ability = new SimpleActivatedAbility(new MillCardsControllerEffect(2), new TapSourceCost()); + ability.addEffect(new ConditionalOneShotEffect( + new TransformSourceEffect(), condition, + "Then if there is a colorless creature card in your graveyard, transform {this}" + )); } private GrizzledAngler(final GrizzledAngler card) { @@ -48,38 +59,3 @@ public final class GrizzledAngler extends CardImpl { return new GrizzledAngler(this); } } - -class GrizzledAnglerEffect extends OneShotEffect { - - private static final FilterCreatureCard filter = new FilterCreatureCard("a colorless creature card in your graveyard"); - - static { - filter.add(ColorlessPredicate.instance); - } - - public GrizzledAnglerEffect() { - super(Outcome.Benefit); - staticText = "Mill two cards. Then if there is a colorless creature card in your graveyard, transform {this}"; - } - - private GrizzledAnglerEffect(final GrizzledAnglerEffect effect) { - super(effect); - } - - @Override - public GrizzledAnglerEffect copy() { - return new GrizzledAnglerEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - controller.millCards(2, source, game); - if (controller.getGraveyard().count(filter, source.getControllerId(), source, game) >= 1) { - return new TransformSourceEffect().apply(game, source); - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/g/GrothamaAllDevouring.java b/Mage.Sets/src/mage/cards/g/GrothamaAllDevouring.java index 76598f8f73e..7830b4f8fe0 100644 --- a/Mage.Sets/src/mage/cards/g/GrothamaAllDevouring.java +++ b/Mage.Sets/src/mage/cards/g/GrothamaAllDevouring.java @@ -19,7 +19,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.WatcherScope; -import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; @@ -152,7 +151,7 @@ class GrothamaAllDevouringDrawCardsEffect extends OneShotEffect { if (watcher == null) { return false; } - Map damageMap = watcher.getDamageMap(new MageObjectReference(source.getSourceId(), source.getSourceObjectZoneChangeCounter() - 1, game)); + Map damageMap = watcher.getDamageMap(new MageObjectReference(source.getSourceId(), source.getStackMomentSourceZCC() - 1, game)); for (UUID playerId : game.getPlayerList()) { Player player = game.getPlayer(playerId); if (player != null) { diff --git a/Mage.Sets/src/mage/cards/g/Groundskeeper.java b/Mage.Sets/src/mage/cards/g/Groundskeeper.java index b5b35ee5935..57fb607e64a 100644 --- a/Mage.Sets/src/mage/cards/g/Groundskeeper.java +++ b/Mage.Sets/src/mage/cards/g/Groundskeeper.java @@ -9,8 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInYourGraveyard; import java.util.UUID; @@ -20,8 +19,6 @@ import java.util.UUID; */ public final class Groundskeeper extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("basic land card from your graveyard"); - public Groundskeeper(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); this.subtype.add(SubType.HUMAN); @@ -31,7 +28,7 @@ public final class Groundskeeper extends CardImpl { // {1}{G}: Return target basic land card from your graveyard to your hand. Ability ability = new SimpleActivatedAbility(new ReturnFromGraveyardToHandTargetEffect(), new ManaCostsImpl<>("{1}{G}")); - ability.addTarget(new TargetCardInYourGraveyard(filter)); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_BASIC_LAND)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GrowExtraArms.java b/Mage.Sets/src/mage/cards/g/GrowExtraArms.java new file mode 100644 index 00000000000..0afce620725 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GrowExtraArms.java @@ -0,0 +1,47 @@ +package mage.cards.g; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceTargetsPermanentCondition; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GrowExtraArms extends CardImpl { + + private static final Condition condition + = new SourceTargetsPermanentCondition(new FilterPermanent(SubType.SPIDER, "a Spider")); + + public GrowExtraArms(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // This spell costs {1} less to cast if it targets a Spider. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(1, condition).setCanWorksOnStackOnly(true) + ).setRuleAtTheTop(true)); + + // Target creature gets +4/+4 until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(4, 4)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private GrowExtraArms(final GrowExtraArms card) { + super(card); + } + + @Override + public GrowExtraArms copy() { + return new GrowExtraArms(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/Gurzigost.java b/Mage.Sets/src/mage/cards/g/Gurzigost.java index dfbf9edb8b6..a9faa349cd3 100644 --- a/Mage.Sets/src/mage/cards/g/Gurzigost.java +++ b/Mage.Sets/src/mage/cards/g/Gurzigost.java @@ -92,7 +92,7 @@ class GurzigostCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/g/GutterShortcut.java b/Mage.Sets/src/mage/cards/g/GutterShortcut.java index 179bfeb8eb2..bfb716b5d02 100644 --- a/Mage.Sets/src/mage/cards/g/GutterShortcut.java +++ b/Mage.Sets/src/mage/cards/g/GutterShortcut.java @@ -1,13 +1,12 @@ package mage.cards.g; import mage.abilities.Ability; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.combat.CantBeBlockedAttachedEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -38,8 +37,7 @@ public final class GutterShortcut extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature can't be blocked as long as it's attacking alone. this.addAbility(new SimpleStaticAbility(new ConditionalRestrictionEffect( @@ -48,7 +46,7 @@ public final class GutterShortcut extends CardImpl { ))); // If Gutter Shortcut would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private GutterShortcut(final GutterShortcut card) { diff --git a/Mage.Sets/src/mage/cards/g/GwenStacy.java b/Mage.Sets/src/mage/cards/g/GwenStacy.java new file mode 100644 index 00000000000..d5076e6e817 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GwenStacy.java @@ -0,0 +1,67 @@ +package mage.cards.g; + +import mage.abilities.common.*; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardSetInfo; +import mage.cards.ModalDoubleFacedCard; +import mage.constants.*; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class GwenStacy extends ModalDoubleFacedCard { + + public GwenStacy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.PERFORMER, SubType.HERO}, "{1}{R}", + "Ghost-Spider", + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.SPIDER, SubType.HUMAN, SubType.HERO}, "{2}{U}{R}{W}"); + + this.getLeftHalfCard().setPT(2, 1); + this.getRightHalfCard().setPT(4, 4); + + // When Gwen Stacy enters, exile the top card of your library. You may play that card for as long as you control this creature. + this.getLeftHalfCard().addAbility(new EntersBattlefieldTriggeredAbility(new ExileTopXMayPlayUntilEffect(1, Duration.WhileControlled) + .withTextOptions("that card", true))); + // {2}{U}{R}{W}: Transform Gwen Stacy. Activate only as a sorcery. + this.getLeftHalfCard().addAbility(new ActivateAsSorceryActivatedAbility(new TransformSourceEffect(), new ManaCostsImpl<>("{2}{U}{R}{W}"))); + + // Ghost-Spider + // Flying + this.getRightHalfCard().addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.getRightHalfCard().addAbility(VigilanceAbility.getInstance()); + + // Haste + this.getRightHalfCard().addAbility(HasteAbility.getInstance()); + + // Whenever you play a land from exile or cast a spell from exile, put a +1/+1 counter on Ghost-Spider. + this.getRightHalfCard().addAbility(new PlayLandOrCastSpellTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), true, false)); + // Remove two counters from Ghost-Spider: Exile the top card of your library. You may play that card this turn. + this.getRightHalfCard().addAbility(new SimpleActivatedAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn), + new RemoveCountersSourceCost(CounterType.P1P1.createInstance(2)))); + } + + private GwenStacy(final GwenStacy card) { + super(card); + } + + @Override + public GwenStacy copy() { + return new GwenStacy(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GwenomRemorseless.java b/Mage.Sets/src/mage/cards/g/GwenomRemorseless.java new file mode 100644 index 00000000000..dc637d370e3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GwenomRemorseless.java @@ -0,0 +1,124 @@ +package mage.cards.g; + +import mage.MageIdentifier; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class GwenomRemorseless extends CardImpl { + + public GwenomRemorseless(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SYMBIOTE); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HERO); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever Gwenom attacks, until end of turn you may look at the top card of your library any time and you may play cards from the top of your library. If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost. + ContinuousEffect libraryAnyTimeEffect = new LookAtTopCardOfLibraryAnyTimeEffect(Duration.EndOfTurn); + libraryAnyTimeEffect.setText("until end of turn you may look at the top card of your library any time"); + libraryAnyTimeEffect.concatBy(" "); + AsThoughEffectImpl playCardEffect = new GwenomRemorselessPlayTopCardEffect(); + playCardEffect.concatBy("and"); + this.addAbility(new AttacksTriggeredAbility(new AddContinuousEffectToGame(libraryAnyTimeEffect, playCardEffect)) + .setIdentifier(MageIdentifier.GwenomRemorselessAlternateCast)); + } + + private GwenomRemorseless(final GwenomRemorseless card) { + super(card); + } + + @Override + public GwenomRemorseless copy() { + return new GwenomRemorseless(this); + } +} + +class GwenomRemorselessPlayTopCardEffect extends AsThoughEffectImpl { + + GwenomRemorselessPlayTopCardEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, + Duration.EndOfTurn, Outcome.AIDontUseIt); // AI will need help with this + staticText = "you may play cards from the top of your library. If you cast a spell this way, " + + "pay life equal to its mana value rather than pay its mana cost."; + } + + private GwenomRemorselessPlayTopCardEffect(final GwenomRemorselessPlayTopCardEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public GwenomRemorselessPlayTopCardEffect copy() { + return new GwenomRemorselessPlayTopCardEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + // current card's part + Card cardToCheck = game.getCard(objectId); + if (cardToCheck == null) { + return false; + } + + // must be you + if (!affectedControllerId.equals(source.getControllerId())) { + return false; + } + + // must be your card + Player player = game.getPlayer(cardToCheck.getOwnerId()); + if (player == null || !player.getId().equals(affectedControllerId)) { + return false; + } + + // must be from your library + Card topCard = player.getLibrary().getFromTop(game); + if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) { + return false; + } + // allows to play/cast with alternative life cost + if (!cardToCheck.isLand(game)) { + PayLifeCost lifeCost = new PayLifeCost(cardToCheck.getSpellAbility().getManaCosts().manaValue()); + Costs newCosts = new CostsImpl(); + newCosts.add(lifeCost); + newCosts.addAll(cardToCheck.getSpellAbility().getCosts()); + player.setCastSourceIdWithAlternateMana(cardToCheck.getId(), null, newCosts, MageIdentifier.GwenomRemorselessAlternateCast); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HakodaSelflessCommander.java b/Mage.Sets/src/mage/cards/h/HakodaSelflessCommander.java new file mode 100644 index 00000000000..09523f531aa --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HakodaSelflessCommander.java @@ -0,0 +1,72 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HakodaSelflessCommander extends CardImpl { + + private static final FilterCard filter = new FilterCard(SubType.ALLY, "Ally spells"); + + public HakodaSelflessCommander(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.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // You may cast Ally spells from the top of your library. + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); + + // Sacrifice Hakoda: Creatures you control get +0/+5 and gain indestructible until end of turn. + Ability ability = new SimpleActivatedAbility( + new BoostControlledEffect(0, 5, Duration.EndOfTurn) + .setText("creatures you control get +0/+5"), + new SacrificeSourceCost() + ); + ability.addEffect(new GainAbilityAllEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURE + ).setText("and gain indestructible until end of turn")); + this.addAbility(ability); + } + + private HakodaSelflessCommander(final HakodaSelflessCommander card) { + super(card); + } + + @Override + public HakodaSelflessCommander copy() { + return new HakodaSelflessCommander(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HaktosTheUnscarred.java b/Mage.Sets/src/mage/cards/h/HaktosTheUnscarred.java index 170498ae371..dc4c3aebd73 100644 --- a/Mage.Sets/src/mage/cards/h/HaktosTheUnscarred.java +++ b/Mage.Sets/src/mage/cards/h/HaktosTheUnscarred.java @@ -95,7 +95,7 @@ class HaktosTheUnscarredChooseEffect extends OneShotEffect { } int number = 2 + RandomUtil.nextInt(3); game.informPlayers(permanent.getLogName() + ": " + controller.getLogName() + " has chosen " + number + " at random"); - game.getState().setValue(permanent.getId() + "" + source.getSourceObjectZoneChangeCounter() + "_haktos_number", number); + game.getState().setValue(permanent.getId() + "" + source.getStackMomentSourceZCC() + "_haktos_number", number); permanent.addInfo("chosen number", CardUtil.addToolTipMarkTags("Chosen number: " + number), game); return true; } diff --git a/Mage.Sets/src/mage/cards/h/HamletGlutton.java b/Mage.Sets/src/mage/cards/h/HamletGlutton.java index 703877d465b..376bef9b089 100644 --- a/Mage.Sets/src/mage/cards/h/HamletGlutton.java +++ b/Mage.Sets/src/mage/cards/h/HamletGlutton.java @@ -63,7 +63,7 @@ enum HamletGluttonAdjuster implements CostAdjuster { @Override public void reduceCost(Ability ability, Game game) { if (BargainedCondition.instance.apply(game, ability) - || (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) { + || (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) { CardUtil.reduceCost(ability, 2); } } diff --git a/Mage.Sets/src/mage/cards/h/HapatraVizierOfPoisons.java b/Mage.Sets/src/mage/cards/h/HapatraVizierOfPoisons.java index de22bd36181..f9377b114d2 100644 --- a/Mage.Sets/src/mage/cards/h/HapatraVizierOfPoisons.java +++ b/Mage.Sets/src/mage/cards/h/HapatraVizierOfPoisons.java @@ -3,7 +3,7 @@ package mage.cards.h; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; -import mage.abilities.common.PutCounterOnCreatureTriggeredAbility; +import mage.abilities.common.PutCounterOnPermanentTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; @@ -12,6 +12,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.game.permanent.token.DeathtouchSnakeToken; import mage.target.common.TargetCreaturePermanent; @@ -37,7 +38,8 @@ public final class HapatraVizierOfPoisons extends CardImpl { this.addAbility(ability); // Whenever you put one or more -1/-1 counters on a creature, create a 1/1 green Snake creature token with deathtouch. - this.addAbility(new PutCounterOnCreatureTriggeredAbility(new CreateTokenEffect(new DeathtouchSnakeToken()), CounterType.M1M1.createInstance())); + this.addAbility(new PutCounterOnPermanentTriggeredAbility(new CreateTokenEffect(new DeathtouchSnakeToken()), + CounterType.M1M1, StaticFilters.FILTER_PERMANENT_CREATURE)); } private HapatraVizierOfPoisons(final HapatraVizierOfPoisons card) { diff --git a/Mage.Sets/src/mage/cards/h/HarbingerOfTheSeas.java b/Mage.Sets/src/mage/cards/h/HarbingerOfTheSeas.java index f7856b72692..8a7f1d34cc2 100644 --- a/Mage.Sets/src/mage/cards/h/HarbingerOfTheSeas.java +++ b/Mage.Sets/src/mage/cards/h/HarbingerOfTheSeas.java @@ -54,6 +54,7 @@ class HarbingerOfTheSeasEffect extends ContinuousEffectImpl { HarbingerOfTheSeasEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); this.staticText = "Nonbasic lands are Islands"; + dependendToTypes.add(DependencyType.BecomeNonbasicLand); dependencyTypes.add(DependencyType.BecomeIsland); } diff --git a/Mage.Sets/src/mage/cards/h/HaruHiddenTalent.java b/Mage.Sets/src/mage/cards/h/HaruHiddenTalent.java new file mode 100644 index 00000000000..bad79858e09 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HaruHiddenTalent.java @@ -0,0 +1,56 @@ + +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author Grath + */ +public final class HaruHiddenTalent extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ALLY, "another Ally you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + public HaruHiddenTalent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.subtype.add(SubType.ALLY); + + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever another Ally you control enters, earthbend 1. + Ability ability = new EntersBattlefieldAllTriggeredAbility(new EarthbendTargetEffect(1), filter); + ability.addTarget(new TargetControlledLandPermanent()); + this.addAbility(ability); + } + + private HaruHiddenTalent(final HaruHiddenTalent card) { + super(card); + } + + @Override + public HaruHiddenTalent copy() { + return new HaruHiddenTalent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HaukensInsight.java b/Mage.Sets/src/mage/cards/h/HaukensInsight.java index 19b896ecdd0..d2b944a5f9e 100644 --- a/Mage.Sets/src/mage/cards/h/HaukensInsight.java +++ b/Mage.Sets/src/mage/cards/h/HaukensInsight.java @@ -80,7 +80,7 @@ class HaukensInsightExileEffect extends OneShotEffect { if (controller != null) { Card card = controller.getLibrary().getFromTop(game); if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); card.setFaceDown(true, game); diff --git a/Mage.Sets/src/mage/cards/h/HauntingVoyage.java b/Mage.Sets/src/mage/cards/h/HauntingVoyage.java index 345561f7f42..ee33c73254d 100644 --- a/Mage.Sets/src/mage/cards/h/HauntingVoyage.java +++ b/Mage.Sets/src/mage/cards/h/HauntingVoyage.java @@ -1,28 +1,26 @@ package mage.cards.h; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.condition.common.ForetoldCondition; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseCreatureTypeEffect; import mage.abilities.keyword.ForetellAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; +import mage.target.TargetCard; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class HauntingVoyage extends CardImpl { @@ -70,30 +68,15 @@ class HauntingVoyageEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); SubType chosenSubType = ChooseCreatureTypeEffect.getChosenCreatureType(source.getSourceId(), game); - if (controller != null && chosenSubType != null) { - Set cardsToBattlefield = new LinkedHashSet<>(); - if (!ForetoldCondition.instance.apply(game, source)) { - TargetCardInYourGraveyard target = new TargetCardInYourGraveyard(0, 2, new FilterBySubtypeCard(chosenSubType), true); - controller.chooseTarget(outcome, target, source, game); - for (UUID cardId : target.getTargets()) { - Card card = game.getCard(cardId); - if (card != null) { - cardsToBattlefield.add(card); - } - } - } else { - for (UUID cardId : controller.getGraveyard()) { - Card card = game.getCard(cardId); - if (card != null && card.hasSubtype(chosenSubType, game)) { - cardsToBattlefield.add(card); - } - } - } - if (!cardsToBattlefield.isEmpty()) { - controller.moveCards(cardsToBattlefield, Zone.BATTLEFIELD, source, game); - return true; - } + if (controller == null || chosenSubType == null) { + return false; } - return false; + FilterCard filter = new FilterCard(chosenSubType); + if (ForetoldCondition.instance.apply(game, source)) { + return controller.moveCards(controller.getGraveyard().getCards(filter, game), Zone.BATTLEFIELD, source, game); + } + TargetCard target = new TargetCardInYourGraveyard(0, 2, filter, true); + controller.chooseTarget(outcome, target, source, game); + return controller.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); } } diff --git a/Mage.Sets/src/mage/cards/h/HeiBaiSpiritOfBalance.java b/Mage.Sets/src/mage/cards/h/HeiBaiSpiritOfBalance.java new file mode 100644 index 00000000000..4ea8c21f004 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeiBaiSpiritOfBalance.java @@ -0,0 +1,56 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.PutSourceCountersOnTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HeiBaiSpiritOfBalance extends CardImpl { + + public HeiBaiSpiritOfBalance(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W/B}{W/B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BEAR); + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever Hei Bai enters or attacks, you may sacrifice another creature or artifact. If you do, put two +1/+1 counters on Hei Bai. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new DoIfCostPaid( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT) + ))); + + // When Hei Bai leaves the battlefield, put its counters on target creature you control. + Ability ability = new LeavesBattlefieldTriggeredAbility(new PutSourceCountersOnTargetEffect()); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private HeiBaiSpiritOfBalance(final HeiBaiSpiritOfBalance card) { + super(card); + } + + @Override + public HeiBaiSpiritOfBalance copy() { + return new HeiBaiSpiritOfBalance(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HeirloomMirror.java b/Mage.Sets/src/mage/cards/h/HeirloomMirror.java index 4d71dcec837..69323e07bc4 100644 --- a/Mage.Sets/src/mage/cards/h/HeirloomMirror.java +++ b/Mage.Sets/src/mage/cards/h/HeirloomMirror.java @@ -2,21 +2,23 @@ package mage.cards.h; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.DiscardCardCost; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.RemoveAllCountersSourceEffect; import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import java.util.UUID; @@ -25,6 +27,8 @@ import java.util.UUID; */ public final class HeirloomMirror extends CardImpl { + private static final Condition condition = new SourceHasCounterCondition(CounterType.RITUAL, 3); + public HeirloomMirror(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{B}"); @@ -32,10 +36,16 @@ public final class HeirloomMirror extends CardImpl { // {1}, {T}, Pay 1 life, Discard a card: Draw a card, mill a card, then put a ritual counter on Heirloom Mirror. Then if it has 3 or more ritual counters on it, remove them and transform it. Activate only as a sorcery. this.addAbility(new TransformAbility()); - Ability ability = new ActivateAsSorceryActivatedAbility(new HeirloomMirrorEffect(), new GenericManaCost(1)); + Ability ability = new ActivateAsSorceryActivatedAbility(new DrawCardSourceControllerEffect(1), new GenericManaCost(1)); ability.addCost(new TapSourceCost()); ability.addCost(new PayLifeCost(1)); ability.addCost(new DiscardCardCost()); + ability.addEffect(new MillCardsControllerEffect(1).concatBy(",")); + ability.addEffect(new AddCountersSourceEffect(CounterType.RITUAL.createInstance()).concatBy(", then")); + ability.addEffect(new ConditionalOneShotEffect( + new RemoveAllCountersSourceEffect(CounterType.RITUAL), condition, + "Then if it has 3 or more ritual counters on it, remove them and transform it" + ).addEffect(new TransformSourceEffect())); this.addAbility(ability); } @@ -48,40 +58,3 @@ public final class HeirloomMirror extends CardImpl { return new HeirloomMirror(this); } } - -class HeirloomMirrorEffect extends OneShotEffect { - - HeirloomMirrorEffect() { - super(Outcome.Benefit); - staticText = "draw a card, mill a card, then put a ritual counter on {this}. " + - "Then if it has three or more ritual counters on it, remove them and transform it"; - } - - private HeirloomMirrorEffect(final HeirloomMirrorEffect effect) { - super(effect); - } - - @Override - public HeirloomMirrorEffect copy() { - return new HeirloomMirrorEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (player == null || permanent == null) { - return false; - } - player.drawCards(1, source, game); - player.millCards(1, source, game); - permanent.addCounters(CounterType.RITUAL.createInstance(), source.getControllerId(), source, game); - int counters = permanent.getCounters(game).getCount(CounterType.RITUAL); - if (counters < 3) { - return true; - } - permanent.removeCounters(CounterType.RITUAL.createInstance(counters), source, game); - new TransformSourceEffect().apply(game, source); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/h/HellToPay.java b/Mage.Sets/src/mage/cards/h/HellToPay.java index 640b2be38b8..8b3e35e9589 100644 --- a/Mage.Sets/src/mage/cards/h/HellToPay.java +++ b/Mage.Sets/src/mage/cards/h/HellToPay.java @@ -60,13 +60,11 @@ class HellToPayEffect extends OneShotEffect { if (permanent == null) { return false; } - int damage = CardUtil.getSourceCostsTag(game, source, "X", 0); - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), damage); - permanent.damage(damage, source.getSourceId(), source, game); - if (damage > lethal) { - new TreasureToken().putOntoBattlefield( - damage - lethal, game, source, source.getControllerId(), true, false - ); + int excess = permanent.damageWithExcess( + CardUtil.getSourceCostsTag(game, source, "X", 0), source, game + ); + if (excess > 0) { + new TreasureToken().putOntoBattlefield(excess, game, source, source.getControllerId(), true, false); } return true; } diff --git a/Mage.Sets/src/mage/cards/h/HeroesHangout.java b/Mage.Sets/src/mage/cards/h/HeroesHangout.java new file mode 100644 index 00000000000..64bca996bad --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeroesHangout.java @@ -0,0 +1,41 @@ +package mage.cards.h; + +import mage.abilities.Mode; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class HeroesHangout extends CardImpl { + + public HeroesHangout(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}"); + + + // Choose one -- + // * Date Night -- 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 ExileTopXMayPlayUntilEffect(2, true, Duration.UntilEndOfYourNextTurn)); + + // * Patrol Night -- One or two target creatures each get +1/+0 and gain first strike until end of turn. + this.getSpellAbility().addMode(new Mode(new BoostTargetEffect(1, 0)).addTarget(new TargetCreaturePermanent(1, 2))); + + } + + private HeroesHangout(final HeroesHangout card) { + super(card); + } + + @Override + public HeroesHangout copy() { + return new HeroesHangout(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HideOnTheCeiling.java b/Mage.Sets/src/mage/cards/h/HideOnTheCeiling.java new file mode 100644 index 00000000000..8c6dd4b8ca4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HideOnTheCeiling.java @@ -0,0 +1,51 @@ +package mage.cards.h; + +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ExileReturnBattlefieldNextEndStepTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; +import mage.target.targetadjustment.XTargetsCountAdjuster; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class HideOnTheCeiling extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifacts and/or creatures"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate() + )); + } + + public HideOnTheCeiling(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{U}"); + + + // Exile X target artifacts and/or creatures. Return the exiled cards to the battlefield under their owners' control at the beginning of the next end step. + Effect effect = new ExileReturnBattlefieldNextEndStepTargetEffect() + .returnExiledOnly(true); + effect.setText("Exile X target artifacts and/or creatures. Return the exiled cards to the battlefield under their owners' control at the beginning of the next end step"); + this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().setTargetAdjuster(new XTargetsCountAdjuster()); + } + + private HideOnTheCeiling(final HideOnTheCeiling card) { + super(card); + } + + @Override + public HideOnTheCeiling copy() { + return new HideOnTheCeiling(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HippoCows.java b/Mage.Sets/src/mage/cards/h/HippoCows.java new file mode 100644 index 00000000000..057228b5452 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HippoCows.java @@ -0,0 +1,37 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HippoCows extends CardImpl { + + public HippoCows(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.HIPPO); + this.subtype.add(SubType.OX); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + } + + private HippoCows(final HippoCows card) { + super(card); + } + + @Override + public HippoCows copy() { + return new HippoCows(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HobgoblinMantledMarauder.java b/Mage.Sets/src/mage/cards/h/HobgoblinMantledMarauder.java new file mode 100644 index 00000000000..74d5882600e --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HobgoblinMantledMarauder.java @@ -0,0 +1,50 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.effects.common.DiscardCardControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HobgoblinMantledMarauder extends CardImpl { + + public HobgoblinMantledMarauder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever you discard a card, Hobgoblin gets +2/+0 until end of turn. + this.addAbility(new DiscardCardControllerTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn), false)); + } + + private HobgoblinMantledMarauder(final HobgoblinMantledMarauder card) { + super(card); + } + + @Override + public HobgoblinMantledMarauder copy() { + return new HobgoblinMantledMarauder(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HogMonkey.java b/Mage.Sets/src/mage/cards/h/HogMonkey.java new file mode 100644 index 00000000000..07a53c05ff4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HogMonkey.java @@ -0,0 +1,53 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.ExhaustAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HogMonkey extends CardImpl { + + public HogMonkey(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.BOAR); + this.subtype.add(SubType.MONKEY); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // At the beginning of combat on your turn, target creature you control with a +1/+1 counter on it gains menace until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility(new GainAbilityTargetEffect(new MenaceAbility(false))); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_CREATURE_P1P1)); + this.addAbility(ability); + + // Exhaust -- {5}: Put two +1/+1 counters on this creature. + this.addAbility(new ExhaustAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), new GenericManaCost(5) + )); + } + + private HogMonkey(final HogMonkey card) { + super(card); + } + + @Override + public HogMonkey copy() { + return new HogMonkey(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java b/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java index a88998a3287..0108ea30338 100644 --- a/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java +++ b/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java @@ -13,7 +13,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; import mage.game.permanent.token.HumanSoldierToken; import mage.target.common.TargetCardInLibrary; @@ -24,7 +23,7 @@ import java.util.UUID; */ public final class HonoredKnightCaptain extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.EQUIPMENT); + private static final FilterCard filter = new FilterCard(SubType.EQUIPMENT); public HonoredKnightCaptain(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); diff --git a/Mage.Sets/src/mage/cards/h/HookHauntDrifter.java b/Mage.Sets/src/mage/cards/h/HookHauntDrifter.java index 20484b801dd..b63a9bb5b09 100644 --- a/Mage.Sets/src/mage/cards/h/HookHauntDrifter.java +++ b/Mage.Sets/src/mage/cards/h/HookHauntDrifter.java @@ -1,8 +1,7 @@ package mage.cards.h; import mage.MageInt; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -29,7 +28,7 @@ public final class HookHauntDrifter extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // If Hook-Haunt Drifter would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private HookHauntDrifter(final HookHauntDrifter card) { diff --git a/Mage.Sets/src/mage/cards/h/HopeOfGhirapur.java b/Mage.Sets/src/mage/cards/h/HopeOfGhirapur.java index 34598fa9a96..8daafe3625b 100644 --- a/Mage.Sets/src/mage/cards/h/HopeOfGhirapur.java +++ b/Mage.Sets/src/mage/cards/h/HopeOfGhirapur.java @@ -164,7 +164,7 @@ class HopeOfGhirapurCombatDamageWatcher extends Watcher { MageObjectReference mor; if (stackObject instanceof StackAbility) { // This is neccessary because the source object was sacrificed as cost and the correct zone change counter for target calid check can only be get from stack - mor = new MageObjectReference(objectId, ((StackAbility) stackObject).getSourceObjectZoneChangeCounter(), game); + mor = new MageObjectReference(objectId, ((StackAbility) stackObject).getStackMomentSourceZCC(), game); } else { mor = new MageObjectReference(objectId, game); } diff --git a/Mage.Sets/src/mage/cards/h/HostileHostel.java b/Mage.Sets/src/mage/cards/h/HostileHostel.java index 7b7e4ae7012..8e8a5a156c4 100644 --- a/Mage.Sets/src/mage/cards/h/HostileHostel.java +++ b/Mage.Sets/src/mage/cards/h/HostileHostel.java @@ -2,32 +2,33 @@ package mage.cards.h; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.RemoveAllCountersSourceEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.UntapSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.filter.StaticFilters; import java.util.UUID; -import mage.filter.StaticFilters; - /** * @author LePwnerer */ public final class HostileHostel extends CardImpl { + private static final Condition condition = new SourceHasCounterCondition(CounterType.SOUL, 3); + public HostileHostel(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); this.secondSideCardClazz = mage.cards.c.CreepingInn.class; @@ -37,9 +38,13 @@ public final class HostileHostel extends CardImpl { // {1}, {T}, Sacrifice a creature: Put a soul counter on Hostile Hostel. Then if there are three or more soul counters on it, remove those counters, transform it, then untap it. Activate only as a sorcery. this.addAbility(new TransformAbility()); - Ability ability = new ActivateAsSorceryActivatedAbility(Zone.BATTLEFIELD, new HostileHostelEffect(), new ManaCostsImpl<>("{1}")); + Ability ability = new ActivateAsSorceryActivatedAbility(new AddCountersSourceEffect(CounterType.SOUL.createInstance()), new ManaCostsImpl<>("{1}")); ability.addCost(new TapSourceCost()); ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE)); + ability.addEffect(new ConditionalOneShotEffect( + new RemoveAllCountersSourceEffect(CounterType.SOUL), condition, "Then if there are three " + + "or more soul counters on it, remove those counters, transform it, then untap it" + ).addEffect(new TransformSourceEffect()).addEffect(new UntapSourceEffect())); this.addAbility(ability); } @@ -52,38 +57,3 @@ public final class HostileHostel extends CardImpl { return new HostileHostel(this); } } - -class HostileHostelEffect extends OneShotEffect { - - HostileHostelEffect() { - super(Outcome.Benefit); - this.staticText = "Put a soul counter on {this}. " + - "Then if there are three or more soul counters on it, remove those counters, transform it, then untap it."; - } - - HostileHostelEffect(final mage.cards.h.HostileHostelEffect effect) { - super(effect); - } - - @Override - public mage.cards.h.HostileHostelEffect copy() { - return new mage.cards.h.HostileHostelEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null && player != null) { - permanent.addCounters(CounterType.SOUL.createInstance(), source.getControllerId(), source, game); - int counters = permanent.getCounters(game).getCount(CounterType.SOUL); - if (counters > 2) { - permanent.removeCounters(CounterType.SOUL.getName(), counters, source, game); - permanent.transform(source, game); - permanent.untap(game); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/h/HotDogCart.java b/Mage.Sets/src/mage/cards/h/HotDogCart.java new file mode 100644 index 00000000000..89f69f2f668 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HotDogCart.java @@ -0,0 +1,36 @@ +package mage.cards.h; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.FoodToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HotDogCart extends CardImpl { + + public HotDogCart(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // When this artifact enters, create a Food token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new FoodToken()))); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + } + + private HotDogCart(final HotDogCart card) { + super(card); + } + + @Override + public HotDogCart copy() { + return new HotDogCart(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HowToStartARiot.java b/Mage.Sets/src/mage/cards/h/HowToStartARiot.java new file mode 100644 index 00000000000..7c93cc5ea8c --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HowToStartARiot.java @@ -0,0 +1,84 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HowToStartARiot extends CardImpl { + + public HowToStartARiot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + this.subtype.add(SubType.LESSON); + + // Target creature gains menace until end of turn. + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(new MenaceAbility(false))); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // Creatures target player controls get +2/+0 until end of turn. + this.getSpellAbility().addEffect(new HowToStartARiotEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + private HowToStartARiot(final HowToStartARiot card) { + super(card); + } + + @Override + public HowToStartARiot copy() { + return new HowToStartARiot(this); + } +} + +class HowToStartARiotEffect extends OneShotEffect { + + HowToStartARiotEffect() { + super(Outcome.Benefit); + this.setTargetPointer(new SecondTargetPointer()); + staticText = "creatures target player controls get +2/+0 until end of turn"; + this.concatBy("
"); + } + + private HowToStartARiotEffect(final HowToStartARiotEffect effect) { + super(effect); + } + + @Override + public HowToStartARiotEffect copy() { + return new HowToStartARiotEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + FilterCreaturePermanent filter = new FilterCreaturePermanent(); + filter.add(new ControllerIdPredicate(player.getId())); + game.addEffect(new BoostAllEffect( + 2, 0, Duration.EndOfTurn, filter, false + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HydroManFluidFelon.java b/Mage.Sets/src/mage/cards/h/HydroManFluidFelon.java new file mode 100644 index 00000000000..f8befbd42cf --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HydroManFluidFelon.java @@ -0,0 +1,116 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.UntapSourceEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.mana.BlueManaAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterSpell; +import mage.filter.StaticFilters; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.awt.*; +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class HydroManFluidFelon extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a blue spell"); + + private static final Condition condition = new SourceMatchesFilterCondition( + "this {this} is a creature", StaticFilters.FILTER_PERMANENT_CREATURE + ); + + static { + filter.add(new ColorPredicate(ObjectColor.BLUE)); + } + + public HydroManFluidFelon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you cast a blue spell, if Hydro-Man is a creature, he gets +1/+1 until end of turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new BoostSourceEffect(1, 1, Duration.EndOfTurn), filter,false) + .withInterveningIf(condition).withRuleTextReplacement(true)); + + // At the beginning of your end step, untap Hydro-Man. Until your next turn, he becomes a land and gains "{T}: Add {U}." + Ability ability = new BeginningOfEndStepTriggeredAbility(new UntapSourceEffect()); + ability.addEffect(new HydroManFluidFelonEffect()); + this.addAbility(ability); + } + + private HydroManFluidFelon(final HydroManFluidFelon card) { + super(card); + } + + @Override + public HydroManFluidFelon copy() { + return new HydroManFluidFelon(this); + } +} +class HydroManFluidFelonEffect extends ContinuousEffectImpl { + + HydroManFluidFelonEffect() { + super(Duration.UntilYourNextTurn, Outcome.Neutral); + this.staticText = "Until your next turn, he becomes a land and gains \"{T}: Add {U}.\""; + this.addDependencyType(DependencyType.BecomeNonbasicLand); + } + + protected HydroManFluidFelonEffect(final HydroManFluidFelonEffect effect) { + super(effect); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + return false; + } + switch (layer) { + case TypeChangingEffects_4: + permanent.removeAllCardTypes(game); + permanent.removeAllCreatureTypes(game); + permanent.addCardType(game, CardType.LAND); + break; + case AbilityAddingRemovingEffects_6: + permanent.addAbility(new BlueManaAbility(), source.getSourceId(), game); + break; + } + return true; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.TypeChangingEffects_4 || layer == Layer.AbilityAddingRemovingEffects_6; + } + + @Override + public HydroManFluidFelonEffect copy() { + return new HydroManFluidFelonEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IanTheReckless.java b/Mage.Sets/src/mage/cards/i/IanTheReckless.java index f809522becf..659edde93e2 100644 --- a/Mage.Sets/src/mage/cards/i/IanTheReckless.java +++ b/Mage.Sets/src/mage/cards/i/IanTheReckless.java @@ -3,8 +3,7 @@ package mage.cards.i; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.condition.Condition; -import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.condition.common.SourceModifiedCondition; import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; import mage.abilities.effects.common.DamageControllerEffect; import mage.abilities.effects.common.DamageTargetEffect; @@ -13,8 +12,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.permanent.ModifiedPredicate; import mage.target.common.TargetAnyTarget; import java.util.UUID; @@ -24,14 +21,6 @@ import java.util.UUID; */ public final class IanTheReckless extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("it's modified"); - - static { - filter.add(ModifiedPredicate.instance); - } - - private static final Condition condition = new SourceMatchesFilterCondition(filter); - public IanTheReckless(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); @@ -42,8 +31,10 @@ public final class IanTheReckless extends CardImpl { this.toughness = new MageInt(1); // Whenever Ian the Reckless attacks, if it's modified, you may have it deal damage equal to its power to you and any target. - Ability ability = new AttacksTriggeredAbility(new DamageControllerEffect(SourcePermanentPowerValue.NOT_NEGATIVE) - .setText("have it deal damage equal to its power to you"), true).withInterveningIf(condition); + Ability ability = new AttacksTriggeredAbility( + new DamageControllerEffect(SourcePermanentPowerValue.NOT_NEGATIVE) + .setText("have it deal damage equal to its power to you"), true + ).withInterveningIf(SourceModifiedCondition.instance); ability.addEffect(new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE).setText("and any target")); ability.addTarget(new TargetAnyTarget()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/i/IceOut.java b/Mage.Sets/src/mage/cards/i/IceOut.java index 6d4dcf0eeb3..97fa2d5a392 100644 --- a/Mage.Sets/src/mage/cards/i/IceOut.java +++ b/Mage.Sets/src/mage/cards/i/IceOut.java @@ -54,7 +54,7 @@ enum IceOutAdjuster implements CostAdjuster { @Override public void reduceCost(Ability ability, Game game) { if (BargainedCondition.instance.apply(game, ability) - || (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) { + || (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) { CardUtil.reduceCost(ability, 1); } } diff --git a/Mage.Sets/src/mage/cards/i/IguanaParrot.java b/Mage.Sets/src/mage/cards/i/IguanaParrot.java new file mode 100644 index 00000000000..ad7c9131c72 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IguanaParrot.java @@ -0,0 +1,46 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ProwessAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IguanaParrot extends CardImpl { + + public IguanaParrot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.PIRATE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Prowess + this.addAbility(new ProwessAbility()); + } + + private IguanaParrot(final IguanaParrot card) { + super(card); + } + + @Override + public IguanaParrot copy() { + return new IguanaParrot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IlluminatedFolio.java b/Mage.Sets/src/mage/cards/i/IlluminatedFolio.java index 22bf2737054..1018125175e 100644 --- a/Mage.Sets/src/mage/cards/i/IlluminatedFolio.java +++ b/Mage.Sets/src/mage/cards/i/IlluminatedFolio.java @@ -1,7 +1,5 @@ package mage.cards.i; -import mage.MageItem; -import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.RevealTargetFromHandCost; @@ -19,9 +17,9 @@ import mage.game.Game; import mage.target.common.TargetCardInHand; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; /** * @author TheElk801 @@ -67,51 +65,8 @@ class IlluminatedFolioTarget extends TargetCardInHand { } @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return super.canChoose(sourceControllerId, source, game) - && !possibleTargets(sourceControllerId, source, game).isEmpty(); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - if (this.getTargets().size() == 1) { - Card card = game.getCard(this.getTargets().get(0)); - possibleTargets.removeIf( - uuid -> !game - .getCard(uuid) - .getColor(game) - .shares(card.getColor(game)) - ); - return possibleTargets; - } - if (possibleTargets.size() < 2) { - possibleTargets.clear(); - return possibleTargets; - } - Set allTargets = possibleTargets - .stream() - .map(game::getCard) - .collect(Collectors.toSet()); - possibleTargets.clear(); - for (ObjectColor color : ObjectColor.getAllColors()) { - Set inColor = allTargets - .stream() - .filter(card -> card.getColor(game).shares(color)) - .collect(Collectors.toSet()); - if (inColor.size() > 1) { - inColor.stream().map(MageItem::getId).forEach(possibleTargets::add); - } - if (possibleTargets.size() == allTargets.size()) { - break; - } - } - return possibleTargets; - } - - @Override - public boolean canTarget(UUID id, Game game) { - if (!super.canTarget(id, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } List targetList = this.getTargets(); @@ -123,9 +78,17 @@ class IlluminatedFolioTarget extends TargetCardInHand { && targetList .stream() .map(game::getCard) + .filter(Objects::nonNull) .anyMatch(c -> c.getColor(game).shares(card.getColor(game))); } + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); + return possibleTargets; + } + @Override public IlluminatedFolioTarget copy() { return new IlluminatedFolioTarget(this); diff --git a/Mage.Sets/src/mage/cards/i/ImpelledGiant.java b/Mage.Sets/src/mage/cards/i/ImpelledGiant.java index 5a32d0d837f..1cd50299212 100644 --- a/Mage.Sets/src/mage/cards/i/ImpelledGiant.java +++ b/Mage.Sets/src/mage/cards/i/ImpelledGiant.java @@ -99,7 +99,7 @@ class ImpelledGiantCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/i/ImperialHellkite.java b/Mage.Sets/src/mage/cards/i/ImperialHellkite.java index d896db14b09..5aae2539a15 100644 --- a/Mage.Sets/src/mage/cards/i/ImperialHellkite.java +++ b/Mage.Sets/src/mage/cards/i/ImperialHellkite.java @@ -1,11 +1,8 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.MorphAbility; @@ -13,31 +10,34 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author fireshoes */ public final class ImperialHellkite extends CardImpl { - + + private static final FilterCard filter = new FilterCard(SubType.DRAGON); + public ImperialHellkite(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{R}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{R}{R}"); this.subtype.add(SubType.DRAGON); this.power = new MageInt(6); this.toughness = new MageInt(6); // Flying this.addAbility(FlyingAbility.getInstance()); - + // Morph {6}{R}{R} this.addAbility(new MorphAbility(this, new ManaCostsImpl<>("{6}{R}{R}"))); - + // When Imperial Hellkite is turned face up, you may search your library for a Dragon card, reveal it, and put it into your hand. If you do, shuffle your library. - Effect effect = new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 1, new FilterBySubtypeCard(SubType.DRAGON)), true); - effect.setText("you may search your library for a Dragon card, reveal it, put it into your hand, then shuffle"); - this.addAbility(new TurnedFaceUpSourceTriggeredAbility(effect)); + this.addAbility(new TurnedFaceUpSourceTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), false, true + )); } private ImperialHellkite(final ImperialHellkite card) { diff --git a/Mage.Sets/src/mage/cards/i/ImpostorSyndrome.java b/Mage.Sets/src/mage/cards/i/ImpostorSyndrome.java new file mode 100644 index 00000000000..f6ea5272b05 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/ImpostorSyndrome.java @@ -0,0 +1,39 @@ +package mage.cards.i; + +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ImpostorSyndrome extends CardImpl { + + public ImpostorSyndrome(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{U}{U}"); + + // Whenever a nontoken creature you control deals combat damage to a player, create a token that's a copy of it, except it isn't legendary. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new CreateTokenCopyTargetEffect() + .setIsntLegendary(true) + .setText("create a token that's a copy of it, except it isn't legendary"), + StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN, false, + SetTargetPointer.PERMANENT, true + )); + } + + private ImpostorSyndrome(final ImpostorSyndrome card) { + super(card); + } + + @Override + public ImpostorSyndrome copy() { + return new ImpostorSyndrome(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InfectiousRage.java b/Mage.Sets/src/mage/cards/i/InfectiousRage.java index ecf4aba4374..b341977041e 100644 --- a/Mage.Sets/src/mage/cards/i/InfectiousRage.java +++ b/Mage.Sets/src/mage/cards/i/InfectiousRage.java @@ -83,7 +83,7 @@ class InfectiousRageReattachEffect extends OneShotEffect { if (controller == null || auraCard == null) { return false; } - if (source.getSourceObjectZoneChangeCounter() != auraCard.getZoneChangeCounter(game)) { + if (source.getStackMomentSourceZCC() != auraCard.getZoneChangeCounter(game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/i/InfernalCaretaker.java b/Mage.Sets/src/mage/cards/i/InfernalCaretaker.java index 550e2064979..e5144999d67 100644 --- a/Mage.Sets/src/mage/cards/i/InfernalCaretaker.java +++ b/Mage.Sets/src/mage/cards/i/InfernalCaretaker.java @@ -1,11 +1,8 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.ReturnToHandFromGraveyardAllEffect; import mage.abilities.keyword.MorphAbility; import mage.cards.CardImpl; @@ -13,18 +10,18 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; + +import java.util.UUID; /** - * * @author cg5 */ public final class InfernalCaretaker extends CardImpl { - private static FilterCard zombieCard = new FilterBySubtypeCard(SubType.ZOMBIE); - + private static FilterCard zombieCard = new FilterCard(SubType.ZOMBIE); + public InfernalCaretaker(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.CLERIC); this.power = new MageInt(2); @@ -32,11 +29,10 @@ public final class InfernalCaretaker extends CardImpl { // Morph {3}{B} this.addAbility(new MorphAbility(this, new ManaCostsImpl<>("{3}{B}"))); - + // When Infernal Caretaker is turned face up, return all Zombie cards from all graveyards to their owners' hands. - Effect effect = new ReturnToHandFromGraveyardAllEffect(zombieCard); - effect.setText("return all Zombie cards from all graveyards to their owners' hands"); - this.addAbility(new TurnedFaceUpSourceTriggeredAbility(effect)); + this.addAbility(new TurnedFaceUpSourceTriggeredAbility(new ReturnToHandFromGraveyardAllEffect(zombieCard) + .setText("return all Zombie cards from all graveyards to their owners' hands"))); } private InfernalCaretaker(final InfernalCaretaker card) { @@ -47,4 +43,4 @@ public final class InfernalCaretaker extends CardImpl { public InfernalCaretaker copy() { return new InfernalCaretaker(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/i/InheritedFiend.java b/Mage.Sets/src/mage/cards/i/InheritedFiend.java index 79afa9126fc..22343bddfb5 100644 --- a/Mage.Sets/src/mage/cards/i/InheritedFiend.java +++ b/Mage.Sets/src/mage/cards/i/InheritedFiend.java @@ -12,9 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreatureCard; import mage.target.common.TargetCardInGraveyard; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/i/InitiatesOfTheEbonHand.java b/Mage.Sets/src/mage/cards/i/InitiatesOfTheEbonHand.java index 408079e1561..3136d4bfa93 100644 --- a/Mage.Sets/src/mage/cards/i/InitiatesOfTheEbonHand.java +++ b/Mage.Sets/src/mage/cards/i/InitiatesOfTheEbonHand.java @@ -66,7 +66,7 @@ class InitiatesOfTheEbonHandEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - ActivationInfo activationInfo = ActivationInfo.getInstance(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + ActivationInfo activationInfo = ActivationInfo.getInstance(game, source.getSourceId(), source.getStackMomentSourceZCC()); activationInfo.addActivation(game); if (activationInfo.getActivationCounter() == 4) { DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new SacrificeSourceEffect()); diff --git a/Mage.Sets/src/mage/cards/i/InkTreaderNephilim.java b/Mage.Sets/src/mage/cards/i/InkTreaderNephilim.java index 9f584c8820d..e6122255b1d 100644 --- a/Mage.Sets/src/mage/cards/i/InkTreaderNephilim.java +++ b/Mage.Sets/src/mage/cards/i/InkTreaderNephilim.java @@ -99,8 +99,8 @@ class InkTreaderNephilimTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || !permanent.getId().equals(getSourceId())) { return false; } - if (getSourceObjectZoneChangeCounter() != 0 - && getSourceObjectZoneChangeCounter() != permanent.getZoneChangeCounter(game)) { + if (getStackMomentSourceZCC() != 0 + && getStackMomentSourceZCC() != permanent.getZoneChangeCounter(game)) { return false; } flag = true; diff --git a/Mage.Sets/src/mage/cards/i/InnerDemonsGangsters.java b/Mage.Sets/src/mage/cards/i/InnerDemonsGangsters.java new file mode 100644 index 00000000000..4e46530dd65 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InnerDemonsGangsters.java @@ -0,0 +1,51 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class InnerDemonsGangsters extends CardImpl { + + public InnerDemonsGangsters(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Discard a card: This creature gets +1/+0 and gains menace until end of turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new BoostSourceEffect(1, 0 , Duration.EndOfTurn).setText("{this} gets +1/+0"), + new DiscardCardCost() + ); + ability.addEffect(new GainAbilitySourceEffect(new MenaceAbility(), Duration.EndOfTurn) + .setText("and gains menace until end of turn")); + this.addAbility(ability); + } + + private InnerDemonsGangsters(final InnerDemonsGangsters card) { + super(card); + } + + @Override + public InnerDemonsGangsters copy() { + return new InnerDemonsGangsters(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InnocentTraveler.java b/Mage.Sets/src/mage/cards/i/InnocentTraveler.java index 6dc1f24151b..76e0b94422b 100644 --- a/Mage.Sets/src/mage/cards/i/InnocentTraveler.java +++ b/Mage.Sets/src/mage/cards/i/InnocentTraveler.java @@ -2,9 +2,9 @@ package mage.cards.i; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.TransformAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -34,9 +34,7 @@ public final class InnocentTraveler extends CardImpl { // At the beginning of your upkeep, any opponent may sacrifice a creature. If no one does, transform Innocent Traveler. this.addAbility(new TransformAbility()); - this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new InnocentTravelerEffect() - )); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new InnocentTravelerEffect())); } private InnocentTraveler(final InnocentTraveler card) { diff --git a/Mage.Sets/src/mage/cards/i/InsidiousMist.java b/Mage.Sets/src/mage/cards/i/InsidiousMist.java index fee4e67eaf6..25546fc0589 100644 --- a/Mage.Sets/src/mage/cards/i/InsidiousMist.java +++ b/Mage.Sets/src/mage/cards/i/InsidiousMist.java @@ -1,13 +1,10 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksAndIsNotBlockedTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; @@ -18,18 +15,18 @@ import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; +import mage.constants.SubType; + +import java.util.UUID; /** - * * @author fireshoes */ public final class InsidiousMist extends CardImpl { public InsidiousMist(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},""); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); this.subtype.add(SubType.ELEMENTAL); this.power = new MageInt(0); this.toughness = new MageInt(1); @@ -45,9 +42,7 @@ public final class InsidiousMist extends CardImpl { // Insideous Mist can't block and can't be blocked. Ability ability = new SimpleStaticAbility(new CantBlockSourceEffect(Duration.WhileOnBattlefield)); - Effect effect = new CantBeBlockedSourceEffect(); - effect.setText("and can't be blocked"); - ability.addEffect(effect); + ability.addEffect(new CantBeBlockedSourceEffect().setText("and can't be blocked")); this.addAbility(ability); // Whenever Insideous Mist attacks and isn't blocked, you may pay {2}{B}. If you do, transform it. diff --git a/Mage.Sets/src/mage/cards/i/IntellectDevourer.java b/Mage.Sets/src/mage/cards/i/IntellectDevourer.java index b6bd578112e..1e84ac9092f 100644 --- a/Mage.Sets/src/mage/cards/i/IntellectDevourer.java +++ b/Mage.Sets/src/mage/cards/i/IntellectDevourer.java @@ -187,7 +187,7 @@ class IntellectDevourerReturnExiledCardEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (sourceObject != null && controller != null) { - ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())); + ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC())); Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); if (exile != null && sourcePermanent != null) { controller.moveCards(exile, Zone.HAND, source, game); diff --git a/Mage.Sets/src/mage/cards/i/InterdimensionalWebWatch.java b/Mage.Sets/src/mage/cards/i/InterdimensionalWebWatch.java new file mode 100644 index 00000000000..21fc4f09a57 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InterdimensionalWebWatch.java @@ -0,0 +1,82 @@ +package mage.cards.i; + +import mage.ConditionalMana; +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.mana.ConditionalAnyColorManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.stack.Spell; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class InterdimensionalWebWatch extends CardImpl { + + public InterdimensionalWebWatch(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + + // When this artifact enters, exile the top two cards of your library. Until the end of your next turn, you may play those cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new ExileTopXMayPlayUntilEffect(2, Duration.UntilEndOfYourNextTurn))); + + // {T}: Add two mana in any combination of colors. Spend this mana only to cast spells from exile. + this.addAbility(new ConditionalAnyColorManaAbility(2, new InterdimensionalWebWatchManaBuilder())); + } + + private InterdimensionalWebWatch(final InterdimensionalWebWatch card) { + super(card); + } + + @Override + public InterdimensionalWebWatch copy() { + return new InterdimensionalWebWatch(this); + } +} + +class InterdimensionalWebWatchManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new InterdimensionalWebWatchConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to cast spells from exile"; + } +} + +class InterdimensionalWebWatchConditionalMana extends ConditionalMana { + + public InterdimensionalWebWatchConditionalMana(Mana mana) { + super(mana); + staticText = "Spend this mana only to cast spells from exile"; + addCondition(new InterdimensionalWebWatchCondition()); + } +} + +class InterdimensionalWebWatchCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + MageObject object = game.getObject(source.getSourceId()); + if (game.inCheckPlayableState()) { + return object instanceof Card && game.getState().getZone(source.getSourceId()) == Zone.EXILED; + } + return object instanceof Spell && ((Spell) object).getFromZone() == Zone.EXILED; + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfGobakhan.java b/Mage.Sets/src/mage/cards/i/InvasionOfGobakhan.java index a180880981f..d44cf14a51b 100644 --- a/Mage.Sets/src/mage/cards/i/InvasionOfGobakhan.java +++ b/Mage.Sets/src/mage/cards/i/InvasionOfGobakhan.java @@ -79,9 +79,7 @@ class InvasionOfGobakhanEffect extends OneShotEffect { if (controller == null || opponent == null || opponent.getHand().isEmpty()) { return false; } - TargetCard target = new TargetCardInHand( - 0, 1, StaticFilters.FILTER_CARD_A_NON_LAND - ); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); controller.choose(outcome, opponent.getHand(), target, source, game); Card card = opponent.getHand().get(target.getFirstTarget(), game); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/i/IrohFirebendingInstructor.java b/Mage.Sets/src/mage/cards/i/IrohFirebendingInstructor.java new file mode 100644 index 00000000000..c42876393e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IrohFirebendingInstructor.java @@ -0,0 +1,45 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +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.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IrohFirebendingInstructor extends CardImpl { + + public IrohFirebendingInstructor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever Iroh attacks, attacking creatures get +1/+1 until end of turn. + this.addAbility(new AttacksTriggeredAbility(new BoostAllEffect( + 1, 1, Duration.EndOfTurn, StaticFilters.FILTER_ATTACKING_CREATURES, false + ))); + } + + private IrohFirebendingInstructor(final IrohFirebendingInstructor card) { + super(card); + } + + @Override + public IrohFirebendingInstructor copy() { + return new IrohFirebendingInstructor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IronSpiderStarkUpgrade.java b/Mage.Sets/src/mage/cards/i/IronSpiderStarkUpgrade.java new file mode 100644 index 00000000000..1b97e9734d0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IronSpiderStarkUpgrade.java @@ -0,0 +1,74 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCounterCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IronSpiderStarkUpgrade extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent("creature and/or Vehicle you control"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + } + + public IronSpiderStarkUpgrade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // {T}: Put a +1/+1 counter on each artifact creature and/or Vehicle you control. + this.addAbility(new SimpleActivatedAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter), new TapSourceCost() + )); + + // {2}, Remove two +1/+1 counters from among artifacts you control: Draw a card. + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new GenericManaCost(2)); + ability.addCost(new RemoveCounterCost(new TargetPermanent( + 1, 2, StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT + ), CounterType.P1P1, 2).setText("remove two +1/+1 counters from among creatures you control")); + this.addAbility(ability); + } + + private IronSpiderStarkUpgrade(final IronSpiderStarkUpgrade card) { + super(card); + } + + @Override + public IronSpiderStarkUpgrade copy() { + return new IronSpiderStarkUpgrade(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/ItllQuenchYa.java b/Mage.Sets/src/mage/cards/i/ItllQuenchYa.java new file mode 100644 index 00000000000..8564c1ce5ea --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/ItllQuenchYa.java @@ -0,0 +1,36 @@ +package mage.cards.i; + +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CounterUnlessPaysEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ItllQuenchYa extends CardImpl { + + public ItllQuenchYa(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + this.subtype.add(SubType.LESSON); + + // Counter target spell unless its controller pays {2}. + this.getSpellAbility().addEffect(new CounterUnlessPaysEffect(new GenericManaCost(2))); + this.getSpellAbility().addTarget(new TargetSpell()); + } + + private ItllQuenchYa(final ItllQuenchYa card) { + super(card); + } + + @Override + public ItllQuenchYa copy() { + return new ItllQuenchYa(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IwamoriOfTheOpenFist.java b/Mage.Sets/src/mage/cards/i/IwamoriOfTheOpenFist.java index dd0f1b169ab..47ebfcd443e 100644 --- a/Mage.Sets/src/mage/cards/i/IwamoriOfTheOpenFist.java +++ b/Mage.Sets/src/mage/cards/i/IwamoriOfTheOpenFist.java @@ -1,32 +1,27 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.TrampleAbility; import mage.cards.*; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetCardInHand; +import mage.target.TargetCard; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class IwamoriOfTheOpenFist extends CardImpl { public IwamoriOfTheOpenFist(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.MONK); @@ -80,15 +75,11 @@ class IwamoriOfTheOpenFistEffect extends OneShotEffect { Cards cards = new CardsImpl(); for (UUID playerId : game.getOpponents(controller.getId())) { Player opponent = game.getPlayer(playerId); - Target target = new TargetCardInHand(filter); - if (opponent != null && target.canChoose(opponent.getId(), source, game)) { - if (opponent.chooseUse(Outcome.PutCreatureInPlay, "Put a legendary creature card from your hand onto the battlefield?", source, game)) { - if (target.chooseTarget(Outcome.PutCreatureInPlay, opponent.getId(), source, game)) { - Card card = game.getCard(target.getFirstTarget()); - if (card != null) { - cards.add(card); - } - } + Target target = new TargetCard(0, 1, Zone.HAND, filter).withChooseHint("put from hand to battlefield"); + if (target.choose(Outcome.PutCreatureInPlay, opponent.getId(), source, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + cards.add(card); } } } diff --git a/Mage.Sets/src/mage/cards/i/IxalansBinding.java b/Mage.Sets/src/mage/cards/i/IxalansBinding.java index 76da782b496..3f80cd14da5 100644 --- a/Mage.Sets/src/mage/cards/i/IxalansBinding.java +++ b/Mage.Sets/src/mage/cards/i/IxalansBinding.java @@ -12,7 +12,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.ExileZone; import mage.game.Game; @@ -78,7 +77,7 @@ class IxalansBindingReplacementEffect extends ContinuousRuleModifyingEffectImpl } Card card = spellAbility.getCharacteristics(game); if (sourcePermanent != null && card != null) { - UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); if (exileZone != null) { ExileZone exile = game.getExile().getExileZone(exileZone); if (exile == null) { diff --git a/Mage.Sets/src/mage/cards/i/IymrithDesertDoom.java b/Mage.Sets/src/mage/cards/i/IymrithDesertDoom.java index 12e07f87554..3d551bc436c 100644 --- a/Mage.Sets/src/mage/cards/i/IymrithDesertDoom.java +++ b/Mage.Sets/src/mage/cards/i/IymrithDesertDoom.java @@ -1,27 +1,27 @@ package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.InvertCondition; import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.DrawCardsEqualToDifferenceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; -import mage.abilities.keyword.WardAbility; -import mage.constants.*; import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WardAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.game.Game; -import mage.players.Player; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; /** - * * @author weirddan455 */ public final class IymrithDesertDoom extends CardImpl { @@ -40,13 +40,13 @@ public final class IymrithDesertDoom extends CardImpl { // Iymrith, Desert Doom has ward {4} as long as it's untapped. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(new WardAbility(new GenericManaCost(4)), Duration.WhileOnBattlefield), - SourceTappedCondition.UNTAPPED, - "{this} has ward {4} as long as it's untapped" + SourceTappedCondition.UNTAPPED, "{this} has ward {4} as long as it's untapped" ))); // Whenever Iymrith deals combat damage to a player, draw a card. Then if you have fewer than three cards in hand, draw cards equal to the difference. - Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new DrawCardSourceControllerEffect(1), false); - ability.addEffect(new IymrithDesertDoomEffect()); + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new DrawCardSourceControllerEffect(1)); + ability.addEffect(new DrawCardsEqualToDifferenceEffect(3) + .concatBy("Then if you have fewer than three cards in hand,")); this.addAbility(ability); } @@ -59,33 +59,3 @@ public final class IymrithDesertDoom extends CardImpl { return new IymrithDesertDoom(this); } } - -class IymrithDesertDoomEffect extends OneShotEffect { - - IymrithDesertDoomEffect() { - super(Outcome.DrawCard); - this.staticText = "Then if you have fewer than three cards in hand, draw cards equal to the difference"; - } - - private IymrithDesertDoomEffect(final IymrithDesertDoomEffect effect) { - super(effect); - } - - @Override - public IymrithDesertDoomEffect copy() { - return new IymrithDesertDoomEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int handSize = controller.getHand().size(); - if (handSize < 3) { - controller.drawCards(3 - handSize, source, game); - return true; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/j/JJonahJameson.java b/Mage.Sets/src/mage/cards/j/JJonahJameson.java new file mode 100644 index 00000000000..5db86ffcf5c --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JJonahJameson.java @@ -0,0 +1,61 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksAllTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.SuspectTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.permanent.token.TreasureToken; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JJonahJameson extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature you control with menace"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + filter.add(new AbilityPredicate(MenaceAbility.class)); + } + + public JJonahJameson(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When J. Jonah Jameson enters, suspect up to one target creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new SuspectTargetEffect()); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // Whenever a creature you control with menace attacks, create a Treasure token. + this.addAbility(new AttacksAllTriggeredAbility( + new CreateTokenEffect(new TreasureToken()), false, + filter, SetTargetPointer.NONE, false + )); + } + + private JJonahJameson(final JJonahJameson card) { + super(card); + } + + @Override + public JJonahJameson copy() { + return new JJonahJameson(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JaceReawakened.java b/Mage.Sets/src/mage/cards/j/JaceReawakened.java index becdbb91226..b110e241803 100644 --- a/Mage.Sets/src/mage/cards/j/JaceReawakened.java +++ b/Mage.Sets/src/mage/cards/j/JaceReawakened.java @@ -1,14 +1,13 @@ package mage.cards.j; -import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.LoyaltyAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.CopyTargetStackObjectEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.abilities.effects.common.MayExileCardFromHandPlottedEffect; +import mage.abilities.effects.common.ruleModifying.CantCastDuringFirstThreeTurnsEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -18,7 +17,6 @@ import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; -import mage.players.Player; import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -43,7 +41,7 @@ public final class JaceReawakened extends CardImpl { this.setStartingLoyalty(3); // You can't cast this spell during your first, second, or third turns of the game. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new CantCastJaceReawakenedEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new CantCastDuringFirstThreeTurnsEffect())); // +1: Draw a card, then discard a card. this.addAbility(new LoyaltyAbility(new DrawDiscardControllerEffect(1, 1), 1)); @@ -69,43 +67,6 @@ public final class JaceReawakened extends CardImpl { } } -/** - * Same as {@link mage.cards.s.SerraAvenger Serra Avenger} - */ -class CantCastJaceReawakenedEffect extends ContinuousRuleModifyingEffectImpl { - - CantCastJaceReawakenedEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - staticText = "You can't cast this spell during your first, second, or third turns of the game"; - } - - private CantCastJaceReawakenedEffect(final CantCastJaceReawakenedEffect effect) { - super(effect); - } - - @Override - public CantCastJaceReawakenedEffect copy() { - return new CantCastJaceReawakenedEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.CAST_SPELL; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getSourceId().equals(source.getSourceId())) { - Player controller = game.getPlayer(source.getControllerId()); - // it can be cast on other players turn 1 - 3 if some effect let allow you to do this - if (controller != null && controller.getTurns() <= 3 && game.isActivePlayer(source.getControllerId())) { - return true; - } - } - return false; - } -} - class JaceReawakenedDelayedTriggeredAbility extends DelayedTriggeredAbility { JaceReawakenedDelayedTriggeredAbility() { diff --git a/Mage.Sets/src/mage/cards/j/JaceTelepathUnbound.java b/Mage.Sets/src/mage/cards/j/JaceTelepathUnbound.java index bcf3a7cd88e..66e1b793d62 100644 --- a/Mage.Sets/src/mage/cards/j/JaceTelepathUnbound.java +++ b/Mage.Sets/src/mage/cards/j/JaceTelepathUnbound.java @@ -2,7 +2,6 @@ package mage.cards.j; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.GetEmblemEffect; import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; @@ -35,9 +34,9 @@ public final class JaceTelepathUnbound extends CardImpl { this.setStartingLoyalty(5); // +1: Up to one target creature gets -2/-0 until your next turn. - Effect effect = new BoostTargetEffect(-2, 0, Duration.UntilYourNextTurn); - effect.setText("Up to one target creature gets -2/-0 until your next turn"); - Ability ability = new LoyaltyAbility(effect, 1); + Ability ability = new LoyaltyAbility(new BoostTargetEffect( + -2, 0, Duration.UntilYourNextTurn + ).setText("Up to one target creature gets -2/-0 until your next turn"), 1); ability.addTarget(new TargetCreaturePermanent(0, 1)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/j/JaceVrynsProdigy.java b/Mage.Sets/src/mage/cards/j/JaceVrynsProdigy.java index 12945820c6f..7c8eb721011 100644 --- a/Mage.Sets/src/mage/cards/j/JaceVrynsProdigy.java +++ b/Mage.Sets/src/mage/cards/j/JaceVrynsProdigy.java @@ -1,15 +1,12 @@ - package mage.cards.j; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.constants.Pronoun; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; import mage.abilities.condition.common.CardsInControllerGraveyardCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalOneShotEffect; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.abilities.effects.common.ExileAndReturnSourceEffect; import mage.abilities.keyword.TransformAbility; @@ -17,14 +14,17 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class JaceVrynsProdigy extends CardImpl { + private static final Condition condition = new CardsInControllerGraveyardCondition(5); + public JaceVrynsProdigy(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WIZARD); @@ -35,11 +35,13 @@ public final class JaceVrynsProdigy extends CardImpl { // {T}: Draw a card, then discard a card. If there are five or more cards in your graveyard, exile Jace, Vryn's Prodigy, then return him to the battefield transformed under his owner's control. this.addAbility(new TransformAbility()); - Ability ability = new SimpleActivatedAbility(new DrawDiscardControllerEffect(1, 1), new TapSourceCost()); - Effect effect = new ConditionalOneShotEffect(new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED,Pronoun.HE), new CardsInControllerGraveyardCondition(5)); - ability.addEffect(effect); + Ability ability = new SimpleActivatedAbility( + new DrawDiscardControllerEffect(1, 1), new TapSourceCost() + ); + ability.addEffect(new ConditionalOneShotEffect( + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED, Pronoun.HE), condition + )); this.addAbility(ability); - } private JaceVrynsProdigy(final JaceVrynsProdigy card) { diff --git a/Mage.Sets/src/mage/cards/j/JackalGeniusGeneticist.java b/Mage.Sets/src/mage/cards/j/JackalGeniusGeneticist.java new file mode 100644 index 00000000000..a149dd7f5e2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JackalGeniusGeneticist.java @@ -0,0 +1,88 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.CopyTargetStackObjectEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.common.FilterCreatureSpell; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.functions.RemoveTypeCopyApplier; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class JackalGeniusGeneticist extends CardImpl { + + private static final FilterCreatureSpell filter = new FilterCreatureSpell("a creature spell with mana value equal to {this}'s power"); + + static { + filter.add(JackalGeniusGeneticistPredicate.instance); + } + + public JackalGeniusGeneticist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCIENTIST); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever you cast a creature spell with mana value equal to Jackal's power, copy that spell, except the copy isn't legendary. Then put a +1/+1 counter on Jackal. + Ability ability = new SpellCastControllerTriggeredAbility( + new CopyTargetStackObjectEffect(false, false, false, 1, new RemoveTypeCopyApplier(SuperType.LEGENDARY)) + .setText("copy that spell, except the copy isn't legendary."), + filter, + false, + SetTargetPointer.SPELL + ); + ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + .concatBy("Then")); + this.addAbility(ability); + } + + private JackalGeniusGeneticist(final JackalGeniusGeneticist card) { + super(card); + } + + @Override + public JackalGeniusGeneticist copy() { + return new JackalGeniusGeneticist(this); + } +} + +enum JackalGeniusGeneticistPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + Permanent sourcePermanent = input.getSource().getSourcePermanentOrLKI(game); + return sourcePermanent != null && input.getObject().getManaValue() == sourcePermanent.getPower().getValue(); + } + + @Override + public String toString() { + return "mana value equal to {this}'s power"; + } +} + diff --git a/Mage.Sets/src/mage/cards/j/JacobHaukenInspector.java b/Mage.Sets/src/mage/cards/j/JacobHaukenInspector.java index 38379f9ed13..c71aedd5f2c 100644 --- a/Mage.Sets/src/mage/cards/j/JacobHaukenInspector.java +++ b/Mage.Sets/src/mage/cards/j/JacobHaukenInspector.java @@ -84,7 +84,7 @@ class JacobHaukenInspectorExileEffect extends OneShotEffect { controller.chooseTarget(outcome, controller.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); card.setFaceDown(true, game); diff --git a/Mage.Sets/src/mage/cards/j/JeongJeongsDeserters.java b/Mage.Sets/src/mage/cards/j/JeongJeongsDeserters.java new file mode 100644 index 00000000000..da65b8b4f03 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JeongJeongsDeserters.java @@ -0,0 +1,44 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JeongJeongsDeserters extends CardImpl { + + public JeongJeongsDeserters(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.REBEL); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // When this creature enters, put a +1/+1 counter on target creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private JeongJeongsDeserters(final JeongJeongsDeserters card) { + super(card); + } + + @Override + public JeongJeongsDeserters copy() { + return new JeongJeongsDeserters(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JohannsStopgap.java b/Mage.Sets/src/mage/cards/j/JohannsStopgap.java index fc34f2aa974..ff579d29591 100644 --- a/Mage.Sets/src/mage/cards/j/JohannsStopgap.java +++ b/Mage.Sets/src/mage/cards/j/JohannsStopgap.java @@ -56,7 +56,7 @@ enum JohannsStopgapAdjuster implements CostAdjuster { @Override public void reduceCost(Ability ability, Game game) { if (BargainedCondition.instance.apply(game, ability) - || (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) { + || (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) { CardUtil.reduceCost(ability, 2); } } diff --git a/Mage.Sets/src/mage/cards/j/JotunGrunt.java b/Mage.Sets/src/mage/cards/j/JotunGrunt.java index 1d271b5065b..0571e036c0e 100644 --- a/Mage.Sets/src/mage/cards/j/JotunGrunt.java +++ b/Mage.Sets/src/mage/cards/j/JotunGrunt.java @@ -78,7 +78,7 @@ class JotunGruntCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java b/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java index e78ed4ef421..ef35d8307a4 100644 --- a/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java +++ b/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java @@ -14,7 +14,6 @@ import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; import mage.players.Player; import mage.target.TargetCard; -import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInYourGraveyard; import java.util.Objects; @@ -62,28 +61,31 @@ class JourneyForTheElixirEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { return false; } - TargetCardInLibrary targetCardInLibrary = new JourneyForTheElixirLibraryTarget(); - player.searchLibrary(targetCardInLibrary, source, game); - Cards cards = new CardsImpl(targetCardInLibrary.getTargets()); - TargetCard target = new JourneyForTheElixirGraveyardTarget(cards); - player.choose(outcome, target, source, game); - cards.addAll(target.getTargets()); - player.revealCards(source, cards, game); - player.moveCards(cards, Zone.HAND, source, game); - player.shuffleLibrary(source, game); - return true; + + // search your library and graveyard for 2 cards + Cards allCards = new CardsImpl(); + allCards.addAll(controller.getLibrary().getCardList()); + allCards.addAll(controller.getGraveyard()); + TargetCard target = new JourneyForTheElixirTarget(); + if (controller.choose(Outcome.Benefit, allCards, target, source, game)) { + Cards cards = new CardsImpl(target.getTargets()); + controller.revealCards(source, cards, game); + controller.moveCards(cards, Zone.HAND, source, game); + controller.shuffleLibrary(source, game); + return true; + } + return false; } } -class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary { +class JourneyForTheElixirTarget extends TargetCard { private static final String name = "Jiang Yanggu"; - private static final FilterCard filter - = new FilterCard("a basic land card and a card named Jiang Yanggu"); + private static final FilterCard filter = new FilterCard("a basic land card and a card named Jiang Yanggu"); static { filter.add(Predicates.or( @@ -95,17 +97,17 @@ class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary { )); } - JourneyForTheElixirLibraryTarget() { - super(0, 2, filter); + JourneyForTheElixirTarget() { + super(2, 2, Zone.ALL, filter); } - private JourneyForTheElixirLibraryTarget(final JourneyForTheElixirLibraryTarget target) { + private JourneyForTheElixirTarget(final JourneyForTheElixirTarget target) { super(target); } @Override - public JourneyForTheElixirLibraryTarget copy() { - return new JourneyForTheElixirLibraryTarget(this); + public JourneyForTheElixirTarget copy() { + return new JourneyForTheElixirTarget(this); } @Override @@ -117,95 +119,35 @@ class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary { if (card == null) { return false; } - if (this.getTargets().isEmpty()) { - return true; - } + Cards cards = new CardsImpl(this.getTargets()); - if (card.isBasic(game) - && card.isLand(game) - && cards + boolean hasLand = cards .getCards(game) .stream() .filter(Objects::nonNull) .filter(c -> c.isBasic(game)) - .anyMatch(c -> c.isLand(game))) { - return false; - } - if (name.equals(card.getName()) - && cards + .anyMatch(c -> c.isLand(game)); + boolean hasJiang = cards .getCards(game) .stream() .map(MageObject::getName) - .anyMatch(name::equals)) { - return false; + .anyMatch(name::equals); + + if (!hasLand && card.isBasic(game) && card.isLand(game)) { + return true; } - return true; - } -} -class JourneyForTheElixirGraveyardTarget extends TargetCardInYourGraveyard { + if (!hasJiang && name.equals(card.getName())) { + return true; + } - private static final String name = "Jiang Yanggu"; - private static final FilterCard filter - = new FilterCard("a basic land card and a card named Jiang Yanggu"); - - static { - filter.add(Predicates.or( - Predicates.and( - SuperType.BASIC.getPredicate(), - CardType.LAND.getPredicate() - ), - new NamePredicate(name) - )); - } - - private final Cards cards = new CardsImpl(); - - JourneyForTheElixirGraveyardTarget(Cards cards) { - super(0, Integer.MAX_VALUE, filter, true); - this.cards.addAll(cards); - } - - private JourneyForTheElixirGraveyardTarget(final JourneyForTheElixirGraveyardTarget target) { - super(target); - this.cards.addAll(target.cards); - } - - @Override - public JourneyForTheElixirGraveyardTarget copy() { - return new JourneyForTheElixirGraveyardTarget(this); + return false; } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - Cards alreadyTargeted = new CardsImpl(this.getTargets()); - alreadyTargeted.addAll(cards); - boolean hasBasic = alreadyTargeted - .getCards(game) - .stream() - .filter(Objects::nonNull) - .filter(c -> c.isLand(game)) - .anyMatch(c -> c.isBasic(game)); - possibleTargets.removeIf(uuid -> { - Card card = game.getCard(uuid); - return card != null - && hasBasic - && card.isLand(game) - && card.isBasic(game); - }); - boolean hasYanggu = alreadyTargeted - .getCards(game) - .stream() - .filter(Objects::nonNull) - .map(MageObject::getName) - .anyMatch(name::equals); - possibleTargets.removeIf(uuid -> { - Card card = game.getCard(uuid); - return card != null - && hasYanggu - && name.equals(card.getName()); - }); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/j/JourneyToEternity.java b/Mage.Sets/src/mage/cards/j/JourneyToEternity.java index ef16a392044..f3759a3341d 100644 --- a/Mage.Sets/src/mage/cards/j/JourneyToEternity.java +++ b/Mage.Sets/src/mage/cards/j/JourneyToEternity.java @@ -1,8 +1,6 @@ package mage.cards.j; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.common.DiesAttachedTriggeredAbility; import mage.abilities.effects.OneShotEffect; @@ -13,16 +11,14 @@ import mage.abilities.keyword.TransformAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.TargetPermanent; +import java.util.UUID; + /** * @author LevelX2 */ @@ -40,12 +36,11 @@ public final class JourneyToEternity extends CardImpl { TargetPermanent auraTarget = new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // When enchanted creature dies, return it to the battlefield under your control, then return Journey to Eternity to the battlefield transformed under your control. this.addAbility(new TransformAbility()); - ability = new DiesAttachedTriggeredAbility(new ReturnToBattlefieldUnderYourControlAttachedEffect("it"), "enchanted creature"); + Ability ability = new DiesAttachedTriggeredAbility(new ReturnToBattlefieldUnderYourControlAttachedEffect("it"), "enchanted creature"); ability.addEffect(new JourneyToEternityReturnTransformedSourceEffect()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/j/JundPanorama.java b/Mage.Sets/src/mage/cards/j/JundPanorama.java index 7a84beb4ea0..17ee36d0611 100644 --- a/Mage.Sets/src/mage/cards/j/JundPanorama.java +++ b/Mage.Sets/src/mage/cards/j/JundPanorama.java @@ -2,7 +2,6 @@ package mage.cards.j; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; @@ -12,21 +11,23 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public final class JundPanorama extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Swamp, Mountain, or Forest card"); + private static final FilterCard filter = new FilterCard("a basic Swamp, Mountain, or Forest card"); static { - filter.add(CardType.LAND.getPredicate()); filter.add(SuperType.BASIC.getPredicate()); filter.add(Predicates.or( SubType.SWAMP.getPredicate(), @@ -35,7 +36,7 @@ public final class JundPanorama extends CardImpl { } public JundPanorama(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},null); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, null); this.addAbility(new ColorlessManaAbility()); TargetCardInLibrary target = new TargetCardInLibrary(filter); Ability ability = new SimpleActivatedAbility(new SearchLibraryPutInPlayEffect(target, true), new GenericManaCost(1)); diff --git a/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java b/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java index 37d1f67a3b1..eff94a35fce 100644 --- a/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java +++ b/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java @@ -88,8 +88,8 @@ class KairiTheSwirlingSkyTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 6, game); } diff --git a/Mage.Sets/src/mage/cards/k/KamizObscuraOculus.java b/Mage.Sets/src/mage/cards/k/KamizObscuraOculus.java index 592a648f543..b08245c145f 100644 --- a/Mage.Sets/src/mage/cards/k/KamizObscuraOculus.java +++ b/Mage.Sets/src/mage/cards/k/KamizObscuraOculus.java @@ -6,7 +6,7 @@ import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; -import mage.abilities.effects.keyword.ConniveSourceEffect; +import mage.abilities.effects.keyword.ConniveTargetEffect; import mage.abilities.keyword.DoubleStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -39,7 +39,7 @@ public final class KamizObscuraOculus extends CardImpl { // Whenever you attack, target attacking creature can't be blocked this turn. It connives. Then choose another attacking creature with lesser power. That creature gains double strike until end of turn. Ability ability = new AttacksWithCreaturesTriggeredAbility(new CantBeBlockedTargetEffect(), 1); - ability.addEffect(new KamizConniveEffect()); + ability.addEffect(new ConniveTargetEffect().setText("it connives")); ability.addEffect(new KamizDoubleStrikeEffect().concatBy("Then")); ability.addTarget(new TargetAttackingCreature()); this.addAbility(ability); @@ -55,29 +55,6 @@ public final class KamizObscuraOculus extends CardImpl { } } -class KamizConniveEffect extends OneShotEffect { - - KamizConniveEffect() { - super(Outcome.Benefit); - staticText = "it connives"; - } - - private KamizConniveEffect(final KamizConniveEffect effect) { - super(effect); - } - - @Override - public KamizConniveEffect copy() { - return new KamizConniveEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); - return ConniveSourceEffect.connive(permanent, 1, source, game); - } -} - class KamizDoubleStrikeEffect extends OneShotEffect { KamizDoubleStrikeEffect() { @@ -109,8 +86,8 @@ class KamizDoubleStrikeEffect extends OneShotEffect { if (target.choose(outcome, source.getControllerId(), source.getSourceId(), source, game)) { game.addEffect( new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance()) - .setTargetPointer(new FixedTarget(target.getFirstTarget(), game)) - , source); + .setTargetPointer(new FixedTarget(target.getFirstTarget(), game)), source + ); } return true; } diff --git a/Mage.Sets/src/mage/cards/k/KarnLiberated.java b/Mage.Sets/src/mage/cards/k/KarnLiberated.java index 8d77814e4c7..c38cd26e79b 100644 --- a/Mage.Sets/src/mage/cards/k/KarnLiberated.java +++ b/Mage.Sets/src/mage/cards/k/KarnLiberated.java @@ -81,7 +81,7 @@ class KarnLiberatedEffect extends OneShotEffect { } List keepExiled = new ArrayList<>(); for (ExileZone zone : game.getExile().getExileZones()) { - exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); if (zone.getId().equals(exileId)) { for (Card card : zone.getCards(game)) { if (!card.hasSubtype(SubType.AURA, game) @@ -243,7 +243,7 @@ class KarnPlayerExileEffect extends OneShotEffect { TargetCardInHand target = new TargetCardInHand(); if (target.canChoose(player.getId(), source, game) && target.chooseTarget(Outcome.Exile, player.getId(), source, game)) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); return player.moveCardsToExile(new CardsImpl(target.getTargets()).getCards(game), source, game, true, exileId, sourceObject.getIdName()); } return false; diff --git a/Mage.Sets/src/mage/cards/k/KarnsSylex.java b/Mage.Sets/src/mage/cards/k/KarnsSylex.java index df61aadcc9b..f4e19c66515 100644 --- a/Mage.Sets/src/mage/cards/k/KarnsSylex.java +++ b/Mage.Sets/src/mage/cards/k/KarnsSylex.java @@ -2,21 +2,22 @@ package mage.cards.k; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.CantPayLifeOrSacrificeAbility; import mage.abilities.common.EntersBattlefieldTappedAbility; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.ExileSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.SuperType; import mage.filter.common.FilterNonlandPermanent; import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.util.CardUtil; import java.util.UUID; @@ -33,7 +34,7 @@ public class KarnsSylex extends CardImpl { this.addAbility(new EntersBattlefieldTappedAbility()); // Players can’t pay life to cast spells or to activate abilities that aren’t mana abilities. - this.addAbility(new SimpleStaticAbility(new KarnsSylexEffect())); + this.addAbility(new CantPayLifeOrSacrificeAbility(true, null)); // {X}, {T}, Exile Karn’s Sylex: Destroy each nonland permanent with mana value X or less. Activate only as a sorcery. Ability ability = new ActivateAsSorceryActivatedAbility(new KarnsSylexDestroyEffect(), new ManaCostsImpl<>("{X}")); @@ -52,32 +53,6 @@ public class KarnsSylex extends CardImpl { } } -class KarnsSylexEffect extends ContinuousEffectImpl { - - KarnsSylexEffect() { - super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment); - staticText = "Players can't pay life to cast spells or to activate abilities that aren't mana abilities"; - } - - private KarnsSylexEffect(final KarnsSylexEffect effect) { - super(effect); - } - - @Override - public KarnsSylexEffect copy() { - return new KarnsSylexEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { - Player player = game.getPlayer(playerId); - player.setPayLifeCostLevel(Player.PayLifeCostLevel.onlyManaAbilities); - } - return true; - } -} - class KarnsSylexDestroyEffect extends OneShotEffect { KarnsSylexDestroyEffect() { diff --git a/Mage.Sets/src/mage/cards/k/KataraBendingProdigy.java b/Mage.Sets/src/mage/cards/k/KataraBendingProdigy.java new file mode 100644 index 00000000000..0a40304dd68 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KataraBendingProdigy.java @@ -0,0 +1,51 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KataraBendingProdigy extends CardImpl { + + public KataraBendingProdigy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // At the beginning of your end step, if Katara is tapped, put a +1/+1 counter on her. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()).setText("put a +1/+1 counter on her") + ).withInterveningIf(SourceTappedCondition.TAPPED)); + + // Waterbend {6}: Draw a card. + this.addAbility(new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new WaterbendCost(6))); + } + + private KataraBendingProdigy(final KataraBendingProdigy card) { + super(card); + } + + @Override + public KataraBendingProdigy copy() { + return new KataraBendingProdigy(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KataraHeroicHealer.java b/Mage.Sets/src/mage/cards/k/KataraHeroicHealer.java new file mode 100644 index 00000000000..3ffec0a09ed --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KataraHeroicHealer.java @@ -0,0 +1,49 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KataraHeroicHealer extends CardImpl { + + public KataraHeroicHealer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When Katara enters, put a +1/+1 counter on each other creature you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_OTHER_CONTROLLED_CREATURE + ))); + } + + private KataraHeroicHealer(final KataraHeroicHealer card) { + super(card); + } + + @Override + public KataraHeroicHealer copy() { + return new KataraHeroicHealer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KataraTheFearless.java b/Mage.Sets/src/mage/cards/k/KataraTheFearless.java new file mode 100644 index 00000000000..5928803430f --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KataraTheFearless.java @@ -0,0 +1,79 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KataraTheFearless extends CardImpl { + + public KataraTheFearless(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // If a triggered ability of an Ally you control triggers, that ability triggers an additional time. + this.addAbility(new SimpleStaticAbility(new KataraTheFearlessEffect())); + } + + private KataraTheFearless(final KataraTheFearless card) { + super(card); + } + + @Override + public KataraTheFearless copy() { + return new KataraTheFearless(this); + } +} + +class KataraTheFearlessEffect extends ReplacementEffectImpl { + + KataraTheFearlessEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "if a triggered ability of an Ally you control triggers, that ability triggers an additional time"; + } + + private KataraTheFearlessEffect(final KataraTheFearlessEffect effect) { + super(effect); + } + + @Override + public KataraTheFearlessEffect copy() { + return new KataraTheFearlessEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + return permanent != null + && permanent.isControlledBy(source.getControllerId()) + && permanent.hasSubtype(SubType.ALLY, game); + } + + @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/k/KataraWaterTribesHope.java b/Mage.Sets/src/mage/cards/k/KataraWaterTribesHope.java new file mode 100644 index 00000000000..35f78ab4521 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KataraWaterTribesHope.java @@ -0,0 +1,64 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.costs.mana.VariableManaCost; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAllEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.AllyToken; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KataraWaterTribesHope extends CardImpl { + + public KataraWaterTribesHope(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When Katara enters, create a 1/1 white Ally creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new AllyToken()))); + + // Waterbend {X}: Creatures you control have base power and toughness X/X until end of turn. X can't be 0. Activate only during your turn. + Ability ability = new ActivateIfConditionActivatedAbility(new SetBasePowerToughnessAllEffect( + GetXValue.instance, GetXValue.instance, Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURES + ), new WaterbendCost("{X}"), MyTurnCondition.instance); + CardUtil.castStream(ability.getCosts(), VariableManaCost.class).forEach(cost -> cost.setMinX(1)); + this.addAbility(ability); + } + + private KataraWaterTribesHope(final KataraWaterTribesHope card) { + super(card); + } + + @Override + public KataraWaterTribesHope copy() { + return new KataraWaterTribesHope(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KataraWaterbendingMaster.java b/Mage.Sets/src/mage/cards/k/KataraWaterbendingMaster.java new file mode 100644 index 00000000000..a6123b98f0b --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KataraWaterbendingMaster.java @@ -0,0 +1,60 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.common.OpponentsTurnCondition; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersControllerCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersPlayersEffect; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KataraWaterbendingMaster extends CardImpl { + + private static final DynamicValue xValue = new CountersControllerCount(CounterType.EXPERIENCE); + + public KataraWaterbendingMaster(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.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Whenever you cast a spell during an opponent's turn, you get an experience counter. + this.addAbility(new SpellCastControllerTriggeredAbility(new AddCountersPlayersEffect( + CounterType.EXPERIENCE.createInstance(), TargetController.YOU), false + ).withTriggerCondition(OpponentsTurnCondition.instance)); + + // Whenever Katara attacks, you may draw a card for each experience counter you have. If you do, discard a card. + Ability ability = new AttacksTriggeredAbility(new DrawCardSourceControllerEffect(xValue) + .setText("draw a card for each experience counter you have")); + ability.addEffect(new DiscardControllerEffect(1).concatBy("If you do,")); + this.addAbility(ability); + } + + private KataraWaterbendingMaster(final KataraWaterbendingMaster card) { + super(card); + } + + @Override + public KataraWaterbendingMaster copy() { + return new KataraWaterbendingMaster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KateStewart.java b/Mage.Sets/src/mage/cards/k/KateStewart.java index 93309859588..3318f2e4053 100644 --- a/Mage.Sets/src/mage/cards/k/KateStewart.java +++ b/Mage.Sets/src/mage/cards/k/KateStewart.java @@ -2,8 +2,8 @@ package mage.cards.k; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.PutCounterOnPermanentTriggeredAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; @@ -18,7 +18,6 @@ import mage.constants.*; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.token.SoldierToken; import java.util.UUID; @@ -38,7 +37,8 @@ public final class KateStewart extends CardImpl { this.toughness = new MageInt(3); // Whenever you put one or more time counters on a permanent you control, create a 1/1 white Soldier creature token. - this.addAbility(new KateStewartTriggeredAbility()); + this.addAbility(new PutCounterOnPermanentTriggeredAbility(new CreateTokenEffect(new SoldierToken()), + CounterType.TIME, StaticFilters.FILTER_CONTROLLED_PERMANENT)); // Whenever Kate Stewart attacks, you may pay {8}. If you do, attacking creatures get +X/+X until end of turn, where X is the number of time counters among permanents you control. this.addAbility(new AttacksTriggeredAbility(new DoIfCostPaid(new BoostAllEffect( @@ -57,35 +57,6 @@ public final class KateStewart extends CardImpl { } } -class KateStewartTriggeredAbility extends TriggeredAbilityImpl { - - KateStewartTriggeredAbility() { - super(Zone.BATTLEFIELD, new CreateTokenEffect(new SoldierToken())); - setTriggerPhrase("Whenever you put one or more time counters on a permanent you control, "); - } - - private KateStewartTriggeredAbility(final KateStewartTriggeredAbility ability) { - super(ability); - } - - @Override - public KateStewartTriggeredAbility copy() { - return new KateStewartTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.COUNTERS_ADDED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return CounterType.TIME.getName().equals(event.getData()) - && this.isControlledBy(event.getPlayerId()) - && this.isControlledBy(game.getControllerId(event.getTargetId())); - } -} - enum KateStewartValue implements DynamicValue { instance; private static final Hint hint = new ValueHint("Time counters among permanents you control", instance); diff --git a/Mage.Sets/src/mage/cards/k/KatildasRisingDawn.java b/Mage.Sets/src/mage/cards/k/KatildasRisingDawn.java index ff48034a4d7..dc2907dacea 100644 --- a/Mage.Sets/src/mage/cards/k/KatildasRisingDawn.java +++ b/Mage.Sets/src/mage/cards/k/KatildasRisingDawn.java @@ -1,20 +1,15 @@ package mage.cards.k; import mage.abilities.Ability; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; -import mage.abilities.keyword.EnchantAbility; -import mage.abilities.keyword.FlyingAbility; -import mage.abilities.keyword.LifelinkAbility; -import mage.abilities.keyword.ProtectionAbility; +import mage.abilities.keyword.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -75,7 +70,7 @@ public final class KatildasRisingDawn extends CardImpl { this.addAbility(ability.addHint(hint)); // If Katilda's Rising Dawn would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private KatildasRisingDawn(final KatildasRisingDawn card) { diff --git a/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java b/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java index f2d8dc1aa49..37b4fb74752 100644 --- a/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java +++ b/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java @@ -219,7 +219,7 @@ class KayaSpiritsJusticeExileEffect extends OneShotEffect { controller.choose(outcome, target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); return controller.moveCardsToExile(card, source, game, true, exileId, exileName); diff --git a/Mage.Sets/src/mage/cards/k/KaylasCommand.java b/Mage.Sets/src/mage/cards/k/KaylasCommand.java index f132c26673a..f6fc7fc9006 100644 --- a/Mage.Sets/src/mage/cards/k/KaylasCommand.java +++ b/Mage.Sets/src/mage/cards/k/KaylasCommand.java @@ -1,7 +1,5 @@ package mage.cards.k; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; @@ -15,10 +13,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.counters.CounterType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.Construct2Token; @@ -27,20 +23,13 @@ import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class KaylasCommand extends CardImpl { - private static final FilterCard filter - = new FilterCard("a basic Plains card"); - - static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); - } - public KaylasCommand(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}{W}"); @@ -55,7 +44,7 @@ public final class KaylasCommand extends CardImpl { this.getSpellAbility().addMode(new Mode(new KaylasCommandCounterEffect())); // * Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. - this.getSpellAbility().addMode(new Mode(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true))); + this.getSpellAbility().addMode(new Mode(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true))); // * You gain 2 life and scry 2. Mode mode = new Mode(new GainLifeEffect(2)); diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheAccord.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheAccord.java index 6867dc7a039..cfc8ae722b7 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheAccord.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheAccord.java @@ -10,9 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.constants.TargetController; -import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.game.Game; @@ -26,13 +24,6 @@ import java.util.UUID; */ public final class KeeperOfTheAccord extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - public KeeperOfTheAccord(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); @@ -48,7 +39,7 @@ public final class KeeperOfTheAccord extends CardImpl { // At the beginning of each opponent's end step, if that player controls more lands than you, you may search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle your library. this.addAbility(new BeginningOfEndStepTriggeredAbility( - TargetController.OPPONENT, new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true), true + TargetController.OPPONENT, new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true), true ).withInterveningIf(KeeperOfTheAccordCondition.LANDS)); } diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheBeasts.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheBeasts.java index 8900516e43b..697e9c531c6 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheBeasts.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheBeasts.java @@ -1,7 +1,6 @@ package mage.cards.k; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -15,10 +14,8 @@ import mage.filter.FilterOpponent; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.token.BeastToken4; -import mage.players.Player; import mage.target.TargetPlayer; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -35,7 +32,7 @@ public final class KeeperOfTheBeasts extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(2); - // {G}, {tap}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield. + // {G}, {T}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield. Ability ability = new SimpleActivatedAbility(new CreateTokenEffect(new BeastToken4()).setText("Choose target opponent who controlled more creatures than you did as you activated this ability. Create a 2/2 green Beast creature token."), new ManaCostsImpl<>("{G}")); ability.addCost(new TapSourceCost()); @@ -65,42 +62,12 @@ class KeeperOfTheBeastsTarget extends TargetPlayer { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - int creaturesController = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game); - - for (UUID targetId : availablePossibleTargets) { - if (game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, targetId, game) > creaturesController) { - possibleTargets.add(targetId); - } - } + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + int myCount = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game); + possibleTargets.removeIf(playerId -> game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game) < myCount); return possibleTargets; } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - MageObject targetSource = game.getObject(source); - Player controller = game.getPlayer(sourceControllerId); - if (controller != null && targetSource != null) { - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null - && game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game) - < game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game) - && !player.hasLeft() - && filter.match(player, sourceControllerId, source, game) - && player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - } - return false; - } - @Override public KeeperOfTheBeastsTarget copy() { return new KeeperOfTheBeastsTarget(this); diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java index 85b5284fff2..45f0f0f72c7 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java @@ -1,10 +1,7 @@ package mage.cards.k; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.MageObject; +import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -15,21 +12,23 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterPlayer; import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.TargetPlayer; +import java.util.Set; +import java.util.UUID; + /** - * * @author spjspj */ public final class KeeperOfTheDead extends CardImpl { @@ -95,48 +94,37 @@ class KeeperOfDeadPredicate implements ObjectSourcePlayerPredicate { class KeeperOfTheDeadCreatureTarget extends TargetPermanent { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonblack creature that player controls"); + + static { + filter.add(Predicates.not(new ColorPredicate(ObjectColor.BLACK))); + } + public KeeperOfTheDeadCreatureTarget() { - super(1, 1, new FilterCreaturePermanent("nonblack creature that player controls"), false); + super(1, 1, filter); } private KeeperOfTheDeadCreatureTarget(final KeeperOfTheDeadCreatureTarget target) { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - UUID firstTarget = source.getFirstTarget(); - Permanent permanent = game.getPermanent(id); - if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) { - return super.canTarget(id, source, game); - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (StackObject item : game.getState().getStack()) { - if (item.getId().equals(source.getSourceId())) { - object = item; - } - if (item.getSourceId().equals(source.getSourceId())) { - object = item; - } + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && StaticFilters.FILTER_PERMANENT_CREATURE_NON_BLACK.match(permanent, game) && permanent.isControlledBy(playerId)) { - possibleTargets.add(targetId); - } - } - } return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheLight.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheLight.java index 6e24e7d16b2..b4a54b45b4b 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheLight.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheLight.java @@ -1,8 +1,6 @@ - package mage.cards.k; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -12,13 +10,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterOpponent; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -67,43 +63,19 @@ class KeeperOfTheLightTarget extends TargetPlayer { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - int lifeController = game.getPlayer(sourceControllerId).getLife(); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (UUID targetId : availablePossibleTargets) { - Player opponent = game.getPlayer(targetId); - if (opponent != null) { - int lifeOpponent = opponent.getLife(); - if (lifeOpponent > lifeController) { - possibleTargets.add(targetId); - } - } - } - return possibleTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - MageObject targetSource = game.getObject(source); Player controller = game.getPlayer(sourceControllerId); - if (controller != null && targetSource != null) { - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null - && controller.getLife() < player.getLife() - && !player.hasLeft() - && filter.match(player, sourceControllerId, source, game) - && player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } + if (controller == null) { + return possibleTargets; } - return false; + + possibleTargets.removeIf(playerId -> { + Player player = game.getPlayer(playerId); + return player == null || player.getLife() >= controller.getLife(); + }); + + return possibleTargets; } @Override diff --git a/Mage.Sets/src/mage/cards/k/KeldonBattlewagon.java b/Mage.Sets/src/mage/cards/k/KeldonBattlewagon.java index 1543346e478..6283587baa9 100644 --- a/Mage.Sets/src/mage/cards/k/KeldonBattlewagon.java +++ b/Mage.Sets/src/mage/cards/k/KeldonBattlewagon.java @@ -105,7 +105,7 @@ class KeldonBattlewagonCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/k/KnightOfTheWhiteOrchid.java b/Mage.Sets/src/mage/cards/k/KnightOfTheWhiteOrchid.java index aeac56a3b82..b45d6879bb0 100644 --- a/Mage.Sets/src/mage/cards/k/KnightOfTheWhiteOrchid.java +++ b/Mage.Sets/src/mage/cards/k/KnightOfTheWhiteOrchid.java @@ -12,7 +12,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBySubtypeCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -22,7 +21,7 @@ import java.util.UUID; */ public final class KnightOfTheWhiteOrchid extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.PLAINS); + private static final FilterCard filter = new FilterCard(SubType.PLAINS); private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS); public KnightOfTheWhiteOrchid(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/k/KnowledgePool.java b/Mage.Sets/src/mage/cards/k/KnowledgePool.java index 356eeb493c7..e553c7110c3 100644 --- a/Mage.Sets/src/mage/cards/k/KnowledgePool.java +++ b/Mage.Sets/src/mage/cards/k/KnowledgePool.java @@ -86,7 +86,7 @@ class KnowledgePoolExileThreeCardsEffect extends OneShotEffect { source, game, true, - CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), + CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()), sourceObject.getIdName() + " (" + sourceObject.getZoneChangeCounter(game) + ')' ); } diff --git a/Mage.Sets/src/mage/cards/k/KomodoRhino.java b/Mage.Sets/src/mage/cards/k/KomodoRhino.java new file mode 100644 index 00000000000..a355f77936a --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KomodoRhino.java @@ -0,0 +1,37 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KomodoRhino extends CardImpl { + + public KomodoRhino(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.RHINO); + this.power = new MageInt(5); + this.toughness = new MageInt(2); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + } + + private KomodoRhino(final KomodoRhino card) { + super(card); + } + + @Override + public KomodoRhino copy() { + return new KomodoRhino(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KondasBanner.java b/Mage.Sets/src/mage/cards/k/KondasBanner.java index e706ee087a0..72005df704a 100644 --- a/Mage.Sets/src/mage/cards/k/KondasBanner.java +++ b/Mage.Sets/src/mage/cards/k/KondasBanner.java @@ -7,15 +7,14 @@ import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.keyword.EquipAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.SubType; -import mage.constants.SuperType; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.target.TargetCard; import mage.target.TargetPermanent; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/k/KorCartographer.java b/Mage.Sets/src/mage/cards/k/KorCartographer.java index 7e8597e6836..305f2e19512 100644 --- a/Mage.Sets/src/mage/cards/k/KorCartographer.java +++ b/Mage.Sets/src/mage/cards/k/KorCartographer.java @@ -1,7 +1,5 @@ - package mage.cards.k; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; @@ -9,17 +7,20 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author North */ public final class KorCartographer extends CardImpl { + private static final FilterCard filter = new FilterCard(SubType.PLAINS); + public KorCartographer(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); this.subtype.add(SubType.KOR); this.subtype.add(SubType.SCOUT); @@ -27,7 +28,9 @@ public final class KorCartographer extends CardImpl { this.toughness = new MageInt(2); // When Kor Cartographer enters the battlefield, you may search your library for a Plains card, put it onto the battlefield tapped, then shuffle your library. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(new FilterBySubtypeCard(SubType.PLAINS)), true), true)); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true), true + )); } private KorCartographer(final KorCartographer card) { diff --git a/Mage.Sets/src/mage/cards/k/KormusBell.java b/Mage.Sets/src/mage/cards/k/KormusBell.java index 84bbcf7abdc..d186e67963b 100644 --- a/Mage.Sets/src/mage/cards/k/KormusBell.java +++ b/Mage.Sets/src/mage/cards/k/KormusBell.java @@ -27,11 +27,12 @@ public final class KormusBell extends CardImpl { new CreatureToken(1, 1, "1/1 black creatures").withColor("B"), "lands", filter, Duration.WhileOnBattlefield, true); - effect.addDependedToType(DependencyType.BecomeSwamp); // TODO: are these dependencies correct/complete? - effect.addDependedToType(DependencyType.BecomeIsland); + effect.addDependedToType(DependencyType.BecomeNonbasicLand); effect.addDependedToType(DependencyType.BecomeForest); + effect.addDependedToType(DependencyType.BecomeIsland); effect.addDependedToType(DependencyType.BecomeMountain); effect.addDependedToType(DependencyType.BecomePlains); + effect.addDependedToType(DependencyType.BecomeSwamp); this.addAbility(new SimpleStaticAbility(effect)); } diff --git a/Mage.Sets/src/mage/cards/k/KothFireOfResistance.java b/Mage.Sets/src/mage/cards/k/KothFireOfResistance.java index 0261497d5a7..2fe11c98c2e 100644 --- a/Mage.Sets/src/mage/cards/k/KothFireOfResistance.java +++ b/Mage.Sets/src/mage/cards/k/KothFireOfResistance.java @@ -2,16 +2,20 @@ package mage.cards.k; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.GetEmblemEffect; 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; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.filter.common.FilterControlledPermanent; import mage.game.command.emblems.KothFireOfResistanceEmblem; import mage.target.common.TargetCardInLibrary; @@ -24,14 +28,11 @@ import java.util.UUID; */ public final class KothFireOfResistance extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Mountain card"); - private static final FilterControlledPermanent filter2 = new FilterControlledPermanent("Mountains you control"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.MOUNTAIN.getPredicate()); - filter2.add(SubType.MOUNTAIN.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.MOUNTAIN); + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount( + new FilterControlledPermanent(SubType.MOUNTAIN, "Mountains you control") + ); + private static final Hint hint = new ValueHint(xValue.getMessage(), xValue); public KothFireOfResistance(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{R}"); @@ -44,10 +45,10 @@ public final class KothFireOfResistance extends CardImpl { this.addAbility(new LoyaltyAbility(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), 2)); // −3: Koth, Fire of Resistance deals damage to target creature equal to the number of Mountains you control. - Ability ability = new LoyaltyAbility(new DamageTargetEffect(new PermanentsOnBattlefieldCount(filter2)) + Ability ability = new LoyaltyAbility(new DamageTargetEffect(xValue) .setText("{this} deals damage to target creature equal to the number of Mountains you control"), -3); ability.addTarget(new TargetCreaturePermanent()); - this.addAbility(ability); + this.addAbility(ability.addHint(hint)); // −7: You get an emblem with "Whenever a Mountain you control enters, this emblem deals 4 damage to any target." this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new KothFireOfResistanceEmblem()), -7)); diff --git a/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java b/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java index 4e6aa44c4b6..d478459ff8c 100644 --- a/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java +++ b/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java @@ -15,6 +15,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; import mage.players.Player; +import mage.target.TargetCard; import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInLibrary; @@ -107,9 +108,9 @@ class KotoseTheSilentSpiderEffect extends OneShotEffect { cards.addAll(targetCardInGraveyard.getTargets()); filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s hand"); - TargetCardInHand targetCardInHand = new TargetCardInHand(0, Integer.MAX_VALUE, filter); - controller.choose(outcome, opponent.getHand(), targetCardInHand, source, game); - cards.addAll(targetCardInHand.getTargets()); + TargetCard targetCard = new TargetCard(0, Integer.MAX_VALUE, Zone.HAND, filter); + controller.choose(outcome, opponent.getHand(), targetCard, source, game); + cards.addAll(targetCard.getTargets()); filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s library"); TargetCardInLibrary target = new TargetCardInLibrary(0, Integer.MAX_VALUE, filter); diff --git a/Mage.Sets/src/mage/cards/k/KozilekTheGreatDistortion.java b/Mage.Sets/src/mage/cards/k/KozilekTheGreatDistortion.java index b8842f6f4b9..61396a8b402 100644 --- a/Mage.Sets/src/mage/cards/k/KozilekTheGreatDistortion.java +++ b/Mage.Sets/src/mage/cards/k/KozilekTheGreatDistortion.java @@ -7,9 +7,9 @@ import mage.abilities.condition.Condition; import mage.abilities.condition.common.CardsInHandCondition; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CastSourceTriggeredAbility; import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.DrawCardsEqualToDifferenceEffect; import mage.abilities.keyword.MenaceAbility; import mage.cards.Card; import mage.cards.CardImpl; @@ -44,7 +44,7 @@ public final class KozilekTheGreatDistortion extends CardImpl { this.toughness = new MageInt(12); // When you cast Kozilek, the Great Distortion, if you have fewer than seven cards in hand, draw cards equal to the difference. - this.addAbility(new CastSourceTriggeredAbility(new KozilekDrawEffect(), false).withInterveningIf(condition)); + this.addAbility(new CastSourceTriggeredAbility(new DrawCardsEqualToDifferenceEffect(7)).withInterveningIf(condition)); // Menace this.addAbility(new MenaceAbility(false)); @@ -65,33 +65,6 @@ public final class KozilekTheGreatDistortion extends CardImpl { } } -class KozilekDrawEffect extends OneShotEffect { - - KozilekDrawEffect() { - super(Outcome.DrawCard); - this.staticText = "draw cards equal to the difference"; - } - - private KozilekDrawEffect(final KozilekDrawEffect effect) { - super(effect); - } - - @Override - public KozilekDrawEffect copy() { - return new KozilekDrawEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - controller.drawCards(7 - controller.getHand().size(), source, game); - return true; - } - return false; - } -} - class KozilekDiscardCost extends CostImpl { public KozilekDiscardCost() { @@ -156,5 +129,4 @@ class KozilekDiscardCost extends CostImpl { public KozilekDiscardCost copy() { return new KozilekDiscardCost(this); } - } diff --git a/Mage.Sets/src/mage/cards/k/KravenProudPredator.java b/Mage.Sets/src/mage/cards/k/KravenProudPredator.java new file mode 100644 index 00000000000..81aa267a90e --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KravenProudPredator.java @@ -0,0 +1,49 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KravenProudPredator extends CardImpl { + + public KravenProudPredator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Top of the Food Chain -- Kraven's power is equal to the greatest mana value among permanents you control. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SetBasePowerSourceEffect(GreatestAmongPermanentsValue.MANAVALUE_CONTROLLED_PERMANENTS) + ).withFlavorWord("Top of the Food Chain")); + } + + private KravenProudPredator(final KravenProudPredator card) { + super(card); + } + + @Override + public KravenProudPredator copy() { + return new KravenProudPredator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KravenTheHunter.java b/Mage.Sets/src/mage/cards/k/KravenTheHunter.java new file mode 100644 index 00000000000..41d5d2596e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KravenTheHunter.java @@ -0,0 +1,59 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.GreatestPowerControlledPredicate; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class KravenTheHunter extends CardImpl { + private static final FilterPermanent filter = new FilterCreaturePermanent("creature an opponent controls with the greatest power among creatures that player controls"); + + static { + filter.add(GreatestPowerControlledPredicate.instance); + } + public KravenTheHunter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever a creature an opponent controls with the greatest power among creatures that player controls dies, draw a card and put a +1/+1 counter on Kraven the Hunter. + Ability ability = new DiesCreatureTriggeredAbility(new DrawCardSourceControllerEffect(1), false, filter); + ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + .concatBy("and")); + this.addAbility(ability); + } + + private KravenTheHunter(final KravenTheHunter card) { + super(card); + } + + @Override + public KravenTheHunter copy() { + return new KravenTheHunter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KravensLastHunt.java b/Mage.Sets/src/mage/cards/k/KravensLastHunt.java new file mode 100644 index 00000000000..50efa741010 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KravensLastHunt.java @@ -0,0 +1,144 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KravensLastHunt extends CardImpl { + + public KravensLastHunt(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{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 -- Mill five cards. When you do, this Saga deals damage equal to the greatest power among creature cards in your graveyard to target creature. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new MillCardsControllerEffect(4), new KravensLastHuntEffect() + ); + + // II -- Target creature you control gets +2/+2 until end of turn. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, + new BoostTargetEffect(2, 2), + new TargetControlledCreaturePermanent() + ); + + // III -- Return target creature card from your graveyard to your hand. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_III, new ReturnFromGraveyardToHandTargetEffect(), + new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD) + ); + this.addAbility(sagaAbility.addHint(KravensLastHuntValue.getHint())); + } + + private KravensLastHunt(final KravensLastHunt card) { + super(card); + } + + @Override + public KravensLastHunt copy() { + return new KravensLastHunt(this); + } +} + +enum KravensLastHuntValue implements DynamicValue { + instance; + private static final Hint hint = new ValueHint("Greatest power among creature cards in your graveyard", instance); + + public static Hint getHint() { + return hint; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Player player = game.getPlayer(sourceAbility.getControllerId()); + if (player == null) { + return 0; + } + return player + .getGraveyard() + .getCards(StaticFilters.FILTER_CARD_CREATURE, game) + .stream() + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .max() + .orElse(0); + } + + @Override + public KravensLastHuntValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "1"; + } +} + +class KravensLastHuntEffect extends OneShotEffect { + + + KravensLastHuntEffect() { + super(Outcome.Benefit); + staticText = "When you do, {this} deals damage equal to the greatest power among creature cards in your graveyard to target creature"; + } + + private KravensLastHuntEffect(final KravensLastHuntEffect effect) { + super(effect); + } + + @Override + public KravensLastHuntEffect copy() { + return new KravensLastHuntEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new DamageTargetEffect(KravensLastHuntValue.instance) + .setText("{this} deals damage equal to the greatest power " + + "among creature cards in your graveyard to target creature"), false + ); + ability.addTarget(new TargetCreaturePermanent()); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KrosDefenseContractor.java b/Mage.Sets/src/mage/cards/k/KrosDefenseContractor.java index 400c90449e9..7ca5c749599 100644 --- a/Mage.Sets/src/mage/cards/k/KrosDefenseContractor.java +++ b/Mage.Sets/src/mage/cards/k/KrosDefenseContractor.java @@ -3,7 +3,7 @@ package mage.cards.k; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.common.PutCounterOnCreatureTriggeredAbility; +import mage.abilities.common.PutCounterOnPermanentTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.combat.GoadTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; @@ -42,7 +42,8 @@ public final class KrosDefenseContractor extends CardImpl { this.addAbility(ability); // Whenever you put one or more counters on a creature you don't control, tap that creature and goad it. It gains trample until your next turn. - this.addAbility(new PutCounterOnCreatureTriggeredAbility(new KrosDefenseContractorEffect(), null, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL, true)); + this.addAbility(new PutCounterOnPermanentTriggeredAbility(new KrosDefenseContractorEffect(), + null, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL, true, false)); } private KrosDefenseContractor(final KrosDefenseContractor card) { diff --git a/Mage.Sets/src/mage/cards/k/KroxaTitanOfDeathsHunger.java b/Mage.Sets/src/mage/cards/k/KroxaTitanOfDeathsHunger.java index 3cc9434f5fa..44d3d508232 100644 --- a/Mage.Sets/src/mage/cards/k/KroxaTitanOfDeathsHunger.java +++ b/Mage.Sets/src/mage/cards/k/KroxaTitanOfDeathsHunger.java @@ -82,7 +82,7 @@ class KroxaTitanOfDeathsHungerEntersEffect extends OneShotEffect { if (permanent == null) { return false; } - if (EscapeAbility.wasCastedWithEscape(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())) { + if (EscapeAbility.wasCastedWithEscape(game, source.getSourceId(), source.getStackMomentSourceZCC())) { return false; } return permanent.sacrifice(source, game); diff --git a/Mage.Sets/src/mage/cards/k/KyoshiWarriorGuard.java b/Mage.Sets/src/mage/cards/k/KyoshiWarriorGuard.java new file mode 100644 index 00000000000..d34844caece --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KyoshiWarriorGuard.java @@ -0,0 +1,34 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KyoshiWarriorGuard extends CardImpl { + + public KyoshiWarriorGuard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + } + + private KyoshiWarriorGuard(final KyoshiWarriorGuard card) { + super(card); + } + + @Override + public KyoshiWarriorGuard copy() { + return new KyoshiWarriorGuard(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LacerateFlesh.java b/Mage.Sets/src/mage/cards/l/LacerateFlesh.java index 0d4f8c4eea3..9e060e65cd7 100644 --- a/Mage.Sets/src/mage/cards/l/LacerateFlesh.java +++ b/Mage.Sets/src/mage/cards/l/LacerateFlesh.java @@ -55,14 +55,13 @@ class LacerateFleshEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), 4); - permanent.damage(4, source.getSourceId(), source, game); - if (lethal < 4) { - new BloodToken().putOntoBattlefield(4 - lethal, game, source); + int excess = permanent.damageWithExcess(4, source, game); + if (excess > 0) { + new BloodToken().putOntoBattlefield(excess, game, source); } return true; } diff --git a/Mage.Sets/src/mage/cards/l/LadyOctopusInspiredInventor.java b/Mage.Sets/src/mage/cards/l/LadyOctopusInspiredInventor.java new file mode 100644 index 00000000000..a3389f0457e --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LadyOctopusInspiredInventor.java @@ -0,0 +1,60 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.common.DrawNthOrNthCardTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.cost.CastFromHandForFreeEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.common.FilterArtifactCard; +import mage.filter.predicate.mageobject.ManaValueCompareToCountersSourceCountPredicate; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class LadyOctopusInspiredInventor extends CardImpl { + + private static final FilterCard filter = new FilterArtifactCard("an artifact spell from your hand with mana value less than or " + + "equal to the number of ingenuity counters on {this}"); + + static { + filter.add(new ManaValueCompareToCountersSourceCountPredicate(CounterType.INGENUITY, ComparisonType.OR_LESS)); + } + + public LadyOctopusInspiredInventor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCIENTIST); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(0); + this.toughness = new MageInt(2); + + // Whenever you draw your first or second card each turn, put an ingenuity counter on Lady Octopus. + this.addAbility(new DrawNthOrNthCardTriggeredAbility(new AddCountersSourceEffect(CounterType.INGENUITY.createInstance()))); + + // {T}: You may cast an artifact spell from your hand with mana value less than or equal to the number of ingenuity counters on Lady Octopus without paying its mana cost. + this.addAbility(new SimpleActivatedAbility(new CastFromHandForFreeEffect(filter), new TapSourceCost())); + } + + private LadyOctopusInspiredInventor(final LadyOctopusInspiredInventor card) { + super(card); + } + + @Override + public LadyOctopusInspiredInventor copy() { + return new LadyOctopusInspiredInventor(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/l/LagrellaTheMagpie.java b/Mage.Sets/src/mage/cards/l/LagrellaTheMagpie.java index 12fae5b95ff..50cec499a1a 100644 --- a/Mage.Sets/src/mage/cards/l/LagrellaTheMagpie.java +++ b/Mage.Sets/src/mage/cards/l/LagrellaTheMagpie.java @@ -130,8 +130,8 @@ class LagrellaTheMagpieTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/l/LambholtPacifist.java b/Mage.Sets/src/mage/cards/l/LambholtPacifist.java index baf535a3d1d..36969373dc5 100644 --- a/Mage.Sets/src/mage/cards/l/LambholtPacifist.java +++ b/Mage.Sets/src/mage/cards/l/LambholtPacifist.java @@ -1,18 +1,20 @@ package mage.cards.l; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.WerewolfFrontTriggeredAbility; -import mage.abilities.effects.RestrictionEffect; +import mage.abilities.condition.Condition; +import mage.abilities.condition.InvertCondition; +import mage.abilities.condition.common.FerociousCondition; +import mage.abilities.decorator.ConditionalRestrictionEffect; +import mage.abilities.effects.common.combat.CantAttackSourceEffect; +import mage.abilities.hint.common.FerociousHint; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.mageobject.PowerPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; import java.util.UUID; @@ -21,6 +23,8 @@ import java.util.UUID; */ public final class LambholtPacifist extends CardImpl { + private static final Condition condition = new InvertCondition(FerociousCondition.instance); + public LambholtPacifist(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); this.subtype.add(SubType.HUMAN); @@ -32,7 +36,10 @@ public final class LambholtPacifist extends CardImpl { this.secondSideCardClazz = mage.cards.l.LambholtButcher.class; // Lambholt Pacifist can't attack unless you control a creature with power 4 or greater. - this.addAbility(new SimpleStaticAbility(new LambholtPacifistEffect())); + this.addAbility(new SimpleStaticAbility(new ConditionalRestrictionEffect( + new CantAttackSourceEffect(Duration.WhileOnBattlefield), condition, + "{this} can't attack unless you control a creature with power 4 or greater" + )).addHint(FerociousHint.instance)); // At the beginning of each upkeep, if no spells were cast last turn, transform Lambholt Pacifist. this.addAbility(new TransformAbility()); @@ -48,39 +55,3 @@ public final class LambholtPacifist extends CardImpl { return new LambholtPacifist(this); } } - -class LambholtPacifistEffect extends RestrictionEffect { - - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("a creature with power 4 or greater"); - - static { - filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); - } - - public LambholtPacifistEffect() { - super(Duration.WhileOnBattlefield); - staticText = "{this} can't attack unless you control a creature with power 4 or greater"; - } - - private LambholtPacifistEffect(final LambholtPacifistEffect effect) { - super(effect); - } - - @Override - public LambholtPacifistEffect copy() { - return new LambholtPacifistEffect(this); - } - - @Override - public boolean canAttack(Game game, boolean canUseChooseDialogs) { - return false; - } - - @Override - public boolean applies(Permanent permanent, Ability source, Game game) { - if (permanent.getId().equals(source.getSourceId())) { - return game.getBattlefield().countAll(filter, source.getControllerId(), game) <= 0; - } // do not apply to other creatures. - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/l/LanternsLift.java b/Mage.Sets/src/mage/cards/l/LanternsLift.java index f3a2a2f87d3..7cb53c9d83e 100644 --- a/Mage.Sets/src/mage/cards/l/LanternsLift.java +++ b/Mage.Sets/src/mage/cards/l/LanternsLift.java @@ -1,12 +1,11 @@ package mage.cards.l; import mage.abilities.Ability; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -33,11 +32,10 @@ public final class LanternsLift extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature gets +1/+1 and has flying. - ability = new SimpleStaticAbility(new BoostEnchantedEffect( + Ability ability = new SimpleStaticAbility(new BoostEnchantedEffect( 1, 1, Duration.WhileOnBattlefield )); ability.addEffect(new GainAbilityAttachedEffect( @@ -46,7 +44,7 @@ public final class LanternsLift extends CardImpl { this.addAbility(ability); // If Lanterns' Lift would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private LanternsLift(final LanternsLift card) { diff --git a/Mage.Sets/src/mage/cards/l/LazavWearerOfFaces.java b/Mage.Sets/src/mage/cards/l/LazavWearerOfFaces.java index b3152bf3bee..33bd4cd10e6 100644 --- a/Mage.Sets/src/mage/cards/l/LazavWearerOfFaces.java +++ b/Mage.Sets/src/mage/cards/l/LazavWearerOfFaces.java @@ -94,7 +94,7 @@ class LazavWearerOfFacesEffect extends OneShotEffect { return false; } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exile = game.getExile().getExileZone(exileId); if (exile == null) { return false; diff --git a/Mage.Sets/src/mage/cards/l/LegionsLanding.java b/Mage.Sets/src/mage/cards/l/LegionsLanding.java index deca2d827c1..7051affd2a3 100644 --- a/Mage.Sets/src/mage/cards/l/LegionsLanding.java +++ b/Mage.Sets/src/mage/cards/l/LegionsLanding.java @@ -1,23 +1,19 @@ package mage.cards.l; -import java.util.UUID; - -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; -import mage.constants.SuperType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; +import mage.constants.SuperType; import mage.game.permanent.token.IxalanVampireToken; +import java.util.UUID; + /** * @author TheElk801 */ @@ -35,7 +31,7 @@ public final class LegionsLanding extends CardImpl { // When you attack with three or more creatures, transform Legion's Landing. this.addAbility(new TransformAbility()); - this.addAbility(new LegionsLandingTriggeredAbility(new TransformSourceEffect())); + this.addAbility(new AttacksWithCreaturesTriggeredAbility(new TransformSourceEffect(), 3).setTriggerPhrase("When you attack with three or more creatures, ")); } private LegionsLanding(final LegionsLanding card) { @@ -47,30 +43,3 @@ public final class LegionsLanding extends CardImpl { return new LegionsLanding(this); } } - -class LegionsLandingTriggeredAbility extends TriggeredAbilityImpl { - - public LegionsLandingTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect, false); - setTriggerPhrase("When you attack with three or more creatures, " ); - } - - private LegionsLandingTriggeredAbility(final LegionsLandingTriggeredAbility ability) { - super(ability); - } - - @Override - public LegionsLandingTriggeredAbility copy() { - return new LegionsLandingTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return game.getCombat().getAttackers().size() >= 3 && game.getCombat().getAttackingPlayerId().equals(getControllerId()); - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/l/LethalProtection.java b/Mage.Sets/src/mage/cards/l/LethalProtection.java new file mode 100644 index 00000000000..5e121d32484 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LethalProtection.java @@ -0,0 +1,40 @@ +package mage.cards.l; + +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LethalProtection extends CardImpl { + + public LethalProtection(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}"); + + // Destroy target creature. Return up to one target creature card from your graveyard to your hand. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new ReturnFromGraveyardToHandTargetEffect().setTargetPointer(new SecondTargetPointer())); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard( + 0, 1, StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD + )); + } + + private LethalProtection(final LethalProtection card) { + super(card); + } + + @Override + public LethalProtection copy() { + return new LethalProtection(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LethalScheme.java b/Mage.Sets/src/mage/cards/l/LethalScheme.java index 03c07511b81..15f1e57ce00 100644 --- a/Mage.Sets/src/mage/cards/l/LethalScheme.java +++ b/Mage.Sets/src/mage/cards/l/LethalScheme.java @@ -69,7 +69,7 @@ class LethalSchemeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { HashSet convokingCreatures = CardUtil.getSourceCostsTag(game, source, - ConvokeAbility.convokingCreaturesKey, new HashSet<>(0)); + ConvokeAbility.convokingCreaturesKey, new HashSet<>()); Set> playerPermanentsPairs = convokingCreatures .stream() diff --git a/Mage.Sets/src/mage/cards/l/LeylineOfSingularity.java b/Mage.Sets/src/mage/cards/l/LeylineOfSingularity.java index e2fd00ab094..c83a5a500e3 100644 --- a/Mage.Sets/src/mage/cards/l/LeylineOfSingularity.java +++ b/Mage.Sets/src/mage/cards/l/LeylineOfSingularity.java @@ -3,7 +3,6 @@ package mage.cards.l; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.Mode; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.keyword.LeylineAbility; @@ -47,6 +46,7 @@ class SetSupertypeAllEffect extends ContinuousEffectImpl { public SetSupertypeAllEffect() { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment); this.staticText = "All nonland permanents are legendary"; + this.dependendToTypes.add(DependencyType.BecomeNonbasicLand); } private SetSupertypeAllEffect(final SetSupertypeAllEffect effect) { diff --git a/Mage.Sets/src/mage/cards/l/LeylineOfTheGuildpact.java b/Mage.Sets/src/mage/cards/l/LeylineOfTheGuildpact.java index df4416bb366..a3de4e672b2 100644 --- a/Mage.Sets/src/mage/cards/l/LeylineOfTheGuildpact.java +++ b/Mage.Sets/src/mage/cards/l/LeylineOfTheGuildpact.java @@ -50,6 +50,7 @@ class LeylineOfTheGuildpactEffect extends ContinuousEffectImpl { LeylineOfTheGuildpactEffect() { super(Duration.WhileOnBattlefield, Layer.ColorChangingEffects_5, SubLayer.NA, Outcome.Benefit); staticText = "each nonland permanent you control is all colors"; + dependendToTypes.add(DependencyType.BecomeNonbasicLand); } private LeylineOfTheGuildpactEffect(final LeylineOfTheGuildpactEffect effect) { diff --git a/Mage.Sets/src/mage/cards/l/LidlessGaze.java b/Mage.Sets/src/mage/cards/l/LidlessGaze.java index 2f12678b403..2ecb73f2623 100644 --- a/Mage.Sets/src/mage/cards/l/LidlessGaze.java +++ b/Mage.Sets/src/mage/cards/l/LidlessGaze.java @@ -65,7 +65,7 @@ class LidlessGazeEffect extends OneShotEffect { return false; } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); diff --git a/Mage.Sets/src/mage/cards/l/LifeAndLimb.java b/Mage.Sets/src/mage/cards/l/LifeAndLimb.java index 28847dcff26..a6f00bb8bd0 100644 --- a/Mage.Sets/src/mage/cards/l/LifeAndLimb.java +++ b/Mage.Sets/src/mage/cards/l/LifeAndLimb.java @@ -50,7 +50,12 @@ class LifeAndLimbEffect extends ContinuousEffectImpl { super(Duration.WhileOnBattlefield, Outcome.Neutral); staticText = "All Forests and all Saprolings are 1/1 green Saproling creatures and Forest lands in addition to their other types"; + this.dependendToTypes.add(DependencyType.BecomeNonbasicLand); this.dependendToTypes.add(DependencyType.BecomeForest); + this.dependendToTypes.add(DependencyType.BecomeIsland); + this.dependendToTypes.add(DependencyType.BecomeMountain); + this.dependendToTypes.add(DependencyType.BecomePlains); + this.dependendToTypes.add(DependencyType.BecomeSwamp); this.dependendToTypes.add(DependencyType.BecomeCreature); } diff --git a/Mage.Sets/src/mage/cards/l/LifestreamsBlessing.java b/Mage.Sets/src/mage/cards/l/LifestreamsBlessing.java index 450b5f036bf..08e83d92d03 100644 --- a/Mage.Sets/src/mage/cards/l/LifestreamsBlessing.java +++ b/Mage.Sets/src/mage/cards/l/LifestreamsBlessing.java @@ -135,7 +135,7 @@ class LifestreamsBlessingWatcher extends Watcher { .getWatcher(LifestreamsBlessingWatcher.class) .map .getOrDefault(new MageObjectReference( - source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game + source.getSourceId(), source.getStackMomentSourceZCC(), game ), 0); } } diff --git a/Mage.Sets/src/mage/cards/l/LilianaDefiantNecromancer.java b/Mage.Sets/src/mage/cards/l/LilianaDefiantNecromancer.java index cefbd5b0def..a4250a436d0 100644 --- a/Mage.Sets/src/mage/cards/l/LilianaDefiantNecromancer.java +++ b/Mage.Sets/src/mage/cards/l/LilianaDefiantNecromancer.java @@ -23,7 +23,7 @@ import java.util.UUID; */ public final class LilianaDefiantNecromancer extends CardImpl { - protected static final FilterCreatureCard filter = new FilterCreatureCard("nonlegendary creature card with mana value X from your graveyard"); + private static final FilterCreatureCard filter = new FilterCreatureCard("nonlegendary creature card with mana value X from your graveyard"); static { filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); diff --git a/Mage.Sets/src/mage/cards/l/LilianaHereticalHealer.java b/Mage.Sets/src/mage/cards/l/LilianaHereticalHealer.java index e0c42b03e97..2f3557624a4 100644 --- a/Mage.Sets/src/mage/cards/l/LilianaHereticalHealer.java +++ b/Mage.Sets/src/mage/cards/l/LilianaHereticalHealer.java @@ -1,7 +1,6 @@ package mage.cards.l; import mage.MageInt; -import mage.constants.Pronoun; import mage.abilities.common.DiesCreatureTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.ExileAndReturnSourceEffect; @@ -10,7 +9,8 @@ import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.permanent.TokenPredicate; import mage.game.permanent.token.ZombieToken; @@ -22,10 +22,9 @@ import java.util.UUID; */ public final class LilianaHereticalHealer extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("another nontoken creature you control"); + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another nontoken creature you control"); static { - filter.add(TargetController.YOU.getControllerPredicate()); filter.add(AnotherPredicate.instance); filter.add(TokenPredicate.FALSE); } diff --git a/Mage.Sets/src/mage/cards/l/LionVulture.java b/Mage.Sets/src/mage/cards/l/LionVulture.java new file mode 100644 index 00000000000..adf1b4da970 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LionVulture.java @@ -0,0 +1,51 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.common.OpponentsLostLifeCondition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.OpponentsLostLifeHint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LionVulture extends CardImpl { + + public LionVulture(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.BIRD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // At the beginning of your end step, if an opponent lost life this turn, put a +1/+1 counter on this creature and draw a card. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + ).withInterveningIf(OpponentsLostLifeCondition.instance); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability.addHint(OpponentsLostLifeHint.instance)); + } + + private LionVulture(final LionVulture card) { + super(card); + } + + @Override + public LionVulture copy() { + return new LionVulture(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LivelyDirge.java b/Mage.Sets/src/mage/cards/l/LivelyDirge.java index 39579ba8ca9..a9d8b5ddca2 100644 --- a/Mage.Sets/src/mage/cards/l/LivelyDirge.java +++ b/Mage.Sets/src/mage/cards/l/LivelyDirge.java @@ -103,8 +103,8 @@ class LivelyDirgeTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 4, game); } diff --git a/Mage.Sets/src/mage/cards/l/LivingBrainMechanicalMarvel.java b/Mage.Sets/src/mage/cards/l/LivingBrainMechanicalMarvel.java new file mode 100644 index 00000000000..f1efe82bb29 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LivingBrainMechanicalMarvel.java @@ -0,0 +1,61 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.predicate.Predicates; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class LivingBrainMechanicalMarvel extends CardImpl { + + private static final FilterControlledArtifactPermanent filter = new FilterControlledArtifactPermanent("non-Equipment artifact you control"); + + static { + filter.add(Predicates.not(SubType.EQUIPMENT.getPredicate())); + } + + public LivingBrainMechanicalMarvel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // At the beginning of combat on your turn, target non-Equipment artifact you control becomes an artifact creature with base power and toughness 3/3 until end of turn. Untap it. + CreatureToken token = new CreatureToken(3, 3, "artifact creature with base power and toughness 3/3") + .withType(CardType.ARTIFACT); + Ability ability = new BeginningOfCombatTriggeredAbility( + new BecomesCreatureTargetEffect(token, false, false, Duration.EndOfTurn, false, true, false) + ); + ability.addEffect(new UntapTargetEffect("untap it")); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private LivingBrainMechanicalMarvel(final LivingBrainMechanicalMarvel card) { + super(card); + } + + @Override + public LivingBrainMechanicalMarvel copy() { + return new LivingBrainMechanicalMarvel(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LivingLands.java b/Mage.Sets/src/mage/cards/l/LivingLands.java index fee8a2eadcf..2b371781fd5 100644 --- a/Mage.Sets/src/mage/cards/l/LivingLands.java +++ b/Mage.Sets/src/mage/cards/l/LivingLands.java @@ -26,7 +26,12 @@ public final class LivingLands extends CardImpl { ContinuousEffect effect = new BecomesCreatureAllEffect( new CreatureToken(1, 1, "1/1 creatures"), "lands", filter, Duration.WhileOnBattlefield, false); - effect.getDependencyTypes().add(DependencyType.BecomeForest); // TODO: are these dependencies correct/complete? + effect.getDependedToTypes().add(DependencyType.BecomeNonbasicLand); + effect.addDependedToType(DependencyType.BecomeForest); + effect.addDependedToType(DependencyType.BecomeIsland); + effect.addDependedToType(DependencyType.BecomeMountain); + effect.addDependedToType(DependencyType.BecomePlains); + effect.addDependedToType(DependencyType.BecomeSwamp); this.addAbility(new SimpleStaticAbility(effect)); } diff --git a/Mage.Sets/src/mage/cards/l/LivingPlane.java b/Mage.Sets/src/mage/cards/l/LivingPlane.java index 3ad0b887dcd..a9ca5b7904a 100644 --- a/Mage.Sets/src/mage/cards/l/LivingPlane.java +++ b/Mage.Sets/src/mage/cards/l/LivingPlane.java @@ -2,13 +2,11 @@ package mage.cards.l; import java.util.UUID; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.common.continuous.BecomesCreatureAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterLandPermanent; import mage.game.permanent.token.custom.CreatureToken; @@ -25,9 +23,11 @@ public final class LivingPlane extends CardImpl { this.supertype.add(SuperType.WORLD); // All lands are 1/1 creatures that are still lands. - this.addAbility(new SimpleStaticAbility(new BecomesCreatureAllEffect( + ContinuousEffect effect = new BecomesCreatureAllEffect( new CreatureToken(1, 1, "1/1 creatures"), - "lands", filter, Duration.WhileOnBattlefield, false))); + "lands", filter, Duration.WhileOnBattlefield, false); + effect.getDependedToTypes().add(DependencyType.BecomeNonbasicLand); + this.addAbility(new SimpleStaticAbility(effect)); } private LivingPlane(final LivingPlane card) { diff --git a/Mage.Sets/src/mage/cards/l/LizardConnorssCurse.java b/Mage.Sets/src/mage/cards/l/LizardConnorssCurse.java new file mode 100644 index 00000000000..c67ae20fb01 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LizardConnorssCurse.java @@ -0,0 +1,64 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +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.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class LizardConnorssCurse extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other target creature"); + + static { + filter.add(AnotherPredicate.instance); + } + + public LizardConnorssCurse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Lizard Formula -- When Lizard, Connors's Curse enters, up to one other target creature loses all abilities and becomes a green Lizard creature with base power and toughness 4/4. + CreatureToken token = new CreatureToken(4, 4, "green Lizard creature with base power and toughness 4/4", SubType.LIZARD) + .withColor("G"); + Ability ability = new EntersBattlefieldTriggeredAbility( + new BecomesCreatureTargetEffect(token, true, false, Duration.WhileOnBattlefield) + ); + ability.addTarget(new TargetPermanent(0, 1, filter)); + ability.withFlavorWord("Lizard Formula"); + this.addAbility(ability); + } + + private LizardConnorssCurse(final LizardConnorssCurse card) { + super(card); + } + + @Override + public LizardConnorssCurse copy() { + return new LizardConnorssCurse(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/Lobotomy.java b/Mage.Sets/src/mage/cards/l/Lobotomy.java index 73cb29ae2c2..040971b51f9 100644 --- a/Mage.Sets/src/mage/cards/l/Lobotomy.java +++ b/Mage.Sets/src/mage/cards/l/Lobotomy.java @@ -74,7 +74,7 @@ class LobotomyEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExile TargetCard target = new TargetCard(Zone.HAND, filter); target.withNotTarget(true); Card chosenCard = null; - if (controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { + if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { chosenCard = game.getCard(target.getFirstTarget()); } diff --git a/Mage.Sets/src/mage/cards/l/LodestoneBauble.java b/Mage.Sets/src/mage/cards/l/LodestoneBauble.java index 32f8886f014..129b19bce06 100644 --- a/Mage.Sets/src/mage/cards/l/LodestoneBauble.java +++ b/Mage.Sets/src/mage/cards/l/LodestoneBauble.java @@ -1,8 +1,6 @@ package mage.cards.l; -import java.util.List; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -14,23 +12,21 @@ import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardTargetEffect; -import mage.cards.Card; -import mage.cards.Cards; -import mage.cards.CardImpl; -import mage.cards.CardsImpl; -import mage.cards.CardSetInfo; +import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; import mage.target.common.TargetCardInGraveyard; import mage.target.targetpointer.FixedTarget; +import java.util.List; +import java.util.UUID; + /** - * * @author ThomasLerner, LevelX2 & L_J */ public final class LodestoneBauble extends CardImpl { @@ -61,7 +57,7 @@ public final class LodestoneBauble extends CardImpl { class LodestoneBaubleTarget extends TargetCardInGraveyard { public LodestoneBaubleTarget() { - super(0, 4, new FilterBasicLandCard("basic land cards from a player's graveyard")); + super(0, 4, StaticFilters.FILTER_CARD_BASIC_LANDS); } private LodestoneBaubleTarget(final LodestoneBaubleTarget target) { @@ -87,7 +83,7 @@ class LodestoneBaubleTarget extends TargetCardInGraveyard { } class LodestoneBaubleEffect extends OneShotEffect { - + LodestoneBaubleEffect() { super(Outcome.Detriment); this.staticText = "Put up to four target basic land cards from a player's graveyard on top of their library in any order"; diff --git a/Mage.Sets/src/mage/cards/l/LoneRider.java b/Mage.Sets/src/mage/cards/l/LoneRider.java index 4043af88cec..c6450c5473a 100644 --- a/Mage.Sets/src/mage/cards/l/LoneRider.java +++ b/Mage.Sets/src/mage/cards/l/LoneRider.java @@ -3,9 +3,8 @@ package mage.cards.l; import mage.MageInt; import mage.abilities.condition.Condition; import mage.abilities.condition.common.YouGainedLifeCondition; +import mage.abilities.dynamicvalue.common.ControllerGainedLifeCount; import mage.abilities.effects.common.TransformSourceEffect; -import mage.abilities.hint.ConditionHint; -import mage.abilities.hint.Hint; import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.LifelinkAbility; import mage.abilities.keyword.TransformAbility; @@ -26,7 +25,6 @@ import java.util.UUID; public final class LoneRider extends CardImpl { private static final Condition condition = new YouGainedLifeCondition(ComparisonType.MORE_THAN, 2); - private static final Hint hint = new ConditionHint(condition); public LoneRider(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); @@ -47,7 +45,7 @@ public final class LoneRider extends CardImpl { this.addAbility(new TransformAbility()); this.addAbility(new BeginningOfEndStepTriggeredAbility( TargetController.NEXT, new TransformSourceEffect(), false - ).withInterveningIf(condition).addHint(hint), new PlayerGainedLifeWatcher()); + ).withInterveningIf(condition).addHint(ControllerGainedLifeCount.getHint()), new PlayerGainedLifeWatcher()); } private LoneRider(final LoneRider card) { diff --git a/Mage.Sets/src/mage/cards/l/LongFengGrandSecretariat.java b/Mage.Sets/src/mage/cards/l/LongFengGrandSecretariat.java new file mode 100644 index 00000000000..b3d6d812afb --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LongFengGrandSecretariat.java @@ -0,0 +1,72 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.PutIntoGraveFromBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LongFengGrandSecretariat extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("another creature you control or a land you control"); + + static { + filter.add(LongFengGrandSecretariatPredicate.instance); + } + + public LongFengGrandSecretariat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B/G}{B/G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever another creature you control or a land you control is put into a graveyard from the battlefield, put a +1/+1 counter on target creature you control. + Ability ability = new PutIntoGraveFromBattlefieldAllTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + false, filter, false + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private LongFengGrandSecretariat(final LongFengGrandSecretariat card) { + super(card); + } + + @Override + public LongFengGrandSecretariat copy() { + return new LongFengGrandSecretariat(this); + } +} + +enum LongFengGrandSecretariatPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input.getObject().isLand(game) + || input.getObject().isCreature(game) + && AnotherPredicate.instance.apply(input, game); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LongRest.java b/Mage.Sets/src/mage/cards/l/LongRest.java index 04e84e63d25..68532a86f3b 100644 --- a/Mage.Sets/src/mage/cards/l/LongRest.java +++ b/Mage.Sets/src/mage/cards/l/LongRest.java @@ -21,7 +21,6 @@ import java.util.Set; import java.util.UUID; /** - * * @author weirddan455 */ public final class LongRest extends CardImpl { @@ -67,19 +66,22 @@ class LongRestTarget extends TargetCardInYourGraveyard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - Set manaValues = new HashSet<>(); + + Set usedManaValues = new HashSet<>(); for (UUID targetId : this.getTargets()) { Card card = game.getCard(targetId); if (card != null) { - manaValues.add(card.getManaValue()); + usedManaValues.add(card.getManaValue()); } } + for (UUID possibleTargetId : super.possibleTargets(sourceControllerId, source, game)) { Card card = game.getCard(possibleTargetId); - if (card != null && !manaValues.contains(card.getManaValue())) { + if (card != null && !usedManaValues.contains(card.getManaValue())) { possibleTargets.add(possibleTargetId); } } + return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/l/LostInTheSpiritWorld.java b/Mage.Sets/src/mage/cards/l/LostInTheSpiritWorld.java new file mode 100644 index 00000000000..e486eeb4d6d --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LostInTheSpiritWorld.java @@ -0,0 +1,35 @@ +package mage.cards.l; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.SpiritWorldToken; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LostInTheSpiritWorld extends CardImpl { + + public LostInTheSpiritWorld(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}"); + + // Return up to one target creature to its owner's hand. Create a 1/1 colorless Spirit creature token with "This token can't block or be blocked by non-Spirit creatures." + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new SpiritWorldToken())); + } + + private LostInTheSpiritWorld(final LostInTheSpiritWorld card) { + super(card); + } + + @Override + public LostInTheSpiritWorld copy() { + return new LostInTheSpiritWorld(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LostInThought.java b/Mage.Sets/src/mage/cards/l/LostInThought.java index 703d907f8d2..42e130ef789 100644 --- a/Mage.Sets/src/mage/cards/l/LostInThought.java +++ b/Mage.Sets/src/mage/cards/l/LostInThought.java @@ -173,7 +173,7 @@ class LostInThoughtIgnoreEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - String key = source.getSourceId().toString() + source.getSourceObjectZoneChangeCounter() + LostInThought.keyString + game.getTurnNum() + ((ActivatedAbilityImpl) source).getActivatorId(); + String key = source.getSourceId().toString() + source.getStackMomentSourceZCC() + LostInThought.keyString + game.getTurnNum() + ((ActivatedAbilityImpl) source).getActivatorId(); game.getState().setValue(key, true); return true; } diff --git a/Mage.Sets/src/mage/cards/l/LostVale.java b/Mage.Sets/src/mage/cards/l/LostVale.java index 0c9a1ee88bb..2936d1430ff 100644 --- a/Mage.Sets/src/mage/cards/l/LostVale.java +++ b/Mage.Sets/src/mage/cards/l/LostVale.java @@ -1,17 +1,15 @@ - package mage.cards.l; -import java.util.UUID; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.mana.AddManaOfAnyColorEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class LostVale extends CardImpl { @@ -22,7 +20,7 @@ public final class LostVale extends CardImpl { this.nightCard = true; // T: Add three mana of any one color. - this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new AddManaOfAnyColorEffect(3), new TapSourceCost())); + this.addAbility(new SimpleManaAbility(new AddManaOfAnyColorEffect(3), new TapSourceCost())); } private LostVale(final LostVale card) { diff --git a/Mage.Sets/src/mage/cards/l/LoyalFireSage.java b/Mage.Sets/src/mage/cards/l/LoyalFireSage.java new file mode 100644 index 00000000000..112d79e8089 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LoyalFireSage.java @@ -0,0 +1,45 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FirebendingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.AllyToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LoyalFireSage extends CardImpl { + + public LoyalFireSage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Firebending 1 + this.addAbility(new FirebendingAbility(1)); + + // {5}: Create a 1/1 white Ally creature token. + this.addAbility(new SimpleActivatedAbility(new CreateTokenEffect(new AllyToken()), new GenericManaCost(5))); + } + + private LoyalFireSage(final LoyalFireSage card) { + super(card); + } + + @Override + public LoyalFireSage copy() { + return new LoyalFireSage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LoyalWarhound.java b/Mage.Sets/src/mage/cards/l/LoyalWarhound.java index b4939d4e19e..c9dc6e69b41 100644 --- a/Mage.Sets/src/mage/cards/l/LoyalWarhound.java +++ b/Mage.Sets/src/mage/cards/l/LoyalWarhound.java @@ -10,9 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -22,7 +20,6 @@ import java.util.UUID; */ public final class LoyalWarhound extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard(SubType.PLAINS); private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS); public LoyalWarhound(UUID ownerId, CardSetInfo setInfo) { @@ -38,7 +35,7 @@ public final class LoyalWarhound extends CardImpl { // When Loyal Warhound enters the battlefield, if an opponent controls more lands than you, // search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true) + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true) ).withInterveningIf(condition)); } diff --git a/Mage.Sets/src/mage/cards/l/LudevicsTestSubject.java b/Mage.Sets/src/mage/cards/l/LudevicsTestSubject.java index e14febe228d..4dd297875f1 100644 --- a/Mage.Sets/src/mage/cards/l/LudevicsTestSubject.java +++ b/Mage.Sets/src/mage/cards/l/LudevicsTestSubject.java @@ -1,11 +1,13 @@ - package mage.cards.l; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.RemoveAllCountersSourceEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.DefenderAbility; @@ -13,12 +15,8 @@ import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; import java.util.UUID; @@ -27,6 +25,8 @@ import java.util.UUID; */ public final class LudevicsTestSubject extends CardImpl { + private static final Condition condition = new SourceHasCounterCondition(CounterType.HATCHLING, 5); + public LudevicsTestSubject(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); this.subtype.add(SubType.LIZARD, SubType.EGG); @@ -37,10 +37,16 @@ public final class LudevicsTestSubject extends CardImpl { this.secondSideCardClazz = mage.cards.l.LudevicsAbomination.class; this.addAbility(DefenderAbility.getInstance()); + // {1}{U}: Put a hatchling counter on Ludevic's Test Subject. Then if there are five or more hatchling counters on it, remove all of them and transform it. this.addAbility(new TransformAbility()); - Ability ability = new SimpleActivatedAbility(new AddCountersSourceEffect(CounterType.HATCHLING.createInstance()), new ManaCostsImpl<>("{1}{U}")); - ability.addEffect(new LudevicsTestSubjectEffect()); + Ability ability = new SimpleActivatedAbility( + new AddCountersSourceEffect(CounterType.HATCHLING.createInstance()), new ManaCostsImpl<>("{1}{U}") + ); + ability.addEffect(new ConditionalOneShotEffect( + new RemoveAllCountersSourceEffect(CounterType.HATCHLING), condition, + "Then if there are five or more hatchling counters on it, remove all of them and transform it" + ).addEffect(new TransformSourceEffect())); this.addAbility(ability); } @@ -53,35 +59,3 @@ public final class LudevicsTestSubject extends CardImpl { return new LudevicsTestSubject(this); } } - -class LudevicsTestSubjectEffect extends OneShotEffect { - - LudevicsTestSubjectEffect() { - super(Outcome.Benefit); - staticText = "Then if there are five or more hatchling counters on it, remove all of them and transform it"; - } - - private LudevicsTestSubjectEffect(final LudevicsTestSubjectEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent == null) { - return false; - } - - if (permanent.getCounters(game).getCount(CounterType.HATCHLING) >= 5) { - permanent.removeAllCounters(CounterType.HATCHLING.getName(), source, game); - TransformSourceEffect effect = new TransformSourceEffect(); - return effect.apply(game, source); - } - return false; - } - - @Override - public LudevicsTestSubjectEffect copy() { - return new LudevicsTestSubjectEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/l/LumberingBattlement.java b/Mage.Sets/src/mage/cards/l/LumberingBattlement.java index 05320c9c291..4c27966baef 100644 --- a/Mage.Sets/src/mage/cards/l/LumberingBattlement.java +++ b/Mage.Sets/src/mage/cards/l/LumberingBattlement.java @@ -130,7 +130,7 @@ enum LumberingBattlementValue implements DynamicValue { } ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId( game, sourceAbility.getSourceId(), - sourceAbility.getSourceObjectZoneChangeCounter() + sourceAbility.getStackMomentSourceZCC() )); if (exileZone == null) { exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, sourceAbility)); diff --git a/Mage.Sets/src/mage/cards/l/LuminousPhantom.java b/Mage.Sets/src/mage/cards/l/LuminousPhantom.java index 7710801da8d..123b2665457 100644 --- a/Mage.Sets/src/mage/cards/l/LuminousPhantom.java +++ b/Mage.Sets/src/mage/cards/l/LuminousPhantom.java @@ -2,9 +2,8 @@ package mage.cards.l; import mage.MageInt; import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -36,7 +35,7 @@ public final class LuminousPhantom extends CardImpl { this.addAbility(new LeavesBattlefieldAllTriggeredAbility(new GainLifeEffect(1), StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL)); // If Luminous Phantom would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private LuminousPhantom(final LuminousPhantom card) { diff --git a/Mage.Sets/src/mage/cards/l/LunarchInquisitors.java b/Mage.Sets/src/mage/cards/l/LunarchInquisitors.java index 715cfc59b25..191be943d48 100644 --- a/Mage.Sets/src/mage/cards/l/LunarchInquisitors.java +++ b/Mage.Sets/src/mage/cards/l/LunarchInquisitors.java @@ -8,9 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -20,12 +18,6 @@ import java.util.UUID; */ public final class LunarchInquisitors extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("another target creature"); - - static { - filter.add(AnotherPredicate.instance); - } - public LunarchInquisitors(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); this.subtype.add(SubType.HUMAN); @@ -39,7 +31,7 @@ public final class LunarchInquisitors extends CardImpl { // When this creature transforms into Lunarch Inquisitors, you may exile another target creature until Lunarch Inquisitors leaves the battlefield. Ability ability = new TransformIntoSourceTriggeredAbility(new ExileUntilSourceLeavesEffect(), true); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/m/MadameWebClairvoyant.java b/Mage.Sets/src/mage/cards/m/MadameWebClairvoyant.java new file mode 100644 index 00000000000..13f526d4a12 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MadameWebClairvoyant.java @@ -0,0 +1,63 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class MadameWebClairvoyant extends CardImpl { + + private static final FilterCard filter = new FilterCard("cast Spider spells and noncreature spells"); + + static { + filter.add(Predicates.or( + Predicates.not(CardType.CREATURE.getPredicate()), + SubType.SPIDER.getPredicate() + )); + } + + public MadameWebClairvoyant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.MUTANT); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // You may cast Spider spells and noncreature spells from the top of your library. + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); + + // Whenever you attack, you may mill a card. + this.addAbility(new AttacksWithCreaturesTriggeredAbility( + Zone.BATTLEFIELD, new MillCardsControllerEffect(1), + 1, StaticFilters.FILTER_PERMANENT_CREATURES, false, true) + ); + } + + private MadameWebClairvoyant(final MadameWebClairvoyant card) { + super(card); + } + + @Override + public MadameWebClairvoyant copy() { + return new MadameWebClairvoyant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheMoon.java b/Mage.Sets/src/mage/cards/m/MagusOfTheMoon.java index 4d1beec9ae5..e07c277a4e8 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheMoon.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheMoon.java @@ -54,6 +54,7 @@ class MagusOfTheMoonEffect extends ContinuousEffectImpl { MagusOfTheMoonEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); this.staticText = "Nonbasic lands are Mountains"; + dependendToTypes.add(DependencyType.BecomeNonbasicLand); dependencyTypes.add(DependencyType.BecomeMountain); } diff --git a/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java b/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java index d01b21e9f6f..dca9f88ccd6 100644 --- a/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java +++ b/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java @@ -4,7 +4,7 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; @@ -15,8 +15,10 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterCard; +import mage.filter.FilterPermanent; import mage.filter.common.FilterPermanentCard; import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.AttachedToAttachedPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -34,14 +36,22 @@ import java.util.stream.Collectors; */ public final class MantleOfTheAncients extends CardImpl { private static final FilterCard filter = new FilterPermanentCard(); + private static final FilterPermanent filter2 = new FilterPermanent("Aura and Equipment attached to it"); static { filter.add(Predicates.or( SubType.AURA.getPredicate(), SubType.EQUIPMENT.getPredicate() )); + filter2.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate() + )); + filter2.add(AttachedToAttachedPredicate.instance); } + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter2); + public MantleOfTheAncients(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{W}"); @@ -59,9 +69,7 @@ public final class MantleOfTheAncients extends CardImpl { this.addAbility(ability); // Enchanted creature gets +1/+1 for each Aura and Equipment attached to it. - this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect( - MantleOfTheAncientsValue.instance, MantleOfTheAncientsValue.instance - ))); + this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(xValue, xValue))); } private MantleOfTheAncients(final MantleOfTheAncients card) { @@ -116,39 +124,3 @@ class MantleOfTheAncientsEffect extends OneShotEffect { return true; } } - -enum MantleOfTheAncientsValue implements DynamicValue { - instance; - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - Permanent sourcePermanent = sourceAbility.getSourcePermanentOrLKI(game); - if (sourcePermanent == null) { - return 0; - } - Permanent permanent = game.getPermanent(sourcePermanent.getAttachedTo()); - return permanent == null ? 0 : permanent - .getAttachments() - .stream() - .map(game::getPermanentOrLKIBattlefield) - .filter(Objects::nonNull) - .map(p -> p.hasSubtype(SubType.EQUIPMENT, game) || p.hasSubtype(SubType.AURA, game)) - .mapToInt(b -> b ? 1 : 0) - .sum(); - } - - @Override - public MantleOfTheAncientsValue copy() { - return instance; - } - - @Override - public String getMessage() { - return "Aura and Equipment attached to it"; - } - - @Override - public String toString() { - return "1"; - } -} diff --git a/Mage.Sets/src/mage/cards/m/MarchFromTheTomb.java b/Mage.Sets/src/mage/cards/m/MarchFromTheTomb.java index 47f751c7aae..73e256f652f 100644 --- a/Mage.Sets/src/mage/cards/m/MarchFromTheTomb.java +++ b/Mage.Sets/src/mage/cards/m/MarchFromTheTomb.java @@ -56,8 +56,8 @@ class MarchFromTheTombTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 8, game); } diff --git a/Mage.Sets/src/mage/cards/m/MaryJaneWatson.java b/Mage.Sets/src/mage/cards/m/MaryJaneWatson.java new file mode 100644 index 00000000000..ed03e55cd2d --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MaryJaneWatson.java @@ -0,0 +1,46 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MaryJaneWatson extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SPIDER); + + public MaryJaneWatson(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G/W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PERFORMER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever a Spider you control enters, draw a card. This ability triggers only once each turn. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new DrawCardSourceControllerEffect(1), filter + ).setTriggersLimitEachTurn(1)); + } + + private MaryJaneWatson(final MaryJaneWatson card) { + super(card); + } + + @Override + public MaryJaneWatson copy() { + return new MaryJaneWatson(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MasterPakku.java b/Mage.Sets/src/mage/cards/m/MasterPakku.java new file mode 100644 index 00000000000..2ee107fed73 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MasterPakku.java @@ -0,0 +1,57 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.ProwessAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MasterPakku extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(new FilterCard(SubType.LESSON, "Lesson cards"), null); + private static final Hint hint = new ValueHint("Lesson cards in your graveyard", xValue); + + public MasterPakku(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.ADVISOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Prowess + this.addAbility(new ProwessAbility()); + + // Whenever Master Pakku becomes tapped, target player mills X cards, where X is the number of Lesson cards in your graveyard. + Ability ability = new BecomesTappedSourceTriggeredAbility(new MillCardsTargetEffect(xValue)); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability.addHint(hint)); + } + + private MasterPakku(final MasterPakku card) { + super(card); + } + + @Override + public MasterPakku copy() { + return new MasterPakku(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MasterPiandao.java b/Mage.Sets/src/mage/cards/m/MasterPiandao.java new file mode 100644 index 00000000000..3c9174dd44f --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MasterPiandao.java @@ -0,0 +1,60 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.FirstStrikeAbility; +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.FilterCard; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MasterPiandao extends CardImpl { + + private static final FilterCard filter = new FilterCard("Ally, Equipment, or Lesson card"); + + static { + filter.add(Predicates.or( + SubType.ALLY.getPredicate(), + SubType.EQUIPMENT.getPredicate(), + SubType.LESSON.getPredicate() + )); + } + + public MasterPiandao(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Whenever Master Piandao attacks, look at the top four cards of your library. You may reveal an Ally, Equipment, or Lesson card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. + this.addAbility(new AttacksTriggeredAbility(new LookLibraryAndPickControllerEffect( + 4, 1, filter, PutCards.HAND, PutCards.BOTTOM_RANDOM + ))); + } + + private MasterPiandao(final MasterPiandao card) { + super(card); + } + + @Override + public MasterPiandao copy() { + return new MasterPiandao(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MatchTheOdds.java b/Mage.Sets/src/mage/cards/m/MatchTheOdds.java new file mode 100644 index 00000000000..259b9e62c1a --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MatchTheOdds.java @@ -0,0 +1,86 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.AllyToken; +import mage.game.permanent.token.Token; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MatchTheOdds extends CardImpl { + + private static final Hint hint = new ValueHint( + "Creatures your opponents control", + new PermanentsOnBattlefieldCount(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE) + ); + + public MatchTheOdds(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); + + this.subtype.add(SubType.LESSON); + + // Create a 1/1 white Ally creature token. Put a +1/+1 counter on it for each creature your opponents control. + this.getSpellAbility().addEffect(new MatchTheOddsEffect()); + this.getSpellAbility().addHint(hint); + } + + private MatchTheOdds(final MatchTheOdds card) { + super(card); + } + + @Override + public MatchTheOdds copy() { + return new MatchTheOdds(this); + } +} + +class MatchTheOddsEffect extends OneShotEffect { + + MatchTheOddsEffect() { + super(Outcome.Benefit); + staticText = "create a 1/1 white Ally creature token. " + + "Put a +1/+1 counter on it for each creature your opponents control"; + } + + private MatchTheOddsEffect(final MatchTheOddsEffect effect) { + super(effect); + } + + @Override + public MatchTheOddsEffect copy() { + return new MatchTheOddsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new AllyToken(); + token.putOntoBattlefield(1, game, source); + int count = game + .getBattlefield() + .count(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, source.getControllerId(), source, game); + if (count < 1) { + return true; + } + for (UUID tokenId : token.getLastAddedTokenIds()) { + Optional.ofNullable(tokenId) + .map(game::getPermanent) + .ifPresent(permanent -> permanent.addCounters(CounterType.P1P1.createInstance(count), source, game)); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MaximumCarnage.java b/Mage.Sets/src/mage/cards/m/MaximumCarnage.java new file mode 100644 index 00000000000..ead24418699 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MaximumCarnage.java @@ -0,0 +1,90 @@ +package mage.cards.m; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.RestrictionEffect; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.combat.AttacksIfAbleAllEffect; +import mage.abilities.effects.mana.AddManaToManaPoolSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class MaximumCarnage extends CardImpl { + + public MaximumCarnage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{R}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters step and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Until your next turn, each creature attacks each combat if able and attacks a player other than you if able. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, new AttacksIfAbleAllEffect( + new FilterOpponentsCreaturePermanent("each creature an opponent controls"), Duration.UntilYourNextTurn + ), new MaximumCarnageEffect()); + + // II -- Add {R}{R}{R}. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new AddManaToManaPoolSourceControllerEffect(new Mana(ManaType.RED, 3))); + + // III -- This Saga deals 5 damage to each opponent. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new DamagePlayersEffect(5, TargetController.OPPONENT)); + } + + private MaximumCarnage(final MaximumCarnage card) { + super(card); + } + + @Override + public MaximumCarnage copy() { + return new MaximumCarnage(this); + } +} +class MaximumCarnageEffect extends RestrictionEffect { + + MaximumCarnageEffect() { + super(Duration.UntilYourNextTurn); + staticText = "and attacks a player other than you if able"; + } + + private MaximumCarnageEffect(final MaximumCarnageEffect effect) { + super(effect); + } + + @Override + public MaximumCarnageEffect copy() { + return new MaximumCarnageEffect(this); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return game.getOpponents(permanent.getControllerId()).contains(source.getControllerId()); + } + + @Override + public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) { + if (defenderId == null + || game.getState().getPlayersInRange(attacker.getControllerId(), game).size() == 2) { // just 2 players left, so it may attack you + return true; + } + // A planeswalker controlled by the controller is the defender + if (game.getPermanent(defenderId) != null) { + return !game.getPermanent(defenderId).getControllerId().equals(source.getControllerId()); + } + // The controller is the defender + return !defenderId.equals(source.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MechanicalGlider.java b/Mage.Sets/src/mage/cards/m/MechanicalGlider.java new file mode 100644 index 00000000000..a827a9ab0a8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MechanicalGlider.java @@ -0,0 +1,46 @@ +package mage.cards.m; + +import mage.abilities.common.EntersBattlefieldAttachToTarget; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.FlyingAbility; +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 MechanicalGlider extends CardImpl { + + public MechanicalGlider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When this Equipment enters, attach it to target creature you control. + this.addAbility(new EntersBattlefieldAttachToTarget()); + + // Equipped creature has flying. + this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect( + FlyingAbility.getInstance(), AttachmentType.EQUIPMENT + ))); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private MechanicalGlider(final MechanicalGlider card) { + super(card); + } + + @Override + public MechanicalGlider copy() { + return new MechanicalGlider(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MechanicalMobster.java b/Mage.Sets/src/mage/cards/m/MechanicalMobster.java new file mode 100644 index 00000000000..df0f73c0ef2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MechanicalMobster.java @@ -0,0 +1,48 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.keyword.ConniveTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MechanicalMobster extends CardImpl { + + public MechanicalMobster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // When this creature enters, exile up to one target card from a graveyard. Target creature you control connives. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect()); + ability.addTarget(new TargetCardInGraveyard(0, 1)); + ability.addEffect(new ConniveTargetEffect().setTargetPointer(new SecondTargetPointer())); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private MechanicalMobster(final MechanicalMobster card) { + super(card); + } + + @Override + public MechanicalMobster copy() { + return new MechanicalMobster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MegatronDestructiveForce.java b/Mage.Sets/src/mage/cards/m/MegatronDestructiveForce.java index 2badfb10257..7b718d6efbd 100644 --- a/Mage.Sets/src/mage/cards/m/MegatronDestructiveForce.java +++ b/Mage.Sets/src/mage/cards/m/MegatronDestructiveForce.java @@ -79,7 +79,7 @@ class MegatronDestructiveForceEffect extends OneShotEffect { return false; } TargetSacrifice target = new TargetSacrifice( - 0, 1, StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT + 0, 1, StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT ); player.choose(outcome, target, source, game); Permanent permanent = game.getPermanent(target.getFirstTarget()); @@ -93,7 +93,7 @@ class MegatronDestructiveForceEffect extends OneShotEffect { } ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new MegatronDestructiveForceReflexiveEffect(manaValue), false + new MegatronDestructiveForceReflexiveEffect(manaValue), false ); ability.addHint(new StaticHint("Sacrificed artifact mana value: " + manaValue)); ability.addTarget(new TargetCreaturePermanent()); @@ -109,8 +109,8 @@ class MegatronDestructiveForceReflexiveEffect extends OneShotEffect { MegatronDestructiveForceReflexiveEffect(int value) { super(Outcome.Damage); staticText = "{this} deals damage equal to the sacrificed artifact's mana value to target " + - "creature. If excess damage would be dealt to that creature this way, instead that damage " + - "is dealt to that creature's controller and you convert {this}."; + "creature. If excess damage would be dealt to that creature this way, instead that damage " + + "is dealt to that creature's controller and you convert {this}."; this.value = value; } @@ -126,36 +126,22 @@ class MegatronDestructiveForceReflexiveEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent sourcePermanent = source.getSourcePermanentOrLKI(game); - if (sourcePermanent == null) { - return false; - } - if (value < 1) { return false; } - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } - - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - int excess = value - lethal; - if (excess <= 0) { - // no excess damage. - permanent.damage(value, source.getSourceId(), source, game); + int excess = permanent.damageWithExcess(value, source, game); + if (excess < 1) { return true; } - - // excess damage. dealing excess to controller's instead. And convert Megatron. - permanent.damage(lethal, source.getSourceId(), source, game); Player player = game.getPlayer(permanent.getControllerId()); if (player != null) { player.damage(excess, source, game); } new TransformSourceEffect().apply(game, source); - return true; } } diff --git a/Mage.Sets/src/mage/cards/m/Melting.java b/Mage.Sets/src/mage/cards/m/Melting.java index d72ded8a51b..81371767199 100644 --- a/Mage.Sets/src/mage/cards/m/Melting.java +++ b/Mage.Sets/src/mage/cards/m/Melting.java @@ -39,6 +39,7 @@ class MeltingEffect extends ContinuousEffectImpl { MeltingEffect() { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment); this.staticText = "All lands are no longer snow"; + this.dependendToTypes.add(DependencyType.BecomeNonbasicLand); } private MeltingEffect(final MeltingEffect effect) { diff --git a/Mage.Sets/src/mage/cards/m/MerchantOfManyHats.java b/Mage.Sets/src/mage/cards/m/MerchantOfManyHats.java new file mode 100644 index 00000000000..7e16cb253af --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MerchantOfManyHats.java @@ -0,0 +1,43 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MerchantOfManyHats extends CardImpl { + + public MerchantOfManyHats(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {2}{B}: Return this card from your graveyard to your hand. + this.addAbility(new SimpleActivatedAbility( + Zone.GRAVEYARD, new ReturnSourceFromGraveyardToHandEffect(), new ManaCostsImpl<>("{2}{B}") + )); + } + + private MerchantOfManyHats(final MerchantOfManyHats card) { + super(card); + } + + @Override + public MerchantOfManyHats copy() { + return new MerchantOfManyHats(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MeriekeRiBerit.java b/Mage.Sets/src/mage/cards/m/MeriekeRiBerit.java index 346391f88f9..4c963312879 100644 --- a/Mage.Sets/src/mage/cards/m/MeriekeRiBerit.java +++ b/Mage.Sets/src/mage/cards/m/MeriekeRiBerit.java @@ -104,7 +104,7 @@ class MeriekeRiBeritDelayedTriggeredAbility extends DelayedTriggeredAbility { @Override public boolean isInactive(Game game) { return getSourceObjectIfItStillExists(game) == null - && game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, getSourceObjectZoneChangeCounter()) == null; + && game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, getStackMomentSourceZCC()) == null; } @Override diff --git a/Mage.Sets/src/mage/cards/m/MesmericFiend.java b/Mage.Sets/src/mage/cards/m/MesmericFiend.java index d9c6b7ce5c8..127d16d0920 100644 --- a/Mage.Sets/src/mage/cards/m/MesmericFiend.java +++ b/Mage.Sets/src/mage/cards/m/MesmericFiend.java @@ -85,8 +85,8 @@ class MesmericFiendExileEffect extends OneShotEffect { if (controller.choose(Outcome.Exile, opponent.getHand(), target, source, game)) { Card card = opponent.getHand().get(target.getFirstTarget(), game); if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - game.getState().setValue(source.getSourceId().toString() + source.getSourceObjectZoneChangeCounter(), exileId); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); + game.getState().setValue(source.getSourceId().toString() + source.getStackMomentSourceZCC(), exileId); controller.moveCardsToExile(card, source, game, true, exileId, sourcePermanent.getName()); } } @@ -118,7 +118,7 @@ class MesmericFiendLeaveEffect extends OneShotEffect { MageObject sourceObject = source.getSourceObject(game); if (controller != null && sourceObject != null) { - int zoneChangeMinusOne = source.getSourceObjectZoneChangeCounter() - 1; + int zoneChangeMinusOne = source.getStackMomentSourceZCC() - 1; UUID exileId = (UUID) game.getState().getValue(source.getSourceId().toString() + zoneChangeMinusOne); if (exileId != null) { Cards cards = game.getExile().getExileZone(exileId); diff --git a/Mage.Sets/src/mage/cards/m/MetzaliTowerOfTriumph.java b/Mage.Sets/src/mage/cards/m/MetzaliTowerOfTriumph.java index 0581ccc1d20..8ff50743a8a 100644 --- a/Mage.Sets/src/mage/cards/m/MetzaliTowerOfTriumph.java +++ b/Mage.Sets/src/mage/cards/m/MetzaliTowerOfTriumph.java @@ -1,10 +1,5 @@ package mage.cards.m; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -18,15 +13,16 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SuperType; import mage.constants.TargetController; -import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.util.RandomUtil; -import mage.watchers.Watcher; import mage.watchers.common.AttackedThisTurnWatcher; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + /** - * * @author LevelX2 */ public final class MetzaliTowerOfTriumph extends CardImpl { @@ -51,7 +47,6 @@ public final class MetzaliTowerOfTriumph extends CardImpl { ability = new SimpleActivatedAbility(new MetzaliTowerOfTriumphEffect(), new ManaCostsImpl<>("{2}{W}")); ability.addCost(new TapSourceCost()); this.addAbility(ability); - } private MetzaliTowerOfTriumph(final MetzaliTowerOfTriumph card) { @@ -69,7 +64,7 @@ class MetzaliTowerOfTriumphEffect extends OneShotEffect { MetzaliTowerOfTriumphEffect() { super(Outcome.DestroyPermanent); - this.staticText = "Choose a creature at random that attacked this turn. Destroy that creature"; + this.staticText = "choose a creature at random that attacked this turn. Destroy that creature"; } private MetzaliTowerOfTriumphEffect(final MetzaliTowerOfTriumphEffect effect) { @@ -83,24 +78,15 @@ class MetzaliTowerOfTriumphEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Watcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class); - if (watcher instanceof AttackedThisTurnWatcher) { - Set attackedThisTurn = ((AttackedThisTurnWatcher) watcher).getAttackedThisTurnCreatures(); - List available = new ArrayList<>(); - for (MageObjectReference mor : attackedThisTurn) { - Permanent permanent = mor.getPermanent(game); - if (permanent != null && permanent.isCreature(game)) { - available.add(permanent); - } - } - if (!available.isEmpty()) { - Permanent permanent = available.get(RandomUtil.nextInt(available.size())); - if (permanent != null) { - permanent.destroy(source, game, false); - } - } - return true; - } - return false; + Permanent permanent = RandomUtil.randomFromCollection( + game.getState() + .getWatcher(AttackedThisTurnWatcher.class) + .getAttackedThisTurnCreatures() + .stream() + .map(mor -> mor.getPermanent(game)) + .filter(Objects::nonNull) + .collect(Collectors.toSet()) + ); + return permanent != null && permanent.destroy(source, game); } } diff --git a/Mage.Sets/src/mage/cards/m/MigrationPath.java b/Mage.Sets/src/mage/cards/m/MigrationPath.java index 88c4442a249..74d1de932d8 100644 --- a/Mage.Sets/src/mage/cards/m/MigrationPath.java +++ b/Mage.Sets/src/mage/cards/m/MigrationPath.java @@ -6,8 +6,7 @@ import mage.abilities.keyword.CyclingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -17,14 +16,12 @@ import java.util.UUID; */ public final class MigrationPath extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("basic land cards"); - public MigrationPath(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); // Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle your library. this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect( - new TargetCardInLibrary(0, 2, filter), true + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS), true )); // Cycling {2} diff --git a/Mage.Sets/src/mage/cards/m/MilesMorales.java b/Mage.Sets/src/mage/cards/m/MilesMorales.java new file mode 100644 index 00000000000..6069cffefeb --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MilesMorales.java @@ -0,0 +1,103 @@ +package mage.cards.m; + +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.BecomesColorSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.common.counter.DoubleCounterOnEachPermanentEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardSetInfo; +import mage.cards.ModalDoubleFacedCard; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MilesMorales extends ModalDoubleFacedCard { + + private static final FilterPermanent filter = new FilterControlledPermanent("Spider and legendary creature you control"); + + static { + filter.add(Predicates.or( + SubType.SPIDER.getPredicate(), + Predicates.and( + SuperType.LEGENDARY.getPredicate(), + CardType.CREATURE.getPredicate() + ))); + } + + public MilesMorales(UUID ownerId, CardSetInfo setInfo) { + super( + ownerId, setInfo, + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.CITIZEN, SubType.HERO}, "{1}{G}", + "Ultimate Spider-Man", + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.SPIDER, SubType.HUMAN, SubType.HERO}, "{3}{R}{G}{W}" + ); + this.getLeftHalfCard().setPT(1, 2); + this.getRightHalfCard().setPT(4, 3); + + // When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetCreaturePermanent(0, 2)); + this.addAbility(ability); + + // {3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery. + this.getLeftHalfCard().addAbility(new ActivateAsSorceryActivatedAbility( + new TransformSourceEffect(), new ManaCostsImpl<>("{3}{R}{G}{W}") + )); + + // Ultimate Spider-Man + // First strike + this.getRightHalfCard().addAbility(FirstStrikeAbility.getInstance()); + + // Haste + this.getRightHalfCard().addAbility(HasteAbility.getInstance()); + + // Camouflage -- {2}: Put a +1/+1 counter on Ultimate Spider-Man. He gains hexproof and becomes colorless until end of turn. + ability = new SimpleActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new GenericManaCost(2) + ); + ability.addEffect(new GainAbilitySourceEffect( + HexproofAbility.getInstance(), Duration.EndOfTurn + ).setText("He gains hexproof")); + ability.addEffect(new BecomesColorSourceEffect( + ObjectColor.COLORLESS, Duration.EndOfTurn + ).setText("and becomes colorless until end of turn")); + this.getRightHalfCard().addAbility(ability.withFlavorWord("Camouflage")); + + // Whenever you attack, double the number of each kind of counter on each Spider and legendary creature you control. + this.getRightHalfCard().addAbility(new AttacksWithCreaturesTriggeredAbility( + new DoubleCounterOnEachPermanentEffect(null, filter), 1 + )); + } + + private MilesMorales(final MilesMorales card) { + super(card); + } + + @Override + public MilesMorales copy() { + return new MilesMorales(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MirrorOfLifeTrapping.java b/Mage.Sets/src/mage/cards/m/MirrorOfLifeTrapping.java index d4e15becb54..7a94684c154 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorOfLifeTrapping.java +++ b/Mage.Sets/src/mage/cards/m/MirrorOfLifeTrapping.java @@ -88,7 +88,7 @@ class MirrorOfLifeTrappingEffect extends OneShotEffect { return false; } - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exileZone = game.getExile().getExileZone(exileZoneId); Cards toBattlefield = null; diff --git a/Mage.Sets/src/mage/cards/m/MisterNegative.java b/Mage.Sets/src/mage/cards/m/MisterNegative.java new file mode 100644 index 00000000000..47f035eccda --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MisterNegative.java @@ -0,0 +1,89 @@ +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.LifelinkAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author Jmlundeen + */ +public final class MisterNegative extends CardImpl { + + public MisterNegative(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Darkforce Inversion — When Mister Negative enters, you may exchange your life total with target opponent. If you lose life this way, draw that many cards. + Ability ability = new EntersBattlefieldTriggeredAbility(new MisterNegativeEffect(), true); + ability.addTarget(new TargetOpponent()); + ability.withFlavorWord("Darkforce Inversion"); + this.addAbility(ability); + } + + private MisterNegative(final MisterNegative card) { + super(card); + } + + @Override + public MisterNegative copy() { + return new MisterNegative(this); + } +} + +class MisterNegativeEffect extends OneShotEffect { + + MisterNegativeEffect() { + super(Outcome.Neutral); + staticText = "When {this} enters, you may exchange your life total with target opponent. If you lose life this way, draw that many cards."; + } + + protected MisterNegativeEffect(final MisterNegativeEffect effect) { + super(effect); + } + + @Override + public MisterNegativeEffect copy() { + return new MisterNegativeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player player = game.getPlayer(source.getFirstTarget()); + if (controller == null || player == null) { + return false; + } + int startingLife = controller.getLife(); + controller.exchangeLife(player, source, game); + int lifeChange = startingLife - controller.getLife(); + if (lifeChange > 0) { + controller.drawCards(lifeChange, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MistriseVillage.java b/Mage.Sets/src/mage/cards/m/MistriseVillage.java index ea2132cfd54..3095a898891 100644 --- a/Mage.Sets/src/mage/cards/m/MistriseVillage.java +++ b/Mage.Sets/src/mage/cards/m/MistriseVillage.java @@ -1,8 +1,5 @@ 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; @@ -10,23 +7,22 @@ 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.ContinuousEffectImpl; 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.constants.*; 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; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.List; +import java.util.UUID; /** * @@ -44,7 +40,7 @@ public final class MistriseVillage extends CardImpl { 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())); @@ -69,15 +65,18 @@ public final class MistriseVillage extends CardImpl { } } -class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl { +class MistriseCantBeCounteredEffect extends ContinuousEffectImpl { + + private int spellsCastThisTurn; public MistriseCantBeCounteredEffect() { - super(Duration.OneUse, Outcome.Benefit, false, true); + super(Duration.EndOfTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); staticText = "the next spell you cast this turn can't be countered"; } protected MistriseCantBeCounteredEffect(final MistriseCantBeCounteredEffect effect) { super(effect); + this.spellsCastThisTurn = effect.spellsCastThisTurn; } @Override @@ -86,27 +85,38 @@ class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl { } @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(); + public void init(Ability source, Game game) { + super.init(source, game); + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + if (watcher != null) { + spellsCastThisTurn = watcher.getSpellsCastThisTurn(source.getControllerId()).size(); } - 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) { + public boolean apply(Game game, Ability source) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + if (watcher == null) { + return false; + } + if (game.getStack().isEmpty() && watcher.getSpellsCastThisTurn(source.getControllerId()).size() >= spellsCastThisTurn + 1) { discard(); + return false; } - return res; + for (StackObject stackObject : game.getStack()) { + if (!(stackObject instanceof Spell) || !stackObject.isControlledBy(source.getControllerId())) { + continue; + } + Spell spell = (Spell) stackObject; + + List spellsCast = watcher.getSpellsCastThisTurn(source.getControllerId()); + for (int i = 0; i < spellsCast.size(); i++) { + if (i == spellsCastThisTurn && spellsCast.get(i).getId().equals(spell.getId())) { + game.getState().addOtherAbility(spell.getCard(), new CantBeCounteredSourceAbility()); + return true; + } + } + } + return false; } } diff --git a/Mage.Sets/src/mage/cards/m/MobLookout.java b/Mage.Sets/src/mage/cards/m/MobLookout.java new file mode 100644 index 00000000000..7c36df269c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MobLookout.java @@ -0,0 +1,43 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.keyword.ConniveTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MobLookout extends CardImpl { + + public MobLookout(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U/B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // When this creature enters, target creature you control connives. + Ability ability = new EntersBattlefieldTriggeredAbility(new ConniveTargetEffect()); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private MobLookout(final MobLookout card) { + super(card); + } + + @Override + public MobLookout copy() { + return new MobLookout(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/ModifyMemory.java b/Mage.Sets/src/mage/cards/m/ModifyMemory.java index b41118ba499..9c4d25397a9 100644 --- a/Mage.Sets/src/mage/cards/m/ModifyMemory.java +++ b/Mage.Sets/src/mage/cards/m/ModifyMemory.java @@ -86,8 +86,8 @@ class ModifyMemoryTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/m/MoltenManInfernoIncarnate.java b/Mage.Sets/src/mage/cards/m/MoltenManInfernoIncarnate.java new file mode 100644 index 00000000000..d7a228a8abb --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoltenManInfernoIncarnate.java @@ -0,0 +1,63 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.SacrificeEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterBasicCard; +import mage.filter.common.FilterControlledLandPermanent; +import mage.filter.common.FilterLandPermanent; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class MoltenManInfernoIncarnate extends CardImpl { + + public MoltenManInfernoIncarnate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // When this enters the battlefield, search your library for a basic Mountain card and put it onto the battlefield tapped. Then shuffle your library. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(new FilterBasicCard(SubType.MOUNTAIN)), true) + )); + + // This creature gets +1/+1 for each Mountain you control. + DynamicValue value = new PermanentsOnBattlefieldCount(new FilterLandPermanent(SubType.MOUNTAIN, "Mountain you control")); + this.addAbility(new SimpleStaticAbility(new BoostSourceEffect(value, value, Duration.WhileOnBattlefield))); + + // When this leaves the battlefield, sacrifice a land. + this.addAbility(new LeavesBattlefieldTriggeredAbility( + new SacrificeEffect(new FilterControlledLandPermanent("a land"), 1, "")) + ); + } + + private MoltenManInfernoIncarnate(final MoltenManInfernoIncarnate card) { + super(card); + } + + @Override + public MoltenManInfernoIncarnate copy() { + return new MoltenManInfernoIncarnate(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MomoFriendlyFlier.java b/Mage.Sets/src/mage/cards/m/MomoFriendlyFlier.java new file mode 100644 index 00000000000..8ee49906584 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MomoFriendlyFlier.java @@ -0,0 +1,132 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalCostModificationEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MomoFriendlyFlier extends CardImpl { + + private static final FilterCard filter = new FilterCard(); + private static final FilterPermanent filter2 = new FilterControlledCreaturePermanent("another creature you control with flying"); + + static { + filter.add(Predicates.not(SubType.LEMUR.getPredicate())); + filter.add(new AbilityPredicate(FlyingAbility.class)); + filter2.add(new AbilityPredicate(FlyingAbility.class)); + } + + public MomoFriendlyFlier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.LEMUR); + this.subtype.add(SubType.BAT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // The first non-Lemur creature spell with flying you cast during each of your turns costs {1} less to cast. + this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( + new SpellsCostReductionControllerEffect(filter, 1), MomoFriendlyFlierCondition.instance, + "the first non-Lemur creature spell with flying you cast during each of your turns costs {1} less to cast" + )).addHint(MomoFriendlyFlierCondition.getHint()), new MomoFriendlyFlierWatcher()); + + // Whenever another creature you control with flying enters, Momo gets +1/+1 until end of turn. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new BoostSourceEffect(1, 1, Duration.EndOfTurn), filter2 + )); + } + + private MomoFriendlyFlier(final MomoFriendlyFlier card) { + super(card); + } + + @Override + public MomoFriendlyFlier copy() { + return new MomoFriendlyFlier(this); + } +} + +enum MomoFriendlyFlierCondition implements Condition { + instance; + private static final Hint hint = new ConditionHint( + instance, "You haven't cast a non-Lemur creature spell with flying during your turn yet" + ); + + public static Hint getHint() { + return hint; + } + + @Override + public boolean apply(Game game, Ability source) { + return game.isActivePlayer(source.getControllerId()) + && !MomoFriendlyFlierWatcher.checkPlayer(game, source); + } +} + +class MomoFriendlyFlierWatcher extends Watcher { + + private final Set set = new HashSet<>(); + + MomoFriendlyFlierWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST) { + return; + } + Spell spell = game.getSpell(event.getTargetId()); + if (spell != null + && spell.isCreature(game) + && !spell.hasSubtype(SubType.LEMUR, game) + && spell.getAbilities(game).containsClass(FlyingAbility.class)) { + set.add(spell.getControllerId()); + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkPlayer(Game game, Ability source) { + return game + .getState() + .getWatcher(MomoFriendlyFlierWatcher.class) + .set + .contains(source.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MomoRambunctiousRascal.java b/Mage.Sets/src/mage/cards/m/MomoRambunctiousRascal.java new file mode 100644 index 00000000000..9a5c1f20eac --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MomoRambunctiousRascal.java @@ -0,0 +1,58 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MomoRambunctiousRascal extends CardImpl { + + private static final FilterPermanent filter = new FilterOpponentsCreaturePermanent("tapped creature an opponent controls"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + public MomoRambunctiousRascal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.LEMUR); + this.subtype.add(SubType.BAT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Momo enters, he deals 4 damage to target tapped creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(4, "he")); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private MomoRambunctiousRascal(final MomoRambunctiousRascal card) { + super(card); + } + + @Override + public MomoRambunctiousRascal copy() { + return new MomoRambunctiousRascal(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MongooseLizard.java b/Mage.Sets/src/mage/cards/m/MongooseLizard.java new file mode 100644 index 00000000000..c11c6e6004d --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MongooseLizard.java @@ -0,0 +1,51 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.MountaincyclingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MongooseLizard extends CardImpl { + + public MongooseLizard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); + + this.subtype.add(SubType.MONGOOSE); + this.subtype.add(SubType.LIZARD); + this.power = new MageInt(5); + this.toughness = new MageInt(6); + + // Menace + this.addAbility(new MenaceAbility()); + + // When this creature enters, it deals 1 damage to any target. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + + // Mountaincycling {2} + this.addAbility(new MountaincyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private MongooseLizard(final MongooseLizard card) { + super(card); + } + + @Override + public MongooseLizard copy() { + return new MongooseLizard(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoonringMirror.java b/Mage.Sets/src/mage/cards/m/MoonringMirror.java index 64e79ec3c56..1bdcdb845f6 100644 --- a/Mage.Sets/src/mage/cards/m/MoonringMirror.java +++ b/Mage.Sets/src/mage/cards/m/MoonringMirror.java @@ -64,7 +64,7 @@ class MoonringMirrorExileEffect extends OneShotEffect { Card card = controller.getLibrary().getFromTop(game); MageObject sourceObject = source.getSourceObject(game); if (card != null && sourceObject != null) { - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); card.setFaceDown(true, game); controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName()); card.setFaceDown(true, game); @@ -110,7 +110,7 @@ class MoonringMirrorEffect extends OneShotEffect { return false; } - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exileZone = game.getExile().getExileZone(exileZoneId); Cards cardsToHand = null; diff --git a/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java b/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java index a619369c7e8..45bcd1a28fd 100644 --- a/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java +++ b/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java @@ -112,8 +112,8 @@ class MoorlandRescuerTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, m -> m.getPower().getValue(), xValue, game); } diff --git a/Mage.Sets/src/mage/cards/m/MorbiusTheLivingVampire.java b/Mage.Sets/src/mage/cards/m/MorbiusTheLivingVampire.java new file mode 100644 index 00000000000..40c76fd98d9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MorbiusTheLivingVampire.java @@ -0,0 +1,61 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class MorbiusTheLivingVampire extends CardImpl { + + public MorbiusTheLivingVampire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.SCIENTIST); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // {U}{B}, Exile this card from your graveyard: Look at the top three cards of your library. Put one of them into your hand and the rest on the bottom of your library in any order. + Ability ability = new SimpleActivatedAbility(new LookLibraryAndPickControllerEffect(3, 1, PutCards.HAND, PutCards.BOTTOM_ANY), + new ManaCostsImpl<>("{U}{B}")); + ability.addCost(new ExileSourceFromGraveCost()); + this.addAbility(ability); + } + + private MorbiusTheLivingVampire(final MorbiusTheLivingVampire card) { + super(card); + } + + @Override + public MorbiusTheLivingVampire copy() { + return new MorbiusTheLivingVampire(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MorlunDevourerOfSpiders.java b/Mage.Sets/src/mage/cards/m/MorlunDevourerOfSpiders.java new file mode 100644 index 00000000000..eb681bdf44c --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MorlunDevourerOfSpiders.java @@ -0,0 +1,56 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class MorlunDevourerOfSpiders extends CardImpl { + + public MorlunDevourerOfSpiders(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Morlun enters with X +1/+1 counters on him. + this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance()))); + + // When Morlun enters, he deals X damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(GetXValue.instance, "he")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private MorlunDevourerOfSpiders(final MorlunDevourerOfSpiders card) { + super(card); + } + + @Override + public MorlunDevourerOfSpiders copy() { + return new MorlunDevourerOfSpiders(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MorningApparition.java b/Mage.Sets/src/mage/cards/m/MorningApparition.java index 3b04cf354d8..22db5e1d308 100644 --- a/Mage.Sets/src/mage/cards/m/MorningApparition.java +++ b/Mage.Sets/src/mage/cards/m/MorningApparition.java @@ -1,8 +1,7 @@ package mage.cards.m; import mage.MageInt; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; @@ -34,7 +33,7 @@ public final class MorningApparition extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // If Morning Apparition would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private MorningApparition(final MorningApparition card) { diff --git a/Mage.Sets/src/mage/cards/m/MournersShield.java b/Mage.Sets/src/mage/cards/m/MournersShield.java index 3c190addd62..68bdd029189 100644 --- a/Mage.Sets/src/mage/cards/m/MournersShield.java +++ b/Mage.Sets/src/mage/cards/m/MournersShield.java @@ -83,7 +83,7 @@ class MournersShieldImprintEffect extends OneShotEffect { if (controller != null) { Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); if (card != null) { - controller.moveCardsToExile(card, source, game, true, CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), source.getSourceObject(game).getIdName()); + controller.moveCardsToExile(card, source, game, true, CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()), source.getSourceObject(game).getIdName()); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (sourcePermanent != null) { sourcePermanent.imprint(this.getTargetPointer().getFirst(game, source), game); diff --git a/Mage.Sets/src/mage/cards/m/MultiversalPassage.java b/Mage.Sets/src/mage/cards/m/MultiversalPassage.java new file mode 100644 index 00000000000..e0fa229ff4d --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MultiversalPassage.java @@ -0,0 +1,125 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.ChooseBasicLandTypeEffect; +import mage.abilities.effects.common.TapSourceUnlessPaysEffect; +import mage.abilities.mana.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class MultiversalPassage extends CardImpl { + + public MultiversalPassage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + + // As this land enters, choose a basic land type. Then you may pay 2 life. If you don't, it enters tapped. + Ability ability = new AsEntersBattlefieldAbility(new ChooseBasicLandTypeEffect(Outcome.Benefit)); + ability.addEffect(new TapSourceUnlessPaysEffect(new PayLifeCost(2))); + this.addAbility(ability); + + // This land is the chosen type. + this.addAbility(new SimpleStaticAbility(new MultiversalPassagePassageEffect())); + } + + private MultiversalPassage(final MultiversalPassage card) { + super(card); + } + + @Override + public MultiversalPassage copy() { + return new MultiversalPassage(this); + } +} +class MultiversalPassagePassageEffect extends ContinuousEffectImpl { + + MultiversalPassagePassageEffect() { + super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral); + staticText = "This land is the chosen type."; + } + + private MultiversalPassagePassageEffect(final MultiversalPassagePassageEffect effect) { + super(effect); + } + + @Override + public MultiversalPassagePassageEffect copy() { + return new MultiversalPassagePassageEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY)); + if (choice == null) { + discard(); + return; + } + + switch (choice) { + case PLAINS: + dependencyTypes.add(DependencyType.BecomePlains); + break; + case ISLAND: + dependencyTypes.add(DependencyType.BecomeIsland); + break; + case SWAMP: + dependencyTypes.add(DependencyType.BecomeSwamp); + break; + case MOUNTAIN: + dependencyTypes.add(DependencyType.BecomeMountain); + break; + case FOREST: + dependencyTypes.add(DependencyType.BecomeForest); + break; + } + } + + @Override + public boolean apply(Game game, Ability source) { + SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY)); + if (choice == null) { + return false; + } + Ability ability; + switch (choice) { + case PLAINS: + ability = new WhiteManaAbility(); + break; + case ISLAND: + ability = new BlueManaAbility(); + break; + case SWAMP: + ability = new BlackManaAbility(); + break; + case MOUNTAIN: + ability = new RedManaAbility(); + break; + case FOREST: + ability = new GreenManaAbility(); + break; + default: + ability = null; + } + Permanent land = game.getPermanent(source.getSourceId()); + if (land == null || land.hasSubtype(choice, game)) { + return false; + } + land.addSubType(game, choice); + land.addAbility(ability, source.getSourceId(), game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/Mutiny.java b/Mage.Sets/src/mage/cards/m/Mutiny.java index 40caae27b00..50ecaac9d64 100644 --- a/Mage.Sets/src/mage/cards/m/Mutiny.java +++ b/Mage.Sets/src/mage/cards/m/Mutiny.java @@ -1,6 +1,5 @@ package mage.cards.m; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -9,18 +8,16 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.permanent.ControllerIdPredicate; -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.TargetCreaturePermanent; +import java.util.Set; import java.util.UUID; /** + * TODO: combine with BreakingOfTheFellowship + * * @author LevelX2 */ public final class Mutiny extends CardImpl { @@ -30,7 +27,8 @@ public final class Mutiny extends CardImpl { // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. this.getSpellAbility().addEffect(new MutinyEffect()); - this.getSpellAbility().addTarget(new MutinyFirstTarget(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.getSpellAbility().addTarget(new MutinySecondTarget()); this.getSpellAbility().addTarget(new TargetPermanent(new FilterCreaturePermanent("another target creature that player controls"))); } @@ -72,74 +70,45 @@ class MutinyEffect extends OneShotEffect { } return true; } - } -class MutinyFirstTarget extends TargetPermanent { +class MutinySecondTarget extends TargetPermanent { - public MutinyFirstTarget(FilterCreaturePermanent filter) { - super(1, 1, filter, false); + public MutinySecondTarget() { + super(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE); } - private MutinyFirstTarget(final MutinyFirstTarget target) { + private MutinySecondTarget(final MutinySecondTarget target) { super(target); } @Override - public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) { - super.addTarget(id, source, game, skipEvent); - // Update the second target - UUID firstController = game.getControllerId(id); - if (firstController != null && source.getTargets().size() > 1) { - Player controllingPlayer = game.getPlayer(firstController); - TargetCreaturePermanent targetCreaturePermanent = (TargetCreaturePermanent) source.getTargets().get(1); - // Set a new filter to the second target with the needed restrictions - FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature that player " + controllingPlayer.getName() + " controls"); - filter.add(new ControllerIdPredicate(firstController)); - filter.add(Predicates.not(new PermanentIdPredicate(id))); - targetCreaturePermanent.replaceFilter(filter); - } - } + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { - // can only target, if the controller has at least two targetable creatures - UUID controllingPlayerId = game.getControllerId(id); - int possibleTargets = 0; - MageObject sourceObject = game.getObject(source.getId()); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllingPlayerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, controllerId, source, game)) { - possibleTargets++; - } + Permanent firstTarget = game.getPermanent(source.getFirstTarget()); + if (firstTarget == null) { + // playable or first target not yet selected + // use all + if (possibleTargets.size() == 1) { + // workaround to make 1 target invalid + possibleTargets.clear(); } - return possibleTargets > 1; + } else { + // real + // filter by same player + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null || !permanent.isControlledBy(firstTarget.getControllerId()); + }); } - return false; + possibleTargets.removeIf(id -> firstTarget != null && firstTarget.getId().equals(id)); + + return possibleTargets; } @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (super.canChoose(sourceControllerId, source, game)) { - UUID controllingPlayerId = game.getControllerId(source.getSourceId()); - for (UUID playerId : game.getOpponents(controllingPlayerId)) { - int possibleTargets = 0; - MageObject sourceObject = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, playerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, controllingPlayerId, source, game)) { - possibleTargets++; - } - } - if (possibleTargets > 1) { - return true; - } - } - } - return false; - } - - @Override - public MutinyFirstTarget copy() { - return new MutinyFirstTarget(this); + public MutinySecondTarget copy() { + return new MutinySecondTarget(this); } } diff --git a/Mage.Sets/src/mage/cards/m/MysterioMasterOfIllusion.java b/Mage.Sets/src/mage/cards/m/MysterioMasterOfIllusion.java new file mode 100644 index 00000000000..865ae79068b --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MysterioMasterOfIllusion.java @@ -0,0 +1,119 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.IllusionVillainToken; +import mage.game.permanent.token.Token; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MysterioMasterOfIllusion extends CardImpl { + + public MysterioMasterOfIllusion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Mysterio enters, create a 3/3 blue Illusion Villain creature token for each nontoken Villain you control. Exile those tokens when Mysterio leaves the battlefield. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MysterioMasterOfIllusionEffect())); + } + + private MysterioMasterOfIllusion(final MysterioMasterOfIllusion card) { + super(card); + } + + @Override + public MysterioMasterOfIllusion copy() { + return new MysterioMasterOfIllusion(this); + } +} + +class MysterioMasterOfIllusionEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.VILLAIN); + + static { + filter.add(TokenPredicate.FALSE); + } + + MysterioMasterOfIllusionEffect() { + super(Outcome.Benefit); + staticText = "create a 3/3 blue Illusion Villain creature token for each nontoken Villain you control. " + + "Exile those tokens when {this} leaves the battlefield"; + } + + private MysterioMasterOfIllusionEffect(final MysterioMasterOfIllusionEffect effect) { + super(effect); + } + + @Override + public MysterioMasterOfIllusionEffect copy() { + return new MysterioMasterOfIllusionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new IllusionVillainToken(); + token.putOntoBattlefield(game.getBattlefield().count(filter, source.getControllerId(), source, game), game, source); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent != null) { + game.addDelayedTriggeredAbility(new MysterioMasterOfIllusionTriggeredAbility(token, game), source); + } + return true; + } +} + +class MysterioMasterOfIllusionTriggeredAbility extends DelayedTriggeredAbility { + + MysterioMasterOfIllusionTriggeredAbility(Token token, Game game) { + super(new ExileTargetEffect().setTargetPointer(new FixedTargets(token, game)), Duration.Custom, true, false); + this.setLeavesTheBattlefieldTrigger(true); + } + + private MysterioMasterOfIllusionTriggeredAbility(final MysterioMasterOfIllusionTriggeredAbility ability) { + super(ability); + } + + @Override + public MysterioMasterOfIllusionTriggeredAbility copy() { + return new MysterioMasterOfIllusionTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return Zone.BATTLEFIELD.match(((ZoneChangeEvent) event).getFromZone()) + && event.getSourceId().equals(getSourceId()); + } + + @Override + public String getRule() { + return "Exile those tokens when {this} leaves the battlefield."; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MysteriosPhantasm.java b/Mage.Sets/src/mage/cards/m/MysteriosPhantasm.java new file mode 100644 index 00000000000..508a4070cfd --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MysteriosPhantasm.java @@ -0,0 +1,46 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MysteriosPhantasm extends CardImpl { + + public MysteriosPhantasm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.ILLUSION); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever this creature attacks, mill a card. + this.addAbility(new AttacksTriggeredAbility(new MillCardsControllerEffect(1))); + } + + private MysteriosPhantasm(final MysteriosPhantasm card) { + super(card); + } + + @Override + public MysteriosPhantasm copy() { + return new MysteriosPhantasm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NahiriForgedInFury.java b/Mage.Sets/src/mage/cards/n/NahiriForgedInFury.java index 291781b2532..4f578491bc0 100644 --- a/Mage.Sets/src/mage/cards/n/NahiriForgedInFury.java +++ b/Mage.Sets/src/mage/cards/n/NahiriForgedInFury.java @@ -11,6 +11,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.EquippedPredicate; import mage.game.Game; import mage.players.Player; @@ -24,6 +25,10 @@ public final class NahiriForgedInFury extends CardImpl { private static final FilterControlledCreaturePermanent equippedFilter = new FilterControlledCreaturePermanent( "an equipped creature you control"); + static { + equippedFilter.add(EquippedPredicate.instance); + } + public NahiriForgedInFury(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{W}"); diff --git a/Mage.Sets/src/mage/cards/n/NahirisSacrifice.java b/Mage.Sets/src/mage/cards/n/NahirisSacrifice.java index 9bb7df08c61..79ee6ad1e61 100644 --- a/Mage.Sets/src/mage/cards/n/NahirisSacrifice.java +++ b/Mage.Sets/src/mage/cards/n/NahirisSacrifice.java @@ -1,5 +1,6 @@ package mage.cards.n; +import mage.abilities.Ability; import mage.abilities.costs.Cost; import mage.abilities.costs.SacrificeCost; import mage.abilities.costs.VariableCostImpl; @@ -15,6 +16,9 @@ import mage.constants.ComparisonType; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; import mage.target.common.TargetCreaturePermanentAmount; import java.util.UUID; @@ -90,4 +94,33 @@ class SacrificeXManaValueCost extends VariableCostImpl implements SacrificeCost return new SacrificeTargetCost(manavaluefilter); } + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + Player controller = game.getPlayer(controllerId); + if (controller == null) { + return false; + } + int validTargets = 0; + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllerId, game)) { + if (controller.canPaySacrificeCost(permanent, source, controllerId, game)) { + validTargets++; + } + } + return validTargets > 0; + } + + @Override + public int getMaxValue(Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return super.getMaxValue(source, game); + } + int maxValue = 0; + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controller.getId(), game)) { + if (controller.canPaySacrificeCost(permanent, source, controller.getId(), game)) { + maxValue = Math.max(maxValue, permanent.getManaValue()); + } + } + return maxValue; + } } diff --git a/Mage.Sets/src/mage/cards/n/NahirisWarcrafting.java b/Mage.Sets/src/mage/cards/n/NahirisWarcrafting.java index cd5f2e4ceab..c170893bc2d 100644 --- a/Mage.Sets/src/mage/cards/n/NahirisWarcrafting.java +++ b/Mage.Sets/src/mage/cards/n/NahirisWarcrafting.java @@ -80,9 +80,8 @@ class NahirisWarcraftingEffect extends OneShotEffect { if (player == null || permanent == null) { return false; } - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - int excess = permanent.damage(5, source, game) - lethal; - if (excess <= 0) { + int excess = permanent.damageWithExcess(5, source, game); + if (excess < 1) { return true; } Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, excess)); diff --git a/Mage.Sets/src/mage/cards/n/NalathniDragon.java b/Mage.Sets/src/mage/cards/n/NalathniDragon.java index 1ef194771d7..d82de639621 100644 --- a/Mage.Sets/src/mage/cards/n/NalathniDragon.java +++ b/Mage.Sets/src/mage/cards/n/NalathniDragon.java @@ -20,7 +20,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; import mage.game.Game; /** @@ -78,7 +77,7 @@ class NalathniDragonEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - ActivationInfo activationInfo = ActivationInfo.getInstance(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + ActivationInfo activationInfo = ActivationInfo.getInstance(game, source.getSourceId(), source.getStackMomentSourceZCC()); activationInfo.addActivation(game); if (activationInfo.getActivationCounter() >= 4) { DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new SacrificeSourceEffect()); diff --git a/Mage.Sets/src/mage/cards/n/NaturalEmergence.java b/Mage.Sets/src/mage/cards/n/NaturalEmergence.java index c847a1f6252..2b98c547b0c 100644 --- a/Mage.Sets/src/mage/cards/n/NaturalEmergence.java +++ b/Mage.Sets/src/mage/cards/n/NaturalEmergence.java @@ -3,12 +3,14 @@ package mage.cards.n; import mage.ObjectColor; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.common.ReturnToHandChosenControlledPermanentEffect; import mage.abilities.effects.common.continuous.BecomesCreatureAllEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.DependencyType; import mage.constants.Duration; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledEnchantmentPermanent; @@ -37,12 +39,14 @@ public final class NaturalEmergence extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new ReturnToHandChosenControlledPermanentEffect(filter), false)); // Lands you control are 2/2 creatures with first strike. They're still lands. - this.addAbility(new SimpleStaticAbility(new BecomesCreatureAllEffect( + ContinuousEffect effect = new BecomesCreatureAllEffect( new CreatureToken( 2, 2, "2/2 creatures with first strike" ).withAbility(FirstStrikeAbility.getInstance()), "lands", StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS, Duration.WhileOnBattlefield, false - ))); + ); + effect.getDependedToTypes().add(DependencyType.BecomeNonbasicLand); + this.addAbility(new SimpleStaticAbility(effect)); } private NaturalEmergence(final NaturalEmergence card) { diff --git a/Mage.Sets/src/mage/cards/n/NaturesRevolt.java b/Mage.Sets/src/mage/cards/n/NaturesRevolt.java index b4f8b3d9df3..ea5ae34c91a 100644 --- a/Mage.Sets/src/mage/cards/n/NaturesRevolt.java +++ b/Mage.Sets/src/mage/cards/n/NaturesRevolt.java @@ -2,12 +2,13 @@ package mage.cards.n; import java.util.UUID; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.common.continuous.BecomesCreatureAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.DependencyType; import mage.constants.Duration; -import mage.constants.Zone; import mage.filter.common.FilterLandPermanent; import mage.game.permanent.token.custom.CreatureToken; @@ -24,9 +25,11 @@ public final class NaturesRevolt extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}{G}"); // All lands are 2/2 creatures that are still lands. - this.addAbility(new SimpleStaticAbility(new BecomesCreatureAllEffect( + ContinuousEffect effect = new BecomesCreatureAllEffect( new CreatureToken(2, 2, "2/2 creatures"), - "lands", filter, Duration.WhileOnBattlefield, false))); + "lands", filter, Duration.WhileOnBattlefield, false); + effect.getDependedToTypes().add(DependencyType.BecomeNonbasicLand); + this.addAbility(new SimpleStaticAbility(effect)); } private NaturesRevolt(final NaturesRevolt card) { diff --git a/Mage.Sets/src/mage/cards/n/NeglectedHeirloom.java b/Mage.Sets/src/mage/cards/n/NeglectedHeirloom.java index a4db3dfc9be..9fa4c5d6e11 100644 --- a/Mage.Sets/src/mage/cards/n/NeglectedHeirloom.java +++ b/Mage.Sets/src/mage/cards/n/NeglectedHeirloom.java @@ -2,7 +2,6 @@ package mage.cards.n; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.keyword.EquipAbility; @@ -10,13 +9,13 @@ import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.game.permanent.Permanent; +import java.util.Optional; import java.util.UUID; /** @@ -38,7 +37,7 @@ public final class NeglectedHeirloom extends CardImpl { this.addAbility(new NeglectedHeirloomTriggeredAbility()); // Equip {1} - this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(1), new TargetControlledCreaturePermanent(), false)); + this.addAbility(new EquipAbility(1, false)); } private NeglectedHeirloom(final NeglectedHeirloom card) { @@ -56,6 +55,7 @@ class NeglectedHeirloomTriggeredAbility extends TriggeredAbilityImpl { public NeglectedHeirloomTriggeredAbility() { super(Zone.BATTLEFIELD, new TransformSourceEffect(), false); + setTriggerPhrase("When equipped creature transforms, "); } private NeglectedHeirloomTriggeredAbility(final NeglectedHeirloomTriggeredAbility ability) { @@ -69,21 +69,15 @@ class NeglectedHeirloomTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.TRANSFORMED) { - if (game.getPermanent(event.getTargetId()).getAttachments().contains(this.getSourceId())) { - return true; - } - } - return false; + return Optional + .ofNullable(getSourcePermanentIfItStillExists(game)) + .map(Permanent::getAttachedTo) + .filter(event.getTargetId()::equals) + .isPresent(); } @Override public NeglectedHeirloomTriggeredAbility copy() { return new NeglectedHeirloomTriggeredAbility(this); } - - @Override - public String getRule() { - return "When equipped creature transforms, transform {this}."; - } } diff --git a/Mage.Sets/src/mage/cards/n/NestOfScarabs.java b/Mage.Sets/src/mage/cards/n/NestOfScarabs.java index d4545e85df7..80971943f4f 100644 --- a/Mage.Sets/src/mage/cards/n/NestOfScarabs.java +++ b/Mage.Sets/src/mage/cards/n/NestOfScarabs.java @@ -1,12 +1,13 @@ package mage.cards.n; -import mage.abilities.common.PutCounterOnCreatureTriggeredAbility; +import mage.abilities.common.PutCounterOnPermanentTriggeredAbility; import mage.abilities.dynamicvalue.common.EffectKeyValue; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.game.permanent.token.NestOfScarabsBlackInsectToken; import java.util.UUID; @@ -20,10 +21,10 @@ public final class NestOfScarabs extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); // Whenever you put one or more -1/-1 counters on a creature, create that many 1/1 black Insect creature tokens. - this.addAbility(new PutCounterOnCreatureTriggeredAbility( + this.addAbility(new PutCounterOnPermanentTriggeredAbility( new CreateTokenEffect(new NestOfScarabsBlackInsectToken(), new EffectKeyValue("countersAdded", "that many")), - CounterType.M1M1.createInstance())); + CounterType.M1M1, StaticFilters.FILTER_PERMANENT_CREATURE)); } private NestOfScarabs(final NestOfScarabs card) { diff --git a/Mage.Sets/src/mage/cards/n/Nethergoyf.java b/Mage.Sets/src/mage/cards/n/Nethergoyf.java index 858e2718695..955bd409171 100644 --- a/Mage.Sets/src/mage/cards/n/Nethergoyf.java +++ b/Mage.Sets/src/mage/cards/n/Nethergoyf.java @@ -22,10 +22,7 @@ import mage.target.common.TargetCardInYourGraveyard; import mage.util.CardUtil; import java.awt.*; -import java.util.Collection; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** @@ -113,7 +110,10 @@ class NethergoyfTarget extends TargetCardInYourGraveyard { return false; } // Check that exiling all the possible cards would have >= 4 different card types - return metCondition(this.possibleTargets(sourceControllerId, source, game), game); + Set idsToCheck = new HashSet<>(); + idsToCheck.addAll(this.getTargets()); + idsToCheck.addAll(this.possibleTargets(sourceControllerId, source, game)); + return metCondition(idsToCheck, game); } private static Set typesAmongSelection(Collection cardsIds, Game game) { diff --git a/Mage.Sets/src/mage/cards/n/NethroiApexOfDeath.java b/Mage.Sets/src/mage/cards/n/NethroiApexOfDeath.java index 0f1746723d8..f55584b1dc5 100644 --- a/Mage.Sets/src/mage/cards/n/NethroiApexOfDeath.java +++ b/Mage.Sets/src/mage/cards/n/NethroiApexOfDeath.java @@ -83,8 +83,8 @@ class NethroiApexOfDeathTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, m -> m.getPower().getValue(), 10, game); } diff --git a/Mage.Sets/src/mage/cards/n/NeverwinterDryad.java b/Mage.Sets/src/mage/cards/n/NeverwinterDryad.java index d32f46b9461..d7b6ffd9207 100644 --- a/Mage.Sets/src/mage/cards/n/NeverwinterDryad.java +++ b/Mage.Sets/src/mage/cards/n/NeverwinterDryad.java @@ -10,8 +10,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -21,12 +21,7 @@ import java.util.UUID; */ public final class NeverwinterDryad extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Forest card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); public NeverwinterDryad(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); diff --git a/Mage.Sets/src/mage/cards/n/NewsHelicopter.java b/Mage.Sets/src/mage/cards/n/NewsHelicopter.java new file mode 100644 index 00000000000..797c25db21c --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NewsHelicopter.java @@ -0,0 +1,42 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.CitizenGreenWhiteToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NewsHelicopter extends CardImpl { + + public NewsHelicopter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.subtype.add(SubType.CONSTRUCT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, create a 1/1 green and white Human Citizen creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new CitizenGreenWhiteToken()))); + } + + private NewsHelicopter(final NewsHelicopter card) { + super(card); + } + + @Override + public NewsHelicopter copy() { + return new NewsHelicopter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java b/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java index b14942908a4..321e6980297 100644 --- a/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java +++ b/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java @@ -150,7 +150,7 @@ class NicolBolasDragonGodPlusOneEffect extends OneShotEffect { TargetPermanent targetPermanent = new TargetControlledPermanent(); targetPermanent.withNotTarget(true); targetPermanent.setTargetController(opponentId); - if (!targetPermanent.possibleTargets(opponentId, game).isEmpty()) { + if (!targetPermanent.possibleTargets(opponentId, source, game).isEmpty()) { possibleTargetTypes.add(targetPermanent); } diff --git a/Mage.Sets/src/mage/cards/n/NissaVastwoodSeer.java b/Mage.Sets/src/mage/cards/n/NissaVastwoodSeer.java index 14fac45f978..01b027d210d 100644 --- a/Mage.Sets/src/mage/cards/n/NissaVastwoodSeer.java +++ b/Mage.Sets/src/mage/cards/n/NissaVastwoodSeer.java @@ -12,6 +12,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.filter.common.FilterLandPermanent; import mage.target.common.TargetCardInLibrary; @@ -22,12 +23,7 @@ import java.util.UUID; */ public final class NissaVastwoodSeer extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); private static final Condition condition = new PermanentsOnTheBattlefieldCondition( new FilterLandPermanent("you control seven or more lands"), diff --git a/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java b/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java index a1bdc5302ca..0c206b20644 100644 --- a/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java +++ b/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java @@ -1,7 +1,6 @@ package mage.cards.n; -import java.util.UUID; import mage.abilities.condition.common.SpellMasteryCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.search.SearchLibraryPutOntoBattlefieldTappedRestInHandEffect; @@ -9,22 +8,18 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class NissasPilgrimage extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest cards"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST, "basic Forest cards"); public NissasPilgrimage(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); diff --git a/Mage.Sets/src/mage/cards/n/NissasTriumph.java b/Mage.Sets/src/mage/cards/n/NissasTriumph.java index b331f7faaaf..9973eec8af1 100644 --- a/Mage.Sets/src/mage/cards/n/NissasTriumph.java +++ b/Mage.Sets/src/mage/cards/n/NissasTriumph.java @@ -8,10 +8,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.common.FilterBasicCard; import mage.filter.common.FilterControlledPlaneswalkerPermanent; import mage.target.common.TargetCardInLibrary; @@ -22,14 +22,9 @@ import java.util.UUID; */ public final class NissasTriumph extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest cards"); + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST, "basic Forest cards"); private static final FilterPermanent filter2 = new FilterControlledPlaneswalkerPermanent(SubType.NISSA); - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } - private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter2); public NissasTriumph(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/n/NivmagusElemental.java b/Mage.Sets/src/mage/cards/n/NivmagusElemental.java index a602a4adec4..f94d741e0d0 100644 --- a/Mage.Sets/src/mage/cards/n/NivmagusElemental.java +++ b/Mage.Sets/src/mage/cards/n/NivmagusElemental.java @@ -91,7 +91,7 @@ class NivmagusElementalCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/n/NoContest.java b/Mage.Sets/src/mage/cards/n/NoContest.java index 3c0b2732f17..6d25a4de9db 100644 --- a/Mage.Sets/src/mage/cards/n/NoContest.java +++ b/Mage.Sets/src/mage/cards/n/NoContest.java @@ -1,5 +1,6 @@ package mage.cards.n; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.effects.common.FightTargetsEffect; import mage.cards.Card; @@ -15,7 +16,9 @@ import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; +import mage.watchers.common.BlockedAttackerWatcher; +import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -54,50 +57,27 @@ class TargetCreatureWithLessPowerPermanent extends TargetPermanent { super(target); } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int maxPower = Integer.MIN_VALUE; // get the most powerful controlled creature that can be targeted - Card sourceCard = game.getCard(source.getSourceId()); - if (sourceCard == null) { - return false; - } - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURES, sourceControllerId, game)) { - if (permanent.getPower().getValue() > maxPower && permanent.canBeTargetedBy(sourceCard, sourceControllerId, source, game)) { - maxPower = permanent.getPower().getValue(); - } - } - // now check, if another creature has less power and can be targeted - FilterCreaturePermanent checkFilter = new FilterCreaturePermanent(); - checkFilter.add(new PowerPredicate(ComparisonType.FEWER_THAN, maxPower)); - for (Permanent permanent : game.getBattlefield().getActivePermanents(checkFilter, sourceControllerId, source, game)) { - if (permanent.canBeTargetedBy(sourceCard, sourceControllerId, source, game)) { - return true; - } - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Spell spell = game.getStack().getSpell(source.getSourceId()); - if (spell != null) { - Permanent firstTarget = getPermanentFromFirstTarget(spell.getSpellAbility(), game); - if (firstTarget != null) { - int power = firstTarget.getPower().getValue(); - // overwrite the filter with the power predicate - filter = new FilterCreaturePermanent("creature with power less than " + power); - filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, power)); + Set possibleTargets = new HashSet<>(); + + Permanent firstPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (firstPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by power + if (firstPermanent.getPower().getValue() > permanent.getPower().getValue()) { + possibleTargets.add(permanent.getId()); + } } } - return super.possibleTargets(sourceControllerId, source, game); - } + possibleTargets.removeIf(id -> firstPermanent != null && firstPermanent.getId().equals(id)); - private Permanent getPermanentFromFirstTarget(Ability source, Game game) { - Permanent firstTarget = null; - if (source.getTargets().size() == 2) { - firstTarget = game.getPermanent(source.getTargets().get(0).getFirstTarget()); - } - return firstTarget; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/n/NormanOsborn.java b/Mage.Sets/src/mage/cards/n/NormanOsborn.java new file mode 100644 index 00000000000..96b5f24f0d9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NormanOsborn.java @@ -0,0 +1,117 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.effects.keyword.ConniveSourceEffect; +import mage.abilities.keyword.*; +import mage.cards.CardSetInfo; +import mage.cards.ModalDoubleFacedCard; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.card.CastFromZonePredicate; +import mage.game.Game; +import mage.players.Player; + +import java.util.Objects; +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class NormanOsborn extends ModalDoubleFacedCard { + + private static final FilterCard filter = new FilterCard("spells you cast from your graveyard"); + + static { + filter.add(new CastFromZonePredicate(Zone.GRAVEYARD)); + } + + public NormanOsborn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.SCIENTIST, SubType.VILLAIN}, "{1}{U}", + "Green Goblin", + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.GOBLIN, SubType.HUMAN, SubType.VILLAIN}, "{1}{U}{B}{R}"); + + this.getLeftHalfCard().setPT(1, 1); + this.getRightHalfCard().setPT(3, 3); + + // Norman Osborn can't be blocked. + this.getLeftHalfCard().addAbility(new CantBeBlockedSourceAbility()); + + // Whenever Norman Osborn deals combat damage to a player, he connives. + this.getLeftHalfCard().addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new ConniveSourceEffect("he"))); + + // {1}{U}{B}{R}: Transform Norman Osborn. Activate only as a sorcery. + this.getLeftHalfCard().addAbility(new ActivateAsSorceryActivatedAbility( + new TransformSourceEffect(), new ManaCostsImpl<>("{1}{U}{B}{R}") + )); + + // Green Goblin + // Flying + this.getRightHalfCard().addAbility(FlyingAbility.getInstance()); + + // Menace + this.getRightHalfCard().addAbility(new MenaceAbility()); + + // Spells you cast from your graveyard cost {2} less to cast. + this.getRightHalfCard().addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 2))); + + // Goblin Formula -- Each nonland card in your graveyard has mayhem. The mayhem cost is equal to its mana cost. + this.getRightHalfCard().addAbility(new SimpleStaticAbility(new GreenGoblinEffect())); + } + + private NormanOsborn(final NormanOsborn card) { + super(card); + } + + @Override + public NormanOsborn copy() { + return new NormanOsborn(this); + } +} +class GreenGoblinEffect extends ContinuousEffectImpl { + + GreenGoblinEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + staticText = "Each nonland card in your graveyard has mayhem. " + + "The mayhem cost is equal to the card's mana cost."; + } + + private GreenGoblinEffect(final GreenGoblinEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + controller + .getGraveyard() + .getCards(game) + .stream() + .filter(Objects::nonNull) + .filter(card -> !card.getManaCost().getText().isEmpty()) // card must have a mana cost + .filter(card -> !card.isLand(game)) + .forEach(card -> { + Ability ability = new MayhemAbility(card, card.getManaCost().getText()); + ability.setSourceId(card.getId()); + ability.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, ability); + }); + return true; + } + + @Override + public GreenGoblinEffect copy() { + return new GreenGoblinEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OKagachiVengefulKami.java b/Mage.Sets/src/mage/cards/o/OKagachiVengefulKami.java index 8fb3f30b2c9..ebfe51e41a6 100644 --- a/Mage.Sets/src/mage/cards/o/OKagachiVengefulKami.java +++ b/Mage.Sets/src/mage/cards/o/OKagachiVengefulKami.java @@ -52,7 +52,8 @@ public final class OKagachiVengefulKami extends CardImpl { ability.addTarget(new TargetPermanent(filter)); ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); ability.withInterveningIf(KagachiVengefulKamiCondition.instance); - this.addAbility(ability); + + this.addAbility(ability, new OKagachiVengefulKamiWatcher()); } private OKagachiVengefulKami(final OKagachiVengefulKami card) { diff --git a/Mage.Sets/src/mage/cards/o/ONaginata.java b/Mage.Sets/src/mage/cards/o/ONaginata.java index 104488f9627..16d3ea98f67 100644 --- a/Mage.Sets/src/mage/cards/o/ONaginata.java +++ b/Mage.Sets/src/mage/cards/o/ONaginata.java @@ -9,12 +9,12 @@ import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.SubType; +import mage.constants.*; import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.PowerPredicate; +import mage.target.TargetCard; import mage.target.TargetPermanent; import java.util.UUID; @@ -24,7 +24,7 @@ import java.util.UUID; */ public final class ONaginata extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("creature with power 3 or greater"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power 3 or greater"); static { filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 2)); diff --git a/Mage.Sets/src/mage/cards/o/ObeliskSpider.java b/Mage.Sets/src/mage/cards/o/ObeliskSpider.java index c9982c40480..06fc55739f7 100644 --- a/Mage.Sets/src/mage/cards/o/ObeliskSpider.java +++ b/Mage.Sets/src/mage/cards/o/ObeliskSpider.java @@ -1,11 +1,9 @@ package mage.cards.o; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsDamageToACreatureTriggeredAbility; -import mage.abilities.common.PutCounterOnCreatureTriggeredAbility; -import mage.abilities.effects.Effect; +import mage.abilities.common.PutCounterOnPermanentTriggeredAbility; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; @@ -15,6 +13,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; /** * @@ -36,10 +37,9 @@ public final class ObeliskSpider extends CardImpl { this.addAbility(new DealsDamageToACreatureTriggeredAbility(new AddCountersTargetEffect(CounterType.M1M1.createInstance(1)), true, false, true)); // Whenever you put one or more -1/-1 counters on a creature, each opponent loses 1 life and you gain 1 life. - Ability ability = new PutCounterOnCreatureTriggeredAbility(new LoseLifeOpponentsEffect(1), CounterType.M1M1.createInstance()); - Effect effect = new GainLifeEffect(1); - effect.setText("and you gain 1 life"); - ability.addEffect(effect); + Ability ability = new PutCounterOnPermanentTriggeredAbility(new LoseLifeOpponentsEffect(1), + CounterType.M1M1, StaticFilters.FILTER_PERMANENT_CREATURE); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/o/ObscuraConfluence.java b/Mage.Sets/src/mage/cards/o/ObscuraConfluence.java index 663747f9a98..4d053ef18a8 100644 --- a/Mage.Sets/src/mage/cards/o/ObscuraConfluence.java +++ b/Mage.Sets/src/mage/cards/o/ObscuraConfluence.java @@ -5,7 +5,7 @@ import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.LoseAllAbilitiesTargetEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; -import mage.abilities.effects.keyword.ConniveSourceEffect; +import mage.abilities.effects.keyword.ConniveTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -14,7 +14,6 @@ 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.TargetCard; import mage.target.TargetPlayer; @@ -44,7 +43,7 @@ public final class ObscuraConfluence extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // • Target creature connives. - this.getSpellAbility().addMode(new Mode(new ObscuraConfluenceConniveEffect()).addTarget(new TargetCreaturePermanent())); + this.getSpellAbility().addMode(new Mode(new ConniveTargetEffect()).addTarget(new TargetCreaturePermanent())); // • Target player returns a creature card from their graveyard to their hand. this.getSpellAbility().addMode(new Mode(new ObscuraConfluenceReturnEffect()).addTarget(new TargetPlayer())); @@ -60,30 +59,6 @@ public final class ObscuraConfluence extends CardImpl { } } -class ObscuraConfluenceConniveEffect extends OneShotEffect { - - ObscuraConfluenceConniveEffect() { - super(Outcome.Benefit); - staticText = "target creature connives. (Draw a card, then discard a card. " + - "If you discarded a nonland card, put a +1/+1 counter on that creature.)"; - } - - private ObscuraConfluenceConniveEffect(final ObscuraConfluenceConniveEffect effect) { - super(effect); - } - - @Override - public ObscuraConfluenceConniveEffect copy() { - return new ObscuraConfluenceConniveEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); - return ConniveSourceEffect.connive(permanent, 1, source, game); - } -} - class ObscuraConfluenceReturnEffect extends OneShotEffect { ObscuraConfluenceReturnEffect() { diff --git a/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java b/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java index ad63fcbc14e..72e646c0ab5 100644 --- a/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java +++ b/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java @@ -13,6 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; @@ -81,7 +82,7 @@ class OildeepGearhulkEffect extends OneShotEffect { } controller.lookAtCards(targetPlayer.getName() + " Hand", targetPlayer.getHand(), game); - TargetCard chosenCard = new TargetCardInHand(0, 1, new FilterCard("card to discard")); + TargetCard chosenCard = new TargetCard(0, 1, Zone.HAND, new FilterCard("card to discard")); if (!controller.choose(Outcome.Discard, targetPlayer.getHand(), chosenCard, source, game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java b/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java index faff60d8cda..c4013547c4b 100644 --- a/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java +++ b/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java @@ -1,10 +1,6 @@ package mage.cards.o; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -16,13 +12,16 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInLibrary; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class OldGrowthDryads extends CardImpl { @@ -70,7 +69,7 @@ class OldGrowthDryadsEffect extends OneShotEffect { for (UUID opponentId : game.getOpponents(source.getControllerId())) { Player opponent = game.getPlayer(opponentId); if (opponent != null && opponent.chooseUse(Outcome.PutLandInPlay, "Search your library for a basic land card and put it onto the battlefield tapped?", source, game)) { - TargetCardInLibrary target = new TargetCardInLibrary(new FilterBasicLandCard()); + TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); if (opponent.searchLibrary(target, source, game)) { Card targetCard = opponent.getLibrary().getCard(target.getFirstTarget(), game); if (targetCard != null) { diff --git a/Mage.Sets/src/mage/cards/o/OmarthisGhostfireInitiate.java b/Mage.Sets/src/mage/cards/o/OmarthisGhostfireInitiate.java index 7dff16ac6f9..f9bb0201478 100644 --- a/Mage.Sets/src/mage/cards/o/OmarthisGhostfireInitiate.java +++ b/Mage.Sets/src/mage/cards/o/OmarthisGhostfireInitiate.java @@ -3,7 +3,7 @@ package mage.cards.o; import mage.MageInt; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.common.PutCounterOnCreatureTriggeredAbility; +import mage.abilities.common.PutCounterOnPermanentTriggeredAbility; import mage.abilities.dynamicvalue.common.CountersSourceCount; import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -49,10 +49,9 @@ public final class OmarthisGhostfireInitiate extends CardImpl { // Whenever you put one or more +1/+1 counters on another colorless creature, you may put a +1/+1 counter on Omarthis. this.addAbility( - new PutCounterOnCreatureTriggeredAbility( + new PutCounterOnPermanentTriggeredAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance()), - CounterType.P1P1.createInstance(), filter, - false, true + CounterType.P1P1, filter, false, true ) ); diff --git a/Mage.Sets/src/mage/cards/o/OminousAsylum.java b/Mage.Sets/src/mage/cards/o/OminousAsylum.java new file mode 100644 index 00000000000..8c6d06632a6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OminousAsylum.java @@ -0,0 +1,46 @@ +package mage.cards.o; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OminousAsylum extends CardImpl { + + public OminousAsylum(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {B} or {R}. + this.addAbility(new BlackManaAbility()); + this.addAbility(new RedManaAbility()); + + // {4}, {T}: Surveil 1. + Ability ability = new SimpleActivatedAbility(new SurveilEffect(1), new GenericManaCost(4)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private OminousAsylum(final OminousAsylum card) { + super(card); + } + + @Override + public OminousAsylum copy() { + return new OminousAsylum(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java b/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java index e5238585a3d..a7436dcb122 100644 --- a/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java +++ b/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java @@ -2,7 +2,6 @@ package mage.cards.o; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -61,10 +60,8 @@ class OrbitalPlungeEffect extends OneShotEffect { if (permanent == null) { return false; } - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - permanent.damage(6, source.getSourceId(), source, game); - if (lethal < 6) { - new CreateTokenEffect(new LanderToken()).apply(game, source); + if (permanent.damageWithExcess(6, source, game) > 0) { + new LanderToken().putOntoBattlefield(1, game, source); } return true; } diff --git a/Mage.Sets/src/mage/cards/o/OrcusPrinceOfUndeath.java b/Mage.Sets/src/mage/cards/o/OrcusPrinceOfUndeath.java index 9f1c1c8c302..c41b9ec34a1 100644 --- a/Mage.Sets/src/mage/cards/o/OrcusPrinceOfUndeath.java +++ b/Mage.Sets/src/mage/cards/o/OrcusPrinceOfUndeath.java @@ -119,8 +119,8 @@ class OrcusPrinceOfUndeathTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, xValue, game); } diff --git a/Mage.Sets/src/mage/cards/o/OrochiMergeKeeper.java b/Mage.Sets/src/mage/cards/o/OrochiMergeKeeper.java index 39b4429664f..b9af61a8062 100644 --- a/Mage.Sets/src/mage/cards/o/OrochiMergeKeeper.java +++ b/Mage.Sets/src/mage/cards/o/OrochiMergeKeeper.java @@ -3,8 +3,7 @@ package mage.cards.o; import mage.MageInt; import mage.Mana; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; -import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.condition.common.SourceModifiedCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; @@ -15,8 +14,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterPermanent; -import mage.filter.predicate.permanent.ModifiedPredicate; import java.util.UUID; @@ -25,14 +22,6 @@ import java.util.UUID; */ public final class OrochiMergeKeeper extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent(); - - static { - filter.add(ModifiedPredicate.instance); - } - - private static final Condition condition = new SourceMatchesFilterCondition(filter); - public OrochiMergeKeeper(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); @@ -48,7 +37,7 @@ public final class OrochiMergeKeeper extends CardImpl { this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(new SimpleManaAbility( Zone.BATTLEFIELD, Mana.GreenMana(2), new TapSourceCost() - )), condition, "as long as {this} is modified, it has \"{T}: Add {G}{G}.\"" + )), SourceModifiedCondition.instance, "as long as {this} is modified, it has \"{T}: Add {G}{G}.\"" ))); } diff --git a/Mage.Sets/src/mage/cards/o/OrvarTheAllForm.java b/Mage.Sets/src/mage/cards/o/OrvarTheAllForm.java index 160f051cb85..557f25730b0 100644 --- a/Mage.Sets/src/mage/cards/o/OrvarTheAllForm.java +++ b/Mage.Sets/src/mage/cards/o/OrvarTheAllForm.java @@ -82,7 +82,7 @@ enum OrvarTheAllFormCondition implements Condition { public boolean apply(Game game, Ability source) { Spell spell = (Spell) source.getEffects().get(0).getValue("spellCast"); MageObjectReference mor; - if (source.getSourceObjectZoneChangeCounter() == 0) { + if (source.getStackMomentSourceZCC() == 0) { mor = new MageObjectReference(source.getSourceId(), game); } else { mor = new MageObjectReference(source); diff --git a/Mage.Sets/src/mage/cards/o/OscorpIndustries.java b/Mage.Sets/src/mage/cards/o/OscorpIndustries.java new file mode 100644 index 00000000000..f694701108b --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OscorpIndustries.java @@ -0,0 +1,50 @@ +package mage.cards.o; + +import mage.abilities.common.EntersBattlefieldFromGraveyardTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.keyword.MayhemLandAbility; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.BlueManaAbility; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class OscorpIndustries extends CardImpl { + + public OscorpIndustries(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When this land enters from a graveyard, you lose 2 life. + this.addAbility(new EntersBattlefieldFromGraveyardTriggeredAbility(new LoseLifeSourceControllerEffect(2))); + + // {T}: Add {U}, {B}, or {R}. + this.addAbility(new BlueManaAbility()); + this.addAbility(new BlackManaAbility()); + this.addAbility(new RedManaAbility()); + + // Mayhem + this.addAbility(new MayhemLandAbility(this)); + + } + + private OscorpIndustries(final OscorpIndustries card) { + super(card); + } + + @Override + public OscorpIndustries copy() { + return new OscorpIndustries(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OstrichHorse.java b/Mage.Sets/src/mage/cards/o/OstrichHorse.java new file mode 100644 index 00000000000..4c4131db6f5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OstrichHorse.java @@ -0,0 +1,43 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.MillThenPutInHandEffect; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OstrichHorse extends CardImpl { + + public OstrichHorse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.HORSE); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // When this creature enters, mill three cards. You may put a land card from among them into your hand. If you don't, put a +1/+1 counter on this creature. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillThenPutInHandEffect( + 3, StaticFilters.FILTER_CARD_LAND, new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + ))); + } + + private OstrichHorse(final OstrichHorse card) { + super(card); + } + + @Override + public OstrichHorse copy() { + return new OstrichHorse(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OtterPenguin.java b/Mage.Sets/src/mage/cards/o/OtterPenguin.java new file mode 100644 index 00000000000..8154ea837e1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OtterPenguin.java @@ -0,0 +1,43 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DrawNthCardTriggeredAbility; +import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OtterPenguin extends CardImpl { + + public OtterPenguin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.OTTER); + this.subtype.add(SubType.BIRD); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Whenever you draw your second card each turn, this creature gets +1/+2 until end of turn and can't be blocked this turn. + Ability ability = new DrawNthCardTriggeredAbility(new BoostSourceEffect(1, 2, Duration.EndOfTurn)); + ability.addEffect(new CantBeBlockedSourceEffect(Duration.EndOfTurn).setText("and can't be blocked this turn")); + this.addAbility(ability); + } + + private OtterPenguin(final OtterPenguin card) { + super(card); + } + + @Override + public OtterPenguin copy() { + return new OtterPenguin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OutOfTime.java b/Mage.Sets/src/mage/cards/o/OutOfTime.java index 0bdb44d4440..78a6ccb5d7d 100644 --- a/Mage.Sets/src/mage/cards/o/OutOfTime.java +++ b/Mage.Sets/src/mage/cards/o/OutOfTime.java @@ -185,7 +185,7 @@ class OutOfTimeReplacementEffect extends ContinuousRuleModifyingEffectImpl { public boolean applies(GameEvent event, Ability source, Game game) { Set creatureIds = (Set) game.getState().getValue("phasedOutCreatures" + source.getId().toString()); - return source.getSourceObjectZoneChangeCounter() == game.getState().getZoneChangeCounter(source.getSourceId()) // blinked + return source.getStackMomentSourceZCC() == game.getState().getZoneChangeCounter(source.getSourceId()) // blinked && creatureIds != null && creatureIds.contains(event.getTargetId()); } diff --git a/Mage.Sets/src/mage/cards/o/OzaisCruelty.java b/Mage.Sets/src/mage/cards/o/OzaisCruelty.java new file mode 100644 index 00000000000..eecca5ff057 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OzaisCruelty.java @@ -0,0 +1,37 @@ +package mage.cards.o; + +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OzaisCruelty extends CardImpl { + + public OzaisCruelty(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + this.subtype.add(SubType.LESSON); + + // Ozai's Cruelty deals 2 damage to target player. That player discards two cards. + this.getSpellAbility().addEffect(new DamageTargetEffect(2)); + this.getSpellAbility().addEffect(new DiscardTargetEffect(2).setText("That player discards two cards")); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + private OzaisCruelty(final OzaisCruelty card) { + super(card); + } + + @Override + public OzaisCruelty copy() { + return new OzaisCruelty(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PairODiceLost.java b/Mage.Sets/src/mage/cards/p/PairODiceLost.java index b6ff94a6107..dd049052382 100644 --- a/Mage.Sets/src/mage/cards/p/PairODiceLost.java +++ b/Mage.Sets/src/mage/cards/p/PairODiceLost.java @@ -101,8 +101,8 @@ class PairODiceLostTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, xValue, game); } diff --git a/Mage.Sets/src/mage/cards/p/PalaceJailer.java b/Mage.Sets/src/mage/cards/p/PalaceJailer.java index 541d407572a..f79717e0b94 100644 --- a/Mage.Sets/src/mage/cards/p/PalaceJailer.java +++ b/Mage.Sets/src/mage/cards/p/PalaceJailer.java @@ -20,13 +20,11 @@ import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.StaticFilters; import mage.game.ExileZone; import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE; @@ -85,7 +83,7 @@ class PalaceJailerExileEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { MageObject sourceObject = source.getSourceObject(game); if (sourceObject != null) { - return new ExileTargetEffect(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), sourceObject.getIdName()).apply(game, source); + return new ExileTargetEffect(CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()), sourceObject.getIdName()).apply(game, source); } return false; } @@ -140,7 +138,7 @@ class PalaceJailerReturnExiledPermanentsEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (sourceObject != null && controller != null) { - UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); if (exileZone != null) { ExileZone exile = game.getExile().getExileZone(exileZone); if (exile != null) { diff --git a/Mage.Sets/src/mage/cards/p/PanickedBystander.java b/Mage.Sets/src/mage/cards/p/PanickedBystander.java index 19a0bc1f76d..fc914a322bb 100644 --- a/Mage.Sets/src/mage/cards/p/PanickedBystander.java +++ b/Mage.Sets/src/mage/cards/p/PanickedBystander.java @@ -1,16 +1,20 @@ package mage.cards.p; import mage.MageInt; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.DiesThisOrAnotherTriggeredAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.common.YouGainedLifeCondition; +import mage.abilities.dynamicvalue.common.ControllerGainedLifeCount; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; 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.StaticFilters; import mage.watchers.common.PlayerGainedLifeWatcher; @@ -42,7 +46,7 @@ public final class PanickedBystander extends CardImpl { this.addAbility(new BeginningOfEndStepTriggeredAbility( TargetController.YOU, new TransformSourceEffect(), false, condition - ), new PlayerGainedLifeWatcher()); + ).addHint(ControllerGainedLifeCount.getHint()), new PlayerGainedLifeWatcher()); } private PanickedBystander(final PanickedBystander card) { diff --git a/Mage.Sets/src/mage/cards/p/ParapetThrasher.java b/Mage.Sets/src/mage/cards/p/ParapetThrasher.java index 48725cea523..382919bec4b 100644 --- a/Mage.Sets/src/mage/cards/p/ParapetThrasher.java +++ b/Mage.Sets/src/mage/cards/p/ParapetThrasher.java @@ -1,7 +1,5 @@ package mage.cards.p; -import java.util.Objects; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -12,30 +10,38 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; import mage.abilities.hint.common.ModesAlreadyUsedHint; -import mage.constants.*; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.common.FilterArtifactPermanent; import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; +import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.DamagedEvent; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster; +import mage.target.targetadjustment.DefineByTriggerTargetAdjuster; +import mage.target.targetpointer.FixedTarget; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; /** - * * @author Jmlundeen */ public final class ParapetThrasher extends CardImpl { private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.DRAGON, "Dragons you control"); - private static final FilterPermanent artifactFilter = new FilterArtifactPermanent("artifact that opponent controls"); public ParapetThrasher(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(3); @@ -45,11 +51,7 @@ public final class ParapetThrasher extends CardImpl { // Whenever one or more Dragons you control deal combat damage to an opponent, choose one that hasn't been chosen this turn -- // * Destroy target artifact that opponent controls. - Ability ability = new OneOrMoreDamagePlayerTriggeredAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), - filter, true, true, SetTargetPointer.PLAYER, false) - .setTriggerPhrase("Whenever one or more Dragons you control deal combat damage to an opponent, "); - ability.addTarget(new TargetPermanent(artifactFilter)); - ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); + Ability ability = new ParapetThrasherTriggeredAbility(new DestroyTargetEffect().setText("destroy target artifact that opponent controls"), filter); ability.setModeTag("destroy artifact"); ability.getModes().setLimitUsageByOnce(true); @@ -78,6 +80,47 @@ public final class ParapetThrasher extends CardImpl { } } +class ParapetThrasherTriggeredAbility extends OneOrMoreDamagePlayerTriggeredAbility { + + + public ParapetThrasherTriggeredAbility(Effect effect, FilterPermanent filter) { + super(Zone.BATTLEFIELD, effect, + filter, true, true, SetTargetPointer.PLAYER, false); + setTargetAdjuster(DefineByTriggerTargetAdjuster.instance); + } + + private ParapetThrasherTriggeredAbility(final ParapetThrasherTriggeredAbility ability) { + super(ability); + } + + @Override + public ParapetThrasherTriggeredAbility copy() { + return new ParapetThrasherTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + List events = getFilteredEvents((DamagedBatchForOnePlayerEvent) event, game); + if (events.isEmpty()) { + return false; + } + this.getAllEffects().setValue("damage", events.stream().mapToInt(DamagedEvent::getAmount).sum()); + Player damagedPlayer = game.getPlayer(event.getTargetId()); + + FilterPermanent artifactFilter = new FilterArtifactPermanent("artifact " + damagedPlayer.getLogName() + " controls"); + artifactFilter.add(new ControllerIdPredicate(damagedPlayer.getId())); + this.getTargets().clear(); + this.addTarget(new TargetPermanent(artifactFilter)); + + for (Effect effect : this.getAllEffects()) { + if (effect instanceof ParapetThrasherDamageEffect) { + effect.setTargetPointer(new FixedTarget(damagedPlayer.getId())); + } + } + return true; + } +} + class ParapetThrasherDamageEffect extends OneShotEffect { ParapetThrasherDamageEffect() { diff --git a/Mage.Sets/src/mage/cards/p/ParkerLuck.java b/Mage.Sets/src/mage/cards/p/ParkerLuck.java new file mode 100644 index 00000000000..72c32246e2b --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ParkerLuck.java @@ -0,0 +1,91 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.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.TargetPlayer; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class ParkerLuck extends CardImpl { + + public ParkerLuck(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); + + + // At the beginning of your end step, two target players each reveal the top card of their library. They each lose life equal to the mana value of the card revealed by the other player. Then they each put the card they revealed into their hand. + Ability ability = new BeginningOfEndStepTriggeredAbility(new ParkerLuckEffect()); + ability.addTarget(new TargetPlayer(2)); + this.addAbility(ability); + } + + private ParkerLuck(final ParkerLuck card) { + super(card); + } + + @Override + public ParkerLuck copy() { + return new ParkerLuck(this); + } +} + +class ParkerLuckEffect extends OneShotEffect { + + ParkerLuckEffect() { + super(Outcome.Damage); + staticText = "two target players each reveal the top card of their library. " + + "They each lose life equal to the mana value of the card " + + "revealed by the other player. Then they each put the card they revealed into their hand"; + } + + protected ParkerLuckEffect(final ParkerLuckEffect effect) { + super(effect); + } + + @Override + public ParkerLuckEffect copy() { + return new ParkerLuckEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player targetOne = game.getPlayer(getTargetPointer().getTargets(game, source).get(0)); + Player targetTwo = game.getPlayer(getTargetPointer().getTargets(game, source).get(1)); + if (targetOne == null || targetTwo == null) { + return false; + } + // each reveal top card + Card targetOneCard = targetOne.getLibrary().getFromTop(game); + int targetOneMv = 0; + Card targetTwoCard = targetTwo.getLibrary().getFromTop(game); + int targetTwoMv = 0; + if (targetOneCard != null) { + targetOne.revealCards(source, new CardsImpl(targetOneCard), game); + targetOneMv = targetOneCard.getManaValue(); + } + if (targetTwoCard != null) { + targetTwo.revealCards(source, new CardsImpl(targetTwoCard), game); + targetTwoMv = targetTwoCard.getManaValue(); + } + // lose life to mana value of each others card + targetOne.loseLife(targetTwoMv, game, source, false); + targetTwo.loseLife(targetOneMv, game, source, false); + // each put card into their hand + targetOne.moveCards(targetOneCard, Zone.HAND, source, game); + targetTwo.moveCards(targetTwoCard, Zone.HAND, source, game); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/p/PassengerFerry.java b/Mage.Sets/src/mage/cards/p/PassengerFerry.java new file mode 100644 index 00000000000..ed5ca4761c8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PassengerFerry.java @@ -0,0 +1,64 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterAttackingCreature; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class PassengerFerry extends CardImpl { + + static final FilterAttackingCreature filter = new FilterAttackingCreature("another target attacking creature"); + + static { + filter.add(AnotherPredicate.instance); + } + + public PassengerFerry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Whenever this Vehicle attacks, you may pay {U}. When you do, another target attacking creature can't be blocked this turn. + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new CantBeBlockedTargetEffect(), false + ); + ability.addTarget(new TargetPermanent(filter)); + + Ability triggeredAbility = new AttacksTriggeredAbility( + new DoWhenCostPaid(ability, new ManaCostsImpl<>("{U}"), + "Make another attacking creature unblockable this turn?"), false); + this.addAbility(triggeredAbility); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + + } + + private PassengerFerry(final PassengerFerry card) { + super(card); + } + + @Override + public PassengerFerry copy() { + return new PassengerFerry(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PatchUp.java b/Mage.Sets/src/mage/cards/p/PatchUp.java index 16237f5b2e6..6717fd3b0eb 100644 --- a/Mage.Sets/src/mage/cards/p/PatchUp.java +++ b/Mage.Sets/src/mage/cards/p/PatchUp.java @@ -58,8 +58,8 @@ class PatchUpTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 3, game); } diff --git a/Mage.Sets/src/mage/cards/p/PathOfTheAnimist.java b/Mage.Sets/src/mage/cards/p/PathOfTheAnimist.java index 364c4813a13..7381e054b1c 100644 --- a/Mage.Sets/src/mage/cards/p/PathOfTheAnimist.java +++ b/Mage.Sets/src/mage/cards/p/PathOfTheAnimist.java @@ -8,7 +8,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.target.common.TargetCardInLibrary; /** diff --git a/Mage.Sets/src/mage/cards/p/PathToRedemption.java b/Mage.Sets/src/mage/cards/p/PathToRedemption.java new file mode 100644 index 00000000000..d891ecc4354 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PathToRedemption.java @@ -0,0 +1,62 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileAttachedEffect; +import mage.abilities.effects.common.combat.CantAttackBlockAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.permanent.token.AllyToken; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PathToRedemption extends CardImpl { + + public PathToRedemption(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature can't attack or block. + this.addAbility(new SimpleStaticAbility(new CantAttackBlockAttachedEffect(AttachmentType.AURA))); + + // {5}, Sacrifice this Aura: Exile enchanted creature. Create a 1/1 white Ally creature token. Activate only during your turn. + Ability ability = new ActivateIfConditionActivatedAbility( + new ExileAttachedEffect(), new GenericManaCost(5), MyTurnCondition.instance + ); + ability.addCost(new SacrificeSourceCost()); + ability.addEffect(new CreateTokenEffect(new AllyToken())); + this.addAbility(ability); + } + + private PathToRedemption(final PathToRedemption card) { + super(card); + } + + @Override + public PathToRedemption copy() { + return new PathToRedemption(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PerilousLandscape.java b/Mage.Sets/src/mage/cards/p/PerilousLandscape.java index bddc03dd3ac..dbabbbf75ff 100644 --- a/Mage.Sets/src/mage/cards/p/PerilousLandscape.java +++ b/Mage.Sets/src/mage/cards/p/PerilousLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class PerilousLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Island, Mountain, or Plains card"); + private static final FilterCard filter = new FilterBasicCard("a basic Island, Mountain, or Plains card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/p/PeterParker.java b/Mage.Sets/src/mage/cards/p/PeterParker.java new file mode 100644 index 00000000000..05522c22f56 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PeterParker.java @@ -0,0 +1,130 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.keyword.WebSlingingAbility; +import mage.cards.Card; +import mage.cards.CardSetInfo; +import mage.cards.ModalDoubleFacedCard; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorlessPredicate; +import mage.game.Game; +import mage.game.permanent.token.Spider21Token; +import mage.game.stack.Spell; +import mage.players.Player; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class PeterParker extends ModalDoubleFacedCard { + + public PeterParker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, + new SuperType[]{SuperType.LEGENDARY},new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.SCIENTIST, SubType.HERO}, "{1}{W}", + "Amazing Spider-Man", + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.SPIDER, SubType.HUMAN, SubType.HERO}, "{1}{G}{W}{U}"); + + this.getLeftHalfCard().setPT(0, 1); + this.getRightHalfCard().setPT(4, 4); + + // When Peter Parker enters, create a 2/1 green Spider creature token with reach. + this.getLeftHalfCard().addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new Spider21Token()))); + + // {1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery. + this.getLeftHalfCard().addAbility(new ActivateAsSorceryActivatedAbility( + new TransformSourceEffect(), new ManaCostsImpl<>("{1}{G}{W}{U}") + )); + + // Amazing Spider-Man + // Vigilance + this.getRightHalfCard().addAbility(VigilanceAbility.getInstance()); + + // Reach + this.getRightHalfCard().addAbility(ReachAbility.getInstance()); + + // Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}. + this.getRightHalfCard().addAbility(new SimpleStaticAbility(new AmazingSpiderManEffect())); + } + + private PeterParker(final PeterParker card) { + super(card); + } + + @Override + public PeterParker copy() { + return new PeterParker(this); + } +} +class AmazingSpiderManEffect extends ContinuousEffectImpl { + + static final FilterCard filter = new FilterCard("legendary spell that's one or more colors"); + + static { + filter.add(Predicates.not(ColorlessPredicate.instance)); + filter.add(SuperType.LEGENDARY.getPredicate()); + } + + AmazingSpiderManEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + staticText = "Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}"; + } + + private AmazingSpiderManEffect(final AmazingSpiderManEffect effect) { + super(effect) ; + } + + @Override + public AmazingSpiderManEffect copy() { + return new AmazingSpiderManEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Set cardsToGainAbility = new HashSet<>(); + cardsToGainAbility.addAll(controller.getHand().getCards(filter, game)); + cardsToGainAbility.addAll(controller.getGraveyard().getCards(filter, game)); + controller.getLibrary().getCards(game).stream() + .filter(c -> filter.match(c, game)) + .forEach(cardsToGainAbility::add); + game.getExile().getAllCardsByRange(game, controller.getId()).stream() + .filter(c -> filter.match(c, game)) + .forEach(cardsToGainAbility::add); + game.getCommanderCardsFromCommandZone(controller, CommanderCardType.ANY).stream() + .filter(c -> filter.match(c, game)) + .forEach(cardsToGainAbility::add); + game.getStack().stream() + .filter(Spell.class::isInstance) + .filter(s -> s.isControlledBy(controller.getId())) + .filter(s -> filter.match((Spell) s, game)) + .map(s -> game.getCard(s.getSourceId())) + .filter(Objects::nonNull) + .forEach(cardsToGainAbility::add); + for (Card card : cardsToGainAbility) { + Ability ability = new WebSlingingAbility(card, "{G}{W}{U}"); + ability.setSourceId(card.getId()); + ability.setControllerId(card.getControllerOrOwnerId()); + game.getState().addOtherAbility(card, ability); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/p/PeterParkersCamera.java b/Mage.Sets/src/mage/cards/p/PeterParkersCamera.java new file mode 100644 index 00000000000..1cff6f90e94 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PeterParkersCamera.java @@ -0,0 +1,58 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CopyTargetStackObjectEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.FilterStackObject; +import mage.filter.common.FilterActivatedOrTriggeredAbility; +import mage.target.TargetStackObject; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PeterParkersCamera extends CardImpl { + + private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("activated or triggered ability you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public PeterParkersCamera(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + // This artifact enters with three film counters on it. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.FILM.createInstance(3)), + "with three film counters on it" + )); + + // {2}, {T}, Remove a film counter from this artifact: Copy target activated or triggered ability you control. You may choose new targets for the copy. + Ability ability = new SimpleActivatedAbility(new CopyTargetStackObjectEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addCost(new RemoveCountersSourceCost(CounterType.FILM.createInstance())); + ability.addTarget(new TargetStackObject(filter)); + this.addAbility(ability); + } + + private PeterParkersCamera(final PeterParkersCamera card) { + super(card); + } + + @Override + public PeterParkersCamera copy() { + return new PeterParkersCamera(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java b/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java index 2170f980cb3..7407044a59c 100644 --- a/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java +++ b/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java @@ -105,7 +105,7 @@ class PhenomenonInvestigatorsReturnCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/p/PhlageTitanOfFiresFury.java b/Mage.Sets/src/mage/cards/p/PhlageTitanOfFiresFury.java index 8da5502b868..aecefff7e03 100644 --- a/Mage.Sets/src/mage/cards/p/PhlageTitanOfFiresFury.java +++ b/Mage.Sets/src/mage/cards/p/PhlageTitanOfFiresFury.java @@ -79,7 +79,7 @@ class PhlageTitanOfFiresFuryEffect extends OneShotEffect { if (permanent == null) { return false; } - if (EscapeAbility.wasCastedWithEscape(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())) { + if (EscapeAbility.wasCastedWithEscape(game, source.getSourceId(), source.getStackMomentSourceZCC())) { return false; } return permanent.sacrifice(source, game); diff --git a/Mage.Sets/src/mage/cards/p/PhoenixWardenOfFire.java b/Mage.Sets/src/mage/cards/p/PhoenixWardenOfFire.java index f95a9a46b15..9a9ff1da417 100644 --- a/Mage.Sets/src/mage/cards/p/PhoenixWardenOfFire.java +++ b/Mage.Sets/src/mage/cards/p/PhoenixWardenOfFire.java @@ -94,8 +94,8 @@ class PhoenixWardenOfFireTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 6, game ); diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianDreadnought.java b/Mage.Sets/src/mage/cards/p/PhyrexianDreadnought.java index e08c351495b..dbeecf28f34 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianDreadnought.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianDreadnought.java @@ -17,6 +17,7 @@ import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.players.Player; import mage.target.TargetPermanent; import java.util.UUID; @@ -83,9 +84,15 @@ class PhyrexianDreadnoughtSacrificeCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + Player controller = game.getPlayer(controllerId); + if (controller == null) { + return false; + } int sumPower = 0; for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, controllerId, game)) { - sumPower += permanent.getPower().getValue(); + if (controller.canPaySacrificeCost(permanent, source, controllerId, game)) { + sumPower += permanent.getPower().getValue(); + } } return sumPower >= 12; } diff --git a/Mage.Sets/src/mage/cards/p/PicturesOfSpiderMan.java b/Mage.Sets/src/mage/cards/p/PicturesOfSpiderMan.java new file mode 100644 index 00000000000..480383b957c --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PicturesOfSpiderMan.java @@ -0,0 +1,48 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +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.CreateTokenEffect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.filter.StaticFilters; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PicturesOfSpiderMan extends CardImpl { + + public PicturesOfSpiderMan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{G}"); + + // When this artifact enters, look at the top five cards of your library. You may reveal up to two creature cards from among them and put them into your hand. Put the rest on the bottom of your library in a random order. + this.addAbility(new EntersBattlefieldTriggeredAbility(new LookLibraryAndPickControllerEffect( + 5, 2, StaticFilters.FILTER_CARD_CREATURES, PutCards.HAND, PutCards.BOTTOM_RANDOM + ))); + + // {1}, {T}, Sacrifice this artifact: Create a Treasure token. + Ability ability = new SimpleActivatedAbility(new CreateTokenEffect(new TreasureToken()), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private PicturesOfSpiderMan(final PicturesOfSpiderMan card) { + super(card); + } + + @Override + public PicturesOfSpiderMan copy() { + return new PicturesOfSpiderMan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PilgrimOfTheAges.java b/Mage.Sets/src/mage/cards/p/PilgrimOfTheAges.java index d5253ad0410..161b6dfcf56 100644 --- a/Mage.Sets/src/mage/cards/p/PilgrimOfTheAges.java +++ b/Mage.Sets/src/mage/cards/p/PilgrimOfTheAges.java @@ -10,9 +10,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.constants.Zone; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -22,13 +21,6 @@ import java.util.UUID; */ public final class PilgrimOfTheAges extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - public PilgrimOfTheAges(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); @@ -38,7 +30,7 @@ public final class PilgrimOfTheAges extends CardImpl { // When Pilgrim of the Ages enters the battlefield, you may search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInHandEffect( - new TargetCardInLibrary(filter), true + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true ), true)); // {6}: Return Pilgrim of the Ages from your graveyard to your hand. diff --git a/Mage.Sets/src/mage/cards/p/PillarLaunch.java b/Mage.Sets/src/mage/cards/p/PillarLaunch.java new file mode 100644 index 00000000000..53beb87d8d7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PillarLaunch.java @@ -0,0 +1,37 @@ +package mage.cards.p; + +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PillarLaunch extends CardImpl { + + public PillarLaunch(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); + + // Target creature gets +2/+2 and gains reach until end of turn. Untap it. + this.getSpellAbility().addEffect(new BoostTargetEffect(2, 2).setText("target creature gets +2/+2")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(ReachAbility.getInstance()).setText("and gain reach until end of turn")); + this.getSpellAbility().addEffect(new UntapTargetEffect("Untap it")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private PillarLaunch(final PillarLaunch card) { + super(card); + } + + @Override + public PillarLaunch copy() { + return new PillarLaunch(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PlagueReaver.java b/Mage.Sets/src/mage/cards/p/PlagueReaver.java index 8e9eed7c5fd..27bd07c4b44 100644 --- a/Mage.Sets/src/mage/cards/p/PlagueReaver.java +++ b/Mage.Sets/src/mage/cards/p/PlagueReaver.java @@ -128,7 +128,7 @@ class PlagueReaverDelayedTriggeredAbility extends DelayedTriggeredAbility { PlagueReaverDelayedTriggeredAbility(UUID playerId, Ability source) { super(new PlagueReaverReturnEffect(playerId).setTargetPointer( - new FixedTarget(source.getSourceId(), source.getSourceObjectZoneChangeCounter() + 1) + new FixedTarget(source.getSourceId(), source.getStackMomentSourceZCC() + 1) ), Duration.Custom, true, false); this.playerId = playerId; } diff --git a/Mage.Sets/src/mage/cards/p/Plasmancer.java b/Mage.Sets/src/mage/cards/p/Plasmancer.java index 3b34c120737..52e56ae77fc 100644 --- a/Mage.Sets/src/mage/cards/p/Plasmancer.java +++ b/Mage.Sets/src/mage/cards/p/Plasmancer.java @@ -8,8 +8,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -19,12 +19,7 @@ import java.util.UUID; */ public final class Plasmancer extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Swamp card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.SWAMP.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.SWAMP); public Plasmancer(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{B}{B}"); diff --git a/Mage.Sets/src/mage/cards/p/PolukranosUnchained.java b/Mage.Sets/src/mage/cards/p/PolukranosUnchained.java index b8cf9c491f8..0fd264eeeb2 100644 --- a/Mage.Sets/src/mage/cards/p/PolukranosUnchained.java +++ b/Mage.Sets/src/mage/cards/p/PolukranosUnchained.java @@ -98,7 +98,7 @@ class PolukranosUnchainedEffect extends OneShotEffect { int counters = 12; if (!(spellAbility instanceof EscapeAbility) || !spellAbility.getSourceId().equals(source.getSourceId()) - || permanent.getZoneChangeCounter(game) != spellAbility.getSourceObjectZoneChangeCounter()) { + || permanent.getZoneChangeCounter(game) != spellAbility.getStackMomentSourceZCC()) { counters = 6; } List appliedEffects = (ArrayList) this.getValue("appliedEffects"); diff --git a/Mage.Sets/src/mage/cards/p/PortalManipulator.java b/Mage.Sets/src/mage/cards/p/PortalManipulator.java index b4f39834bba..6ca9febadfb 100644 --- a/Mage.Sets/src/mage/cards/p/PortalManipulator.java +++ b/Mage.Sets/src/mage/cards/p/PortalManipulator.java @@ -38,7 +38,7 @@ import java.util.stream.Collectors; */ public final class PortalManipulator extends CardImpl { - private static final Condition condition = new IsStepCondition(PhaseStep.DECLARE_ATTACKERS); + private static final Condition condition = new IsStepCondition(PhaseStep.DECLARE_ATTACKERS, false); private static final FilterPermanent filter = new FilterAttackingCreature("attacking creatures controlled by that player's opponents"); static { diff --git a/Mage.Sets/src/mage/cards/p/PretendingPoxbearers.java b/Mage.Sets/src/mage/cards/p/PretendingPoxbearers.java new file mode 100644 index 00000000000..8c63e454914 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PretendingPoxbearers.java @@ -0,0 +1,40 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.AllyToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PretendingPoxbearers extends CardImpl { + + public PretendingPoxbearers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W/B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // When this creature dies, create a 1/1 white Ally creature token. + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new AllyToken()))); + } + + private PretendingPoxbearers(final PretendingPoxbearers card) { + super(card); + } + + @Override + public PretendingPoxbearers copy() { + return new PretendingPoxbearers(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PrimalAmulet.java b/Mage.Sets/src/mage/cards/p/PrimalAmulet.java index a687c4d3aba..fcf75d1ecbf 100644 --- a/Mage.Sets/src/mage/cards/p/PrimalAmulet.java +++ b/Mage.Sets/src/mage/cards/p/PrimalAmulet.java @@ -4,14 +4,13 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.StaticFilters; @@ -27,7 +26,7 @@ import java.util.UUID; */ public final class PrimalAmulet extends CardImpl { - private static final FilterCard filter = new FilterCard("Instant and sorcery spells"); + private static final FilterCard filter = new FilterCard("instant and sorcery spells"); static { filter.add(Predicates.or( @@ -45,7 +44,12 @@ public final class PrimalAmulet extends CardImpl { // Whenever you cast an instant or sorcery spell, put a charge counter on Primal Amulet. Then if there are four or more charge counters on it, you may remove those counters and transform it. this.addAbility(new TransformAbility()); - this.addAbility(new SpellCastControllerTriggeredAbility(new PrimalAmuletEffect(), StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false)); + Ability ability = new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.CHARGE.createInstance()), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false + ); + ability.addEffect(new PrimalAmuletEffect()); + this.addAbility(ability); } private PrimalAmulet(final PrimalAmulet card) { @@ -62,9 +66,8 @@ class PrimalAmuletEffect extends OneShotEffect { PrimalAmuletEffect() { super(Outcome.Benefit); - this.staticText = "put a charge counter on {this}. " - + "Then if there are four or more charge counters on it, " - + "you may remove those counters and transform it"; + this.staticText = "Then if there are four or more charge counters on it, " + + "you may remove those counters and transform it"; } private PrimalAmuletEffect(final PrimalAmuletEffect effect) { @@ -79,16 +82,15 @@ class PrimalAmuletEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null && player != null) { - permanent.addCounters(CounterType.CHARGE.createInstance(), source.getControllerId(), source, game); - int counters = permanent.getCounters(game).getCount(CounterType.CHARGE); - if (counters > 3 && player.chooseUse(Outcome.Benefit, "Transform this?", source, game)) { - permanent.removeCounters(CounterType.CHARGE.getName(), counters, source, game); - new TransformSourceEffect().apply(game, source); - } - return true; + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null + || player == null + || permanent.getCounters(game).getCount(CounterType.CHARGE) <= 3 + || !player.chooseUse(Outcome.Benefit, "Remove all charge counters from this and transform it?", source, game)) { + return false; } - return false; + permanent.removeAllCounters(CounterType.CHARGE.getName(), source, game); + permanent.transform(source, game); + return true; } } diff --git a/Mage.Sets/src/mage/cards/p/PrimalWellspring.java b/Mage.Sets/src/mage/cards/p/PrimalWellspring.java index 46e8959f144..024e7274a48 100644 --- a/Mage.Sets/src/mage/cards/p/PrimalWellspring.java +++ b/Mage.Sets/src/mage/cards/p/PrimalWellspring.java @@ -1,7 +1,5 @@ - package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -17,8 +15,9 @@ import mage.game.events.GameEvent; import mage.game.stack.Spell; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class PrimalWellspring extends CardImpl { @@ -33,9 +32,10 @@ public final class PrimalWellspring extends CardImpl { this.addAbility(ability); // When that mana is spent to cast an instant or sorcery spell, copy that spell and you may choose new targets for the copy. - Effect effect = new CopyTargetStackObjectEffect(true); - effect.setText("copy that spell and you may choose new targets for the copy"); - this.addAbility(new PyrimalWellspringTriggeredAbility(ability.getOriginalId(), effect)); + this.addAbility(new PrimalWellspringTriggeredAbility( + ability.getOriginalId(), new CopyTargetStackObjectEffect(true) + .setText("copy that spell and you may choose new targets for the copy") + )); } private PrimalWellspring(final PrimalWellspring card) { @@ -48,26 +48,26 @@ public final class PrimalWellspring extends CardImpl { } } -class PyrimalWellspringTriggeredAbility extends TriggeredAbilityImpl { +class PrimalWellspringTriggeredAbility extends TriggeredAbilityImpl { private static final FilterInstantOrSorcerySpell filter = new FilterInstantOrSorcerySpell(); String abilityOriginalId; - public PyrimalWellspringTriggeredAbility(UUID abilityOriginalId, Effect effect) { + public PrimalWellspringTriggeredAbility(UUID abilityOriginalId, Effect effect) { super(Zone.ALL, effect, false); this.abilityOriginalId = abilityOriginalId.toString(); setTriggerPhrase("When that mana is used to cast an instant or sorcery spell, "); } - private PyrimalWellspringTriggeredAbility(final PyrimalWellspringTriggeredAbility ability) { + private PrimalWellspringTriggeredAbility(final PrimalWellspringTriggeredAbility ability) { super(ability); this.abilityOriginalId = ability.abilityOriginalId; } @Override - public PyrimalWellspringTriggeredAbility copy() { - return new PyrimalWellspringTriggeredAbility(this); + public PrimalWellspringTriggeredAbility copy() { + return new PrimalWellspringTriggeredAbility(this); } @Override diff --git a/Mage.Sets/src/mage/cards/p/PrimordialMist.java b/Mage.Sets/src/mage/cards/p/PrimordialMist.java index 8960f2f2ef2..50718656957 100644 --- a/Mage.Sets/src/mage/cards/p/PrimordialMist.java +++ b/Mage.Sets/src/mage/cards/p/PrimordialMist.java @@ -78,7 +78,7 @@ class PrimordialMistCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/p/PrisonBreak.java b/Mage.Sets/src/mage/cards/p/PrisonBreak.java new file mode 100644 index 00000000000..3ac0e4aacef --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrisonBreak.java @@ -0,0 +1,38 @@ +package mage.cards.p; + +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.abilities.keyword.MayhemAbility; +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 PrisonBreak extends CardImpl { + + public PrisonBreak(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}"); + + // Return target creature card from your graveyard to the battlefield with a +1/+1 counter on it. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.P1P1.createInstance())); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + + // Mayhem {3}{B} + this.addAbility(new MayhemAbility(this, "{3}{B}")); + } + + private PrisonBreak(final PrisonBreak card) { + super(card); + } + + @Override + public PrisonBreak copy() { + return new PrisonBreak(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/ProfaneProcession.java b/Mage.Sets/src/mage/cards/p/ProfaneProcession.java index ae78c26a88a..881bab6e555 100644 --- a/Mage.Sets/src/mage/cards/p/ProfaneProcession.java +++ b/Mage.Sets/src/mage/cards/p/ProfaneProcession.java @@ -1,28 +1,25 @@ package mage.cards.p; -import java.util.UUID; - -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.ExileTargetForSourceEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SuperType; -import mage.constants.Zone; -import mage.game.ExileZone; import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; +import java.util.Optional; +import java.util.UUID; + /** * @author LevelX2 */ @@ -37,7 +34,11 @@ public final class ProfaneProcession extends CardImpl { // {3}{W}{B}: Exile target creature. Then if there are three or more cards exiled with Profane Procession, transform it. this.addAbility(new TransformAbility()); - Ability ability = new SimpleActivatedAbility(new ProfaneProcessionEffect(), new ManaCostsImpl<>("{3}{W}{B}")); + Ability ability = new SimpleActivatedAbility(new ExileTargetForSourceEffect(), new ManaCostsImpl<>("{3}{W}{B}")); + ability.addEffect(new ConditionalOneShotEffect( + new TransformSourceEffect(), ProfaneProcessionCondition.instance, + "Then if there are three or more cards exiled with {this}, transform it" + )); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } @@ -52,36 +53,14 @@ public final class ProfaneProcession extends CardImpl { } } -class ProfaneProcessionEffect extends OneShotEffect { - - ProfaneProcessionEffect() { - super(Outcome.Exile); - this.staticText = "Exile target creature. Then if there are three or more cards exiled with {this}, transform it."; - } - - private ProfaneProcessionEffect(final ProfaneProcessionEffect effect) { - super(effect); - } - - @Override - public ProfaneProcessionEffect copy() { - return new ProfaneProcessionEffect(this); - } +enum ProfaneProcessionCondition implements Condition { + instance; @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - UUID exileId = CardUtil.getCardExileZoneId(game, source); - MageObject sourceObject = source.getSourceObject(game); - if (controller != null && exileId != null && sourceObject != null) { - new ExileTargetEffect(exileId, sourceObject.getIdName()).setTargetPointer(this.getTargetPointer().copy()).apply(game, source); - game.processAction(); - ExileZone exileZone = game.getExile().getExileZone(exileId); - if (exileZone != null && exileZone.size() > 2) { - new TransformSourceEffect().apply(game, source); - } - return true; - } - return false; + return Optional + .ofNullable(game.getExile().getExileZone(CardUtil.getExileZoneId(game, source))) + .filter(cards -> cards.size() >= 3) + .isPresent(); } } diff --git a/Mage.Sets/src/mage/cards/p/ProfessionalWrestler.java b/Mage.Sets/src/mage/cards/p/ProfessionalWrestler.java new file mode 100644 index 00000000000..dadadaec3e1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ProfessionalWrestler.java @@ -0,0 +1,45 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.combat.CantBeBlockedByMoreThanOneSourceEffect; +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 ProfessionalWrestler extends CardImpl { + + public ProfessionalWrestler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.PERFORMER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When this creature enters, create a Treasure token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new TreasureToken()))); + + // This creature can't be blocked by more than one creature. + this.addAbility(new SimpleStaticAbility(new CantBeBlockedByMoreThanOneSourceEffect())); + } + + private ProfessionalWrestler(final ProfessionalWrestler card) { + super(card); + } + + @Override + public ProfessionalWrestler copy() { + return new ProfessionalWrestler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/ProteanHulk.java b/Mage.Sets/src/mage/cards/p/ProteanHulk.java index afde70d42f8..5a0a7d68168 100644 --- a/Mage.Sets/src/mage/cards/p/ProteanHulk.java +++ b/Mage.Sets/src/mage/cards/p/ProteanHulk.java @@ -64,8 +64,8 @@ class ProteanHulkTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 6, game); } diff --git a/Mage.Sets/src/mage/cards/p/ProtectorOfTheWastes.java b/Mage.Sets/src/mage/cards/p/ProtectorOfTheWastes.java index 4ab7cb7b658..90554e75de2 100644 --- a/Mage.Sets/src/mage/cards/p/ProtectorOfTheWastes.java +++ b/Mage.Sets/src/mage/cards/p/ProtectorOfTheWastes.java @@ -1,36 +1,36 @@ package mage.cards.p; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.ExileTargetEffect; -import mage.abilities.keyword.MonstrosityAbility; -import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.MonstrosityAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterArtifactOrEnchantmentPermanent; +import mage.game.Controllable; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + /** - * * @author Jmlundeen */ public final class ProtectorOfTheWastes extends CardImpl { public ProtectorOfTheWastes(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}"); - + this.subtype.add(SubType.DRAGON); this.power = new MageInt(5); this.toughness = new MageInt(5); @@ -76,36 +76,19 @@ class ProtectorOfTheWastesTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID id, Ability source, Game game) { - if (!super.canTarget(id, source, game)) { - return false; - } - Permanent permanent = game.getPermanent(id); - if (permanent == null) { - return false; - } - return this.getTargets().stream() + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + + Set usedControllers = this.getTargets().stream() .map(game::getPermanent) .filter(Objects::nonNull) - .noneMatch(perm -> !perm.getId().equals(permanent.getId()) - && perm.isControlledBy(permanent.getControllerId())); - } + .map(Controllable::getControllerId) + .collect(Collectors.toSet()); + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null || usedControllers.contains(permanent.getControllerId()); + }); - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - boolean validTarget = this.getTargets().stream() - .map(game::getPermanent) - .filter(Objects::nonNull) - .noneMatch(perm -> !perm.getId().equals(permanent.getId()) && perm.isControlledBy(permanent.getControllerId())); - if (validTarget) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } - } return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/p/ProwlerClawedThief.java b/Mage.Sets/src/mage/cards/p/ProwlerClawedThief.java new file mode 100644 index 00000000000..cd3983cd176 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ProwlerClawedThief.java @@ -0,0 +1,55 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.keyword.ConniveSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ProwlerClawedThief extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent(SubType.VILLAIN, "another Villain you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + public ProwlerClawedThief(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever another Villain you control enters, Prowler connives. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(new ConniveSourceEffect("{this}"), filter)); + } + + private ProwlerClawedThief(final ProwlerClawedThief card) { + super(card); + } + + @Override + public ProwlerClawedThief copy() { + return new ProwlerClawedThief(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PucasMischief.java b/Mage.Sets/src/mage/cards/p/PucasMischief.java index 9b19fbab9b1..7e6c133608b 100644 --- a/Mage.Sets/src/mage/cards/p/PucasMischief.java +++ b/Mage.Sets/src/mage/cards/p/PucasMischief.java @@ -1,21 +1,18 @@ package mage.cards.p; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.TargetController; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetControlledPermanent; import java.util.HashSet; import java.util.Set; @@ -33,7 +30,7 @@ public final class PucasMischief extends CardImpl { // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. Ability ability = new BeginningOfUpkeepTriggeredAbility(new ExchangeControlTargetEffect(Duration.EndOfGame, rule, false, true), true); - ability.addTarget(new TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_NON_LAND)); ability.addTarget(new PucasMischiefSecondTarget()); this.addAbility(ability); @@ -49,89 +46,53 @@ public final class PucasMischief extends CardImpl { } } -class TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent extends TargetControlledPermanent { - - public TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent() { - super(); - this.filter = this.filter.copy(); - filter.add(Predicates.not(CardType.LAND.getPredicate())); - withTargetName("nonland permanent you control"); - } - - private TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent(final TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent target) { - super(target); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } - } - return possibleTargets; - } - - @Override - public TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent copy() { - return new TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent(this); - } -} - class PucasMischiefSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public PucasMischiefSecondTarget() { - super(); - this.filter = this.filter.copy(); - filter.add(TargetController.OPPONENT.getControllerPredicate()); - filter.add(Predicates.not(CardType.LAND.getPredicate())); + super(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND); withTargetName("permanent an opponent controls with an equal or lesser mana value"); } private PucasMischiefSecondTarget(final PucasMischiefSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } @Override public boolean canTarget(UUID id, Ability source, Game game) { - if (super.canTarget(id, source, game)) { - Permanent target1 = game.getPermanent(source.getFirstTarget()); - Permanent opponentPermanent = game.getPermanent(id); - if (target1 != null && opponentPermanent != null) { - return target1.getManaValue() >= opponentPermanent.getManaValue(); - } + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + Permanent possiblePermanent = game.getPermanent(id); + if (ownPermanent == null || possiblePermanent == null) { + return false; } - return false; + return super.canTarget(id, source, game) && ownPermanent.getManaValue() >= possiblePermanent.getManaValue(); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (firstTarget.getManaValue() >= permanent.getManaValue()) { - possibleTargets.add(permanent.getId()); - } - } + + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (ownPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by cmc + if (ownPermanent.getManaValue() >= permanent.getManaValue()) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); + // AI hint with better outcome return super.chooseTarget(Outcome.GainControl, playerId, source, game); } diff --git a/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java b/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java index 8ef4d238bb9..6436185825a 100644 --- a/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java +++ b/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java @@ -11,7 +11,6 @@ import mage.constants.PutCards; import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; import java.util.UUID; @@ -20,7 +19,7 @@ import java.util.UUID; */ public final class PulsarSquadronAce extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.SPACECRAFT); + private static final FilterCard filter = new FilterCard(SubType.SPACECRAFT); public PulsarSquadronAce(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); diff --git a/Mage.Sets/src/mage/cards/p/PumpkinBombardment.java b/Mage.Sets/src/mage/cards/p/PumpkinBombardment.java new file mode 100644 index 00000000000..273d286e56e --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PumpkinBombardment.java @@ -0,0 +1,40 @@ +package mage.cards.p; + +import mage.abilities.costs.OrCost; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PumpkinBombardment extends CardImpl { + + public PumpkinBombardment(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B/R}"); + + // As an additional cost to cast this spell, discard a card or pay {2}. + this.getSpellAbility().addCost(new OrCost( + "discard a card or pay {2}", new DiscardCardCost(), new GenericManaCost(2) + )); + + // Pumpkin Bombardment deals 3 damage to target creature. + this.getSpellAbility().addEffect(new DamageTargetEffect(3)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private PumpkinBombardment(final PumpkinBombardment card) { + super(card); + } + + @Override + public PumpkinBombardment copy() { + return new PumpkinBombardment(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PuppetMaster.java b/Mage.Sets/src/mage/cards/p/PuppetMaster.java index fef7c930484..f819c91578e 100644 --- a/Mage.Sets/src/mage/cards/p/PuppetMaster.java +++ b/Mage.Sets/src/mage/cards/p/PuppetMaster.java @@ -85,7 +85,7 @@ class PuppetMasterEffect extends OneShotEffect { return false; } card = game.getCard(source.getSourceId()); - if (card == null || card.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter() + 1) { + if (card == null || card.getZoneChangeCounter(game) != source.getStackMomentSourceZCC() + 1) { return false; } Cost cost = new ManaCostsImpl<>("{U}{U}{U}"); diff --git a/Mage.Sets/src/mage/cards/p/PurplePentapus.java b/Mage.Sets/src/mage/cards/p/PurplePentapus.java new file mode 100644 index 00000000000..54cbd6152fd --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PurplePentapus.java @@ -0,0 +1,52 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PurplePentapus extends CardImpl { + + public PurplePentapus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.subtype.add(SubType.OCTOPUS); + this.subtype.add(SubType.STARFISH); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When this creature enters, surveil 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SurveilEffect(1))); + + // {2}{B}, Tap an untapped creature you control: Return this card from your graveyard to the battlefield tapped. + Ability ability = new SimpleActivatedAbility( + Zone.GRAVEYARD, new ReturnSourceFromGraveyardToBattlefieldEffect(true), new ManaCostsImpl<>("{2}{B}") + ); + ability.addCost(new TapTargetCost(StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURE)); + this.addAbility(ability); + } + + private PurplePentapus(final PurplePentapus card) { + super(card); + } + + @Override + public PurplePentapus copy() { + return new PurplePentapus(this); + } +} diff --git a/Mage.Sets/src/mage/cards/q/QueenKaylaBinKroog.java b/Mage.Sets/src/mage/cards/q/QueenKaylaBinKroog.java index ae49d30f05b..812ba34c56e 100644 --- a/Mage.Sets/src/mage/cards/q/QueenKaylaBinKroog.java +++ b/Mage.Sets/src/mage/cards/q/QueenKaylaBinKroog.java @@ -109,26 +109,11 @@ class QueenKaylaBinKroogTarget extends TargetCard { return new QueenKaylaBinKroogTarget(this); } - @Override - public boolean canTarget(UUID playerId, UUID id, Ability ability, Game game) { - if (!super.canTarget(playerId, id, ability, game)) { - return false; - } - Card card = game.getCard(id); - return card != null && 1 <= card.getManaValue() && card.getManaValue() <= 3 && this - .getTargets() - .stream() - .map(game::getCard) - .filter(Objects::nonNull) - .mapToInt(MageObject::getManaValue) - .noneMatch(x -> card.getManaValue() == x); - } - - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set manaValues = this + + Set usedManaValues = this .getTargets() .stream() .map(game::getCard) @@ -137,8 +122,9 @@ class QueenKaylaBinKroogTarget extends TargetCard { .collect(Collectors.toSet()); possibleTargets.removeIf(uuid -> { Card card = game.getCard(uuid); - return card != null && manaValues.contains(card.getManaValue()); + return card == null || usedManaValues.contains(card.getManaValue()); }); + return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/r/RabarooTroop.java b/Mage.Sets/src/mage/cards/r/RabarooTroop.java new file mode 100644 index 00000000000..e62ca233ca2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RabarooTroop.java @@ -0,0 +1,49 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LandfallAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.PlainscyclingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RabarooTroop extends CardImpl { + + public RabarooTroop(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + + this.subtype.add(SubType.RABBIT); + this.subtype.add(SubType.KANGAROO); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Landfall -- Whenever a land you control enters, this creature gains flying until end of turn and you gain 1 life. + Ability ability = new LandfallAbility(new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn)); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + this.addAbility(ability); + + // Plainscycling {2} + this.addAbility(new PlainscyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private RabarooTroop(final RabarooTroop card) { + super(card); + } + + @Override + public RabarooTroop copy() { + return new RabarooTroop(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RadioactiveSpider.java b/Mage.Sets/src/mage/cards/r/RadioactiveSpider.java new file mode 100644 index 00000000000..01ca91ace24 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RadioactiveSpider.java @@ -0,0 +1,61 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RadioactiveSpider extends CardImpl { + + private static final FilterCard filter = new FilterCard("Spider Hero card"); + + static { + filter.add(SubType.SPIDER.getPredicate()); + filter.add(SubType.HERO.getPredicate()); + } + + public RadioactiveSpider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.SPIDER); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Fateful Bite -- {2}, Sacrifice this creature: Search your library for a Spider Hero card, reveal it, put it into your hand, then shuffle. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), new GenericManaCost(2) + ); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability.withFlavorWord("Fateful Bite")); + } + + private RadioactiveSpider(final RadioactiveSpider card) { + super(card); + } + + @Override + public RadioactiveSpider copy() { + return new RadioactiveSpider(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RagingGoblinoids.java b/Mage.Sets/src/mage/cards/r/RagingGoblinoids.java new file mode 100644 index 00000000000..6ced9aed0d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RagingGoblinoids.java @@ -0,0 +1,42 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.MayhemAbility; +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 RagingGoblinoids extends CardImpl { + + public RagingGoblinoids(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.BERSERKER); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Mayhem {2}{R} + this.addAbility(new MayhemAbility(this, "{2}{R}")); + } + + private RagingGoblinoids(final RagingGoblinoids card) { + super(card); + } + + @Override + public RagingGoblinoids copy() { + return new RagingGoblinoids(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java b/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java index bfd78c1a564..1f32d863dae 100644 --- a/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java +++ b/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java @@ -9,17 +9,20 @@ import mage.abilities.condition.common.YouGainedLifeCondition; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.effects.common.UntapSourceEffect; import mage.abilities.effects.common.continuous.AddCardSubtypeAllEffect; import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; import mage.abilities.token.FoodAbility; import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; +import mage.watchers.common.PlayerGainedLifeWatcher; import java.util.UUID; @@ -29,6 +32,7 @@ import java.util.UUID; public final class RagostDeftGastronaut extends CardImpl { private static final Condition condition = new YouGainedLifeCondition(); + private static final Hint hint = new ConditionHint(condition); public RagostDeftGastronaut(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}"); @@ -40,13 +44,17 @@ public final class RagostDeftGastronaut extends CardImpl { this.toughness = new MageInt(2); // Artifacts you control are Foods in addition to their other types and have "{2}, {T}, Sacrifice this artifact: You gain 3 life." - Ability ability = new SimpleStaticAbility(new AddCardSubtypeAllEffect( + ContinuousEffect effect = new AddCardSubtypeAllEffect( StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACTS, SubType.FOOD, null - )); - ability.addEffect(new GainAbilityAllEffect( + ); + effect.getDependedToTypes().add(DependencyType.ArtifactAddingRemoving); + Ability ability = new SimpleStaticAbility(effect); + effect = new GainAbilityAllEffect( new FoodAbility(), Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACTS, "and have \"{2}, {T}, Sacrifice this artifact: You gain 3 life.\"" - )); + ); + effect.getDependedToTypes().add(DependencyType.ArtifactAddingRemoving); + ability.addEffect(effect); this.addAbility(ability); // {1}, {T}, Sacrifice a Food: Ragost deals 3 damage to each opponent. @@ -60,7 +68,7 @@ public final class RagostDeftGastronaut extends CardImpl { // At the beginning of each end step, if you gained life this turn, untap Ragost. this.addAbility(new BeginningOfEndStepTriggeredAbility( TargetController.ANY, new UntapSourceEffect(), false, condition - ).addHint(new ConditionHint(condition))); + ).addHint(hint), new PlayerGainedLifeWatcher()); } private RagostDeftGastronaut(final RagostDeftGastronaut card) { diff --git a/Mage.Sets/src/mage/cards/r/RainOfGore.java b/Mage.Sets/src/mage/cards/r/RainOfGore.java index dc769b3a515..32c46e62b10 100644 --- a/Mage.Sets/src/mage/cards/r/RainOfGore.java +++ b/Mage.Sets/src/mage/cards/r/RainOfGore.java @@ -1,7 +1,5 @@ - package mage.cards.r; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; @@ -10,20 +8,20 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.StackObject; import mage.players.Player; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class RainOfGore extends CardImpl { public RainOfGore(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{B}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}{R}"); // If a spell or ability would cause its controller to gain life, that player loses that much life instead. @@ -71,13 +69,10 @@ class RainOfGoreEffect extends ReplacementEffectImpl { public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.GAIN_LIFE; } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!game.getStack().isEmpty()) { - StackObject stackObject = game.getStack().getFirst(); - return stackObject.isControlledBy(event.getPlayerId()); - } - return false; + StackObject stackObject = game.getStack().getFirstOrNull(); + return stackObject != null && stackObject.isControlledBy(event.getPlayerId()); } } diff --git a/Mage.Sets/src/mage/cards/r/RaiseTheDraugr.java b/Mage.Sets/src/mage/cards/r/RaiseTheDraugr.java index f53cdc145ba..5ca5445d4bd 100644 --- a/Mage.Sets/src/mage/cards/r/RaiseTheDraugr.java +++ b/Mage.Sets/src/mage/cards/r/RaiseTheDraugr.java @@ -58,8 +58,8 @@ class RaiseTheDraugrTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (getTargets().isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/r/RalLeylineProdigy.java b/Mage.Sets/src/mage/cards/r/RalLeylineProdigy.java index a900ac858f3..10e3fac4eb7 100644 --- a/Mage.Sets/src/mage/cards/r/RalLeylineProdigy.java +++ b/Mage.Sets/src/mage/cards/r/RalLeylineProdigy.java @@ -150,7 +150,7 @@ class RalLeylineProdigyMinusEightEffect extends OneShotEffect { return false; } Set cards = player.getLibrary().getTopCards(game, 8); - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); player.moveCardsToExile(cards, source, game, true, exileId, sourceObject.getIdName()); for (Card card : cards) { if (game.getState().getZone(card.getId()) == Zone.EXILED) { @@ -203,4 +203,4 @@ class RalLeylineProdigyCastEffect extends AsThoughEffectImpl { allowCardToPlayWithoutMana(mainId, source, affectedControllerId, MageIdentifier.WithoutPayingManaCostAlternateCast, game); return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/r/RamThrough.java b/Mage.Sets/src/mage/cards/r/RamThrough.java index 582f0ccb988..18bac9209d1 100644 --- a/Mage.Sets/src/mage/cards/r/RamThrough.java +++ b/Mage.Sets/src/mage/cards/r/RamThrough.java @@ -7,15 +7,18 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.StaticFilters; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.EachTargetPointer; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import static mage.filter.StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL; @@ -47,6 +50,7 @@ class RamThroughEffect extends OneShotEffect { RamThroughEffect() { super(Outcome.Benefit); + this.setTargetPointer(new EachTargetPointer()); staticText = "Target creature you control deals damage equal to its power to target creature you don't control. " + "If the creature you control has trample, excess damage is dealt to that creature's controller instead."; } @@ -62,29 +66,31 @@ class RamThroughEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - if (source.getTargets().size() != 2) { - throw new IllegalStateException("It must have two targets, but found " + source.getTargets().size()); - } - - Permanent myPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - Permanent anotherPermanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - - if (myPermanent == null || anotherPermanent == null) { + List permanents = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (permanents.size() < 2) { return false; } - int power = myPermanent.getPower().getValue(); + Permanent permanent = permanents.get(0); + int power = permanent.getPower().getValue(); if (power < 1) { return false; } - if (!myPermanent.getAbilities().containsKey(TrampleAbility.getInstance().getId())) { - return anotherPermanent.damage(power, myPermanent.getId(), source, game, false, true) > 0; + Permanent creature = permanents.get(1); + if (!permanent.hasAbility(TrampleAbility.getInstance(), game)) { + return creature.damage(power, permanent.getId(), source, game) > 0; } - int lethal = anotherPermanent.getLethalDamage(myPermanent.getId(), game); - lethal = Math.min(lethal, power); - anotherPermanent.damage(lethal, myPermanent.getId(), source, game); - Player player = game.getPlayer(anotherPermanent.getControllerId()); - if (player != null && lethal < power) { - player.damage(power - lethal, myPermanent.getId(), source, game); + int excess = creature.damageWithExcess(power, permanent.getId(), source, game); + if (excess > 0) { + Optional.ofNullable(creature) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.damage(excess, permanent.getId(), source, game)); } return true; } diff --git a/Mage.Sets/src/mage/cards/r/RampagingClassmate.java b/Mage.Sets/src/mage/cards/r/RampagingClassmate.java new file mode 100644 index 00000000000..4f6e97b8695 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RampagingClassmate.java @@ -0,0 +1,47 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RampagingClassmate extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_ATTACKING_CREATURE, 1); + + public RampagingClassmate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.BERSERKER); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Whenever this creature attacks, it gets +1/+0 until end of turn for each other attacking creature. + this.addAbility(new AttacksTriggeredAbility(new BoostSourceEffect( + xValue, StaticValue.get(0), Duration.EndOfTurn, "it" + ))); + } + + private RampagingClassmate(final RampagingClassmate card) { + super(card); + } + + @Override + public RampagingClassmate copy() { + return new RampagingClassmate(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RampagingYaoGuai.java b/Mage.Sets/src/mage/cards/r/RampagingYaoGuai.java index 6a1fbc46c72..ab15c13a4ae 100644 --- a/Mage.Sets/src/mage/cards/r/RampagingYaoGuai.java +++ b/Mage.Sets/src/mage/cards/r/RampagingYaoGuai.java @@ -82,8 +82,8 @@ class RampagingYaoGuaiTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, GetXValue.instance.calculate(game, source, null), game); } diff --git a/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java b/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java index ad16f667ac9..4a63e71307c 100644 --- a/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java +++ b/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java @@ -89,7 +89,7 @@ class RanarTheEverWatchfulCostReductionEffect extends CostModificationEffectImpl public boolean applies(Ability abilityToModify, Ability source, Game game) { ForetoldWatcher watcher = game.getState().getWatcher(ForetoldWatcher.class); return (watcher != null - && watcher.countNumberForetellThisTurn() == 0 + && watcher.getPlayerForetellCountThisTurn(source.getControllerId()) == 0 && abilityToModify.isControlledBy(source.getControllerId()) && abilityToModify instanceof ForetellAbility); } diff --git a/Mage.Sets/src/mage/cards/r/RasputinDreamweaver.java b/Mage.Sets/src/mage/cards/r/RasputinDreamweaver.java index d499b452b17..34bfd6ab556 100644 --- a/Mage.Sets/src/mage/cards/r/RasputinDreamweaver.java +++ b/Mage.Sets/src/mage/cards/r/RasputinDreamweaver.java @@ -102,7 +102,7 @@ class RasputinDreamweaverWatcher extends Watcher { filter.add(TappedPredicate.UNTAPPED); } - private final Set startedUntapped = new HashSet<>(0); + private final Set startedUntapped = new HashSet<>(); RasputinDreamweaverWatcher() { super(WatcherScope.GAME); diff --git a/Mage.Sets/src/mage/cards/r/RaucousAudience.java b/Mage.Sets/src/mage/cards/r/RaucousAudience.java new file mode 100644 index 00000000000..70ea6650c62 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RaucousAudience.java @@ -0,0 +1,46 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.condition.common.FerociousCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.decorator.ConditionalManaEffect; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.abilities.hint.common.FerociousHint; +import mage.abilities.mana.SimpleManaAbility; +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 RaucousAudience extends CardImpl { + + public RaucousAudience(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // {T}: Add {G}. If you control a creature with power 4 or greater, add {G}{G} instead. + this.addAbility(new SimpleManaAbility(new ConditionalManaEffect( + new BasicManaEffect(Mana.GreenMana(2)), new BasicManaEffect(Mana.GreenMana(1)), + FerociousCondition.instance, "Add {G}. If you control a creature with power 4 or greater, add {G}{G} instead." + ), new TapSourceCost()).addHint(FerociousHint.instance)); + } + + private RaucousAudience(final RaucousAudience card) { + super(card); + } + + @Override + public RaucousAudience copy() { + return new RaucousAudience(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java b/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java index 365f3243085..ed4bd054210 100644 --- a/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java +++ b/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java @@ -1,7 +1,6 @@ package mage.cards.r; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.TransformIntoSourceTriggeredAbility; import mage.abilities.common.WerewolfBackTriggeredAbility; @@ -15,12 +14,11 @@ import mage.constants.SubType; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetOpponentOrPlaneswalker; +import mage.target.targetpointer.EachTargetPointer; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -69,6 +67,7 @@ class RavagerOfTheFellsEffect extends OneShotEffect { RavagerOfTheFellsEffect() { super(Outcome.Damage); + this.setTargetPointer(new EachTargetPointer()); staticText = "it deals 2 damage to target opponent or planeswalker and 2 damage " + "to up to one target creature that player or that planeswalker's controller controls."; } @@ -84,10 +83,11 @@ class RavagerOfTheFellsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - game.damagePlayerOrPermanent(source.getTargets().get(0).getFirstTarget(), 2, source.getSourceId(), source, game, false, true); - Permanent creature = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - if (creature != null) { - creature.damage(2, source.getSourceId(), source, game, false, true); + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + game.damagePlayerOrPermanent( + targetId, 2, source.getSourceId(), source, + game, false, true + ); } return true; } @@ -97,54 +97,30 @@ class RavagerOfTheFellsEffect extends OneShotEffect { class RavagerOfTheFellsTarget extends TargetPermanent { RavagerOfTheFellsTarget() { - super(0, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false); + super(0, 1, StaticFilters.FILTER_PERMANENT_CREATURE); } private RavagerOfTheFellsTarget(final RavagerOfTheFellsTarget target) { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Player player = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); - if (player == null) { - return false; - } - UUID firstTarget = player.getId(); - Permanent permanent = game.getPermanent(id); - if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) { - return super.canTarget(id, source, game); - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (StackObject item : game.getState().getStack()) { - if (item.getId().equals(source.getSourceId())) { - object = item; - } - if (item.getSourceId().equals(source.getSourceId())) { - object = item; - } + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - Player player = game.getPlayerOrPlaneswalkerController(playerId); - if (player != null) { - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.isControlledBy(player.getId())) { - possibleTargets.add(targetId); - } - } - } - } return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/r/RavenousDemon.java b/Mage.Sets/src/mage/cards/r/RavenousDemon.java index cef1ce7b990..3feef6706d8 100644 --- a/Mage.Sets/src/mage/cards/r/RavenousDemon.java +++ b/Mage.Sets/src/mage/cards/r/RavenousDemon.java @@ -1,8 +1,5 @@ - package mage.cards.r; -import java.util.UUID; - import mage.MageInt; import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; @@ -12,19 +9,16 @@ 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.target.common.TargetControlledPermanent; + +import java.util.UUID; /** * @author intimidatingant */ public final class RavenousDemon extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("Human"); - static { - filter.add(SubType.HUMAN.getPredicate()); - } + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.HUMAN, "Human"); public RavenousDemon(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); @@ -37,7 +31,7 @@ public final class RavenousDemon extends CardImpl { // Sacrifice a Human: Transform Ravenous Demon. Activate this ability only any time you could cast a sorcery. this.addAbility(new TransformAbility()); - this.addAbility(new ActivateAsSorceryActivatedAbility(Zone.BATTLEFIELD, new TransformSourceEffect(), new SacrificeTargetCost(filter))); + this.addAbility(new ActivateAsSorceryActivatedAbility(new TransformSourceEffect(), new SacrificeTargetCost(filter))); } private RavenousDemon(final RavenousDemon card) { diff --git a/Mage.Sets/src/mage/cards/r/RavenousSlime.java b/Mage.Sets/src/mage/cards/r/RavenousSlime.java index a0b95f69f27..1e59c281c3c 100644 --- a/Mage.Sets/src/mage/cards/r/RavenousSlime.java +++ b/Mage.Sets/src/mage/cards/r/RavenousSlime.java @@ -68,8 +68,7 @@ class RavenousSlimeEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); - Permanent sourceCreature = game.getPermanent(source.getSourceId()); - if (controller == null || sourceCreature == null) { + if (controller == null) { return false; } if (((ZoneChangeEvent) event).getFromZone() != Zone.BATTLEFIELD) { @@ -81,9 +80,8 @@ class RavenousSlimeEffect extends ReplacementEffectImpl { } int power = permanent.getPower().getValue(); controller.moveCards(permanent, Zone.EXILED, source, game); - return new AddCountersSourceEffect( - CounterType.P1P1.createInstance(power) - ).apply(game, source); + new AddCountersSourceEffect(CounterType.P1P1.createInstance(power)).apply(game, source); + return true; } @Override diff --git a/Mage.Sets/src/mage/cards/r/RazorPendulum.java b/Mage.Sets/src/mage/cards/r/RazorPendulum.java index a611d207e30..6a0e1d0d6b0 100644 --- a/Mage.Sets/src/mage/cards/r/RazorPendulum.java +++ b/Mage.Sets/src/mage/cards/r/RazorPendulum.java @@ -27,7 +27,7 @@ public final class RazorPendulum extends CardImpl { TargetController.EACH_PLAYER, new DamageTargetEffect(2, true, "that player"), false, condition - ).withTargetPointerSet(true)); + )); } private RazorPendulum(final RazorPendulum card) { diff --git a/Mage.Sets/src/mage/cards/r/RazorRings.java b/Mage.Sets/src/mage/cards/r/RazorRings.java new file mode 100644 index 00000000000..de184c5311d --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RazorRings.java @@ -0,0 +1,72 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetAttackingOrBlockingCreature; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RazorRings extends CardImpl { + + public RazorRings(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Razor Rings deals 4 damage to target attacking or blocking creature. You gain life equal to the excess damage dealt this way. + this.getSpellAbility().addEffect(new RazorRingsEffect()); + this.getSpellAbility().addTarget(new TargetAttackingOrBlockingCreature()); + } + + private RazorRings(final RazorRings card) { + super(card); + } + + @Override + public RazorRings copy() { + return new RazorRings(this); + } +} + +class RazorRingsEffect extends OneShotEffect { + + RazorRingsEffect() { + super(Outcome.Benefit); + staticText = "{this} deals 4 damage to target attacking or blocking creature. " + + "You gain life equal to the excess damage dealt this way"; + } + + private RazorRingsEffect(final RazorRingsEffect effect) { + super(effect); + } + + @Override + public RazorRingsEffect copy() { + return new RazorRingsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + int excess = permanent.damageWithExcess(4, source, game); + if (excess > 0) { + Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.gainLife(excess, game, source)); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/Realmwright.java b/Mage.Sets/src/mage/cards/r/Realmwright.java index 41d5a7a9ef0..1b45657dfe8 100644 --- a/Mage.Sets/src/mage/cards/r/Realmwright.java +++ b/Mage.Sets/src/mage/cards/r/Realmwright.java @@ -51,6 +51,7 @@ class RealmwrightEffect extends ContinuousEffectImpl { RealmwrightEffect() { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral); staticText = "Lands you control are the chosen type in addition to their other types"; + dependendToTypes.add(DependencyType.BecomeNonbasicLand); } private RealmwrightEffect(final RealmwrightEffect effect) { diff --git a/Mage.Sets/src/mage/cards/r/ReapIntellect.java b/Mage.Sets/src/mage/cards/r/ReapIntellect.java index 1e9f975dbfc..028cca76fd4 100644 --- a/Mage.Sets/src/mage/cards/r/ReapIntellect.java +++ b/Mage.Sets/src/mage/cards/r/ReapIntellect.java @@ -85,7 +85,7 @@ class ReapIntellectEffect extends OneShotEffect { int xCost = Math.min(CardUtil.getSourceCostsTag(game, source, "X", 0), targetPlayer.getHand().size()); TargetCard target = new TargetCard(0, xCost, Zone.HAND, filterNonLands); target.withNotTarget(true); - controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game); + controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game); for (UUID cardId : target.getTargets()) { Card chosenCard = game.getCard(cardId); if (chosenCard != null) { diff --git a/Mage.Sets/src/mage/cards/r/RebelliousCaptives.java b/Mage.Sets/src/mage/cards/r/RebelliousCaptives.java new file mode 100644 index 00000000000..bfb736d6453 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RebelliousCaptives.java @@ -0,0 +1,49 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.abilities.keyword.ExhaustAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RebelliousCaptives extends CardImpl { + + public RebelliousCaptives(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Exhaust -- {6}: Put two +1/+1 counters on this creature, then earthbend 2. + Ability ability = new ExhaustAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), new GenericManaCost(6) + ); + ability.addEffect(new EarthbendTargetEffect(2).concatBy(", then")); + ability.addTarget(new TargetControlledLandPermanent()); + this.addAbility(ability); + } + + private RebelliousCaptives(final RebelliousCaptives card) { + super(card); + } + + @Override + public RebelliousCaptives copy() { + return new RebelliousCaptives(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/Reciprocate.java b/Mage.Sets/src/mage/cards/r/Reciprocate.java index 500e2f03c4c..ddb05c1c5ad 100644 --- a/Mage.Sets/src/mage/cards/r/Reciprocate.java +++ b/Mage.Sets/src/mage/cards/r/Reciprocate.java @@ -1,20 +1,15 @@ package mage.cards.r; -import mage.MageObject; -import mage.abilities.Ability; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate; import mage.target.TargetPermanent; -import mage.watchers.common.PlayerDamagedBySourceWatcher; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -22,12 +17,18 @@ import java.util.UUID; */ public final class Reciprocate extends CardImpl { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature that dealt damage to you this turn"); + + static { + filter.add(new DamagedPlayerThisTurnPredicate(TargetController.YOU)); + } + public Reciprocate(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); // Exile target creature that dealt damage to you this turn. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addTarget(new ReciprocateTarget()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); } private Reciprocate(final Reciprocate card) { @@ -40,66 +41,3 @@ public final class Reciprocate extends CardImpl { } } - -class ReciprocateTarget extends TargetPermanent { - - public ReciprocateTarget() { - super(1, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false); - targetName = "creature that dealt damage to you this turn"; - } - - private ReciprocateTarget(final ReciprocateTarget target) { - super(target); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, source.getControllerId()); - if (watcher != null && watcher.hasSourceDoneDamage(id, game)) { - return super.canTarget(id, source, game); - } - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId); - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && watcher != null && watcher.hasSourceDoneDamage(targetId, game)) { - possibleTargets.add(targetId); - } - } - return possibleTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int remainingTargets = this.minNumberOfTargets - targets.size(); - if (remainingTargets == 0) { - return true; - } - int count = 0; - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game) - && watcher != null && watcher.hasSourceDoneDamage(permanent.getId(), game)) { - count++; - if (count >= remainingTargets) { - return true; - } - } - } - } - return false; - } - - @Override - public ReciprocateTarget copy() { - return new ReciprocateTarget(this); - } -} diff --git a/Mage.Sets/src/mage/cards/r/ReckonerShakedown.java b/Mage.Sets/src/mage/cards/r/ReckonerShakedown.java index 0d616b47e77..72f208a3d11 100644 --- a/Mage.Sets/src/mage/cards/r/ReckonerShakedown.java +++ b/Mage.Sets/src/mage/cards/r/ReckonerShakedown.java @@ -7,6 +7,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.Game; @@ -68,7 +69,7 @@ class ReckonerShakedownEffect extends OneShotEffect { return false; } player.revealCards(source, player.getHand(), game); - TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_A_NON_LAND); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); controller.choose(Outcome.Discard, player.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/r/RedirectLightning.java b/Mage.Sets/src/mage/cards/r/RedirectLightning.java new file mode 100644 index 00000000000..60822f32d1b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RedirectLightning.java @@ -0,0 +1,49 @@ +package mage.cards.r; + +import mage.abilities.costs.OrCost; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.ChooseNewTargetsTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterStackObject; +import mage.filter.predicate.other.NumberOfTargetsPredicate; +import mage.target.TargetStackObject; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RedirectLightning extends CardImpl { + + private static final FilterStackObject filter = new FilterStackObject("spell or ability with a single target"); + + static { + filter.add(new NumberOfTargetsPredicate(1)); + } + + public RedirectLightning(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); + + this.subtype.add(SubType.LESSON); + + // As an additional cost to cast this spell, pay 5 life or pay {2}. + this.getSpellAbility().addCost(new OrCost("pay 5 life or pay {2}", new PayLifeCost(5), new GenericManaCost(2))); + + // Change the target of target spell or ability with a single target. + this.getSpellAbility().addEffect(new ChooseNewTargetsTargetEffect(true, true)); + this.getSpellAbility().addTarget(new TargetStackObject(filter)); + } + + private RedirectLightning(final RedirectLightning card) { + super(card); + } + + @Override + public RedirectLightning copy() { + return new RedirectLightning(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RentIsDue.java b/Mage.Sets/src/mage/cards/r/RentIsDue.java new file mode 100644 index 00000000000..74d857c89f3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RentIsDue.java @@ -0,0 +1,52 @@ +package mage.cards.r; + +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.TappedPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RentIsDue extends CardImpl { + + private static final FilterControlledPermanent filter + = new FilterControlledPermanent("untapped creatures and/or Treasures you control"); + + static { + filter.add(TappedPredicate.UNTAPPED); + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.TREASURE.getPredicate() + )); + } + + public RentIsDue(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}"); + + // At the beginning of your end step, you may tap two untapped creatures and/or Treasures you control. If you do, draw a card. Otherwise, sacrifice this enchantment. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), + new SacrificeSourceEffect(), new TapTargetCost(2, filter) + ).setOtherwiseText("Otherwise"))); + } + + private RentIsDue(final RentIsDue card) { + super(card); + } + + @Override + public RentIsDue copy() { + return new RentIsDue(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/Retether.java b/Mage.Sets/src/mage/cards/r/Retether.java index 97db1ae084e..23315d003bd 100644 --- a/Mage.Sets/src/mage/cards/r/Retether.java +++ b/Mage.Sets/src/mage/cards/r/Retether.java @@ -87,7 +87,7 @@ class RetetherEffect extends OneShotEffect { for (Ability ability : aura.getAbilities()) { if (ability instanceof SpellAbility) { for (Target abilityTarget : ability.getTargets()) { - if (abilityTarget.possibleTargets(controller.getId(), game).contains(permanent.getId())) { + if (abilityTarget.possibleTargets(controller.getId(), source, game).contains(permanent.getId())) { target = abilityTarget.copy(); break auraLegalitySearch; } diff --git a/Mage.Sets/src/mage/cards/r/ReturnFromExtinction.java b/Mage.Sets/src/mage/cards/r/ReturnFromExtinction.java index 7c77ece8f41..2e00840a16d 100644 --- a/Mage.Sets/src/mage/cards/r/ReturnFromExtinction.java +++ b/Mage.Sets/src/mage/cards/r/ReturnFromExtinction.java @@ -58,8 +58,8 @@ class ReturnFromExtinctionTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (getTargets().isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java b/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java index 50f9f129c53..8309d525b8d 100644 --- a/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java +++ b/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java @@ -61,8 +61,8 @@ class ReunionOfTheHouseTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, m -> m.getPower().getValue(), 10, game); } diff --git a/Mage.Sets/src/mage/cards/r/RevealingEye.java b/Mage.Sets/src/mage/cards/r/RevealingEye.java index cbd392e737c..284c7eee69b 100644 --- a/Mage.Sets/src/mage/cards/r/RevealingEye.java +++ b/Mage.Sets/src/mage/cards/r/RevealingEye.java @@ -11,6 +11,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; @@ -82,7 +83,7 @@ class RevealingEyeEffect extends OneShotEffect { if (opponent.getHand().count(StaticFilters.FILTER_CARD_NON_LAND, game) < 1) { return true; } - TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_NON_LAND); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_NON_LAND); controller.choose(outcome, opponent.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/r/RevivalExperiment.java b/Mage.Sets/src/mage/cards/r/RevivalExperiment.java index bf79a994bd5..caa7c28ec3f 100644 --- a/Mage.Sets/src/mage/cards/r/RevivalExperiment.java +++ b/Mage.Sets/src/mage/cards/r/RevivalExperiment.java @@ -120,7 +120,7 @@ class RevivalExperimentTarget extends TargetCardInYourGraveyard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/r/RexCyberHound.java b/Mage.Sets/src/mage/cards/r/RexCyberHound.java index 3b04fb97384..90f7f61121b 100644 --- a/Mage.Sets/src/mage/cards/r/RexCyberHound.java +++ b/Mage.Sets/src/mage/cards/r/RexCyberHound.java @@ -129,7 +129,6 @@ class RexCyberhoundContinuousEffect extends ContinuousEffectImpl { for (Ability ability : card.getAbilities(game)) { if (ability.isActivatedAbility()) { ActivatedAbility copyAbility = (ActivatedAbility) ability.copy(); - copyAbility.setMaxActivationsPerTurn(1); perm.addAbility(copyAbility, source.getSourceId(), game, true); } } diff --git a/Mage.Sets/src/mage/cards/r/RhinoBarrelingBrute.java b/Mage.Sets/src/mage/cards/r/RhinoBarrelingBrute.java new file mode 100644 index 00000000000..d60ad82328e --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RhinoBarrelingBrute.java @@ -0,0 +1,118 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RhinoBarrelingBrute extends CardImpl { + + public RhinoBarrelingBrute(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}{G}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(6); + this.toughness = new MageInt(7); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever Rhino attacks, if you've cast a spell with mana value 4 or greater this turn, draw a card. + this.addAbility(new AttacksTriggeredAbility(new DrawCardSourceControllerEffect(1)) + .withInterveningIf(RhinoBarrelingBruteCondition.instance) + .addHint(RhinoBarrelingBruteCondition.getHint()), new RhinoBarrelingBruteWatcher()); + } + + private RhinoBarrelingBrute(final RhinoBarrelingBrute card) { + super(card); + } + + @Override + public RhinoBarrelingBrute copy() { + return new RhinoBarrelingBrute(this); + } +} + +enum RhinoBarrelingBruteCondition implements Condition { + instance; + private static final Hint hint = new ConditionHint(instance); + + public static Hint getHint() { + return hint; + } + + @Override + public boolean apply(Game game, Ability source) { + return RhinoBarrelingBruteWatcher.checkPlayer(game, source); + } + + @Override + public String toString() { + return "you've cast a spell with mana value 4 or greater this turn"; + } +} + +class RhinoBarrelingBruteWatcher extends Watcher { + + private final Set set = new HashSet<>(); + + RhinoBarrelingBruteWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST) { + return; + } + Spell spell = game.getSpell(event.getTargetId()); + if (spell != null && spell.getManaValue() >= 4) { + set.add(event.getPlayerId()); + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkPlayer(Game game, Ability source) { + return game + .getState() + .getWatcher(RhinoBarrelingBruteWatcher.class) + .set + .contains(source.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RhinosRampage.java b/Mage.Sets/src/mage/cards/r/RhinosRampage.java new file mode 100644 index 00000000000..8ed68d09334 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RhinosRampage.java @@ -0,0 +1,95 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +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.ComparisonType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class RhinosRampage extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature an opponent controls"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public RhinosRampage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R/G}"); + + + // Target creature you control gets +1/+0 until end of turn. It fights target creature an opponent controls. When excess damage is dealt to the creature an opponent controls this way, destroy up to one target noncreature artifact with mana value 3 or less. + this.getSpellAbility().addEffect(new BoostTargetEffect(1, 0)); + this.getSpellAbility().addEffect(new FightTargetsEffect() + .setText("It fights target creature an opponent controls")); + this.getSpellAbility().addEffect(new RhinosRampageEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + + } + + private RhinosRampage(final RhinosRampage card) { + super(card); + } + + @Override + public RhinosRampage copy() { + return new RhinosRampage(this); + } +} + +class RhinosRampageEffect extends OneShotEffect { + + RhinosRampageEffect() { + super(Outcome.BoostCreature); + staticText = "When excess damage is dealt to the creature an opponent controls this way, destroy up to one target noncreature " + + "artifact with mana value 3 or less"; + } + + protected RhinosRampageEffect(final RhinosRampageEffect effect) { + super(effect); + } + + @Override + public RhinosRampageEffect copy() { + return new RhinosRampageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanentOrLKIBattlefield(source.getTargets().get(1).getFirstTarget()); + if (permanent == null || permanent.getDamage() <= permanent.getToughness().getBaseValue()) { + return false; + } + FilterPermanent filter = new FilterArtifactPermanent("noncreature artifact with mana value 3 or less"); + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 3)); + + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new DestroyTargetEffect(), false); + ability.addTarget(new TargetPermanent(0, 1, filter)); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RiftElemental.java b/Mage.Sets/src/mage/cards/r/RiftElemental.java index cb9a06e5890..dab8891b0cb 100644 --- a/Mage.Sets/src/mage/cards/r/RiftElemental.java +++ b/Mage.Sets/src/mage/cards/r/RiftElemental.java @@ -99,7 +99,7 @@ class RiftElementalCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { Target target = new TargetPermanentOrSuspendedCard(filter, true); - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/r/RikkuResourcefulGuardian.java b/Mage.Sets/src/mage/cards/r/RikkuResourcefulGuardian.java index 360e59dfc59..b9636978bde 100644 --- a/Mage.Sets/src/mage/cards/r/RikkuResourcefulGuardian.java +++ b/Mage.Sets/src/mage/cards/r/RikkuResourcefulGuardian.java @@ -2,22 +2,22 @@ package mage.cards.r; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.PutCounterOnPermanentTriggeredAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.combat.CantBeBlockedByAllTargetEffect; import mage.abilities.effects.common.counter.MoveCounterTargetsEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.events.GameEvent; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetOpponentsCreaturePermanent; -import java.util.Optional; import java.util.UUID; /** @@ -35,7 +35,12 @@ public final class RikkuResourcefulGuardian extends CardImpl { this.toughness = new MageInt(3); // Whenever you put one or more counters on a creature, until end of turn, that creature can't be blocked by creatures your opponents control. - this.addAbility(new RikkuResourcefulGuardianTriggeredAbility()); + this.addAbility(new PutCounterOnPermanentTriggeredAbility( + new CantBeBlockedByAllTargetEffect( + StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, Duration.EndOfTurn + ).setText("until end of turn, that creature can't be blocked by creatures your opponents control"), + null, StaticFilters.FILTER_PERMANENT_CREATURE, true, false + )); // Steal -- {1}, {T}: Move a counter from target creature an opponent controls onto target creature you control. Activate only as a sorcery. Ability ability = new ActivateAsSorceryActivatedAbility(new MoveCounterTargetsEffect(), new GenericManaCost(1)); @@ -54,38 +59,3 @@ public final class RikkuResourcefulGuardian extends CardImpl { return new RikkuResourcefulGuardian(this); } } - -class RikkuResourcefulGuardianTriggeredAbility extends TriggeredAbilityImpl { - - RikkuResourcefulGuardianTriggeredAbility() { - super(Zone.BATTLEFIELD, new CantBeBlockedByAllTargetEffect( - StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, Duration.EndOfTurn - ).setText("until end of turn, that creature can't be blocked by creatures your opponents control")); - this.setTriggerPhrase("Whenever you put one or more counters on a creature, "); - } - - private RikkuResourcefulGuardianTriggeredAbility(final RikkuResourcefulGuardianTriggeredAbility ability) { - super(ability); - } - - @Override - public RikkuResourcefulGuardianTriggeredAbility copy() { - return new RikkuResourcefulGuardianTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.COUNTERS_ADDED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return isControlledBy(event.getPlayerId()) - && Optional - .ofNullable(event) - .map(GameEvent::getTargetId) - .map(game::getPermanent) - .map(permanent -> permanent.isCreature(game)) - .orElse(false); - } -} diff --git a/Mage.Sets/src/mage/cards/r/RivalsDuel.java b/Mage.Sets/src/mage/cards/r/RivalsDuel.java index 8e4851faec7..1f31733aa21 100644 --- a/Mage.Sets/src/mage/cards/r/RivalsDuel.java +++ b/Mage.Sets/src/mage/cards/r/RivalsDuel.java @@ -90,8 +90,8 @@ class RivalsDuelTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/r/RoboticsMastery.java b/Mage.Sets/src/mage/cards/r/RoboticsMastery.java new file mode 100644 index 00000000000..6502bf00d18 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RoboticsMastery.java @@ -0,0 +1,55 @@ +package mage.cards.r; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.permanent.token.RobotFlyingToken; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RoboticsMastery extends CardImpl { + + public RoboticsMastery(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{U}"); + + this.subtype.add(SubType.AURA); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When this Aura enters, create two 1/1 colorless Robot artifact creature tokens with flying. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new RobotFlyingToken(), 2))); + + // Enchanted creature gets +2/+2. + this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(2, 2))); + } + + private RoboticsMastery(final RoboticsMastery card) { + super(card); + } + + @Override + public RoboticsMastery copy() { + return new RoboticsMastery(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RocketPoweredGoblinGlider.java b/Mage.Sets/src/mage/cards/r/RocketPoweredGoblinGlider.java new file mode 100644 index 00000000000..aa519064a2a --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RocketPoweredGoblinGlider.java @@ -0,0 +1,59 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAttachToTarget; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CastFromGraveyardSourceCondition; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.MayhemAbility; +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 RocketPoweredGoblinGlider extends CardImpl { + + public RocketPoweredGoblinGlider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When this Equipment enters, if it was cast from your graveyard, attach it to target creature you control. + this.addAbility(new EntersBattlefieldAttachToTarget().withInterveningIf(CastFromGraveyardSourceCondition.instance)); + + // Equipped creature gets +2/+0 and has flying and haste. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 0)); + ability.addEffect(new GainAbilityAttachedEffect( + FlyingAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText("and has flying")); + ability.addEffect(new GainAbilityAttachedEffect( + HasteAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText("and haste")); + this.addAbility(ability); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + + // Mayhem {2} + this.addAbility(new MayhemAbility(this, "{2}")); + } + + private RocketPoweredGoblinGlider(final RocketPoweredGoblinGlider card) { + super(card); + } + + @Override + public RocketPoweredGoblinGlider copy() { + return new RocketPoweredGoblinGlider(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RockyRebuke.java b/Mage.Sets/src/mage/cards/r/RockyRebuke.java new file mode 100644 index 00000000000..799d43f7b6c --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RockyRebuke.java @@ -0,0 +1,34 @@ +package mage.cards.r; + +import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RockyRebuke extends CardImpl { + + public RockyRebuke(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // Target creature you control deals damage equal to its power to target creature an opponent controls. + this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetOpponentsCreaturePermanent()); + } + + private RockyRebuke(final RockyRebuke card) { + super(card); + } + + @Override + public RockyRebuke copy() { + return new RockyRebuke(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RoguesGallery.java b/Mage.Sets/src/mage/cards/r/RoguesGallery.java index e7769a5831e..2dad3fa0abd 100644 --- a/Mage.Sets/src/mage/cards/r/RoguesGallery.java +++ b/Mage.Sets/src/mage/cards/r/RoguesGallery.java @@ -84,7 +84,7 @@ class RoguesGalleryTarget extends TargetCardInYourGraveyard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/r/RoilingRegrowth.java b/Mage.Sets/src/mage/cards/r/RoilingRegrowth.java index 20f43c3368f..fc7f8d5a4c7 100644 --- a/Mage.Sets/src/mage/cards/r/RoilingRegrowth.java +++ b/Mage.Sets/src/mage/cards/r/RoilingRegrowth.java @@ -5,9 +5,7 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -17,8 +15,6 @@ import java.util.UUID; */ public final class RoilingRegrowth extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("basic land cards"); - public RoilingRegrowth(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); @@ -27,7 +23,7 @@ public final class RoilingRegrowth extends CardImpl { StaticFilters.FILTER_LAND, 1, null ).setText("Sacrifice a land.")); this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect( - new TargetCardInLibrary(0, 2, filter), true + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS), true )); } diff --git a/Mage.Sets/src/mage/cards/r/RokusMastery.java b/Mage.Sets/src/mage/cards/r/RokusMastery.java new file mode 100644 index 00000000000..628368354f8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RokusMastery.java @@ -0,0 +1,50 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.Game; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RokusMastery extends CardImpl { + + public RokusMastery(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{R}{R}"); + + // Roku's Mastery deals X damage to target creature. If X is 4 or greater, scry 2. + this.getSpellAbility().addEffect(new DamageTargetEffect(GetXValue.instance)); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new ScryEffect(2), RokusMasteryCondition.instance, "If X is 4 or greater, scry 2" + )); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private RokusMastery(final RokusMastery card) { + super(card); + } + + @Override + public RokusMastery copy() { + return new RokusMastery(this); + } +} + +enum RokusMasteryCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return GetXValue.instance.calculate(game, source, null) >= 4; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RoleReversal.java b/Mage.Sets/src/mage/cards/r/RoleReversal.java index 61a31de577e..12d30093ed1 100644 --- a/Mage.Sets/src/mage/cards/r/RoleReversal.java +++ b/Mage.Sets/src/mage/cards/r/RoleReversal.java @@ -53,8 +53,8 @@ class TargetPermanentsThatShareCardType extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (super.canTarget(playerId, id, source, game)) { if (!getTargets().isEmpty()) { Permanent targetOne = game.getPermanent(getTargets().get(0)); Permanent targetTwo = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/r/RootpathPurifier.java b/Mage.Sets/src/mage/cards/r/RootpathPurifier.java index 29b1953fdf6..056e37f8147 100644 --- a/Mage.Sets/src/mage/cards/r/RootpathPurifier.java +++ b/Mage.Sets/src/mage/cards/r/RootpathPurifier.java @@ -47,6 +47,7 @@ class RootpathPurifierEffect extends ContinuousEffectImpl { RootpathPurifierEffect() { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); staticText = "lands you control and land cards in your library are basic"; + dependendToTypes.add(DependencyType.BecomeNonbasicLand); } private RootpathPurifierEffect(final RootpathPurifierEffect effect) { diff --git a/Mage.Sets/src/mage/cards/r/RoughRhinoCavalry.java b/Mage.Sets/src/mage/cards/r/RoughRhinoCavalry.java new file mode 100644 index 00000000000..77b3f745b3a --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RoughRhinoCavalry.java @@ -0,0 +1,54 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.ExhaustAbility; +import mage.abilities.keyword.FirebendingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RoughRhinoCavalry extends CardImpl { + + public RoughRhinoCavalry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Firebending 2 + this.addAbility(new FirebendingAbility(2)); + + // Exhaust -- {8}: Put two +1/+1 counters on this creature. It gains trample until end of turn. + Ability ability = new ExhaustAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), new GenericManaCost(8) + ); + ability.addEffect(new GainAbilitySourceEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn + ).setText("It gains trample until end of turn")); + this.addAbility(ability); + } + + private RoughRhinoCavalry(final RoughRhinoCavalry card) { + super(card); + } + + @Override + public RoughRhinoCavalry copy() { + return new RoughRhinoCavalry(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RowdySnowballers.java b/Mage.Sets/src/mage/cards/r/RowdySnowballers.java new file mode 100644 index 00000000000..c6651fc17c5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RowdySnowballers.java @@ -0,0 +1,46 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RowdySnowballers extends CardImpl { + + public RowdySnowballers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When this creature enters, tap target creature an opponent controls and put a stun counter on it. + Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()); + ability.addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance()).setText("and put a stun counter on it")); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + } + + private RowdySnowballers(final RowdySnowballers card) { + super(card); + } + + @Override + public RowdySnowballers copy() { + return new RowdySnowballers(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RunAwayTogether.java b/Mage.Sets/src/mage/cards/r/RunAwayTogether.java index d5107d33568..62ab7a5d2f7 100644 --- a/Mage.Sets/src/mage/cards/r/RunAwayTogether.java +++ b/Mage.Sets/src/mage/cards/r/RunAwayTogether.java @@ -59,8 +59,8 @@ class RunAwayTogetherTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/r/RunoStromkirk.java b/Mage.Sets/src/mage/cards/r/RunoStromkirk.java index 1dd67837199..158b4130dae 100644 --- a/Mage.Sets/src/mage/cards/r/RunoStromkirk.java +++ b/Mage.Sets/src/mage/cards/r/RunoStromkirk.java @@ -2,17 +2,20 @@ package mage.cards.r; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.PutOnLibraryTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TransformAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.CardsImpl; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; @@ -46,9 +49,7 @@ public final class RunoStromkirk extends CardImpl { // At the beginning of your upkeep, look at the top card of your library. You may reveal that card. If a creature card with mana value 6 or greater is revealed this way, transform Runo Stromkirk. this.addAbility(new TransformAbility()); - this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new RunoStromkirkEffect() - )); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new RunoStromkirkEffect())); } private RunoStromkirk(final RunoStromkirk card) { diff --git a/Mage.Sets/src/mage/cards/s/SaberToothMooseLion.java b/Mage.Sets/src/mage/cards/s/SaberToothMooseLion.java new file mode 100644 index 00000000000..1b88e2c992c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SaberToothMooseLion.java @@ -0,0 +1,42 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.ForestcyclingAbility; +import mage.abilities.keyword.ReachAbility; +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 SaberToothMooseLion extends CardImpl { + + public SaberToothMooseLion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + + this.subtype.add(SubType.ELK); + this.subtype.add(SubType.CAT); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Forestcycling {2} + this.addAbility(new ForestcyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private SaberToothMooseLion(final SaberToothMooseLion card) { + super(card); + } + + @Override + public SaberToothMooseLion copy() { + return new SaberToothMooseLion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SageOfAncientLore.java b/Mage.Sets/src/mage/cards/s/SageOfAncientLore.java index 36105114896..156aa64d14e 100644 --- a/Mage.Sets/src/mage/cards/s/SageOfAncientLore.java +++ b/Mage.Sets/src/mage/cards/s/SageOfAncientLore.java @@ -4,9 +4,8 @@ import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.WerewolfFrontTriggeredAbility; -import mage.abilities.condition.common.TransformedCondition; +import mage.abilities.condition.common.NotTransformedCondition; import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; @@ -35,10 +34,13 @@ 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.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"))); + this.addAbility(new SimpleStaticAbility( + Zone.ALL, + new ConditionalContinuousEffect( + new SetBasePowerToughnessSourceEffect(CardsInControllerHandCount.ANY), NotTransformedCondition.instance, + "{this}'s power and toughness are each equal to the total number of cards in your hand" + ) + )); // When Sage of Ancient Lore enters the battlefield, draw a card. this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1), false)); diff --git a/Mage.Sets/src/mage/cards/s/SaguWildling.java b/Mage.Sets/src/mage/cards/s/SaguWildling.java index 8d08c6822c1..b81bfcdc405 100644 --- a/Mage.Sets/src/mage/cards/s/SaguWildling.java +++ b/Mage.Sets/src/mage/cards/s/SaguWildling.java @@ -1,27 +1,27 @@ 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.cards.OmenCard; import mage.constants.CardType; -import mage.filter.common.FilterBasicLandCard; +import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @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}"); - + 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); @@ -34,7 +34,7 @@ public final class SaguWildling extends OmenCard { // 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()); + TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); this.getSpellCard().getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(target, true)); this.finalizeOmen(); } diff --git a/Mage.Sets/src/mage/cards/s/SalvationSwan.java b/Mage.Sets/src/mage/cards/s/SalvationSwan.java index 9b5b56c967d..212423de2ce 100644 --- a/Mage.Sets/src/mage/cards/s/SalvationSwan.java +++ b/Mage.Sets/src/mage/cards/s/SalvationSwan.java @@ -123,7 +123,7 @@ class SalvationSwanTargetEffect extends OneShotEffect { game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); // move exiled cards to shared zone for better UX - String exileKey = SalvationSwan.VALUE_PREFIX + "_" + source.getSourceId() + "_" + source.getSourceObjectZoneChangeCounter(); + String exileKey = SalvationSwan.VALUE_PREFIX + "_" + source.getSourceId() + "_" + source.getStackMomentSourceZCC(); ExileZone sharedExileZone = game.getExile().createZone( CardUtil.getExileZoneId(exileKey, game), sourceObject.getIdName() diff --git a/Mage.Sets/src/mage/cards/s/SandmanShiftingScoundrel.java b/Mage.Sets/src/mage/cards/s/SandmanShiftingScoundrel.java new file mode 100644 index 00000000000..ff40e6a5215 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SandmanShiftingScoundrel.java @@ -0,0 +1,65 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.keyword.DauntAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SandmanShiftingScoundrel extends CardImpl { + + public SandmanShiftingScoundrel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SAND); + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Sandman's power and toughness are each equal to the number of lands you control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(LandsYouControlCount.instance))); + + // Sandman can't be blocked by creatures with power 2 or less. + this.addAbility(new DauntAbility()); + + // {3}{G}{G}: Return this card and target land card from your graveyard to the battlefield tapped. + Ability ability = new SimpleActivatedAbility( + new ReturnSourceFromGraveyardToBattlefieldEffect(true) + .setText("return this card"), + new ManaCostsImpl<>("{3}{G}{G}") + ); + ability.addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect(true) + .setText("and target land card from your graveyard to the battlefield tapped")); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_LAND)); + this.addAbility(ability); + } + + private SandmanShiftingScoundrel(final SandmanShiftingScoundrel card) { + super(card); + } + + @Override + public SandmanShiftingScoundrel copy() { + return new SandmanShiftingScoundrel(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SandmansQuicksand.java b/Mage.Sets/src/mage/cards/s/SandmansQuicksand.java new file mode 100644 index 00000000000..7b7574f6504 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SandmansQuicksand.java @@ -0,0 +1,54 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.keyword.MayhemAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SandmansQuicksand extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures your opponents control"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public SandmansQuicksand(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}{B}"); + + + // Mayhem {3}{B} + Ability mayhem = new MayhemAbility(this, "{3}{B}"); + mayhem.addEffect(new BoostAllEffect(-2, -2, Duration.EndOfTurn, filter, false)); + this.addAbility(mayhem); + + // All creatures get -2/-2 until end of turn. If this spell's mayhem cost was paid, creatures your opponents control get -2/-2 until end of turn instead. + ContinuousEffect effect = new BoostAllEffect(-2, -2, Duration.EndOfTurn); + effect.setText("All creatures get -2/-2 until end of turn. If this spell's mayhem cost was paid, " + + "creatures your opponents control get -2/-2 until end of turn instead" + ); + this.getSpellAbility().addEffect(effect); + + } + + private SandmansQuicksand(final SandmansQuicksand card) { + super(card); + } + + @Override + public SandmansQuicksand copy() { + return new SandmansQuicksand(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SatyrFiredancer.java b/Mage.Sets/src/mage/cards/s/SatyrFiredancer.java index 894acc8f25e..d82848028c9 100644 --- a/Mage.Sets/src/mage/cards/s/SatyrFiredancer.java +++ b/Mage.Sets/src/mage/cards/s/SatyrFiredancer.java @@ -1,29 +1,24 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.MageObject; -import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.common.EffectKeyValue; import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * @author LevelX2 */ @@ -53,8 +48,9 @@ public final class SatyrFiredancer extends CardImpl { class SatyrFiredancerTriggeredAbility extends TriggeredAbilityImpl { SatyrFiredancerTriggeredAbility() { - super(Zone.BATTLEFIELD, new SatyrFiredancerDamageEffect(), false); - this.setTargetAdjuster(SatyrFiredancerAdjuster.instance); + super(Zone.BATTLEFIELD, new DamageTargetEffect(new EffectKeyValue("damage", "that much")), false); + this.addTarget(new TargetCreaturePermanent().withTargetName("target creature that player controls")); + this.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); setTriggerPhrase("Whenever an instant or sorcery spell you control deals damage to an opponent, "); } @@ -96,48 +92,3 @@ class SatyrFiredancerTriggeredAbility extends TriggeredAbilityImpl { return true; } } - -class SatyrFiredancerDamageEffect extends OneShotEffect { - - SatyrFiredancerDamageEffect() { - super(Outcome.Damage); - this.staticText = "{this} deals that much damage to target creature that player controls"; - } - - private SatyrFiredancerDamageEffect(final SatyrFiredancerDamageEffect effect) { - super(effect); - } - - @Override - public SatyrFiredancerDamageEffect copy() { - return new SatyrFiredancerDamageEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent targetCreature = game.getPermanent(source.getFirstTarget()); - Player controller = game.getPlayer(source.getControllerId()); - if (targetCreature != null && controller != null) { - int damage = (Integer) this.getValue("damage"); - if (damage > 0) { - targetCreature.damage(damage, source.getSourceId(), source, game, false, true); - } - return true; - } - return false; - } -} - -enum SatyrFiredancerAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - Player opponent = game.getPlayer(ability.getEffects().get(0).getTargetPointer().getFirst(game, ability)); - if (opponent != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("creature controlled by " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponent.getId())); - ability.getTargets().add(new TargetPermanent(filter)); - } - } -} diff --git a/Mage.Sets/src/mage/cards/s/SavageMansion.java b/Mage.Sets/src/mage/cards/s/SavageMansion.java new file mode 100644 index 00000000000..3eaebb2799f --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SavageMansion.java @@ -0,0 +1,46 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.mana.GreenManaAbility; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SavageMansion extends CardImpl { + + public SavageMansion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {R} or {G}. + this.addAbility(new RedManaAbility()); + this.addAbility(new GreenManaAbility()); + + // {4}, {T}: Surveil 1. + Ability ability = new SimpleActivatedAbility(new SurveilEffect(1), new GenericManaCost(4)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private SavageMansion(final SavageMansion card) { + super(card); + } + + @Override + public SavageMansion copy() { + return new SavageMansion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScarletSpiderBenReilly.java b/Mage.Sets/src/mage/cards/s/ScarletSpiderBenReilly.java new file mode 100644 index 00000000000..dbe63994f49 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScarletSpiderBenReilly.java @@ -0,0 +1,100 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.condition.common.WebSlingingCondition; +import mage.abilities.costs.common.ReturnToHandChosenControlledPermanentCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.WebSlingingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class ScarletSpiderBenReilly extends CardImpl { + + public ScarletSpiderBenReilly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Web-slinging {R}{G} + this.addAbility(new WebSlingingAbility(this, "{R}{G}")); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Sensational Save -- If Scarlet Spider was cast using web-slinging, he enters with X +1/+1 counters on him, where X is the mana value of the returned creature. + String ruleText = CardUtil.italicizeWithEmDash("Sensational Save") + "If {this} was cast using web-slinging, " + + "he enters with X +1/+1 counters on him, where X is the mana value of the returned creature."; + this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(), + ScarletSpiderBenReillyValue.instance, false), + WebSlingingCondition.THIS, ruleText, "")); + } + + private ScarletSpiderBenReilly(final ScarletSpiderBenReilly card) { + super(card); + } + + @Override + public ScarletSpiderBenReilly copy() { + return new ScarletSpiderBenReilly(this); + } +} +enum ScarletSpiderBenReillyValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + + Spell spell = game.getSpellOrLKIStack(sourceAbility.getSourceId()); + if (spell == null || spell.getSpellAbility() == null) { + return 0; + } + + ReturnToHandChosenControlledPermanentCost returnCost = (ReturnToHandChosenControlledPermanentCost) spell.getSpellAbility().getCosts().stream() + .filter(cost -> cost instanceof ReturnToHandChosenControlledPermanentCost) + .findFirst() + .orElse(null); + if (returnCost == null || returnCost.getPermanents().isEmpty()) { + return 0; + } + + return returnCost.getPermanents().get(0).getManaValue(); + } + + @Override + public ScarletSpiderBenReillyValue copy() { + return this; + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return ""; + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScarletSpiderKaine.java b/Mage.Sets/src/mage/cards/s/ScarletSpiderKaine.java new file mode 100644 index 00000000000..11bf8e86311 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScarletSpiderKaine.java @@ -0,0 +1,56 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.MayhemAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ScarletSpiderKaine extends CardImpl { + + public ScarletSpiderKaine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Menace + this.addAbility(new MenaceAbility()); + + // When Scarlet Spider enters, you may discard a card. If you do, put a +1/+1 counter on him. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoIfCostPaid( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + .setText("put a +1/+1 counter on him"), + new DiscardCardCost() + ))); + + // Mayhem {B/R} + this.addAbility(new MayhemAbility(this, "{B/R}")); + } + + private ScarletSpiderKaine(final ScarletSpiderKaine card) { + super(card); + } + + @Override + public ScarletSpiderKaine copy() { + return new ScarletSpiderKaine(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SchoolDaze.java b/Mage.Sets/src/mage/cards/s/SchoolDaze.java new file mode 100644 index 00000000000..c48395cf109 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SchoolDaze.java @@ -0,0 +1,41 @@ +package mage.cards.s; + +import mage.abilities.Mode; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SchoolDaze extends CardImpl { + + public SchoolDaze(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}{U}"); + + // Choose one -- + // * Do Homework -- Draw three cards. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(3)); + this.getSpellAbility().withFirstModeFlavorWord("Do Homework"); + + // * Fight Crime -- Counter target spell. Draw a card. + this.getSpellAbility().addMode(new Mode(new CounterTargetEffect()) + .addEffect(new DrawCardSourceControllerEffect(1)) + .addTarget(new TargetSpell()) + .withFlavorWord("Fight Crime")); + } + + private SchoolDaze(final SchoolDaze card) { + super(card); + } + + @Override + public SchoolDaze copy() { + return new SchoolDaze(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScorpionSeethingStriker.java b/Mage.Sets/src/mage/cards/s/ScorpionSeethingStriker.java index ae85ab0fa00..ca42cd43563 100644 --- a/Mage.Sets/src/mage/cards/s/ScorpionSeethingStriker.java +++ b/Mage.Sets/src/mage/cards/s/ScorpionSeethingStriker.java @@ -2,17 +2,16 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.keyword.ConniveSourceEffect; +import mage.abilities.condition.common.MorbidCondition; +import mage.abilities.effects.keyword.ConniveTargetEffect; +import mage.abilities.hint.common.MorbidHint; import mage.abilities.keyword.DeathtouchAbility; 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.game.Game; import mage.target.common.TargetControlledCreaturePermanent; import java.util.UUID; @@ -36,9 +35,9 @@ public final class ScorpionSeethingStriker extends CardImpl { this.addAbility(DeathtouchAbility.getInstance()); // At the beginning of your end step, if a creature died this turn, target creature you control connives. - Ability ability = new BeginningOfEndStepTriggeredAbility(new ScorpionSeethingStrikerEffect()); + Ability ability = new BeginningOfEndStepTriggeredAbility(new ConniveTargetEffect()).withInterveningIf(MorbidCondition.instance); ability.addTarget(new TargetControlledCreaturePermanent()); - this.addAbility(ability); + this.addAbility(ability.addHint(MorbidHint.instance)); } private ScorpionSeethingStriker(final ScorpionSeethingStriker card) { @@ -50,27 +49,3 @@ public final class ScorpionSeethingStriker extends CardImpl { return new ScorpionSeethingStriker(this); } } - -class ScorpionSeethingStrikerEffect extends OneShotEffect { - - ScorpionSeethingStrikerEffect() { - super(Outcome.Benefit); - staticText = "target creature you control connives"; - } - - private ScorpionSeethingStrikerEffect(final ScorpionSeethingStrikerEffect effect) { - super(effect); - } - - @Override - public ScorpionSeethingStrikerEffect copy() { - return new ScorpionSeethingStrikerEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return ConniveSourceEffect.connive( - game.getPermanent(getTargetPointer().getFirst(game, source)), 1, source, game - ); - } -} diff --git a/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java b/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java index 44072938fec..ffc40661fda 100644 --- a/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java +++ b/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java @@ -96,8 +96,8 @@ class ScoutForSurvivorsTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 3, game ); diff --git a/Mage.Sets/src/mage/cards/s/ScoutingHawk.java b/Mage.Sets/src/mage/cards/s/ScoutingHawk.java index e77d45c12f3..5917de178d7 100644 --- a/Mage.Sets/src/mage/cards/s/ScoutingHawk.java +++ b/Mage.Sets/src/mage/cards/s/ScoutingHawk.java @@ -10,8 +10,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; @@ -22,13 +20,6 @@ import java.util.UUID; */ public final class ScoutingHawk extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS); public ScoutingHawk(UUID ownerId, CardSetInfo setInfo) { @@ -43,7 +34,7 @@ public final class ScoutingHawk extends CardImpl { // Keen Sight — When Scouting Hawk enters the battlefield, if an opponent controls more lands than you, search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true) + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true) ).withInterveningIf(condition).withFlavorWord("Keen Sight")); } diff --git a/Mage.Sets/src/mage/cards/s/ScreamsFromWithin.java b/Mage.Sets/src/mage/cards/s/ScreamsFromWithin.java index 6037655954e..a2276bf5f14 100644 --- a/Mage.Sets/src/mage/cards/s/ScreamsFromWithin.java +++ b/Mage.Sets/src/mage/cards/s/ScreamsFromWithin.java @@ -77,7 +77,7 @@ class ScreamsFromWithinEffect extends OneShotEffect { if (aura != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD && player != null && player.getGraveyard().contains(source.getSourceId())) { for (Permanent creaturePermanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source, game)) { for (Target target : aura.getSpellAbility().getTargets()) { - if (target.canTarget(creaturePermanent.getId(), game)) { + if (target.canTarget(creaturePermanent.getId(), source, game)) { return player.moveCards(aura, Zone.BATTLEFIELD, source, game); } } diff --git a/Mage.Sets/src/mage/cards/s/ScroungedScythe.java b/Mage.Sets/src/mage/cards/s/ScroungedScythe.java index 0c1473693d5..0d3e7b0075b 100644 --- a/Mage.Sets/src/mage/cards/s/ScroungedScythe.java +++ b/Mage.Sets/src/mage/cards/s/ScroungedScythe.java @@ -1,11 +1,8 @@ - - package mage.cards.s; -import java.util.UUID; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; import mage.abilities.condition.common.EquippedHasSubtypeCondition; -import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; @@ -13,17 +10,21 @@ import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.MenaceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.target.common.TargetControlledCreaturePermanent; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; /** - * * @author fireshoes */ public final class ScroungedScythe extends CardImpl { + private static final Condition condition = new EquippedHasSubtypeCondition(SubType.HUMAN); + public ScroungedScythe(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},""); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); this.subtype.add(SubType.EQUIPMENT); this.nightCard = true; @@ -32,15 +33,14 @@ public final class ScroungedScythe extends CardImpl { this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(1, 1))); // As long as equipped creature is a Human, it has menace. - this.addAbility(new SimpleStaticAbility( - new ConditionalContinuousEffect( - new GainAbilityAttachedEffect(new MenaceAbility(), AttachmentType.EQUIPMENT), - new EquippedHasSubtypeCondition(SubType.HUMAN), - "As long as equipped creature is a Human, it has menace. " + - "(It can't be blocked except by two or more creatures.)"))); + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilityAttachedEffect(new MenaceAbility(false), AttachmentType.EQUIPMENT), + condition, "As long as equipped creature is a Human, it has menace. " + + "(It can't be blocked except by two or more creatures.)" + ))); // Equip {2} - this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(2), new TargetControlledCreaturePermanent(), false)); + this.addAbility(new EquipAbility(2, false)); } private ScroungedScythe(final ScroungedScythe card) { diff --git a/Mage.Sets/src/mage/cards/s/SearingBlaze.java b/Mage.Sets/src/mage/cards/s/SearingBlaze.java index 9c76774aa3e..113afd02074 100644 --- a/Mage.Sets/src/mage/cards/s/SearingBlaze.java +++ b/Mage.Sets/src/mage/cards/s/SearingBlaze.java @@ -1,6 +1,5 @@ package mage.cards.s; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -11,19 +10,16 @@ import mage.constants.Outcome; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetPlayerOrPlaneswalker; import mage.watchers.common.LandfallWatcher; -import java.util.HashSet; import java.util.Set; import java.util.UUID; /** - * @author BetaSteward_at_googlemail.com - * @author North + * @author BetaSteward_at_googlemail.com, North */ public final class SearingBlaze extends CardImpl { @@ -111,21 +107,21 @@ class SearingBlazeTarget extends TargetPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - Player player = game.getPlayerOrPlaneswalkerController(playerId); - if (player != null) { - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.isControlledBy(player.getId())) { - possibleTargets.add(targetId); - } - } - } + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } + return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/s/SecretIdentity.java b/Mage.Sets/src/mage/cards/s/SecretIdentity.java new file mode 100644 index 00000000000..fa0a7cb8baf --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SecretIdentity.java @@ -0,0 +1,61 @@ +package mage.cards.s; + +import mage.abilities.Mode; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SecretIdentity extends CardImpl { + + public SecretIdentity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + + // Choose one -- + this.getSpellAbility().getModes().setMinModes(1); + this.getSpellAbility().getModes().setMaxModes(1); + // * Conceal -- Until end of turn, target creature you control becomes a Citizen with base power and toughness 1/1 and gains hexproof. + this.getSpellAbility().addEffect(new BecomesCreatureTargetEffect( + new CreatureToken(1, 1, "Citizen with base power and toughness 1/1 and gains hexproof") + .withSubType(SubType.CITIZEN) + .withAbility(HexproofAbility.getInstance()), + false, false, Duration.EndOfTurn) + ); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().withFirstModeFlavorWord("Conceal"); + + // * Reveal -- Until end of turn, target creature you control becomes a Hero with base power and toughness 3/4 and gains flying and vigilance. + Mode mode = new Mode(new BecomesCreatureTargetEffect( + new CreatureToken(3, 4, "Hero with base power and toughness 3/4 and gains flying and vigilance") + .withSubType(SubType.HERO) + .withAbility(FlyingAbility.getInstance()) + .withAbility(VigilanceAbility.getInstance()), + false, false, Duration.EndOfTurn)); + mode.withFlavorWord("Reveal"); + mode.addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addMode(mode); + } + + private SecretIdentity(final SecretIdentity card) { + super(card); + } + + @Override + public SecretIdentity copy() { + return new SecretIdentity(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeethingLandscape.java b/Mage.Sets/src/mage/cards/s/SeethingLandscape.java index 7f300662195..2d0fcb893f2 100644 --- a/Mage.Sets/src/mage/cards/s/SeethingLandscape.java +++ b/Mage.Sets/src/mage/cards/s/SeethingLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class SeethingLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Island, Swamp, or Mountain card"); + private static final FilterCard filter = new FilterBasicCard("a basic Island, Swamp, or Mountain card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/s/SeismicTutelage.java b/Mage.Sets/src/mage/cards/s/SeismicTutelage.java new file mode 100644 index 00000000000..c93c4e06f2c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeismicTutelage.java @@ -0,0 +1,54 @@ +package mage.cards.s; + +import mage.abilities.common.AttacksAttachedTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DoubleCountersTargetEffect; +import mage.abilities.effects.common.counter.AddCountersAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SeismicTutelage extends CardImpl { + + public SeismicTutelage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When this Aura enters, put a +1/+1 counter on enchanted creature. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new AddCountersAttachedEffect(CounterType.P1P1.createInstance(), "enchanted creature") + )); + + // Whenever enchanted creature attacks, double the number of +1/+1 counters on it. + this.addAbility(new AttacksAttachedTriggeredAbility( + new DoubleCountersTargetEffect(CounterType.P1P1), + AttachmentType.AURA, false, SetTargetPointer.PERMANENT + )); + } + + private SeismicTutelage(final SeismicTutelage card) { + super(card); + } + + @Override + public SeismicTutelage copy() { + return new SeismicTutelage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SensationalSpiderMan.java b/Mage.Sets/src/mage/cards/s/SensationalSpiderMan.java index 0549474c5a1..ae40fe883bd 100644 --- a/Mage.Sets/src/mage/cards/s/SensationalSpiderMan.java +++ b/Mage.Sets/src/mage/cards/s/SensationalSpiderMan.java @@ -95,7 +95,7 @@ class SensationalSpiderManEffect extends OneShotEffect { // TODO: this ideally would be able to prevent the player from choosing a number greater than the number of stun counters available to remove TargetPermanentAmount target = new TargetPermanentAmount(3, 0, filter); target.withNotTarget(true); - player.choose(outcome, target, source, game); + player.chooseTarget(outcome, target, source, game); int amountRemoved = target .getTargets() .stream() diff --git a/Mage.Sets/src/mage/cards/s/SereneSleuth.java b/Mage.Sets/src/mage/cards/s/SereneSleuth.java new file mode 100644 index 00000000000..89a8c8b1393 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SereneSleuth.java @@ -0,0 +1,132 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.GoadedPredicate; +import mage.game.Game; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SereneSleuth extends CardImpl { + + private final static FilterPermanent filter = new FilterControlledCreaturePermanent("goaded creatures you control"); + + static { + filter.add(GoadedPredicate.instance); + } + + public SereneSleuth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Serene Sleuth enters the battlefield, investigate. + this.addAbility(new EntersBattlefieldTriggeredAbility(new InvestigateEffect())); + + // At the beginning of combat on your turn, investigate for each goaded creature you control. Then each creature you control is no longer goaded. + Ability triggeredAbility = new BeginningOfCombatTriggeredAbility( + new InvestigateEffect(new PermanentsOnBattlefieldCount(filter)) + .setText("investigate for each goaded creature you control.") + ); + triggeredAbility.addEffect(new SereneSleuthEffect(filter) + .concatBy("Then")); + this.addAbility(triggeredAbility); + } + + private SereneSleuth(final SereneSleuth card) { + super(card); + } + + @Override + public SereneSleuth copy() { + return new SereneSleuth(this); + } +} +class SereneSleuthEffect extends ContinuousEffectImpl { + + private MageObjectReference sourceMor; + private final FilterPermanent filter; + + public SereneSleuthEffect(FilterPermanent filter) { + super(Duration.Custom, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); + staticText = "each creature you control is no longer goaded"; + this.filter = filter; + } + + private SereneSleuthEffect(final SereneSleuthEffect effect) { + super(effect); + this.sourceMor = effect.sourceMor; + this.filter = effect.filter; + } + + public SereneSleuthEffect copy() { + return new SereneSleuthEffect(this); + } + + public MageObjectReference getSourceMor() { + return sourceMor; + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + // discard previous effect if it exists to not clutter + for (ContinuousEffect effect : game.getContinuousEffects().getLayeredEffects(game)) { + if (effect instanceof SereneSleuthEffect) { + MageObjectReference effectsMor = ((SereneSleuthEffect) effect).getSourceMor(); + if (effectsMor != null && effectsMor.refersTo(source.getSourceId(), game)) { + effect.discard(); + this.affectedObjectList.addAll(effect.getAffectedObjects()); + } + } + } + sourceMor = new MageObjectReference(source.getSourceObject(game), game); + setAffectedObjectsSet(true); + game.getBattlefield() + .getActivePermanents( + filter, source.getControllerId(), source, game + ).stream() + .map(permanent -> new MageObjectReference(permanent, game)) + .forEach(mor -> { + if (!this.affectedObjectList.contains(mor)) { + this.affectedObjectList.add(mor); + } + }); + } + + @Override + public boolean apply(Game game, Ability source) { + if (getAffectedObjectsSet()) { + this.affectedObjectList.removeIf(mor -> !mor.zoneCounterIsCurrent(game) + || mor.getPermanent(game) == null); + if (affectedObjectList.isEmpty()) { + discard(); + return false; + } + for (MageObjectReference mor : this.affectedObjectList) { + mor.getPermanent(game).getGoadingPlayers().clear(); + } + return true; + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SerpentOfThePass.java b/Mage.Sets/src/mage/cards/s/SerpentOfThePass.java new file mode 100644 index 00000000000..a868ebce450 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SerpentOfThePass.java @@ -0,0 +1,71 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashSourceEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SerpentOfThePass extends CardImpl { + + private static final Condition condition = new CardsInControllerGraveyardCondition(3, new FilterCard(SubType.LESSON)); + private static final Hint hint = new ValueHint( + "Lesson cards in your graveyard", new CardsInControllerGraveyardCount(new FilterCard(SubType.LESSON)) + ); + private static final FilterCard filter = new FilterCard("noncreature, nonland card"); + + static { + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + filter.add(Predicates.not(CardType.LAND.getPredicate())); + } + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter); + private static final Hint hint2 = new ValueHint("Noncreature, nonland cards in your graveyard", xValue); + + public SerpentOfThePass(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); + + this.subtype.add(SubType.SERPENT); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // If there are three or more Lesson cards in your graveyard, you may cast this spell as though it had flash. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConditionalAsThoughEffect( + new CastAsThoughItHadFlashSourceEffect(Duration.EndOfGame), condition + ).setText("if there are three or more Lesson cards in your graveyard, " + + "you may cast this spell as though it had flash.")).addHint(hint)); + + // This spell costs {1} less to cast for each noncreature, nonland card in your graveyard. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) + ).setRuleAtTheTop(true).addHint(hint2)); + } + + private SerpentOfThePass(final SerpentOfThePass card) { + super(card); + } + + @Override + public SerpentOfThePass copy() { + return new SerpentOfThePass(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SerraAvenger.java b/Mage.Sets/src/mage/cards/s/SerraAvenger.java index 372ca879671..3a6885f576a 100644 --- a/Mage.Sets/src/mage/cards/s/SerraAvenger.java +++ b/Mage.Sets/src/mage/cards/s/SerraAvenger.java @@ -1,24 +1,17 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.ruleModifying.CantCastDuringFirstThreeTurnsEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.players.Player; + +import java.util.UUID; /** @@ -27,21 +20,20 @@ import mage.players.Player; public final class SerraAvenger extends CardImpl { public SerraAvenger(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{W}"); this.subtype.add(SubType.ANGEL); this.power = new MageInt(3); this.toughness = new MageInt(3); // You can't cast Serra Avenger during your first, second, or third turns of the game. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new CantCastSerraAvengerEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new CantCastDuringFirstThreeTurnsEffect())); // Flying this.addAbility(FlyingAbility.getInstance()); // Vigilance this.addAbility(VigilanceAbility.getInstance()); - } private SerraAvenger(final SerraAvenger card) { @@ -53,37 +45,3 @@ public final class SerraAvenger extends CardImpl { return new SerraAvenger(this); } } - -class CantCastSerraAvengerEffect extends ContinuousRuleModifyingEffectImpl { - - CantCastSerraAvengerEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - staticText = "You can't cast this spell during your first, second, or third turns of the game"; - } - - private CantCastSerraAvengerEffect(final CantCastSerraAvengerEffect effect) { - super(effect); - } - - @Override - public CantCastSerraAvengerEffect copy() { - return new CantCastSerraAvengerEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.CAST_SPELL; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getSourceId().equals(source.getSourceId())) { - Player controller = game.getPlayer(source.getControllerId()); - // it can be cast on other players turn 1 - 3 if some effect let allow you to do this - if (controller != null && controller.getTurns() <= 3 && game.isActivePlayer(source.getControllerId())) { - return true; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/s/ServantOfTheScale.java b/Mage.Sets/src/mage/cards/s/ServantOfTheScale.java index 8ecde4bfcbb..0bb686c5766 100644 --- a/Mage.Sets/src/mage/cards/s/ServantOfTheScale.java +++ b/Mage.Sets/src/mage/cards/s/ServantOfTheScale.java @@ -76,8 +76,8 @@ class ServantOfTheScaleEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); Permanent sourcePermanent = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); if (sourcePermanent != null && controller != null - && (sourcePermanent.getZoneChangeCounter(game) == source.getSourceObjectZoneChangeCounter() // Token - || sourcePermanent.getZoneChangeCounter(game) + 1 == source.getSourceObjectZoneChangeCounter())) { // PermanentCard + && (sourcePermanent.getZoneChangeCounter(game) == source.getStackMomentSourceZCC() // Token + || sourcePermanent.getZoneChangeCounter(game) + 1 == source.getStackMomentSourceZCC())) { // PermanentCard int amount = sourcePermanent.getCounters(game).getCount(CounterType.P1P1); if (amount > 0) { Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(amount)); diff --git a/Mage.Sets/src/mage/cards/s/SeverancePriest.java b/Mage.Sets/src/mage/cards/s/SeverancePriest.java index ff17d0efc7b..6f040691e1d 100644 --- a/Mage.Sets/src/mage/cards/s/SeverancePriest.java +++ b/Mage.Sets/src/mage/cards/s/SeverancePriest.java @@ -13,6 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.token.SpiritXXToken; @@ -86,7 +87,7 @@ class SeverancePriestEffect extends OneShotEffect { return false; } opponent.revealCards(source, opponent.getHand(), game); - TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_NON_LAND); + TargetCard target = new TargetCard(0, 1, Zone.HAND, 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( diff --git a/Mage.Sets/src/mage/cards/s/ShadowOfTheGoblin.java b/Mage.Sets/src/mage/cards/s/ShadowOfTheGoblin.java new file mode 100644 index 00000000000..822dbe8d30f --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShadowOfTheGoblin.java @@ -0,0 +1,78 @@ +package mage.cards.s; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.triggers.BeginningOfFirstMainTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class ShadowOfTheGoblin extends CardImpl { + + public ShadowOfTheGoblin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); + + + // Unreliable Visions -- At the beginning of your first main phase, discard a card. If you do, draw a card. + this.addAbility(new BeginningOfFirstMainTriggeredAbility( + new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new DiscardCardCost()), false) + .withFlavorWord("Unreliable Visions") + ); + + // Undying Vengeance -- Whenever you play a land or cast a spell from anywhere other than your hand, this enchantment deals 1 damage to each opponent. + this.addAbility(new ShadowOfTheGoblinTriggeredAbility(new DamagePlayersEffect(1, TargetController.OPPONENT)) + .withFlavorWord("Undying Vengeance")); + + } + + private ShadowOfTheGoblin(final ShadowOfTheGoblin card) { + super(card); + } + + @Override + public ShadowOfTheGoblin copy() { + return new ShadowOfTheGoblin(this); + } +} + +class ShadowOfTheGoblinTriggeredAbility extends TriggeredAbilityImpl { + + ShadowOfTheGoblinTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect, false); + setTriggerPhrase("Whenever you play a land or cast a spell from anywhere other than your hand, "); + } + + protected ShadowOfTheGoblinTriggeredAbility(final ShadowOfTheGoblinTriggeredAbility triggeredAbility) { + super(triggeredAbility); + } + + @Override + public ShadowOfTheGoblinTriggeredAbility copy() { + return new ShadowOfTheGoblinTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.PLAY_LAND || + event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return isControlledBy(event.getPlayerId()) && event.getZone() != Zone.HAND; + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShatteredLandscape.java b/Mage.Sets/src/mage/cards/s/ShatteredLandscape.java index e4f4e1cbce3..3cf79f2a06b 100644 --- a/Mage.Sets/src/mage/cards/s/ShatteredLandscape.java +++ b/Mage.Sets/src/mage/cards/s/ShatteredLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class ShatteredLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Mountain, Plains, or Swamp card"); + private static final FilterCard filter = new FilterBasicCard("a basic Mountain, Plains, or Swamp card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/s/ShelteringLandscape.java b/Mage.Sets/src/mage/cards/s/ShelteringLandscape.java index b649e9d2abb..ad50704c9e7 100644 --- a/Mage.Sets/src/mage/cards/s/ShelteringLandscape.java +++ b/Mage.Sets/src/mage/cards/s/ShelteringLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class ShelteringLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Mountain, Forest, or Plains card"); + private static final FilterCard filter = new FilterBasicCard("a basic Mountain, Forest, or Plains card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/s/ShiftingLoyalties.java b/Mage.Sets/src/mage/cards/s/ShiftingLoyalties.java index 40772d276f3..e7983619e12 100644 --- a/Mage.Sets/src/mage/cards/s/ShiftingLoyalties.java +++ b/Mage.Sets/src/mage/cards/s/ShiftingLoyalties.java @@ -53,8 +53,8 @@ class TargetPermanentsThatShareCardType extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (super.canTarget(playerId, id, source, game)) { if (!getTargets().isEmpty()) { Permanent targetOne = game.getPermanent(getTargets().get(0)); Permanent targetTwo = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/s/ShimianSpecter.java b/Mage.Sets/src/mage/cards/s/ShimianSpecter.java index 408f6f34638..d79a441e3d7 100644 --- a/Mage.Sets/src/mage/cards/s/ShimianSpecter.java +++ b/Mage.Sets/src/mage/cards/s/ShimianSpecter.java @@ -88,9 +88,7 @@ class ShimianSpecterEffect extends SearchTargetGraveyardHandLibraryForCardNameAn // You choose a nonland card from it TargetCard target = new TargetCard(Zone.HAND, new FilterNonlandCard()); - target.withNotTarget(true); - if (target.canChoose(controller.getId(), source, game) - && controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { + if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { return applySearchAndExile(game, source, CardUtil.getCardNameForSameNameSearch(game.getCard(target.getFirstTarget())), getTargetPointer().getFirst(game, source)); } } diff --git a/Mage.Sets/src/mage/cards/s/ShriekTreblemaker.java b/Mage.Sets/src/mage/cards/s/ShriekTreblemaker.java new file mode 100644 index 00000000000..e90bf9be2ef --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShriekTreblemaker.java @@ -0,0 +1,60 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.combat.CantBlockTargetEffect; +import mage.abilities.triggers.BeginningOfFirstMainTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class ShriekTreblemaker extends CardImpl { + + public ShriekTreblemaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B/R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.MUTANT); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // At the beginning of your first main phase, you may discard a card. When you do, target creature can't block this turn. + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new CantBlockTargetEffect(Duration.EndOfTurn), false); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(new BeginningOfFirstMainTriggeredAbility(new DoWhenCostPaid(ability, new DiscardCardCost(), + "Discard a card to make target creature unable to block this turn?")) + ); + + // Sonic Blast -- Whenever a creature an opponent controls dies, Shriek deals 1 damage to that player. + this.addAbility(new DiesCreatureTriggeredAbility( + Zone.BATTLEFIELD, + new DamageTargetEffect(1, true, "that player"), + false, + StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, + SetTargetPointer.PLAYER) + .withFlavorWord("Sonic Blast") + ); + } + + private ShriekTreblemaker(final ShriekTreblemaker card) { + super(card); + } + + @Override + public ShriekTreblemaker copy() { + return new ShriekTreblemaker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/Shuriken.java b/Mage.Sets/src/mage/cards/s/Shuriken.java index 9e922183eff..5ee5b8a7a91 100644 --- a/Mage.Sets/src/mage/cards/s/Shuriken.java +++ b/Mage.Sets/src/mage/cards/s/Shuriken.java @@ -84,7 +84,7 @@ class ShurikenEffect extends OneShotEffect { return true; } game.addEffect(new GainControlTargetEffect( - Duration.Custom, true, attached.getControllerId() + Duration.Custom, true, targetedPermanent.getControllerId() ).setTargetPointer(new FixedTarget(equipment, game)), source); return true; } diff --git a/Mage.Sets/src/mage/cards/s/Shyft.java b/Mage.Sets/src/mage/cards/s/Shyft.java index e1fe7f8d467..97a99ed3ca6 100644 --- a/Mage.Sets/src/mage/cards/s/Shyft.java +++ b/Mage.Sets/src/mage/cards/s/Shyft.java @@ -63,7 +63,7 @@ class ShyftEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Effect effect = new BecomesColorOrColorsTargetEffect(Duration.Custom); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getSourceObjectZoneChangeCounter())); + effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getStackMomentSourceZCC())); return effect.apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/s/SilkWebWeaver.java b/Mage.Sets/src/mage/cards/s/SilkWebWeaver.java new file mode 100644 index 00000000000..d44c2c7ba01 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SilkWebWeaver.java @@ -0,0 +1,71 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.keyword.WebSlingingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.HumanCitizenToken; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SilkWebWeaver extends CardImpl { + + public SilkWebWeaver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Web-slinging {1}{G}{W} + this.addAbility(new WebSlingingAbility(this, "{1}{G}{W}")); + + // Whenever you cast a creature spell, create a 1/1 green and white Human Citizen creature token. + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new HumanCitizenToken()), + StaticFilters.FILTER_SPELL_A_CREATURE, + false + )); + + // {3}{G}{W}: Creatures you control get +2/+2 and gain vigilance until end of turn. + Ability ability = new SimpleActivatedAbility( + new BoostControlledEffect(2, 2, Duration.EndOfTurn).setText("Creatures you control get +2/+2"), + new ManaCostsImpl<>("{3}{G}{W}") + ); + ability.addEffect( + new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES) + .setText("gain vigilance until end of turn") + .concatBy("and") + ); + this.addAbility(ability); + } + + private SilkWebWeaver(final SilkWebWeaver card) { + super(card); + } + + @Override + public SilkWebWeaver copy() { + return new SilkWebWeaver(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SilverSableMercenaryLeader.java b/Mage.Sets/src/mage/cards/s/SilverSableMercenaryLeader.java new file mode 100644 index 00000000000..b04e58a7d28 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SilverSableMercenaryLeader.java @@ -0,0 +1,64 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.ModifiedPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SilverSableMercenaryLeader extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("modified creature you control"); + + static { + filter.add(ModifiedPredicate.instance); + } + + public SilverSableMercenaryLeader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MERCENARY); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When Silver Sable enters, put a +1/+1 counter on another target creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE)); + this.addAbility(ability); + + // Whenever Silver Sable attacks, target modified creature you control gains lifelink until end of turn. + ability = new AttacksTriggeredAbility(new GainAbilityTargetEffect(LifelinkAbility.getInstance())); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private SilverSableMercenaryLeader(final SilverSableMercenaryLeader card) { + super(card); + } + + @Override + public SilverSableMercenaryLeader copy() { + return new SilverSableMercenaryLeader(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SinisterConcierge.java b/Mage.Sets/src/mage/cards/s/SinisterConcierge.java index f7ae1aced8b..09ad27db1ce 100644 --- a/Mage.Sets/src/mage/cards/s/SinisterConcierge.java +++ b/Mage.Sets/src/mage/cards/s/SinisterConcierge.java @@ -69,7 +69,7 @@ class SinisterConciergeEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); Card card = game.getCard(source.getSourceId()); if (controller == null || card == null - || card.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter() + || card.getZoneChangeCounter(game) != source.getStackMomentSourceZCC() || !Zone.GRAVEYARD.match(game.getState().getZone(card.getId()))) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/SinisterHideout.java b/Mage.Sets/src/mage/cards/s/SinisterHideout.java new file mode 100644 index 00000000000..921ea121a24 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SinisterHideout.java @@ -0,0 +1,46 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.BlueManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SinisterHideout extends CardImpl { + + public SinisterHideout(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {U} or {B}. + this.addAbility(new BlueManaAbility()); + this.addAbility(new BlackManaAbility()); + + // {4}, {T}: Surveil 1. + Ability ability = new SimpleActivatedAbility(new SurveilEffect(1), new GenericManaCost(4)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private SinisterHideout(final SinisterHideout card) { + super(card); + } + + @Override + public SinisterHideout copy() { + return new SinisterHideout(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SinnersJudgment.java b/Mage.Sets/src/mage/cards/s/SinnersJudgment.java index 608c412c013..2014fb6d7d0 100644 --- a/Mage.Sets/src/mage/cards/s/SinnersJudgment.java +++ b/Mage.Sets/src/mage/cards/s/SinnersJudgment.java @@ -1,12 +1,12 @@ package mage.cards.s; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -15,9 +15,9 @@ import mage.constants.SubType; import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPlayer; +import java.util.Optional; import java.util.UUID; /** @@ -37,16 +37,15 @@ public final class SinnersJudgment extends CardImpl { TargetPlayer auraTarget = new TargetPlayer(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // At the beginning of your upkeep, put a judgment counter on Sinner's Judgment. Then if there are three or more judgment counters on it, enchanted player loses the game. - this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new SinnersJudgmentEffect() - )); + Ability ability = new BeginningOfUpkeepTriggeredAbility(new AddCountersSourceEffect(CounterType.JUDGMENT.createInstance())); + ability.addEffect(new SinnersJudgmentEffect()); + this.addAbility(ability); // If Sinner's Judgment would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private SinnersJudgment(final SinnersJudgment card) { @@ -63,8 +62,7 @@ class SinnersJudgmentEffect extends OneShotEffect { SinnersJudgmentEffect() { super(Outcome.Benefit); - staticText = "put a judgment counter on {this}. Then if there are three " + - "or more judgment counters on it, enchanted player loses the game"; + staticText = "Then if there are three or more judgment counters on it, enchanted player loses the game"; } private SinnersJudgmentEffect(final SinnersJudgmentEffect effect) { @@ -78,18 +76,15 @@ class SinnersJudgmentEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent == null) { - return false; - } - permanent.addCounters(CounterType.JUDGMENT.createInstance(), source, game); - if (permanent.getCounters(game).getCount(CounterType.JUDGMENT) < 3) { - return true; - } - Player player = game.getPlayer(permanent.getAttachedTo()); - if (player != null) { - player.lost(game); - } - return true; + return Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .filter(permanent -> permanent.getCounters(game).getCount(CounterType.JUDGMENT) >= 3) + .map(Permanent::getAttachedTo) + .map(game::getPlayer) + .filter(player -> { + player.lost(game); + return true; + }) + .isPresent(); } } diff --git a/Mage.Sets/src/mage/cards/s/SirenStormtamer.java b/Mage.Sets/src/mage/cards/s/SirenStormtamer.java index 4800139e41d..55612b969cc 100644 --- a/Mage.Sets/src/mage/cards/s/SirenStormtamer.java +++ b/Mage.Sets/src/mage/cards/s/SirenStormtamer.java @@ -1,9 +1,6 @@ package mage.cards.s; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -15,19 +12,18 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.Filter; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.Spell; -import mage.game.stack.StackAbility; import mage.game.stack.StackObject; import mage.target.Target; -import mage.target.TargetObject; +import mage.target.TargetStackObject; import mage.target.Targets; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author spjspj */ public final class SirenStormtamer extends CardImpl { @@ -46,7 +42,7 @@ public final class SirenStormtamer extends CardImpl { // {U}, Sacrifice Siren Stormtamer: Counter target spell or ability that targets you or a creature you control. Ability ability = new SimpleActivatedAbility(new CounterTargetEffect(), new ManaCostsImpl<>("{U}")); - ability.addTarget(new SirenStormtamerTargetObject()); + ability.addTarget(new SirenStormtamerTarget()); ability.addCost(new SacrificeSourceCost()); this.addAbility(ability); @@ -62,97 +58,45 @@ public final class SirenStormtamer extends CardImpl { } } -class SirenStormtamerTargetObject extends TargetObject { +class SirenStormtamerTarget extends TargetStackObject { - public SirenStormtamerTargetObject() { - this.minNumberOfTargets = 1; - this.maxNumberOfTargets = 1; - this.zone = Zone.STACK; - this.targetName = "spell or ability that targets you or a creature you control"; + public SirenStormtamerTarget() { + super(); + withTargetName("spell or ability that targets you or a creature you control"); } - private SirenStormtamerTargetObject(final SirenStormtamerTargetObject target) { + private SirenStormtamerTarget(final SirenStormtamerTarget target) { super(target); } @Override - public boolean canTarget(UUID id, Ability source, Game game) { - StackObject stackObject = game.getStack().getStackObject(id); - return (stackObject instanceof Spell) || (stackObject instanceof StackAbility); - } + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return canChoose(sourceControllerId, game); - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { + Set targetsMe = new HashSet<>(); for (StackObject stackObject : game.getStack()) { - if ((stackObject instanceof Spell) || (stackObject instanceof StackAbility)) { - Targets objectTargets = stackObject.getStackAbility().getTargets(); - if (!objectTargets.isEmpty()) { - for (Target target : objectTargets) { - for (UUID targetId : target.getTargets()) { - Permanent targetedPermanent = game.getPermanentOrLKIBattlefield(targetId); - if (targetedPermanent != null - && targetedPermanent.isControlledBy(sourceControllerId) - && targetedPermanent.isCreature(game)) { - return true; - } - - if (sourceControllerId.equals(targetId)) { - return true; - } - - } + Targets objectTargets = stackObject.getStackAbility().getTargets(); + for (Target target : objectTargets) { + for (UUID targetId : target.getTargets()) { + Permanent targetedPermanent = game.getPermanentOrLKIBattlefield(targetId); + if (targetedPermanent != null + && targetedPermanent.isControlledBy(sourceControllerId) + && targetedPermanent.isCreature(game)) { + targetsMe.add(stackObject.getId()); + } + if (sourceControllerId.equals(targetId)) { + targetsMe.add(stackObject.getId()); } } } } - return false; - } + possibleTargets.removeIf(id -> !targetsMe.contains(id)); - @Override - public Set possibleTargets(UUID sourceControllerId, - Ability source, Game game) { - return possibleTargets(sourceControllerId, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (StackObject stackObject : game.getStack()) { - if ((stackObject instanceof Spell) || (stackObject instanceof StackAbility)) { - Targets objectTargets = stackObject.getStackAbility().getTargets(); - if (!objectTargets.isEmpty()) { - for (Target target : objectTargets) { - for (UUID targetId : target.getTargets()) { - Permanent targetedPermanent = game.getPermanentOrLKIBattlefield(targetId); - if (targetedPermanent != null - && targetedPermanent.isControlledBy(sourceControllerId) - && targetedPermanent.isCreature(game)) { - possibleTargets.add(stackObject.getId()); - } - - if (sourceControllerId.equals(targetId)) { - possibleTargets.add(stackObject.getId()); - } - } - } - } - } - } return possibleTargets; } @Override - public SirenStormtamerTargetObject copy() { - return new SirenStormtamerTargetObject(this); - } - - @Override - public Filter getFilter() { - throw new UnsupportedOperationException("Not supported yet."); + public SirenStormtamerTarget copy() { + return new SirenStormtamerTarget(this); } } diff --git a/Mage.Sets/src/mage/cards/s/SkyclaveShade.java b/Mage.Sets/src/mage/cards/s/SkyclaveShade.java index 3f984c77990..08e7007de18 100644 --- a/Mage.Sets/src/mage/cards/s/SkyclaveShade.java +++ b/Mage.Sets/src/mage/cards/s/SkyclaveShade.java @@ -102,6 +102,6 @@ class SkyclaveShadeEffect extends AsThoughEffectImpl { Card card = game.getCard(source.getSourceId()); return card != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD - && source.getSourceObjectZoneChangeCounter() == card.getZoneChangeCounter(game); + && source.getStackMomentSourceZCC() == card.getZoneChangeCounter(game); } } diff --git a/Mage.Sets/src/mage/cards/s/SkyserpentSeeker.java b/Mage.Sets/src/mage/cards/s/SkyserpentSeeker.java index 6f124c25c80..f84768364d1 100644 --- a/Mage.Sets/src/mage/cards/s/SkyserpentSeeker.java +++ b/Mage.Sets/src/mage/cards/s/SkyserpentSeeker.java @@ -1,23 +1,25 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.StaticValue; -import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.RevealCardsFromLibraryUntilEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; -import mage.abilities.keyword.ExhaustAbility; -import mage.cards.*; -import mage.constants.*; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.ExhaustAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.PutCards; +import mage.constants.SubType; import mage.counters.CounterType; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** * * @author Jmlundeen @@ -84,8 +86,9 @@ class SkyserpentSeekerEffect extends OneShotEffect { if (lands.size() == 2) { break; } + } else { + revealedCards.add(card); } - revealedCards.add(card); } controller.revealCards(source, revealedCards, game); PutCards.BATTLEFIELD_TAPPED.moveCards(controller, lands, source, game); diff --git a/Mage.Sets/src/mage/cards/s/SkyshipWeatherlight.java b/Mage.Sets/src/mage/cards/s/SkyshipWeatherlight.java index 18681cad8e2..50590ed0cc3 100644 --- a/Mage.Sets/src/mage/cards/s/SkyshipWeatherlight.java +++ b/Mage.Sets/src/mage/cards/s/SkyshipWeatherlight.java @@ -86,7 +86,7 @@ class SkyshipWeatherlightEffect extends SearchEffect { MageObject sourceObject = source.getSourceObject(game); if (sourceObject != null && controller != null) { if (controller.searchLibrary(target, source, game)) { - UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); if (!target.getTargets().isEmpty()) { for (UUID cardID : target.getTargets()) { Card card = controller.getLibrary().getCard(cardID, game); @@ -125,7 +125,7 @@ class SkyshipWeatherlightEffect2 extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (sourceObject != null && controller != null) { - ExileZone exZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())); + ExileZone exZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC())); if (exZone != null) { controller.moveCards(exZone.getRandom(game), Zone.HAND, source, game); } diff --git a/Mage.Sets/src/mage/cards/s/SkywardSpider.java b/Mage.Sets/src/mage/cards/s/SkywardSpider.java new file mode 100644 index 00000000000..2edd517b03d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SkywardSpider.java @@ -0,0 +1,51 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.SourceModifiedCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SkywardSpider extends CardImpl { + + public SkywardSpider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W/U}{W/U}"); + + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // This creature has flying as long as it's modified. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield), + SourceModifiedCondition.instance, "{this} has flying as long as it's modified" + ))); + } + + private SkywardSpider(final SkywardSpider card) { + super(card); + } + + @Override + public SkywardSpider copy() { + return new SkywardSpider(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SkywayRobber.java b/Mage.Sets/src/mage/cards/s/SkywayRobber.java index 25e90be8490..7d526627f66 100644 --- a/Mage.Sets/src/mage/cards/s/SkywayRobber.java +++ b/Mage.Sets/src/mage/cards/s/SkywayRobber.java @@ -85,7 +85,7 @@ class SkywayRobberCastForFreeEffect extends OneShotEffect { if (controller == null) { return false; } - String exileZoneName = CardUtil.getObjectZoneString(CardUtil.SOURCE_EXILE_ZONE_TEXT, source.getSourceId(), game, source.getSourceObjectZoneChangeCounter()-1, false); + String exileZoneName = CardUtil.getObjectZoneString(CardUtil.SOURCE_EXILE_ZONE_TEXT, source.getSourceId(), game, source.getStackMomentSourceZCC()-1, false); UUID exileId = CardUtil.getExileZoneId(exileZoneName, game); ExileZone exileZone = game.getExile().getExileZone(exileId); if (exileZone == null) { diff --git a/Mage.Sets/src/mage/cards/s/SleddingOtterPenguin.java b/Mage.Sets/src/mage/cards/s/SleddingOtterPenguin.java new file mode 100644 index 00000000000..bc1cd5a5f5e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SleddingOtterPenguin.java @@ -0,0 +1,42 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SleddingOtterPenguin extends CardImpl { + + public SleddingOtterPenguin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.OTTER); + this.subtype.add(SubType.BIRD); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // {3}: Put a +1/+1 counter on this creature. + this.addAbility(new SimpleActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new GenericManaCost(3) + )); + } + + private SleddingOtterPenguin(final SleddingOtterPenguin card) { + super(card); + } + + @Override + public SleddingOtterPenguin copy() { + return new SleddingOtterPenguin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SmolderingEgg.java b/Mage.Sets/src/mage/cards/s/SmolderingEgg.java index 7ea48b98e7a..7add8019e87 100644 --- a/Mage.Sets/src/mage/cards/s/SmolderingEgg.java +++ b/Mage.Sets/src/mage/cards/s/SmolderingEgg.java @@ -1,22 +1,31 @@ package mage.cards.s; import mage.MageInt; +import mage.Mana; import mage.abilities.Ability; +import mage.abilities.AbilityImpl; import mage.abilities.common.SpellCastControllerTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.RemoveAllCountersSourceEffect; import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.DefenderAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.game.stack.Spell; -import mage.watchers.common.ManaPaidSourceWatcher; +import java.util.Optional; import java.util.UUID; /** @@ -24,6 +33,8 @@ import java.util.UUID; */ public final class SmolderingEgg extends CardImpl { + private static final Condition condition = new SourceHasCounterCondition(CounterType.EMBER, 7); + public SmolderingEgg(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); @@ -38,10 +49,16 @@ public final class SmolderingEgg extends CardImpl { // Whenever you cast an instant or sorcery spell, put a number of ember counters on Smoldering Egg equal to the amount of mana spent to cast that spell. Then if Smoldering Egg has seven or more ember counters on it, remove them and transform Smoldering Egg. this.addAbility(new TransformAbility()); - this.addAbility(new SpellCastControllerTriggeredAbility( - new SmolderingEggEffect(), StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, - false, SetTargetPointer.SPELL - )); + Ability ability = new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.EMBER.createInstance(), SmolderingEggValue.instance) + .setText("put a number of ember counters on {this} equal to the amount of mana spent to cast that spell"), + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false + ); + ability.addEffect(new ConditionalOneShotEffect( + new RemoveAllCountersSourceEffect(CounterType.EMBER), condition, + "Then if {this} has seven or more ember counters on it, remove them and transform {this}" + ).addEffect(new TransformSourceEffect())); + this.addAbility(ability); } private SmolderingEgg(final SmolderingEgg card) { @@ -54,43 +71,32 @@ public final class SmolderingEgg extends CardImpl { } } -class SmolderingEggEffect extends OneShotEffect { +enum SmolderingEggValue implements DynamicValue { + instance; - SmolderingEggEffect() { - super(Outcome.Benefit); - staticText = "put a number of ember counters on {this} equal to the amount of mana spent to cast that spell. " + - "Then if {this} has seven or more ember counters on it, remove them and transform {this}"; - } - - private SmolderingEggEffect(final SmolderingEggEffect effect) { - super(effect); + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return Optional + .ofNullable((Spell) effect.getValue("spellCast")) + .map(Spell::getSpellAbility) + .map(AbilityImpl::getManaCostsToPay) + .map(ManaCost::getUsedManaToPay) + .map(Mana::count) + .orElse(0); } @Override - public SmolderingEggEffect copy() { - return new SmolderingEggEffect(this); + public SmolderingEggValue copy() { + return this; } @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent == null) { - return false; - } - Spell spell = (Spell) getValue("spellCast"); - if (spell != null) { - permanent.addCounters( - CounterType.EMBER.createInstance( - ManaPaidSourceWatcher.getTotalPaid(spell.getId(), game) - ), source.getControllerId(), source, game - ); - } - int counters = permanent.getCounters(game).getCount(CounterType.EMBER); - if (counters < 7) { - return true; - } - permanent.removeCounters(CounterType.EMBER.createInstance(counters), source, game); - new TransformSourceEffect().apply(game, source); - return true; + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "1"; } } diff --git a/Mage.Sets/src/mage/cards/s/SmolderingWerewolf.java b/Mage.Sets/src/mage/cards/s/SmolderingWerewolf.java index 4edc054e0f8..d20600ca9b2 100644 --- a/Mage.Sets/src/mage/cards/s/SmolderingWerewolf.java +++ b/Mage.Sets/src/mage/cards/s/SmolderingWerewolf.java @@ -1,14 +1,10 @@ - package mage.cards.s; -import java.util.UUID; - import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; @@ -16,9 +12,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * @author fireshoes */ @@ -34,9 +31,7 @@ public final class SmolderingWerewolf extends CardImpl { this.secondSideCardClazz = mage.cards.e.EruptingDreadwolf.class; // When Smoldering Werewolf enters the battlefield, it deals 1 damage to each of up to two target creatures. - Effect effect = new DamageTargetEffect(1); - effect.setText("it deals 1 damage to each of up to two target creatures"); - Ability ability = new EntersBattlefieldTriggeredAbility(effect, false); + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); ability.addTarget(new TargetCreaturePermanent(0, 2)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/s/SokkaBoldBoomeranger.java b/Mage.Sets/src/mage/cards/s/SokkaBoldBoomeranger.java new file mode 100644 index 00000000000..272744db546 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SokkaBoldBoomeranger.java @@ -0,0 +1,60 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.discard.DiscardAndDrawThatManyEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SokkaBoldBoomeranger extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("an artifact or Lesson spell"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + SubType.LESSON.getPredicate() + )); + } + + public SokkaBoldBoomeranger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When Sokka enters, discard up to two cards, then draw that many cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DiscardAndDrawThatManyEffect(2))); + + // Whenever you cast an artifact or Lesson spell, put a +1/+1 counter on Sokka. + this.addAbility(new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter, false + )); + } + + private SokkaBoldBoomeranger(final SokkaBoldBoomeranger card) { + super(card); + } + + @Override + public SokkaBoldBoomeranger copy() { + return new SokkaBoldBoomeranger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SokkaLateralStrategist.java b/Mage.Sets/src/mage/cards/s/SokkaLateralStrategist.java new file mode 100644 index 00000000000..82338117d3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SokkaLateralStrategist.java @@ -0,0 +1,75 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SokkaLateralStrategist extends CardImpl { + + public SokkaLateralStrategist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W/U}{W/U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Sokka and at least one other creature attack, draw a card. + this.addAbility(new SokkaLateralStrategistTriggeredAbility()); + } + + private SokkaLateralStrategist(final SokkaLateralStrategist card) { + super(card); + } + + @Override + public SokkaLateralStrategist copy() { + return new SokkaLateralStrategist(this); + } +} + +class SokkaLateralStrategistTriggeredAbility extends TriggeredAbilityImpl { + + SokkaLateralStrategistTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); + this.setTriggerPhrase("Whenever {this} and at least one other creature attack, "); + } + + private SokkaLateralStrategistTriggeredAbility(final SokkaLateralStrategistTriggeredAbility ability) { + super(ability); + } + + @Override + public SokkaLateralStrategistTriggeredAbility copy() { + return new SokkaLateralStrategistTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return game.getCombat().getAttackers().size() >= 2 && game.getCombat().getAttackers().contains(getSourceId()); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SokkaWolfCovesProtector.java b/Mage.Sets/src/mage/cards/s/SokkaWolfCovesProtector.java new file mode 100644 index 00000000000..378da070f96 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SokkaWolfCovesProtector.java @@ -0,0 +1,40 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SokkaWolfCovesProtector extends CardImpl { + + public SokkaWolfCovesProtector(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + } + + private SokkaWolfCovesProtector(final SokkaWolfCovesProtector card) { + super(card); + } + + @Override + public SokkaWolfCovesProtector copy() { + return new SokkaWolfCovesProtector(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SokkasHaiku.java b/Mage.Sets/src/mage/cards/s/SokkasHaiku.java new file mode 100644 index 00000000000..ea0b7faf6ea --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SokkasHaiku.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.TargetSpell; +import mage.target.common.TargetLandPermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SokkasHaiku extends CardImpl { + + public SokkasHaiku(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}{U}"); + + this.subtype.add(SubType.LESSON); + + // Counter target spell. + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addTarget(new TargetSpell()); + + // Draw a card, then mill three cards. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); + this.getSpellAbility().addEffect(new MillCardsControllerEffect(3).concatBy(", then")); + + // Untap target land. + this.getSpellAbility().addEffect(new UntapTargetEffect("untap target land") + .concatBy("
") + .setTargetPointer(new SecondTargetPointer())); + this.getSpellAbility().addTarget(new TargetLandPermanent()); + } + + private SokkasHaiku(final SokkasHaiku card) { + super(card); + } + + @Override + public SokkasHaiku copy() { + return new SokkasHaiku(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SongOfTheDryads.java b/Mage.Sets/src/mage/cards/s/SongOfTheDryads.java index d34577f660f..1bca8aa2151 100644 --- a/Mage.Sets/src/mage/cards/s/SongOfTheDryads.java +++ b/Mage.Sets/src/mage/cards/s/SongOfTheDryads.java @@ -50,6 +50,7 @@ class BecomesColorlessForestLandEffect extends ContinuousEffectImpl { BecomesColorlessForestLandEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); this.staticText = "Enchanted permanent is a colorless Forest land"; + dependencyTypes.add(DependencyType.BecomeNonbasicLand); dependencyTypes.add(DependencyType.BecomeForest); } diff --git a/Mage.Sets/src/mage/cards/s/SoulOfShandalar.java b/Mage.Sets/src/mage/cards/s/SoulOfShandalar.java index c26c14ca77b..f18b7cf1613 100644 --- a/Mage.Sets/src/mage/cards/s/SoulOfShandalar.java +++ b/Mage.Sets/src/mage/cards/s/SoulOfShandalar.java @@ -1,11 +1,7 @@ package mage.cards.s; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.ExileSourceFromGraveCost; @@ -15,17 +11,19 @@ import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetPlayerOrPlaneswalker; +import java.util.Set; +import java.util.UUID; + /** * @author noxx */ @@ -96,54 +94,30 @@ class SoulOfShandalarEffect extends OneShotEffect { class SoulOfShandalarTarget extends TargetPermanent { public SoulOfShandalarTarget() { - super(0, 1, new FilterCreaturePermanent("creature that the targeted player or planeswalker's controller controls"), false); + super(0, 1, new FilterCreaturePermanent("creature that the targeted player or planeswalker's controller controls")); } private SoulOfShandalarTarget(final SoulOfShandalarTarget target) { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Player player = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); - if (player == null) { - return false; - } - UUID firstTarget = player.getId(); - Permanent permanent = game.getPermanent(id); - if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) { - return super.canTarget(id, source, game); - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (StackObject item : game.getState().getStack()) { - if (item.getId().equals(source.getSourceId())) { - object = item; - } - if (item.getSourceId().equals(source.getSourceId())) { - object = item; - } + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - Player player = game.getPlayerOrPlaneswalkerController(playerId); - if (player != null) { - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.isControlledBy(player.getId())) { - possibleTargets.add(targetId); - } - } - } - } return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/s/SoulSculptor.java b/Mage.Sets/src/mage/cards/s/SoulSculptor.java index d0ab23a7962..3e4a46d323d 100644 --- a/Mage.Sets/src/mage/cards/s/SoulSculptor.java +++ b/Mage.Sets/src/mage/cards/s/SoulSculptor.java @@ -120,13 +120,8 @@ enum SoulSculptorCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - if (!game.getStack().isEmpty()) { - StackObject stackObject = game.getStack().getFirst(); - if (stackObject != null) { - return !stackObject.getCardType(game).contains(CardType.CREATURE); - } - } - return true; + StackObject stackObject = game.getStack().getFirstOrNull(); + return stackObject != null && !stackObject.getCardType(game).contains(CardType.CREATURE); } @Override diff --git a/Mage.Sets/src/mage/cards/s/SoulSearch.java b/Mage.Sets/src/mage/cards/s/SoulSearch.java index 75da014f391..8d53266e750 100644 --- a/Mage.Sets/src/mage/cards/s/SoulSearch.java +++ b/Mage.Sets/src/mage/cards/s/SoulSearch.java @@ -69,7 +69,7 @@ class SoulSearchEffect extends OneShotEffect { if (opponent.getHand().count(StaticFilters.FILTER_CARD_NON_LAND, game) < 1) { return true; } - TargetCard target = new TargetCardInHand(StaticFilters.FILTER_CARD_NON_LAND); + TargetCard target = new TargetCard(1, Zone.HAND, StaticFilters.FILTER_CARD_NON_LAND); controller.choose(Outcome.Discard, opponent.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/s/SoulSeizer.java b/Mage.Sets/src/mage/cards/s/SoulSeizer.java index 5dc4a1fe428..aa0cebb6f39 100644 --- a/Mage.Sets/src/mage/cards/s/SoulSeizer.java +++ b/Mage.Sets/src/mage/cards/s/SoulSeizer.java @@ -68,12 +68,12 @@ class SoulSeizerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); if (permanent == null || !permanent.transform(source, game)) { return false; } Permanent attachTo = game.getPermanent(getTargetPointer().getFirst(game, source)); - return attachTo != null && attachTo.addAttachment(source.getSourceId(), source, game); + return attachTo != null && attachTo.addAttachment(permanent.getId(), source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java b/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java index b6e6f71b085..2f8db38d4a6 100644 --- a/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java +++ b/Mage.Sets/src/mage/cards/s/SoulfireGrandMaster.java @@ -21,6 +21,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.players.Player; import java.util.UUID; @@ -94,18 +95,22 @@ class SoulfireGrandMasterCastFromHandReplacementEffect extends ReplacementEffect @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Spell spell = (Spell) game.getStack().getFirst(); - if (!spell.isCopy() && !spell.isCountered()) { - Card sourceCard = game.getCard(spellId); - if (sourceCard != null && Zone.STACK.equals(game.getState().getZone(spellId))) { - Player player = game.getPlayer(sourceCard.getOwnerId()); - if (player != null) { - player.moveCards(sourceCard, Zone.HAND, source, game); - discard(); - return true; + StackObject stackObject = game.getStack().getFirstOrNull(); + if (stackObject instanceof Spell) { + Spell spell = (Spell) stackObject; + if (!spell.isCopy() && !spell.isCountered()) { + Card sourceCard = game.getCard(spellId); + if (sourceCard != null && Zone.STACK.equals(game.getState().getZone(spellId))) { + Player player = game.getPlayer(sourceCard.getOwnerId()); + if (player != null) { + player.moveCards(sourceCard, Zone.HAND, source, game); + discard(); + return true; + } } } } + return false; } @@ -134,8 +139,8 @@ class SoulfireGrandMasterCastFromHandReplacementEffect extends ReplacementEffect if (zEvent.getFromZone() == Zone.STACK && zEvent.getToZone() == Zone.GRAVEYARD && event.getTargetId().equals(spellId)) { - if (game.getStack().getFirst() instanceof Spell) { - Card cardOfSpell = ((Spell) game.getStack().getFirst()).getCard(); + if (game.getStack().getFirstOrNull() instanceof Spell) { + Card cardOfSpell = ((Spell) game.getStack().getFirstOrNull()).getCard(); return cardOfSpell.getMainCard().getId().equals(spellId); } } diff --git a/Mage.Sets/src/mage/cards/s/SoulgorgerOrgg.java b/Mage.Sets/src/mage/cards/s/SoulgorgerOrgg.java index dce856d8e84..d81b8ed0b72 100644 --- a/Mage.Sets/src/mage/cards/s/SoulgorgerOrgg.java +++ b/Mage.Sets/src/mage/cards/s/SoulgorgerOrgg.java @@ -74,7 +74,7 @@ class SoulgorgerOrggLoseLifeEffect extends OneShotEffect { if (player.getLife() > 1) { lifeValue = player.getLife() - 1; } - game.getState().setValue(source.getSourceId().toString() + source.getControllerId().toString() + source.getSourceObjectZoneChangeCounter() + "_lifeValue", lifeValue); + game.getState().setValue(source.getSourceId().toString() + source.getControllerId().toString() + source.getStackMomentSourceZCC() + "_lifeValue", lifeValue); if (lifeValue > 0) { player.loseLife(lifeValue, game, source, false); } @@ -103,7 +103,7 @@ class SoulgorgerOrggGainLifeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Object obj = game.getState().getValue(source.getSourceId().toString() + source.getControllerId().toString() + (source.getSourceObjectZoneChangeCounter() - 1) + "_lifeValue"); + Object obj = game.getState().getValue(source.getSourceId().toString() + source.getControllerId().toString() + (source.getStackMomentSourceZCC() - 1) + "_lifeValue"); if (!(obj instanceof Integer)) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/SouthernAirTemple.java b/Mage.Sets/src/mage/cards/s/SouthernAirTemple.java new file mode 100644 index 00000000000..ce184fcb615 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SouthernAirTemple.java @@ -0,0 +1,61 @@ +package mage.cards.s; + +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SouthernAirTemple extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.SHRINE)); + private static final Hint hint = new ValueHint("Shrines you control", xValue); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SHRINE, "another Shrine you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + public SouthernAirTemple(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SHRINE); + + // When Southern Air Temple enters, put X +1/+1 counters on each creature you control, where X is the number of Shrines you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), xValue, StaticFilters.FILTER_CONTROLLED_CREATURE + ).setText("put X +1/+1 counters on each creature you control, where X is the number of Shrines you control")).addHint(hint)); + + // Whenever another Shrine you control enters, put a +1/+1 counter on each creature you control. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ), filter)); + } + + private SouthernAirTemple(final SouthernAirTemple card) { + super(card); + } + + @Override + public SouthernAirTemple copy() { + return new SouthernAirTemple(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/Spawnbroker.java b/Mage.Sets/src/mage/cards/s/Spawnbroker.java index 476dc520868..bad7f039c65 100644 --- a/Mage.Sets/src/mage/cards/s/Spawnbroker.java +++ b/Mage.Sets/src/mage/cards/s/Spawnbroker.java @@ -1,18 +1,19 @@ - package mage.cards.s; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetControlledPermanent; import java.util.HashSet; import java.util.Set; @@ -33,8 +34,8 @@ public final class Spawnbroker extends CardImpl { this.toughness = new MageInt(1); // When Spawnbroker enters the battlefield, you may exchange control of target creature you control and target creature with power less than or equal to that creature's power an opponent controls. - Ability ability = new EntersBattlefieldTriggeredAbility(new ExchangeControlTargetEffect(Duration.Custom, rule, false, true), true); - ability.addTarget(new TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent()); + Ability ability = new EntersBattlefieldTriggeredAbility(new ExchangeControlTargetEffect(Duration.EndOfGame, rule, false, true), true); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_CREATURE)); ability.addTarget(new SpawnbrokerSecondTarget()); this.addAbility(ability); @@ -50,89 +51,53 @@ public final class Spawnbroker extends CardImpl { } } -class TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent extends TargetControlledPermanent { - - public TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent() { - super(); - this.filter = this.filter.copy(); - filter.add(CardType.CREATURE.getPredicate()); - withTargetName("creature you control"); - } - - private TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent(final TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent target) { - super(target); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } - } - return possibleTargets; - } - - @Override - public TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent copy() { - return new TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent(this); - } -} - class SpawnbrokerSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public SpawnbrokerSecondTarget() { - super(); - this.filter = this.filter.copy(); - filter.add(TargetController.OPPONENT.getControllerPredicate()); - filter.add(CardType.CREATURE.getPredicate()); + super(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE); withTargetName("creature with power less than or equal to that creature's power an opponent controls"); } private SpawnbrokerSecondTarget(final SpawnbrokerSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } @Override public boolean canTarget(UUID id, Ability source, Game game) { - if (super.canTarget(id, source, game)) { - Permanent target1 = game.getPermanent(source.getFirstTarget()); - Permanent opponentPermanent = game.getPermanent(id); - if (target1 != null && opponentPermanent != null) { - return target1.getPower().getValue() >= opponentPermanent.getPower().getValue(); - } + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + Permanent possiblePermanent = game.getPermanent(id); + if (ownPermanent == null || possiblePermanent == null) { + return false; } - return false; + return super.canTarget(id, source, game) && ownPermanent.getPower().getValue() >= possiblePermanent.getPower().getValue(); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (firstTarget.getPower().getValue() >= permanent.getPower().getValue()) { - possibleTargets.add(permanent.getId()); - } - } + + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (ownPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by power + if (ownPermanent.getPower().getValue() >= permanent.getPower().getValue()) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); + // AI hint with better outcome return super.chooseTarget(Outcome.GainControl, playerId, source, game); } diff --git a/Mage.Sets/src/mage/cards/s/SpearOfHeliod.java b/Mage.Sets/src/mage/cards/s/SpearOfHeliod.java index 85de10c7bbd..015a0bc0950 100644 --- a/Mage.Sets/src/mage/cards/s/SpearOfHeliod.java +++ b/Mage.Sets/src/mage/cards/s/SpearOfHeliod.java @@ -18,7 +18,6 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate; import mage.target.Target; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -27,8 +26,7 @@ import java.util.UUID; */ public final class SpearOfHeliod extends CardImpl { - private static final FilterCreaturePermanent filter - = new FilterCreaturePermanent("creature that dealt damage to you this turn"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature that dealt damage to you this turn"); static { filter.add(new DamagedPlayerThisTurnPredicate(TargetController.YOU)); @@ -38,15 +36,13 @@ public final class SpearOfHeliod extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.ARTIFACT}, "{1}{W}{W}"); this.supertype.add(SuperType.LEGENDARY); - // Creatures you control get +1/+1. this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield))); // {1}{W}{W}, {T}: Destroy target creature that dealt damage to you this turn. Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new ManaCostsImpl<>("{1}{W}{W}")); ability.addCost(new TapSourceCost()); - Target target = new TargetPermanent(filter); - ability.addTarget(target); + ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SpectacularSpiderMan.java b/Mage.Sets/src/mage/cards/s/SpectacularSpiderMan.java new file mode 100644 index 00000000000..82ca900a59a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpectacularSpiderMan.java @@ -0,0 +1,68 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpectacularSpiderMan extends CardImpl { + + public SpectacularSpiderMan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // {1}: Spectacular Spider-Man gains flying until end of turn. + this.addAbility(new SimpleActivatedAbility( + new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), new GenericManaCost(1) + )); + + // {1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn. + Ability ability = new SimpleActivatedAbility(new GainAbilityControlledEffect( + HexproofAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("creatures you control gain hexproof"), new GenericManaCost(1)); + ability.addCost(new SacrificeSourceCost()); + ability.addEffect(new GainAbilityControlledEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("and indestructible until end of turn")); + this.addAbility(ability); + } + + private SpectacularSpiderMan(final SpectacularSpiderMan card) { + super(card); + } + + @Override + public SpectacularSpiderMan copy() { + return new SpectacularSpiderMan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpectersShriek.java b/Mage.Sets/src/mage/cards/s/SpectersShriek.java index 0374dacc8f7..e50892777cc 100644 --- a/Mage.Sets/src/mage/cards/s/SpectersShriek.java +++ b/Mage.Sets/src/mage/cards/s/SpectersShriek.java @@ -77,8 +77,7 @@ class SpectersShriekEffect extends OneShotEffect { return false; } TargetCard target = new TargetCard(0, 1, Zone.HAND, new FilterNonlandCard()); - target.withNotTarget(true); - if (!controller.chooseTarget(Outcome.Benefit, player.getHand(), target, source, game)) { + if (!controller.choose(Outcome.Benefit, player.getHand(), target, source, game)) { return false; } Card card = game.getCard(target.getFirstTarget()); diff --git a/Mage.Sets/src/mage/cards/s/SpectralBinding.java b/Mage.Sets/src/mage/cards/s/SpectralBinding.java index 2e1e000c303..89d2a0f443a 100644 --- a/Mage.Sets/src/mage/cards/s/SpectralBinding.java +++ b/Mage.Sets/src/mage/cards/s/SpectralBinding.java @@ -1,11 +1,9 @@ package mage.cards.s; -import mage.abilities.Ability; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -33,14 +31,13 @@ public final class SpectralBinding extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature gets -2/-0. this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(-2, 0))); // If Spectral Binding would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private SpectralBinding(final SpectralBinding card) { diff --git a/Mage.Sets/src/mage/cards/s/SpellQueller.java b/Mage.Sets/src/mage/cards/s/SpellQueller.java index c2f829bd6bd..aa3ee3feb0a 100644 --- a/Mage.Sets/src/mage/cards/s/SpellQueller.java +++ b/Mage.Sets/src/mage/cards/s/SpellQueller.java @@ -96,7 +96,7 @@ class SpellQuellerEntersEffect extends OneShotEffect { if (controller != null && sourceObject != null) { Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); if (spell != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); return controller.moveCardsToExile(spell, source, game, true, exileId, sourceObject.getIdName()); } return true; diff --git a/Mage.Sets/src/mage/cards/s/SpellweaverHelix.java b/Mage.Sets/src/mage/cards/s/SpellweaverHelix.java index 89bbba6f492..d7c85e42f62 100644 --- a/Mage.Sets/src/mage/cards/s/SpellweaverHelix.java +++ b/Mage.Sets/src/mage/cards/s/SpellweaverHelix.java @@ -83,7 +83,7 @@ class SpellweaverHelixImprintEffect extends OneShotEffect { for (UUID targetId : this.getTargetPointer().getTargets(game, source)) { Card card = game.getCard(targetId); if (card != null) { - controller.moveCardsToExile(card, source, game, true, CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), source.getSourceObject(game).getIdName()); + controller.moveCardsToExile(card, source, game, true, CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()), source.getSourceObject(game).getIdName()); if (sourcePermanent != null) { sourcePermanent.imprint(targetId, game); } diff --git a/Mage.Sets/src/mage/cards/s/SphinxOfTheChimes.java b/Mage.Sets/src/mage/cards/s/SphinxOfTheChimes.java index 7d5070a531b..f36c37b1baa 100644 --- a/Mage.Sets/src/mage/cards/s/SphinxOfTheChimes.java +++ b/Mage.Sets/src/mage/cards/s/SphinxOfTheChimes.java @@ -9,7 +9,6 @@ import mage.abilities.keyword.FlyingAbility; import mage.cards.*; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.mageobject.NamePredicate; @@ -82,27 +81,22 @@ class TargetTwoNonLandCardsWithSameNameInHand extends TargetCardInHand { } @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set newPossibleTargets = new HashSet<>(); + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); Player player = game.getPlayer(sourceControllerId); if (player == null) { - return newPossibleTargets; - } - for (Card card : player.getHand().getCards(filter, game)) { - possibleTargets.add(card.getId()); + return possibleTargets; } - Cards cardsToCheck = new CardsImpl(); - cardsToCheck.addAll(possibleTargets); + Cards cardsToCheck = new CardsImpl(player.getHand().getCards(filter, game)); if (targets.size() == 1) { - // first target is laready chosen, now only targets with the same name are selectable + // first target is already chosen, now only targets with the same name are selectable for (Map.Entry entry : targets.entrySet()) { Card chosenCard = cardsToCheck.get(entry.getKey(), game); if (chosenCard != null) { for (UUID cardToCheck : cardsToCheck) { if (!cardToCheck.equals(chosenCard.getId()) && chosenCard.getName().equals(game.getCard(cardToCheck).getName())) { - newPossibleTargets.add(cardToCheck); + possibleTargets.add(cardToCheck); } } } @@ -116,56 +110,13 @@ class TargetTwoNonLandCardsWithSameNameInHand extends TargetCardInHand { nameFilter.add(new NamePredicate(nameToSearch)); if (cardsToCheck.count(nameFilter, game) > 1) { - newPossibleTargets.add(cardToCheck); + possibleTargets.add(cardToCheck); } } } } - return newPossibleTargets; - } - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - Cards cardsToCheck = new CardsImpl(); - Player player = game.getPlayer(sourceControllerId); - if (player == null) { - return false; - } - for (Card card : player.getHand().getCards(filter, game)) { - cardsToCheck.add(card.getId()); - } - int possibleCards = 0; - for (Card card : cardsToCheck.getCards(game)) { - String nameToSearch = CardUtil.getCardNameForSameNameSearch(card); - FilterCard nameFilter = new FilterCard(); - nameFilter.add(new NamePredicate(nameToSearch)); - - if (cardsToCheck.count(nameFilter, game) > 1) { - ++possibleCards; - } - } - return possibleCards > 0; - } - - @Override - public boolean canTarget(UUID id, Game game) { - if (super.canTarget(id, game)) { - Card card = game.getCard(id); - if (card != null) { - if (targets.size() == 1) { - Card card2 = game.getCard(targets.entrySet().iterator().next().getKey()); - return CardUtil.haveSameNames(card2, card); - } else { - String nameToSearch = CardUtil.getCardNameForSameNameSearch(card); - FilterCard nameFilter = new FilterCard(); - nameFilter.add(new NamePredicate(nameToSearch)); - - Player player = game.getPlayer(card.getOwnerId()); - return player != null && player.getHand().getCards(nameFilter, game).size() > 1; - } - } - } - return false; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/s/SpiderGirlLegacyHero.java b/Mage.Sets/src/mage/cards/s/SpiderGirlLegacyHero.java new file mode 100644 index 00000000000..fe380dcf605 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderGirlLegacyHero.java @@ -0,0 +1,54 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.permanent.token.CitizenGreenWhiteToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderGirlLegacyHero extends CardImpl { + + public SpiderGirlLegacyHero(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // During your turn, Spider-Girl has flying. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield), + MyTurnCondition.instance, "during your turn, {this} has flying" + ))); + + // When Spider-Girl leaves the battlefield, create a 1/1 green and white Human Citizen creature token. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new CreateTokenEffect(new CitizenGreenWhiteToken()))); + } + + private SpiderGirlLegacyHero(final SpiderGirlLegacyHero card) { + super(card); + } + + @Override + public SpiderGirlLegacyHero copy() { + return new SpiderGirlLegacyHero(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderIslanders.java b/Mage.Sets/src/mage/cards/s/SpiderIslanders.java new file mode 100644 index 00000000000..f741921c1c8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderIslanders.java @@ -0,0 +1,38 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.keyword.MayhemAbility; +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 SpiderIslanders extends CardImpl { + + public SpiderIslanders(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HORROR); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Mayhem {1}{R} + this.addAbility(new MayhemAbility(this, "{1}{R}")); + } + + private SpiderIslanders(final SpiderIslanders card) { + super(card); + } + + @Override + public SpiderIslanders copy() { + return new SpiderIslanders(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderMan2099.java b/Mage.Sets/src/mage/cards/s/SpiderMan2099.java new file mode 100644 index 00000000000..badf925b6b5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderMan2099.java @@ -0,0 +1,129 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.ruleModifying.CantCastDuringFirstThreeTurnsEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +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.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderMan2099 extends CardImpl { + + public SpiderMan2099(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // From the Future -- You can't cast Spider-Man 2099 during your first, second, or third turns of the game. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new CantCastDuringFirstThreeTurnsEffect("{this}") + ).withFlavorWord("From the Future")); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE) + .setText("{this} deals damage equal to his power to any target") + ).withInterveningIf(SpiderMan2099Condition.instance); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability.addHint(SpiderMan2099Condition.getHint()), new SpiderMan2099Watcher()); + } + + private SpiderMan2099(final SpiderMan2099 card) { + super(card); + } + + @Override + public SpiderMan2099 copy() { + return new SpiderMan2099(this); + } +} + +enum SpiderMan2099Condition implements Condition { + instance; + private static final Hint hint = new ConditionHint(instance); + + public static Hint getHint() { + return hint; + } + + @Override + public boolean apply(Game game, Ability source) { + return SpiderMan2099Watcher.checkPlayer(game, source); + } + + @Override + public String toString() { + return "you've played a land or cast a spell this turn from anywhere other than your hand"; + } +} + +class SpiderMan2099Watcher extends Watcher { + + private final Set set = new HashSet<>(); + + SpiderMan2099Watcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case LAND_PLAYED: + if (!Zone.HAND.match(event.getZone())) { + set.add(event.getPlayerId()); + } + break; + case SPELL_CAST: + Spell spell = game.getSpell(event.getTargetId()); + if (spell != null && !Zone.HAND.match(spell.getFromZone())) { + set.add(spell.getControllerId()); + } + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkPlayer(Game game, Ability source) { + return game + .getState() + .getWatcher(SpiderMan2099Watcher.class) + .set + .contains(source.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderMan2099MiguelOHara.java b/Mage.Sets/src/mage/cards/s/SpiderMan2099MiguelOHara.java new file mode 100644 index 00000000000..82c23d78c46 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderMan2099MiguelOHara.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderMan2099MiguelOHara extends CardImpl { + + public SpiderMan2099MiguelOHara(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Spider-Man 2099 enters, return up to one target creature to its owner's hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect()); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // Whenever one or more creatures you control deal combat damage to a player, draw a card. + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility(new DrawCardSourceControllerEffect(1))); + } + + private SpiderMan2099MiguelOHara(final SpiderMan2099MiguelOHara card) { + super(card); + } + + @Override + public SpiderMan2099MiguelOHara copy() { + return new SpiderMan2099MiguelOHara(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderManIndia.java b/Mage.Sets/src/mage/cards/s/SpiderManIndia.java new file mode 100644 index 00000000000..24f088ca819 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderManIndia.java @@ -0,0 +1,58 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WebSlingingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderManIndia extends CardImpl { + + public SpiderManIndia(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Web-slinging {1}{G}{W} + this.addAbility(new WebSlingingAbility(this, "{1}{G}{W}")); + + // Pavitr's Seva -- Whenever you cast a creature spell, put a +1/+1 counter on target creature you control. It gains flying until end of turn. + Ability ability = new SpellCastControllerTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_SPELL_A_CREATURE, false + ); + ability.addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance()) + .setText("It gains flying until end of turn")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability.withFlavorWord("Pavitr's Seva")); + } + + private SpiderManIndia(final SpiderManIndia card) { + super(card); + } + + @Override + public SpiderManIndia copy() { + return new SpiderManIndia(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderManNoMore.java b/Mage.Sets/src/mage/cards/s/SpiderManNoMore.java new file mode 100644 index 00000000000..3ea997a0b9e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderManNoMore.java @@ -0,0 +1,58 @@ +package mage.cards.s; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureAttachedEffect; +import mage.abilities.keyword.DefenderAbility; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SpiderManNoMore extends CardImpl { + + public SpiderManNoMore(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature is a Citizen with base power and toughness 1/1. It has defender and loses all other abilities. + Effect effect = new BecomesCreatureAttachedEffect( + new CreatureToken(1, 1, "Citizen with base power and toughness 1/1") + .withSubType(SubType.CITIZEN) + .withAbility(DefenderAbility.getInstance()), + "Enchanted creature is a Citizen with base power and toughness 1/1. It has defender and loses all other abilities", + Duration.WhileOnBattlefield, + BecomesCreatureAttachedEffect.LoseType.ABILITIES_SUBTYPE + ); + this.addAbility(new SimpleStaticAbility(effect)); + } + + private SpiderManNoMore(final SpiderManNoMore card) { + super(card); + } + + @Override + public SpiderManNoMore copy() { + return new SpiderManNoMore(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderManNoir.java b/Mage.Sets/src/mage/cards/s/SpiderManNoir.java new file mode 100644 index 00000000000..abe72d486c8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderManNoir.java @@ -0,0 +1,83 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksAloneControlledTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +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.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderManNoir extends CardImpl { + + public SpiderManNoir(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever a creature you control attacks alone, put a +1/+1 counter on it. Then surveil X, where X is the number of counters on it. + Ability ability = new AttacksAloneControlledTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()) + .setText("put a +1/+1 counter on it") + ); + ability.addEffect(new SpiderManNoirEffect()); + this.addAbility(ability); + } + + private SpiderManNoir(final SpiderManNoir card) { + super(card); + } + + @Override + public SpiderManNoir copy() { + return new SpiderManNoir(this); + } +} + +class SpiderManNoirEffect extends OneShotEffect { + + SpiderManNoirEffect() { + super(Outcome.Benefit); + staticText = "Then surveil X, where X is the number of counters on it"; + } + + private SpiderManNoirEffect(final SpiderManNoirEffect effect) { + super(effect); + } + + @Override + public SpiderManNoirEffect copy() { + return new SpiderManNoirEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); + return player != null + && permanent != null + && player.surveil(permanent.getCounters(game).getTotalCount(), source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderManifestation.java b/Mage.Sets/src/mage/cards/s/SpiderManifestation.java new file mode 100644 index 00000000000..f4aa3a9ade6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderManifestation.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.UntapSourceEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.mana.GreenManaAbility; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ManaValuePredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderManifestation extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a spell with mana value 4 or greater"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.MORE_THAN, 3)); + } + + public SpiderManifestation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R/G}"); + + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // {T}: Add {R} or {G}. + this.addAbility(new RedManaAbility()); + this.addAbility(new GreenManaAbility()); + + // Whenever you cast a spell with mana value 4 or greater, untap this creature. + this.addAbility(new SpellCastControllerTriggeredAbility(new UntapSourceEffect(), filter, false)); + } + + private SpiderManifestation(final SpiderManifestation card) { + super(card); + } + + @Override + public SpiderManifestation copy() { + return new SpiderManifestation(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderMobile.java b/Mage.Sets/src/mage/cards/s/SpiderMobile.java new file mode 100644 index 00000000000..3d2c86c7e69 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderMobile.java @@ -0,0 +1,62 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.AttacksOrBlocksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.CrewAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.common.FilterControlledCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SpiderMobile extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Spider you control"); + + static { + filter.add(SubType.SPIDER.getPredicate()); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + + public SpiderMobile(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever this Vehicle attacks or blocks, it gets +1/+1 until end of turn for each Spider you control. + this.addAbility(new AttacksOrBlocksTriggeredAbility( + new BoostSourceEffect(xValue, xValue, Duration.EndOfTurn), + false + )); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + + } + + private SpiderMobile(final SpiderMobile card) { + super(card); + } + + @Override + public SpiderMobile copy() { + return new SpiderMobile(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderPunk.java b/Mage.Sets/src/mage/cards/s/SpiderPunk.java new file mode 100644 index 00000000000..5c9245b121d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderPunk.java @@ -0,0 +1,157 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.continuous.DamageCantBePreventedEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.RiotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.game.stack.Spell; +import mage.game.stack.StackAbility; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SpiderPunk extends CardImpl { + + static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.SPIDER, "Spiders you control"); + + public SpiderPunk(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Riot + this.addAbility(new RiotAbility()); + + // Other Spiders you control have riot. + Ability ability = new SimpleStaticAbility(new SpiderPunkRiotETBEffect()); + ability.addEffect(new GainAbilityControlledEffect(new RiotAbility(), Duration.WhileOnBattlefield, filter, true) + .setText("")); + this.addAbility(ability); + + // Spells and abilities can't be countered. + this.addAbility(new SimpleStaticAbility(new SpiderPunkCantCounterEffect())); + + // Damage can't be prevented. + this.addAbility(new SimpleStaticAbility(new DamageCantBePreventedEffect(Duration.WhileOnBattlefield))); + } + + private SpiderPunk(final SpiderPunk card) { + super(card); + } + + @Override + public SpiderPunk copy() { + return new SpiderPunk(this); + } +} + +class SpiderPunkCantCounterEffect extends ContinuousRuleModifyingEffectImpl { + + SpiderPunkCantCounterEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "Spells and abilities can't be countered"; + } + + + private SpiderPunkCantCounterEffect(final SpiderPunkCantCounterEffect effect) { + super(effect); + } + + @Override + public SpiderPunkCantCounterEffect copy() { + return new SpiderPunkCantCounterEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTER; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Object object = game.getObject(event.getTargetId()); + return object instanceof Spell || object instanceof StackAbility; + } + +} + +//TODO: Remove after fixing continuous effects working on entering permanents +class SpiderPunkRiotETBEffect extends ReplacementEffectImpl { + + SpiderPunkRiotETBEffect() { + super(Duration.WhileOnBattlefield, Outcome.BoostCreature); + staticText = "Other Spiders you control have riot"; + } + + private SpiderPunkRiotETBEffect(SpiderPunkRiotETBEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget(); + return creature != null + && creature.getId() != source.getSourceId() + && creature.isControlledBy(source.getControllerId()) + && creature.isCreature(game) + && !(creature instanceof PermanentToken); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget(); + Player player = game.getPlayer(source.getControllerId()); + if (creature == null || player == null) { + return false; + } + if (player.chooseUse( + outcome, "Have " + creature.getLogName() + " enter the battlefield with a +1/+1 counter on it or with haste?", + null, "+1/+1 counter", "Haste", source, game + )) { + game.informPlayers(player.getLogName() + " choose to put a +1/+1 counter on " + creature.getName()); + creature.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game, event.getAppliedEffects()); + } else { + ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom); + effect.setTargetPointer(new FixedTarget(creature.getId(), creature.getZoneChangeCounter(game) + 1)); + game.addEffect(effect, source); + } + return false; + } + + @Override + public SpiderPunkRiotETBEffect copy() { + return new SpiderPunkRiotETBEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SpiderSense.java b/Mage.Sets/src/mage/cards/s/SpiderSense.java new file mode 100644 index 00000000000..1b75f437dcc --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderSense.java @@ -0,0 +1,61 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.keyword.WebSlingingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterStackObject; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.target.TargetStackObject; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderSense extends CardImpl { + + private static final FilterStackObject filter + = new FilterStackObject("instant spell, sorcery spell, or triggered ability"); + + static { + filter.add(SpiderSensePredicate.instance); + } + + public SpiderSense(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Web-slinging {U} + this.addAbility(new WebSlingingAbility(this, "{U}")); + + // Counter target instant spell, sorcery spell, or triggered ability. + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addTarget(new TargetStackObject(filter)); + } + + private SpiderSense(final SpiderSense card) { + super(card); + } + + @Override + public SpiderSense copy() { + return new SpiderSense(this); + } +} + +enum SpiderSensePredicate implements Predicate { + instance; + + @Override + public boolean apply(StackObject input, Game game) { + if (input instanceof Spell) { + return input.isInstantOrSorcery(game); + } + return input instanceof Ability && ((Ability) input).isTriggeredAbility(); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderSlayerHatredHoned.java b/Mage.Sets/src/mage/cards/s/SpiderSlayerHatredHoned.java new file mode 100644 index 00000000000..a5640cac784 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderSlayerHatredHoned.java @@ -0,0 +1,68 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToACreatureTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.permanent.token.RobotFlyingToken; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SpiderSlayerHatredHoned extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a Spider"); + + static { + filter.add(SubType.SPIDER.getPredicate()); + } + + public SpiderSlayerHatredHoned(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Whenever Spider-Slayer deals damage to a Spider, destroy that creature. + this.addAbility(new DealsDamageToACreatureTriggeredAbility( + new DestroyTargetEffect(), + false, + false, + true, + filter + )); + + // {6}, Exile this card from your graveyard: Create two tapped 1/1 colorless Robot artifact creature tokens with flying. + Ability ability = new SimpleActivatedAbility( + new CreateTokenEffect(new RobotFlyingToken(), 2, true), + new ManaCostsImpl<>("{6}") + ); + ability.addCost(new ExileSourceFromGraveCost()); + this.addAbility(ability); + } + + private SpiderSlayerHatredHoned(final SpiderSlayerHatredHoned card) { + super(card); + } + + @Override + public SpiderSlayerHatredHoned copy() { + return new SpiderSlayerHatredHoned(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderSuit.java b/Mage.Sets/src/mage/cards/s/SpiderSuit.java new file mode 100644 index 00000000000..fdea7d04ab2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderSuit.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +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 SpiderSuit extends CardImpl { + + public SpiderSuit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +2/+2 and is a Spider Hero in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2)); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.SPIDER, AttachmentType.EQUIPMENT + ).setText("and is a Spider")); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.HERO, AttachmentType.EQUIPMENT + ).setText("Hero in addition to its other types")); + this.addAbility(ability); + + // Equip {3} + this.addAbility(new EquipAbility(3)); + } + + private SpiderSuit(final SpiderSuit card) { + super(card); + } + + @Override + public SpiderSuit copy() { + return new SpiderSuit(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderUK.java b/Mage.Sets/src/mage/cards/s/SpiderUK.java new file mode 100644 index 00000000000..486e15eef8d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderUK.java @@ -0,0 +1,111 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.WebSlingingAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.WatcherScope; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderUK extends CardImpl { + + public SpiderUK(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Web-slinging {2}{W} + this.addAbility(new WebSlingingAbility(this, "{2}{W}")); + + // At the beginning of your end step, if two or more creatures entered the battlefield under your control this turn, you draw a card and gain 2 life. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new DrawCardSourceControllerEffect(1, true) + ).withInterveningIf(SpiderUKCondition.instance); + ability.addEffect(new GainLifeEffect(2).setText("and gain 2 life")); + this.addAbility(ability, new SpiderUKWatcher()); + } + + private SpiderUK(final SpiderUK card) { + super(card); + } + + @Override + public SpiderUK copy() { + return new SpiderUK(this); + } +} + +enum SpiderUKCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return SpiderUKWatcher.checkPlayer(game, source); + } + + @Override + public String toString() { + return "two or more creatures entered the battlefield under your control this turn"; + } +} + +class SpiderUKWatcher extends Watcher { + + private final Map map = new HashMap<>(); + + SpiderUKWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { + return; + } + Optional.of(event) + .map(EntersTheBattlefieldEvent.class::cast) + .map(EntersTheBattlefieldEvent::getTarget) + .map(Controllable::getControllerId) + .ifPresent(uuid -> map.compute(uuid, CardUtil::setOrIncrementValue)); + } + + @Override + public void reset() { + super.reset(); + map.clear(); + } + + static boolean checkPlayer(Game game, Ability source) { + return game + .getState() + .getWatcher(SpiderUKWatcher.class) + .map + .getOrDefault(source.getControllerId(), 0) >= 2; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderVerse.java b/Mage.Sets/src/mage/cards/s/SpiderVerse.java new file mode 100644 index 00000000000..772030ca421 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderVerse.java @@ -0,0 +1,113 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ruleModifying.LegendRuleDoesntApplyEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterSpell; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.card.CastFromZonePredicate; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.util.functions.StackObjectCopyApplier; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SpiderVerse extends CardImpl { + + private static final FilterControlledCreaturePermanent creatureFilter = new FilterControlledCreaturePermanent("Spiders you control"); + + private static final FilterSpell spellFilter = new FilterSpell("a spell from anywhere other than your hand"); + + static { + creatureFilter.add(SubType.SPIDER.getPredicate()); + spellFilter.add(Predicates.not(new CastFromZonePredicate(Zone.HAND))); + } + + public SpiderVerse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}{R}"); + + + // The "legend rule" doesn't apply to Spiders you control. + this.addAbility(new SimpleStaticAbility(new LegendRuleDoesntApplyEffect(creatureFilter))); + + // Whenever you cast a spell from anywhere other than your hand, you may copy it. If you do, you may choose new targets for the copy. If the copy is a permanent spell, it gains haste. Do this only once each turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new SpiderVerseEffect(), + true + ).setDoOnlyOnceEachTurn(true) + ); + } + + private SpiderVerse(final SpiderVerse card) { + super(card); + } + + @Override + public SpiderVerse copy() { + return new SpiderVerse(this); + } +} + +class SpiderVerseEffect extends OneShotEffect { + + SpiderVerseEffect() { + super(Outcome.Benefit); + staticText = "copy it. If you do, you may choose new targets for the copy. " + + "If the copy is a permanent spell, it gains haste"; + } + + protected SpiderVerseEffect(final SpiderVerseEffect effect) { + super(effect); + } + + @Override + public SpiderVerseEffect copy() { + return new SpiderVerseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = (Spell) getValue("spellCast"); + if (spell == null) { + return false; + } + spell.createCopyOnStack( + game, source, source.getControllerId(), true, + 1, SpiderVerseCopyApplier.instance); + return true; + } +} + +enum SpiderVerseCopyApplier implements StackObjectCopyApplier { + instance; + + @Override + public void modifySpell(StackObject stackObject, Game game) { + if (!stackObject.isPermanent(game)) { + return; + } + Spell spell = (Spell) stackObject; + spell.addAbilityForCopy(HasteAbility.getInstance()); + } + + @Override + public MageObjectReferencePredicate getNextNewTargetType() { + return null; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SpiderWomanStunningSavior.java b/Mage.Sets/src/mage/cards/s/SpiderWomanStunningSavior.java new file mode 100644 index 00000000000..1d76f864794 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderWomanStunningSavior.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.PermanentsEnterBattlefieldTappedEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderWomanStunningSavior extends CardImpl { + + public SpiderWomanStunningSavior(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W/U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Venom Blast -- Artifacts and creatures your opponents control enter tapped. + this.addAbility(new SimpleStaticAbility(new PermanentsEnterBattlefieldTappedEffect( + StaticFilters.FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE + ).setText("artifacts and creatures your opponents control enter tapped"))); + } + + private SpiderWomanStunningSavior(final SpiderWomanStunningSavior card) { + super(card); + } + + @Override + public SpiderWomanStunningSavior copy() { + return new SpiderWomanStunningSavior(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpidersManHeroicHorde.java b/Mage.Sets/src/mage/cards/s/SpidersManHeroicHorde.java new file mode 100644 index 00000000000..9aac68caee9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpidersManHeroicHorde.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.WebSlingingCondition; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.WebSlingingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.permanent.token.Spider21Token; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpidersManHeroicHorde extends CardImpl { + + public SpidersManHeroicHorde(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Web-slinging {4}{G}{G} + this.addAbility(new WebSlingingAbility(this, "{4}{G}{G}")); + + // When Spiders-Man enters, if they were cast using web-slinging, you gain 3 life and create two 2/1 green Spider creature tokens with reach. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainLifeEffect(3)).withInterveningIf(WebSlingingCondition.THEY); + ability.addEffect(new CreateTokenEffect(new Spider21Token(), 2).concatBy("and")); + this.addAbility(ability); + } + + private SpidersManHeroicHorde(final SpidersManHeroicHorde card) { + super(card); + } + + @Override + public SpidersManHeroicHorde copy() { + return new SpidersManHeroicHorde(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpinneretAndSpiderling.java b/Mage.Sets/src/mage/cards/s/SpinneretAndSpiderling.java new file mode 100644 index 00000000000..b4dfef440a3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpinneretAndSpiderling.java @@ -0,0 +1,77 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpinneretAndSpiderling extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.SPIDER, "Spiders"); + + public SpinneretAndSpiderling(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Whenever you attack with two or more Spiders, put a +1/+1 counter on Spinneret and Spiderling. + this.addAbility(new AttacksWithCreaturesTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), 2, filter + )); + + // Whenever Spinneret and Spiderling deals 4 or more damage, exile the top card of your library. Until the end of your next turn, you may play that card. + this.addAbility(new SpinneretAndSpiderlingTriggeredAbility()); + } + + private SpinneretAndSpiderling(final SpinneretAndSpiderling card) { + super(card); + } + + @Override + public SpinneretAndSpiderling copy() { + return new SpinneretAndSpiderling(this); + } +} + +class SpinneretAndSpiderlingTriggeredAbility extends DealsDamageSourceTriggeredAbility { + + SpinneretAndSpiderlingTriggeredAbility() { + super(new ExileTopXMayPlayUntilEffect(1, Duration.UntilEndOfYourNextTurn)); + setTriggerPhrase("Whenever {this} deals 4 or more damage, "); + } + + private SpinneretAndSpiderlingTriggeredAbility(final SpinneretAndSpiderlingTriggeredAbility ability) { + super(ability); + } + + @Override + public SpinneretAndSpiderlingTriggeredAbility copy() { + return new SpinneretAndSpiderlingTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return super.checkTrigger(event, game) && event.getAmount() >= 4; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiresOfOrazca.java b/Mage.Sets/src/mage/cards/s/SpiresOfOrazca.java index 2e8945aa7e9..49d9508989b 100644 --- a/Mage.Sets/src/mage/cards/s/SpiresOfOrazca.java +++ b/Mage.Sets/src/mage/cards/s/SpiresOfOrazca.java @@ -1,35 +1,30 @@ - package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.RemoveFromCombatTargetEffect; import mage.abilities.effects.common.UntapTargetEffect; import mage.abilities.mana.ColorlessManaAbility; 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; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterOpponentsCreaturePermanent; import mage.filter.predicate.permanent.AttackingPredicate; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class SpiresOfOrazca extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("attacking creature an opponent controls"); + private static final FilterPermanent filter = new FilterOpponentsCreaturePermanent("attacking creature an opponent controls"); static { filter.add(AttackingPredicate.instance); - filter.add(TargetController.OPPONENT.getControllerPredicate()); } public SpiresOfOrazca(UUID ownerId, CardSetInfo setInfo) { @@ -40,12 +35,12 @@ public final class SpiresOfOrazca extends CardImpl { this.addAbility(new ColorlessManaAbility()); // {T}: Untap target attacking creature an opponent controls and remove it from combat. - Effect effect = new UntapTargetEffect(); - effect.setText("Untap target attacking creature an opponent controls and remove it from combat."); - Ability ability = new SimpleActivatedAbility(effect, new TapSourceCost()); - effect = new RemoveFromCombatTargetEffect(); - effect.setText(" "); - ability.addEffect(effect); + Ability ability = new SimpleActivatedAbility( + new UntapTargetEffect() + .setText("Untap target attacking creature an opponent controls"), + new TapSourceCost() + ); + ability.addEffect(new RemoveFromCombatTargetEffect().setText("and remove it from combat")); ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SquealingDevil.java b/Mage.Sets/src/mage/cards/s/SquealingDevil.java index 556bc576a33..25957faee0a 100644 --- a/Mage.Sets/src/mage/cards/s/SquealingDevil.java +++ b/Mage.Sets/src/mage/cards/s/SquealingDevil.java @@ -1,33 +1,31 @@ - package mage.cards.s; import mage.MageInt; -import mage.abilities.keyword.FearAbility; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.ManaWasSpentCondition; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.SacrificeSourceUnlessConditionEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.FearAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; -import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.constants.Duration; import mage.constants.Outcome; +import mage.constants.SubType; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class SquealingDevil extends CardImpl { @@ -76,24 +74,22 @@ class SquealingDevilEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - ManaCosts cost = new ManaCostsImpl<>("{X}"); - if (player != null) { - if (player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { - int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to boost)", game, source, true); - cost.add(new GenericManaCost(costX)); - if (cost.pay(source, game, source, source.getControllerId(), false, null)) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null && permanent.isCreature(game)) { - ContinuousEffect effect = new BoostTargetEffect(costX, 0, Duration.EndOfTurn); - effect.setTargetPointer(new FixedTarget(permanent, game)); - game.addEffect(effect, source); - return true; - } - return false; - } - } + if (player == null || !player.chooseUse(Outcome.BoostCreature, "Pay {X}?", source, game)) { + return false; } - return false; + int xValue = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to boost)", game, source, true); + ManaCosts cost = new ManaCostsImpl<>("{X}"); + cost.add(new GenericManaCost(xValue)); + if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { + return false; + } + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + game.addEffect(new BoostTargetEffect(xValue, 0, Duration.EndOfTurn) + .setTargetPointer(new FixedTarget(permanent, game)), source); + return true; } @Override diff --git a/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java b/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java index ae0fbeacb18..5497da547fb 100644 --- a/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java +++ b/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java @@ -78,14 +78,19 @@ class StalwartSuccessorTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + int zccOffset = 0; Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent == null) { + permanent = game.getPermanentEntering(event.getTargetId()); + zccOffset = 1; + } if (permanent == null || !permanent.isCreature(game) || !permanent.isControlledBy(getControllerId()) || !StalwartSuccessorWatcher.checkCreature(permanent, event, game)) { return false; } - this.getEffects().setTargetPointer(new FixedTarget(permanent, game)); + this.getEffects().setTargetPointer(new FixedTarget(permanent.getId(), permanent.getZoneChangeCounter(game) + zccOffset)); return true; } } @@ -101,6 +106,11 @@ class StalwartSuccessorWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.COUNTERS_ADDED) { + if (game.getPermanent(event.getTargetId()) == null) { + // permanent entering + Permanent permanent = game.getPermanentEntering(event.getTargetId()); + map.putIfAbsent(new MageObjectReference(event.getTargetId(), permanent.getZoneChangeCounter(game) + 1, game), event.getId()); + } map.putIfAbsent(new MageObjectReference(event.getTargetId(), game), event.getId()); } } diff --git a/Mage.Sets/src/mage/cards/s/SteelWreckingBall.java b/Mage.Sets/src/mage/cards/s/SteelWreckingBall.java new file mode 100644 index 00000000000..39c98d94484 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SteelWreckingBall.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class SteelWreckingBall extends CardImpl { + + public SteelWreckingBall(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); + + + // When this artifact enters, it deals 5 damage to target creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(5, "it")); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + + // {1}{R}, Discard this card: Destroy target artifact. + Ability discardAbility = new SimpleActivatedAbility(Zone.HAND, new DestroyTargetEffect(), + new ManaCostsImpl<>("{1}{R}")); + discardAbility.addCost(new DiscardSourceCost()); + discardAbility.addTarget(new TargetArtifactPermanent()); + this.addAbility(discardAbility); + } + + private SteelWreckingBall(final SteelWreckingBall card) { + super(card); + } + + @Override + public SteelWreckingBall copy() { + return new SteelWreckingBall(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StegronTheDinosaurMan.java b/Mage.Sets/src/mage/cards/s/StegronTheDinosaurMan.java new file mode 100644 index 00000000000..1e0ada677dc --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StegronTheDinosaurMan.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.AddCardSubTypeTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StegronTheDinosaurMan extends CardImpl { + + public StegronTheDinosaurMan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DINOSAUR); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Menace + this.addAbility(new MenaceAbility()); + + // Dinosaur Formula -- {1}{R}, Discard this card: Until end of turn, target creature you control gets +3/+1 and becomes a Dinosaur in addition to its other types. + Ability ability = new SimpleActivatedAbility( + Zone.HAND, + new BoostTargetEffect(3, 1) + .setText("until end of turn, target creature you control gets +3/+1"), + new ManaCostsImpl<>("{1}{R}") + ); + ability.addCost(new DiscardSourceCost()); + ability.addEffect(new AddCardSubTypeTargetEffect(SubType.DINOSAUR, Duration.EndOfTurn) + .setText("and becomes a Dinosaur in addition to its other types")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability.withFlavorWord("Dinosaur Formula")); + } + + private StegronTheDinosaurMan(final StegronTheDinosaurMan card) { + super(card); + } + + @Override + public StegronTheDinosaurMan copy() { + return new StegronTheDinosaurMan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StickTogether.java b/Mage.Sets/src/mage/cards/s/StickTogether.java index 50f8d9b917c..58064f05a35 100644 --- a/Mage.Sets/src/mage/cards/s/StickTogether.java +++ b/Mage.Sets/src/mage/cards/s/StickTogether.java @@ -139,14 +139,7 @@ class StickTogetherTarget extends TargetPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } - - static int checkTargetCount(Ability source, Game game) { - List permanents = game - .getBattlefield() - .getActivePermanents(filterParty, source.getControllerId(), source, game); - return subTypeAssigner.getRoleCount(new CardsImpl(permanents), game); - } } diff --git a/Mage.Sets/src/mage/cards/s/StoicFarmer.java b/Mage.Sets/src/mage/cards/s/StoicFarmer.java index 364f78fdefd..c2c90b44517 100644 --- a/Mage.Sets/src/mage/cards/s/StoicFarmer.java +++ b/Mage.Sets/src/mage/cards/s/StoicFarmer.java @@ -12,8 +12,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; @@ -24,13 +22,6 @@ import java.util.UUID; */ public final class StoicFarmer extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS); public StoicFarmer(UUID ownerId, CardSetInfo setInfo) { @@ -44,8 +35,8 @@ public final class StoicFarmer extends CardImpl { // When Stoic Farmer enters the battlefield, search your library for a basic Plains card and reveal it. If an opponent controls more lands than you, put it onto the battlefield tapped. Otherwise, put it into your hand. Then shuffle your library. this.addAbility(new EntersBattlefieldTriggeredAbility( new ConditionalOneShotEffect( - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true), - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true), + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true), condition, "search your library for a basic Plains card and reveal it. " + "If an opponent controls more lands than you, put it onto the battlefield tapped. " + "Otherwise put it into your hand. Then shuffle" diff --git a/Mage.Sets/src/mage/cards/s/StoneGiant.java b/Mage.Sets/src/mage/cards/s/StoneGiant.java index 5a781fa56be..a0f1062dfc0 100644 --- a/Mage.Sets/src/mage/cards/s/StoneGiant.java +++ b/Mage.Sets/src/mage/cards/s/StoneGiant.java @@ -68,13 +68,13 @@ class StoneGiantTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent sourceCreature = game.getPermanent(source.getSourceId()); Permanent targetCreature = game.getPermanent(id); if (targetCreature != null && sourceCreature != null && targetCreature.getToughness().getValue() < sourceCreature.getPower().getValue()) { - return super.canTarget(controllerId, id, source, game); + return super.canTarget(playerId, id, source, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/s/StranglingGrasp.java b/Mage.Sets/src/mage/cards/s/StranglingGrasp.java index 443a465ff54..e8b2b3ab4ab 100644 --- a/Mage.Sets/src/mage/cards/s/StranglingGrasp.java +++ b/Mage.Sets/src/mage/cards/s/StranglingGrasp.java @@ -1,10 +1,10 @@ package mage.cards.s; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.keyword.EnchantAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -14,11 +14,14 @@ import mage.constants.TargetController; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.permanent.CanBeSacrificedPredicate; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; +import java.util.Optional; import java.util.UUID; /** @@ -44,13 +47,10 @@ public final class StranglingGrasp extends CardImpl { TargetPermanent auraTarget = new TargetPermanent(filter); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // At the beginning of your upkeep, enchanted permanent's controller sacrifices a nonland permanent and loses 1 life. - this.addAbility(new BeginningOfUpkeepTriggeredAbility( - new StranglingGraspEffect() - )); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new StranglingGraspEffect())); } private StranglingGrasp(final StranglingGrasp card) { @@ -69,6 +69,7 @@ class StranglingGraspEffect extends OneShotEffect { static { filter.add(TargetController.YOU.getControllerPredicate()); + filter.add(CanBeSacrificedPredicate.instance); } StranglingGraspEffect() { @@ -87,15 +88,13 @@ class StranglingGraspEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent sourcePermanent = source.getSourcePermanentOrLKI(game); - if (sourcePermanent == null) { - return false; - } - Permanent attachedTo = game.getPermanentOrLKIBattlefield(sourcePermanent.getAttachedTo()); - if (attachedTo == null) { - return false; - } - Player player = game.getPlayer(attachedTo.getControllerId()); + Player player = Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .map(game::getPermanentOrLKIBattlefield) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .orElse(null); if (player == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/StrionicResonator.java b/Mage.Sets/src/mage/cards/s/StrionicResonator.java index 7154eed7fb6..c521659aad9 100644 --- a/Mage.Sets/src/mage/cards/s/StrionicResonator.java +++ b/Mage.Sets/src/mage/cards/s/StrionicResonator.java @@ -10,7 +10,10 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.TargetController; import mage.filter.FilterStackObject; -import mage.target.common.TargetTriggeredAbility; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.stack.StackObject; +import mage.target.TargetStackObject; import java.util.UUID; @@ -22,6 +25,7 @@ public final class StrionicResonator extends CardImpl { private static final FilterStackObject filter = new FilterStackObject("triggered ability you control"); static { + filter.add(StrionicResonatorPredicate.instance); filter.add(TargetController.YOU.getControllerPredicate()); } @@ -31,7 +35,7 @@ public final class StrionicResonator extends CardImpl { // {2}, {T}: Copy target triggered ability you control. You may choose new targets for the copy. Ability ability = new SimpleActivatedAbility(new CopyTargetStackObjectEffect(), new ManaCostsImpl<>("{2}")); ability.addCost(new TapSourceCost()); - ability.addTarget(new TargetTriggeredAbility(filter)); + ability.addTarget(new TargetStackObject(filter)); this.addAbility(ability); } @@ -44,3 +48,12 @@ public final class StrionicResonator extends CardImpl { return new StrionicResonator(this); } } + +enum StrionicResonatorPredicate implements Predicate { + instance; + + @Override + public boolean apply(StackObject input, Game game) { + return input instanceof Ability && ((Ability) input).isTriggeredAbility(); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SuburbanSanctuary.java b/Mage.Sets/src/mage/cards/s/SuburbanSanctuary.java new file mode 100644 index 00000000000..efebac766ed --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SuburbanSanctuary.java @@ -0,0 +1,46 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.mana.GreenManaAbility; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SuburbanSanctuary extends CardImpl { + + public SuburbanSanctuary(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {G} or {W}. + this.addAbility(new GreenManaAbility()); + this.addAbility(new WhiteManaAbility()); + + // {4}, {T}: Surveil 1. + Ability ability = new SimpleActivatedAbility(new SurveilEffect(1), new GenericManaCost(4)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private SuburbanSanctuary(final SuburbanSanctuary card) { + super(card); + } + + @Override + public SuburbanSanctuary copy() { + return new SuburbanSanctuary(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SubwayTrain.java b/Mage.Sets/src/mage/cards/s/SubwayTrain.java new file mode 100644 index 00000000000..152f21e40cb --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SubwayTrain.java @@ -0,0 +1,47 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SubwayTrain extends CardImpl { + + public SubwayTrain(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // When this Vehicle enters, you may pay {G}. If you do, search your library for a basic land card, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoIfCostPaid(new SearchLibraryPutInHandEffect( + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true + ), new ManaCostsImpl<>("{G}")))); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + } + + private SubwayTrain(final SubwayTrain card) { + super(card); + } + + @Override + public SubwayTrain copy() { + return new SubwayTrain(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SuddenStrike.java b/Mage.Sets/src/mage/cards/s/SuddenStrike.java new file mode 100644 index 00000000000..5c3cd824dd3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SuddenStrike.java @@ -0,0 +1,32 @@ +package mage.cards.s; + +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetAttackingOrBlockingCreature; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SuddenStrike extends CardImpl { + + public SuddenStrike(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Destroy target attacking or blocking creature. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetAttackingOrBlockingCreature()); + } + + private SuddenStrike(final SuddenStrike card) { + super(card); + } + + @Override + public SuddenStrike copy() { + return new SuddenStrike(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SukiKyoshiWarrior.java b/Mage.Sets/src/mage/cards/s/SukiKyoshiWarrior.java new file mode 100644 index 00000000000..b5db8835d9b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SukiKyoshiWarrior.java @@ -0,0 +1,49 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +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.permanent.token.AllyToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SukiKyoshiWarrior extends CardImpl { + + public SukiKyoshiWarrior(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G/W}{G/W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Suki's power is equal to the number of creatures you control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(CreaturesYouControlCount.PLURAL))); + + // Whenever Suki attacks, create a 1/1 white Ally creature token that's tapped and attacking. + this.addAbility(new AttacksTriggeredAbility(new CreateTokenEffect(new AllyToken(), 1, true, true))); + } + + private SukiKyoshiWarrior(final SukiKyoshiWarrior card) { + super(card); + } + + @Override + public SukiKyoshiWarrior copy() { + return new SukiKyoshiWarrior(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SunSpiderNimbleWebber.java b/Mage.Sets/src/mage/cards/s/SunSpiderNimbleWebber.java new file mode 100644 index 00000000000..618c633b206 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunSpiderNimbleWebber.java @@ -0,0 +1,67 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SunSpiderNimbleWebber extends CardImpl { + + private static final FilterCard filter = new FilterCard("an Aura or Equipment card"); + + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate() + )); + } + + public SunSpiderNimbleWebber(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W/U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // During your turn, Sun-Spider has flying. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield), + MyTurnCondition.instance, "during your turn, {this} has flying" + ))); + + // When Sun-Spider enters, search your library for an Aura or Equipment card, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + )); + } + + private SunSpiderNimbleWebber(final SunSpiderNimbleWebber card) { + super(card); + } + + @Override + public SunSpiderNimbleWebber copy() { + return new SunSpiderNimbleWebber(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SunbladeSamurai.java b/Mage.Sets/src/mage/cards/s/SunbladeSamurai.java index 7224e1a3e64..658ea9f4b1b 100644 --- a/Mage.Sets/src/mage/cards/s/SunbladeSamurai.java +++ b/Mage.Sets/src/mage/cards/s/SunbladeSamurai.java @@ -10,8 +10,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -21,12 +21,7 @@ import java.util.UUID; */ public final class SunbladeSamurai extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.PLAINS); public SunbladeSamurai(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{W}"); diff --git a/Mage.Sets/src/mage/cards/s/SupportiveParents.java b/Mage.Sets/src/mage/cards/s/SupportiveParents.java new file mode 100644 index 00000000000..22a8f5701bd --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SupportiveParents.java @@ -0,0 +1,41 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.mana.AnyColorManaAbility; +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 SupportiveParents extends CardImpl { + + public SupportiveParents(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Tap two untapped creatures you control: Add one mana of any color. + this.addAbility(new AnyColorManaAbility(new TapTargetCost( + 2, StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURES + ))); + } + + private SupportiveParents(final SupportiveParents card) { + super(card); + } + + @Override + public SupportiveParents copy() { + return new SupportiveParents(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SurgeConductor.java b/Mage.Sets/src/mage/cards/s/SurgeConductor.java index be0d385f7ea..276fee76c2f 100644 --- a/Mage.Sets/src/mage/cards/s/SurgeConductor.java +++ b/Mage.Sets/src/mage/cards/s/SurgeConductor.java @@ -24,7 +24,7 @@ public final class SurgeConductor extends CardImpl { static { filter.add(AnotherPredicate.instance); - filter.add(TokenPredicate.TRUE); + filter.add(TokenPredicate.FALSE); } public SurgeConductor(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/s/SwarmBeingOfBees.java b/Mage.Sets/src/mage/cards/s/SwarmBeingOfBees.java new file mode 100644 index 00000000000..ff0235dacba --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SwarmBeingOfBees.java @@ -0,0 +1,47 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.MayhemAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SwarmBeingOfBees extends CardImpl { + + public SwarmBeingOfBees(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Mayhem {B} + this.addAbility(new MayhemAbility(this, "{B}")); + } + + private SwarmBeingOfBees(final SwarmBeingOfBees card) { + super(card); + } + + @Override + public SwarmBeingOfBees copy() { + return new SwarmBeingOfBees(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SymbioteSpiderMan.java b/Mage.Sets/src/mage/cards/s/SymbioteSpiderMan.java new file mode 100644 index 00000000000..188e9d2b69e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SymbioteSpiderMan.java @@ -0,0 +1,62 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SymbioteSpiderMan extends CardImpl { + + public SymbioteSpiderMan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U/B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SYMBIOTE); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Whenever this creature deals combat damage to a player, look at that many cards from the top of your library. Put one of them into your hand and the rest into your graveyard. + this.addAbility(makeAbility()); + + // Find New Host -- {2}{U/B}, Exile this card from your graveyard: Put a +1/+1 counter on target creature you control. It gains this card's other abilities. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), new ManaCostsImpl<>("{2}{U/B}") + ); + ability.addEffect(new GainAbilityTargetEffect(makeAbility(), Duration.Custom) + .setText("It gains this card's other abilities")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability.withFlavorWord("Find New Host")); + } + + private SymbioteSpiderMan(final SymbioteSpiderMan card) { + super(card); + } + + @Override + public SymbioteSpiderMan copy() { + return new SymbioteSpiderMan(this); + } + + private static Ability makeAbility() { + return new DealsCombatDamageToAPlayerTriggeredAbility(new LookLibraryAndPickControllerEffect( + SavedDamageValue.MANY, 1, PutCards.HAND, PutCards.GRAVEYARD + )); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SynodSanctum.java b/Mage.Sets/src/mage/cards/s/SynodSanctum.java index 3f300841cdf..2f4f54cf0ac 100644 --- a/Mage.Sets/src/mage/cards/s/SynodSanctum.java +++ b/Mage.Sets/src/mage/cards/s/SynodSanctum.java @@ -83,7 +83,7 @@ class SynodSanctumEffect extends OneShotEffect { if (getTargetPointer().getFirst(game, source) != null) { Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { - UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); if (exileZone != null) { controller.moveCardToExileWithInfo(permanent, exileZone, sourceObject.getIdName(), source, game, Zone.BATTLEFIELD, true); } @@ -113,7 +113,7 @@ class SynodSanctumEffect2 extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exileZone = game.getExile().getExileZone(exileId); if (exileZone == null) { return true; diff --git a/Mage.Sets/src/mage/cards/s/SynthesisPod.java b/Mage.Sets/src/mage/cards/s/SynthesisPod.java index b668b9fe5bc..d1d90bedd00 100644 --- a/Mage.Sets/src/mage/cards/s/SynthesisPod.java +++ b/Mage.Sets/src/mage/cards/s/SynthesisPod.java @@ -93,7 +93,7 @@ class SynthesisPodCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TalionTheKindlyLord.java b/Mage.Sets/src/mage/cards/t/TalionTheKindlyLord.java index 19ccefbf0f6..bb9e7e6c6d6 100644 --- a/Mage.Sets/src/mage/cards/t/TalionTheKindlyLord.java +++ b/Mage.Sets/src/mage/cards/t/TalionTheKindlyLord.java @@ -111,7 +111,7 @@ class TalionTheKindlyLordEffect extends OneShotEffect { } int numberChoice = controller.getAmount(1, 10, "Choose a number.", source, game); game.getState().setValue("chosenNumber_" + source.getSourceId() - + '_' + source.getSourceObjectZoneChangeCounter(), numberChoice); + + '_' + source.getStackMomentSourceZCC(), numberChoice); Permanent permanent = game.getPermanentEntering(source.getSourceId()); if (permanent != null) { permanent.addInfo("chosen players", "Chosen Number: " + numberChoice + "", game); diff --git a/Mage.Sets/src/mage/cards/t/Technomancer.java b/Mage.Sets/src/mage/cards/t/Technomancer.java index 830c673ba3d..1db84f1f7b8 100644 --- a/Mage.Sets/src/mage/cards/t/Technomancer.java +++ b/Mage.Sets/src/mage/cards/t/Technomancer.java @@ -107,8 +107,8 @@ class TechnomancerTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 6, game); } diff --git a/Mage.Sets/src/mage/cards/t/TempleOfAclazotz.java b/Mage.Sets/src/mage/cards/t/TempleOfAclazotz.java index 9493630ae49..ca7fc5af6cb 100644 --- a/Mage.Sets/src/mage/cards/t/TempleOfAclazotz.java +++ b/Mage.Sets/src/mage/cards/t/TempleOfAclazotz.java @@ -1,27 +1,21 @@ - package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.Cost; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.dynamicvalue.common.SacrificeCostCreaturesToughness; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.mana.BlackManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.players.Player; -import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class TempleOfAclazotz extends CardImpl { @@ -36,7 +30,8 @@ public final class TempleOfAclazotz extends CardImpl { this.addAbility(new BlackManaAbility()); // {T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness. - Ability ability = new SimpleActivatedAbility(new TempleOfAclazotzEffect(), new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(new GainLifeEffect(SacrificeCostCreaturesToughness.instance) + .setText("you gain life equal to the sacrificed creature's toughness"), new TapSourceCost()); ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE)); this.addAbility(ability); } @@ -50,37 +45,3 @@ public final class TempleOfAclazotz extends CardImpl { return new TempleOfAclazotz(this); } } - -class TempleOfAclazotzEffect extends OneShotEffect { - - TempleOfAclazotzEffect() { - super(Outcome.GainLife); - this.staticText = "You gain life equal to the sacrificed creature's toughness"; - } - - private TempleOfAclazotzEffect(final TempleOfAclazotzEffect effect) { - super(effect); - } - - @Override - public TempleOfAclazotzEffect copy() { - return new TempleOfAclazotzEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - for (Cost cost : source.getCosts()) { - if (cost instanceof SacrificeTargetCost) { - int amount = ((SacrificeTargetCost) cost).getPermanents().get(0).getToughness().getValue(); - if (amount > 0) { - controller.gainLife(amount, game, source); - } - } - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TempleOfTheDragonQueen.java b/Mage.Sets/src/mage/cards/t/TempleOfTheDragonQueen.java index f680b5d10e8..cf986419a7e 100644 --- a/Mage.Sets/src/mage/cards/t/TempleOfTheDragonQueen.java +++ b/Mage.Sets/src/mage/cards/t/TempleOfTheDragonQueen.java @@ -15,7 +15,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -54,7 +54,7 @@ public final class TempleOfTheDragonQueen extends CardImpl { class TempleOfTheDragonQueenEffect extends OneShotEffect { - private static final FilterBySubtypeCard filter = new FilterBySubtypeCard(SubType.DRAGON); + private static final FilterCard filter = new FilterCard(SubType.DRAGON); public TempleOfTheDragonQueenEffect() { super(Outcome.Tap); diff --git a/Mage.Sets/src/mage/cards/t/TemptingWurm.java b/Mage.Sets/src/mage/cards/t/TemptingWurm.java index 1d808530d7e..aeafeff497c 100644 --- a/Mage.Sets/src/mage/cards/t/TemptingWurm.java +++ b/Mage.Sets/src/mage/cards/t/TemptingWurm.java @@ -1,34 +1,30 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.cards.*; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetCardInHand; +import mage.target.TargetCard; + +import java.util.UUID; /** - * * @author Eirkei */ public final class TemptingWurm extends CardImpl { public TemptingWurm(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); this.subtype.add(SubType.WURM); this.power = new MageInt(5); this.toughness = new MageInt(5); @@ -59,43 +55,36 @@ class TemptingWurmEffect extends OneShotEffect { CardType.LAND.getPredicate() )); } - + TemptingWurmEffect() { super(Outcome.Detriment); this.staticText = "each opponent may put any number of artifact, creature, enchantment, and/or land cards from their hand onto the battlefield."; } - + @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - + if (controller != null) { Cards cards = new CardsImpl(); - + for (UUID playerId : game.getOpponents(controller.getId())) { Player opponent = game.getPlayer(playerId); - - if (opponent != null){ - Target target = new TargetCardInHand(0, Integer.MAX_VALUE, filter); - - if (target.canChoose(opponent.getId(), source, game)) { - if (opponent.chooseUse(Outcome.PutCardInPlay , "Put any artifact, creature, enchantment, and/or land cards cards from your hand onto the battlefield?", source, game)) { - if (target.chooseTarget(Outcome.PutCardInPlay, opponent.getId(), source, game)) { - for (UUID cardId: target.getTargets()){ - Card card = game.getCard(cardId); - - if (card != null) { - cards.add(card); - } - } + if (opponent != null) { + Target target = new TargetCard(0, Integer.MAX_VALUE, Zone.HAND, filter).withChooseHint("put from hand to battlefield"); + if (target.chooseTarget(Outcome.PutCardInPlay, opponent.getId(), source, game)) { + for (UUID cardId : target.getTargets()) { + Card card = game.getCard(cardId); + if (card != null) { + cards.add(card); } } } } } - + controller.moveCards(cards.getCards(game), Zone.BATTLEFIELD, source, game, false, false, true, null); - + return true; } @@ -105,7 +94,7 @@ class TemptingWurmEffect extends OneShotEffect { private TemptingWurmEffect(final TemptingWurmEffect effect) { super(effect); } - + @Override public TemptingWurmEffect copy() { return new TemptingWurmEffect(this); diff --git a/Mage.Sets/src/mage/cards/t/Terrasymbiosis.java b/Mage.Sets/src/mage/cards/t/Terrasymbiosis.java index 447a2ac2b93..bc3a48921ea 100644 --- a/Mage.Sets/src/mage/cards/t/Terrasymbiosis.java +++ b/Mage.Sets/src/mage/cards/t/Terrasymbiosis.java @@ -1,6 +1,6 @@ package mage.cards.t; -import mage.abilities.common.PutCounterOnCreatureTriggeredAbility; +import mage.abilities.common.PutCounterOnPermanentTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.EffectKeyValue; import mage.abilities.effects.common.DrawCardSourceControllerEffect; @@ -23,9 +23,9 @@ public final class Terrasymbiosis extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); // Whenever you put one or more +1/+1 counters on a creature you control, you may draw that many cards. Do this only once each turn. - this.addAbility(new PutCounterOnCreatureTriggeredAbility( + this.addAbility(new PutCounterOnPermanentTriggeredAbility( new DrawCardSourceControllerEffect(xValue), - CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + CounterType.P1P1, StaticFilters.FILTER_CONTROLLED_CREATURE ).setDoOnlyOnceEachTurn(true)); } diff --git a/Mage.Sets/src/mage/cards/t/TheAesirEscapeValhalla.java b/Mage.Sets/src/mage/cards/t/TheAesirEscapeValhalla.java index 59e83c71b2d..cb659de5718 100644 --- a/Mage.Sets/src/mage/cards/t/TheAesirEscapeValhalla.java +++ b/Mage.Sets/src/mage/cards/t/TheAesirEscapeValhalla.java @@ -116,7 +116,7 @@ class TheAesirEscapeValhallaTwoEffect extends OneShotEffect { if (controller == null || sourceObject == null) { return false; } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exileZone = game.getExile().getExileZone(exileId); if (exileZone == null || exileZone.isEmpty()) { return false; @@ -154,7 +154,7 @@ class TheAesirEscapeValhallaThreeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exileZone = game.getExile().getExileZone(exileId); Player controller = game.getPlayer(source.getControllerId()); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/t/TheBirthOfMeletis.java b/Mage.Sets/src/mage/cards/t/TheBirthOfMeletis.java index a5c02de00f0..18ee073cf7d 100644 --- a/Mage.Sets/src/mage/cards/t/TheBirthOfMeletis.java +++ b/Mage.Sets/src/mage/cards/t/TheBirthOfMeletis.java @@ -9,8 +9,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SagaChapter; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.game.permanent.token.ArtifactWallToken; import mage.target.common.TargetCardInLibrary; @@ -21,13 +20,6 @@ import java.util.UUID; */ public final class TheBirthOfMeletis extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); - } - public TheBirthOfMeletis(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); @@ -39,7 +31,7 @@ public final class TheBirthOfMeletis extends CardImpl { // I — Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle your library. sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_I, new SearchLibraryPutInHandEffect( - new TargetCardInLibrary(filter), true + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true ) ); diff --git a/Mage.Sets/src/mage/cards/t/TheCabbageMerchant.java b/Mage.Sets/src/mage/cards/t/TheCabbageMerchant.java new file mode 100644 index 00000000000..d79315922f4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheCabbageMerchant.java @@ -0,0 +1,67 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.DealsDamageToYouAllTriggeredAbility; +import mage.abilities.common.SpellCastOpponentTriggeredAbility; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.SacrificeControllerEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.permanent.token.FoodToken; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheCabbageMerchant extends CardImpl { + + private static final FilterControlledPermanent filter + = new FilterControlledPermanent(SubType.FOOD, "untapped Foods you control"); + + static { + filter.add(TappedPredicate.UNTAPPED); + } + + public TheCabbageMerchant(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.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever an opponent casts a noncreature spell, create a Food token. + this.addAbility(new SpellCastOpponentTriggeredAbility( + new CreateTokenEffect(new FoodToken()), StaticFilters.FILTER_SPELL_NON_CREATURE, false + )); + + // Whenever a creature deals combat damage to you, sacrifice a Food token. + this.addAbility(new DealsDamageToYouAllTriggeredAbility( + StaticFilters.FILTER_PERMANENT_CREATURE, + new SacrificeControllerEffect(StaticFilters.FILTER_CONTROLLED_FOOD, 1, ""), true + )); + + // Tap two untapped Foods you control: Add one mana of any color. + this.addAbility(new AnyColorManaAbility(new TapTargetCost(new TargetControlledPermanent(2, filter)))); + } + + private TheCabbageMerchant(final TheCabbageMerchant card) { + super(card); + } + + @Override + public TheCabbageMerchant copy() { + return new TheCabbageMerchant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheCapitolineTriad.java b/Mage.Sets/src/mage/cards/t/TheCapitolineTriad.java index 1b00eec46b6..107603aedee 100644 --- a/Mage.Sets/src/mage/cards/t/TheCapitolineTriad.java +++ b/Mage.Sets/src/mage/cards/t/TheCapitolineTriad.java @@ -1,9 +1,7 @@ package mage.cards.t; import java.awt.*; -import java.util.Collection; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import mage.MageInt; import mage.MageObject; @@ -170,7 +168,10 @@ class TheCapitolineTriadTarget extends TargetCardInYourGraveyard { return false; } // Check that exiling all the possible cards would have >= 4 different card types - return metCondition(this.possibleTargets(sourceControllerId, source, game), game); + Set idsToCheck = new HashSet<>(); + idsToCheck.addAll(this.getTargets()); + idsToCheck.addAll(this.possibleTargets(sourceControllerId, source, game)); + return metCondition(idsToCheck, game); } private static int manaValueOfSelection(Collection cardsIds, Game game) { diff --git a/Mage.Sets/src/mage/cards/t/TheCloneSaga.java b/Mage.Sets/src/mage/cards/t/TheCloneSaga.java new file mode 100644 index 00000000000..b6f244f7644 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheCloneSaga.java @@ -0,0 +1,108 @@ +package mage.cards.t; + +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SagaAbility; +import mage.abilities.common.delayed.CopyNextSpellDelayedTriggeredAbility; +import mage.abilities.effects.common.ChooseACardNameEffect; +import mage.abilities.effects.common.CopyTargetStackObjectEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.keyword.SurveilEffect; +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.ChosenNamePredicate; +import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.util.functions.RemoveTypeCopyApplier; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class TheCloneSaga extends CardImpl { + + + + public TheCloneSaga(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}"); + + 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 -- Surveil 3. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, new SurveilEffect(3)); + + // II -- When you next cast a creature spell this turn, copy it, except the copy isn't legendary. + DelayedTriggeredAbility ability = new CopyNextSpellDelayedTriggeredAbility( + StaticFilters.FILTER_SPELL_A_CREATURE, + new CopyTargetStackObjectEffect(false, false, false, 1, new RemoveTypeCopyApplier(SuperType.LEGENDARY)), + "When you next cast a creature spell this turn, copy it, except the copy isn't legendary" + ); + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new CreateDelayedTriggeredAbilityEffect(ability)); + + // III -- Choose a card name. Whenever a creature with the chosen name deals combat damage to a player this turn, draw a card. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.ALL), + new CreateDelayedTriggeredAbilityEffect(new TheCloneSagaDelayedTrigger())); + + this.addAbility(sagaAbility); + } + + private TheCloneSaga(final TheCloneSaga card) { + super(card); + } + + @Override + public TheCloneSaga copy() { + return new TheCloneSaga(this); + } +} +class TheCloneSagaDelayedTrigger extends DelayedTriggeredAbility { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature with the chosen name"); + + static { + filter.add(ChosenNamePredicate.instance); + } + + TheCloneSagaDelayedTrigger() { + super(new DrawCardSourceControllerEffect(1), Duration.EndOfTurn, false); + } + + private TheCloneSagaDelayedTrigger(final TheCloneSagaDelayedTrigger ability) { + super(ability); + } + + @Override + public TheCloneSagaDelayedTrigger copy() { + return new TheCloneSagaDelayedTrigger(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (((DamagedPlayerEvent) event).isCombatDamage()) { + Permanent creature = game.getPermanent(event.getSourceId()); + return creature != null && filter.match(creature, getControllerId(), this, game); + } + return false; + } + + @Override + public String getRule() { + return "Whenever a creature with the chosen name deals combat damage to a player this turn, draw a card."; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TheCreationOfAvacyn.java b/Mage.Sets/src/mage/cards/t/TheCreationOfAvacyn.java index 17b28d39754..1e89f2a9483 100644 --- a/Mage.Sets/src/mage/cards/t/TheCreationOfAvacyn.java +++ b/Mage.Sets/src/mage/cards/t/TheCreationOfAvacyn.java @@ -119,7 +119,7 @@ class TheCreationOfAvacynTwoEffect extends OneShotEffect { // ability (likely because the triggered ability was copied or the ability triggered a second time), the second // chapter ability will turn all of the exiled cards face up. If at least one of them is a creature card, you'll // lose life equal to the combined mana value of all the exiled cards. - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exileZone = game.getExile().getExileZone(exileId); if (exileZone == null || exileZone.isEmpty()) { return false; @@ -163,7 +163,7 @@ class TheCreationOfAvacynThreeEffect extends OneShotEffect { // when the third chapter ability resolves, if at least one of the exiled cards is a creature card, you may choose // to put all or none of the exiled cards that are permanent cards onto the battlefield. Regardless of what you // choose, any remaining exiled cards will be put into their owners' hands. - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exileZone = game.getExile().getExileZone(exileId); Player controller = game.getPlayer(source.getControllerId()); if (controller == null || exileZone == null || exileZone.isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/t/TheLocustGod.java b/Mage.Sets/src/mage/cards/t/TheLocustGod.java index f1d0f8526c1..62afa8bb57a 100644 --- a/Mage.Sets/src/mage/cards/t/TheLocustGod.java +++ b/Mage.Sets/src/mage/cards/t/TheLocustGod.java @@ -22,7 +22,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Outcome; import mage.constants.SuperType; -import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.token.TheLocustGodInsectToken; import mage.target.targetpointer.FixedTarget; @@ -83,7 +82,7 @@ class TheLocustGodEffect extends OneShotEffect { // Create delayed triggered ability Effect effect = new ReturnToHandTargetEffect(); effect.setText("return {this} to its owner's hand"); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getSourceObjectZoneChangeCounter())); + effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getStackMomentSourceZCC())); DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); game.addDelayedTriggeredAbility(delayedAbility, source); return true; diff --git a/Mage.Sets/src/mage/cards/t/TheMaryJanes.java b/Mage.Sets/src/mage/cards/t/TheMaryJanes.java new file mode 100644 index 00000000000..63210e9109d --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheMaryJanes.java @@ -0,0 +1,52 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CreaturesThatAttackedThisTurnCount; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheMaryJanes extends CardImpl { + + public TheMaryJanes(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.BARD); + this.subtype.add(SubType.PERFORMER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // This spell costs {1} less to cast for each creature that attacked this turn. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, + new SpellCostReductionForEachSourceEffect( + 1, CreaturesThatAttackedThisTurnCount.instance + ) + ).addHint(CreaturesThatAttackedThisTurnCount.getHint())); + + // Menace + this.addAbility(new MenaceAbility()); + } + + private TheMaryJanes(final TheMaryJanes card) { + super(card); + } + + @Override + public TheMaryJanes copy() { + return new TheMaryJanes(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheRestorationOfEiganjo.java b/Mage.Sets/src/mage/cards/t/TheRestorationOfEiganjo.java index 6d8e353f4c7..3de02b60f26 100644 --- a/Mage.Sets/src/mage/cards/t/TheRestorationOfEiganjo.java +++ b/Mage.Sets/src/mage/cards/t/TheRestorationOfEiganjo.java @@ -10,8 +10,12 @@ import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SagaChapter; +import mage.constants.SubType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterPermanentCard; import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.target.common.TargetCardInLibrary; @@ -24,14 +28,10 @@ import java.util.UUID; */ public final class TheRestorationOfEiganjo extends CardImpl { - private static final FilterCard filter - = new FilterCard("a basic Plains card"); private static final FilterCard filter2 = new FilterPermanentCard("permanent card with mana value 2 or less from your graveyard"); static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); filter2.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 3)); } @@ -47,7 +47,7 @@ public final class TheRestorationOfEiganjo extends CardImpl { // I - Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_I, new SearchLibraryPutInHandEffect( - new TargetCardInLibrary(filter), true + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true ) ); diff --git a/Mage.Sets/src/mage/cards/t/TheRiseOfSozin.java b/Mage.Sets/src/mage/cards/t/TheRiseOfSozin.java new file mode 100644 index 00000000000..78a8b2bd579 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheRiseOfSozin.java @@ -0,0 +1,85 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.Effects; +import mage.abilities.effects.common.ChooseACardNameEffect; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.ExileSagaAndReturnTransformedEffect; +import mage.abilities.effects.common.search.SearchTargetGraveyardHandLibraryForCardNameAndExileEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheRiseOfSozin extends CardImpl { + + public TheRiseOfSozin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{B}{B}"); + + this.subtype.add(SubType.SAGA); + this.secondSideCardClazz = mage.cards.f.FireLordSozin.class; + + // (As this Saga enters and after your draw step, add a lore counter.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Destroy all creatures. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES) + ); + + // II -- Choose a card name. Search target opponent's graveyard, hand, and library for up to four cards with that name and exile them. Then that player shuffles. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, + new Effects( + new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.ALL), new TheRiseOfSozinEffect() + ), new TargetOpponent() + ); + + // III -- Exile this Saga, then return it to the battlefield transformed under your control. + this.addAbility(new TransformAbility()); + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new ExileSagaAndReturnTransformedEffect()); + this.addAbility(sagaAbility); + } + + private TheRiseOfSozin(final TheRiseOfSozin card) { + super(card); + } + + @Override + public TheRiseOfSozin copy() { + return new TheRiseOfSozin(this); + } +} + +class TheRiseOfSozinEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExileEffect { + + TheRiseOfSozinEffect() { + super(true, "target opponent's", "up to four cards with that name", false, 4); + } + + private TheRiseOfSozinEffect(final TheRiseOfSozinEffect effect) { + super(effect); + } + + @Override + public TheRiseOfSozinEffect copy() { + return new TheRiseOfSozinEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + String chosenCardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); + return applySearchAndExile(game, source, chosenCardName, getTargetPointer().getFirst(game, source)); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheScarabGod.java b/Mage.Sets/src/mage/cards/t/TheScarabGod.java index 500c819a2ec..17deb6d479a 100644 --- a/Mage.Sets/src/mage/cards/t/TheScarabGod.java +++ b/Mage.Sets/src/mage/cards/t/TheScarabGod.java @@ -127,7 +127,7 @@ class TheScarabGodEffectDieEffect extends OneShotEffect { // Create delayed triggered ability Effect effect = new ReturnToHandTargetEffect(); effect.setText("return {this} to its owner's hand"); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getSourceObjectZoneChangeCounter())); + effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getStackMomentSourceZCC())); DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); game.addDelayedTriggeredAbility(delayedAbility, source); return true; diff --git a/Mage.Sets/src/mage/cards/t/TheScorpionGod.java b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java index 67ecd405fac..32a52560d5a 100644 --- a/Mage.Sets/src/mage/cards/t/TheScorpionGod.java +++ b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java @@ -24,13 +24,11 @@ import mage.constants.Outcome; import mage.constants.SuperType; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; import static mage.filter.StaticFilters.FILTER_ANOTHER_TARGET_CREATURE; @@ -135,7 +133,7 @@ class TheScorpionGodEffect extends OneShotEffect { // Create delayed triggered ability Effect effect = new ReturnToHandTargetEffect(); effect.setText("return {this} to its owner's hand"); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getSourceObjectZoneChangeCounter())); + effect.setTargetPointer(new FixedTarget(source.getSourceId(), source.getStackMomentSourceZCC())); DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); game.addDelayedTriggeredAbility(delayedAbility, source); return true; diff --git a/Mage.Sets/src/mage/cards/t/TheSpotsPortal.java b/Mage.Sets/src/mage/cards/t/TheSpotsPortal.java new file mode 100644 index 00000000000..77fa680c528 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheSpotsPortal.java @@ -0,0 +1,47 @@ +package mage.cards.t; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheSpotsPortal extends CardImpl { + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition( + new FilterControlledPermanent(SubType.VILLAIN), ComparisonType.EQUAL_TO, 0 + ); + + public TheSpotsPortal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); + + // Put target creature on the bottom of its owner's library. You lose 2 life unless you control a Villain. + this.getSpellAbility().addEffect(new PutOnLibraryTargetEffect(false)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new LoseLifeSourceControllerEffect(2), condition, + "You lose 2 life unless you control a Villain" + )); + } + + private TheSpotsPortal(final TheSpotsPortal card) { + super(card); + } + + @Override + public TheSpotsPortal copy() { + return new TheSpotsPortal(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheTerrorOfSerpentsPass.java b/Mage.Sets/src/mage/cards/t/TheTerrorOfSerpentsPass.java new file mode 100644 index 00000000000..f492a5426d2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheTerrorOfSerpentsPass.java @@ -0,0 +1,38 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheTerrorOfSerpentsPass extends CardImpl { + + public TheTerrorOfSerpentsPass(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SERPENT); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + + // Hexproof + this.addAbility(HexproofAbility.getInstance()); + } + + private TheTerrorOfSerpentsPass(final TheTerrorOfSerpentsPass card) { + super(card); + } + + @Override + public TheTerrorOfSerpentsPass copy() { + return new TheTerrorOfSerpentsPass(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java b/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java index 13faadff179..18e338ca97f 100644 --- a/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java +++ b/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java @@ -152,14 +152,14 @@ class TheTombOfAclazotzWatcher extends Watcher { return false; } MageObjectReference mor = new MageObjectReference( - source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game + source.getSourceId(), source.getStackMomentSourceZCC(), game ); return morMap.computeIfAbsent(mor, m -> new HashMap<>()).getOrDefault(playerId, 0) > 0; } void addPlayable(Ability source, Game game) { MageObjectReference mor = new MageObjectReference( - source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game + source.getSourceId(), source.getStackMomentSourceZCC(), game ); morMap.computeIfAbsent(mor, m -> new HashMap<>()) .compute(source.getControllerId(), CardUtil::setOrIncrementValue); diff --git a/Mage.Sets/src/mage/cards/t/TheToymakersTrap.java b/Mage.Sets/src/mage/cards/t/TheToymakersTrap.java index 3207b2c743c..0ddd5af43eb 100644 --- a/Mage.Sets/src/mage/cards/t/TheToymakersTrap.java +++ b/Mage.Sets/src/mage/cards/t/TheToymakersTrap.java @@ -121,7 +121,7 @@ class TheToymakersTrapEffect extends OneShotEffect { } private static List getOrSetValue(Game game, Ability source) { - String key = "chosenNumbers_" + source.getControllerId() + '_' + source.getSourceObjectZoneChangeCounter(); + String key = "chosenNumbers_" + source.getControllerId() + '_' + source.getStackMomentSourceZCC(); List list = (List) game.getState().getValue(key); if (list != null) { return list; diff --git a/Mage.Sets/src/mage/cards/t/TheTricksterGodsHeist.java b/Mage.Sets/src/mage/cards/t/TheTricksterGodsHeist.java index a4eb35e9231..e6cfac11e2b 100644 --- a/Mage.Sets/src/mage/cards/t/TheTricksterGodsHeist.java +++ b/Mage.Sets/src/mage/cards/t/TheTricksterGodsHeist.java @@ -95,8 +95,8 @@ class TheTricksterGodsHeistTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (getTargets().isEmpty()) { @@ -118,7 +118,7 @@ class TheTricksterGodsHeistTarget extends TargetPermanent { return false; } for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { + if (!isNotTarget() && !permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { continue; } for (CardType cardType : permanent.getCardType(game)) { diff --git a/Mage.Sets/src/mage/cards/t/TheTwelfthDoctor.java b/Mage.Sets/src/mage/cards/t/TheTwelfthDoctor.java index cea349b3b7e..b1359692b74 100644 --- a/Mage.Sets/src/mage/cards/t/TheTwelfthDoctor.java +++ b/Mage.Sets/src/mage/cards/t/TheTwelfthDoctor.java @@ -99,12 +99,10 @@ enum FirstSpellCastFromNotHandEachTurnCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - if (game.getStack().isEmpty()) { - return false; - } TheTwelfthDoctorWatcher watcher = game.getState().getWatcher(TheTwelfthDoctorWatcher.class); - StackObject so = game.getStack().getFirst(); - return watcher != null + StackObject so = game.getStack().getFirstOrNull(); + return so != null + && watcher != null && TheTwelfthDoctorWatcher.checkSpell(so, game); } } diff --git a/Mage.Sets/src/mage/cards/t/TheWarInHeaven.java b/Mage.Sets/src/mage/cards/t/TheWarInHeaven.java index b8e1ebd88f9..6f628a029f1 100644 --- a/Mage.Sets/src/mage/cards/t/TheWarInHeaven.java +++ b/Mage.Sets/src/mage/cards/t/TheWarInHeaven.java @@ -128,8 +128,8 @@ class TheWarInHeavenTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 8, game); } diff --git a/Mage.Sets/src/mage/cards/t/ThranPortal.java b/Mage.Sets/src/mage/cards/t/ThranPortal.java index 01dd82ddf01..fda95f376fd 100644 --- a/Mage.Sets/src/mage/cards/t/ThranPortal.java +++ b/Mage.Sets/src/mage/cards/t/ThranPortal.java @@ -7,10 +7,8 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.YouControlPermanentCondition; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseBasicLandTypeEffect; -import mage.abilities.effects.common.continuous.AddChosenSubtypeEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; -import mage.abilities.effects.common.enterAttribute.EnterAttributeAddChosenSubtypeEffect; import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -21,6 +19,7 @@ import mage.game.Game; import mage.game.permanent.Permanent; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -47,16 +46,15 @@ public class ThranPortal extends CardImpl { this.addAbility(new EntersBattlefieldTappedUnlessAbility(condition).addHint(condition.getHint())); // As Thran Portal enters the battlefield, choose a basic land type. - // Thran Portal is the chosen type in addition to its other types. AsEntersBattlefieldAbility chooseLandTypeAbility = new AsEntersBattlefieldAbility(new ChooseBasicLandTypeEffect(Outcome.AddAbility)); - chooseLandTypeAbility.addEffect(new EnterAttributeAddChosenSubtypeEffect()); // While it enters + chooseLandTypeAbility.addEffect(new ThranPortalAddSubtypeEnteringEffect()); this.addAbility(chooseLandTypeAbility); - this.addAbility(new SimpleStaticAbility(new AddChosenSubtypeEffect())); // While on the battlefield + + // Thran Portal is the chosen type in addition to its other types. + this.addAbility(new SimpleStaticAbility(new ThranPortalManaAbilityContinuousEffect())); // Mana abilities of Thran Portal cost an additional 1 life to activate. - // This also adds the mana ability Ability ability = new SimpleStaticAbility(new ThranPortalAdditionalCostEffect()); - ability.addEffect(new ThranPortalManaAbilityContinousEffect()); this.addAbility(ability); } @@ -70,7 +68,34 @@ public class ThranPortal extends CardImpl { } } -class ThranPortalManaAbilityContinousEffect extends ContinuousEffectImpl { +class ThranPortalAddSubtypeEnteringEffect extends OneShotEffect { + + public ThranPortalAddSubtypeEnteringEffect() { + super(Outcome.Benefit); + } + + protected ThranPortalAddSubtypeEnteringEffect(final ThranPortalAddSubtypeEnteringEffect effect) { + super(effect); + } + + @Override + public ThranPortalAddSubtypeEnteringEffect copy() { + return new ThranPortalAddSubtypeEnteringEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent thranPortal = game.getPermanentEntering(source.getSourceId()); + SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY)); + if (thranPortal != null && choice != null) { + thranPortal.addSubType(choice); + return true; + } + return false; + } +} + +class ThranPortalManaAbilityContinuousEffect extends ContinuousEffectImpl { private static final Map abilityMap = new HashMap() {{ put(SubType.PLAINS, new WhiteManaAbility()); @@ -80,18 +105,18 @@ class ThranPortalManaAbilityContinousEffect extends ContinuousEffectImpl { put(SubType.FOREST, new GreenManaAbility()); }}; - public ThranPortalManaAbilityContinousEffect() { + public ThranPortalManaAbilityContinuousEffect() { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral); - staticText = "mana abilities of {this} cost an additional 1 life to activate"; + staticText = "{this} is the chosen type in addition to its other types."; } - private ThranPortalManaAbilityContinousEffect(final ThranPortalManaAbilityContinousEffect effect) { + private ThranPortalManaAbilityContinuousEffect(final ThranPortalManaAbilityContinuousEffect effect) { super(effect); } @Override - public ThranPortalManaAbilityContinousEffect copy() { - return new ThranPortalManaAbilityContinousEffect(this); + public ThranPortalManaAbilityContinuousEffect copy() { + return new ThranPortalManaAbilityContinuousEffect(this); } @Override @@ -143,10 +168,11 @@ class ThranPortalManaAbilityContinousEffect extends ContinuousEffectImpl { } } -class ThranPortalAdditionalCostEffect extends CostModificationEffectImpl { +class ThranPortalAdditionalCostEffect extends ContinuousEffectImpl { ThranPortalAdditionalCostEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); + super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); + staticText = "mana abilities of {this} cost an additional 1 life to activate"; } private ThranPortalAdditionalCostEffect(final ThranPortalAdditionalCostEffect effect) { @@ -159,17 +185,22 @@ class ThranPortalAdditionalCostEffect extends CostModificationEffectImpl { } @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - abilityToModify.addCost(new PayLifeCost(1)); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (!abilityToModify.getSourceId().equals(source.getSourceId())) { + public boolean apply(Game game, Ability source) { + Permanent thranPortal = game.getPermanent(source.getSourceId()); + if (thranPortal == null) { return false; } - - return abilityToModify instanceof ManaAbility; + List abilities = thranPortal.getAbilities(game); + if (abilities.isEmpty()) { + return false; + } + boolean result = false; + for (Ability ability : abilities) { + if (ability.isManaAbility()) { + ability.addCost(new PayLifeCost(1)); + result = true; + } + } + return result; } } diff --git a/Mage.Sets/src/mage/cards/t/ThranPowerSuit.java b/Mage.Sets/src/mage/cards/t/ThranPowerSuit.java index 1522f930017..1abb7c6fbc6 100644 --- a/Mage.Sets/src/mage/cards/t/ThranPowerSuit.java +++ b/Mage.Sets/src/mage/cards/t/ThranPowerSuit.java @@ -4,7 +4,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.keyword.EquipAbility; @@ -14,10 +14,10 @@ import mage.cards.CardSetInfo; import mage.constants.AttachmentType; import mage.constants.CardType; import mage.constants.SubType; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.AttachedToAttachedPredicate; -import java.util.Objects; import java.util.UUID; /** @@ -25,15 +25,25 @@ import java.util.UUID; */ public final class ThranPowerSuit extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent("Aura and Equipment attached to it"); + + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate() + )); + filter.add(AttachedToAttachedPredicate.instance); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, 2); + public ThranPowerSuit(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); this.subtype.add(SubType.EQUIPMENT); // Equipped creature gets +1/+1 for each Aura and Equipment attached to it and has ward {2}. - Ability ability = new SimpleStaticAbility(new BoostEquippedEffect( - ThranPowerSuitValue.instance, ThranPowerSuitValue.instance - )); + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(xValue, xValue)); ability.addEffect(new GainAbilityAttachedEffect(new WardAbility( new GenericManaCost(2), false ), AttachmentType.EQUIPMENT).setText("and has ward {2}. (Whenever equipped creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {2}.)")); @@ -52,39 +62,3 @@ public final class ThranPowerSuit extends CardImpl { return new ThranPowerSuit(this); } } - -enum ThranPowerSuitValue implements DynamicValue { - instance; - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - Permanent sourcePermanent = sourceAbility.getSourcePermanentOrLKI(game); - if (sourcePermanent == null) { - return 0; - } - Permanent permanent = game.getPermanent(sourcePermanent.getAttachedTo()); - return permanent == null ? 0 : permanent - .getAttachments() - .stream() - .map(game::getPermanentOrLKIBattlefield) - .filter(Objects::nonNull) - .map(p -> p.hasSubtype(SubType.EQUIPMENT, game) || p.hasSubtype(SubType.AURA, game)) - .mapToInt(b -> b ? 1 : 0) - .sum(); - } - - @Override - public ThranPowerSuitValue copy() { - return instance; - } - - @Override - public String getMessage() { - return "Aura and Equipment attached to it"; - } - - @Override - public String toString() { - return "1"; - } -} diff --git a/Mage.Sets/src/mage/cards/t/ThreeDogGalaxyNewsDJ.java b/Mage.Sets/src/mage/cards/t/ThreeDogGalaxyNewsDJ.java index be6d2d08ff0..a8cc25fd73f 100644 --- a/Mage.Sets/src/mage/cards/t/ThreeDogGalaxyNewsDJ.java +++ b/Mage.Sets/src/mage/cards/t/ThreeDogGalaxyNewsDJ.java @@ -18,14 +18,12 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterPermanent; import mage.filter.common.FilterAttackingCreature; -import mage.filter.predicate.ObjectSourcePlayer; -import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.AttachedToSourcePredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import java.util.Optional; import java.util.UUID; /** @@ -61,24 +59,7 @@ class ThreeDogGalaxyNewsDJEffect extends OneShotEffect { private static final FilterPermanent filter = new FilterPermanent(SubType.AURA, "Aura attached to this creature"); static { - filter.add(ThreeDogGalaxyNewsDJPredicate.instance); - } - - enum ThreeDogGalaxyNewsDJPredicate implements ObjectSourcePlayerPredicate { - instance; - - @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - return input - .getSource() - .getSourceObjectIfItStillExists(game) != null - && Optional - .ofNullable(input) - .map(ObjectSourcePlayer::getObject) - .map(Permanent::getAttachedTo) - .map(input.getSourceId()::equals) - .orElse(false); - } + filter.add(AttachedToSourcePredicate.instance); } ThreeDogGalaxyNewsDJEffect() { @@ -133,18 +114,18 @@ class ThreeDogGalaxyNewsDJTokenEffect extends OneShotEffect { filter.add(AnotherPredicate.instance); } - private final Permanent permanent; + private final Permanent sacPermanent; ThreeDogGalaxyNewsDJTokenEffect(Permanent permanent) { super(Outcome.Benefit); - this.permanent = permanent != null ? permanent.copy() : null; + this.sacPermanent = permanent != null ? permanent.copy() : null; staticText = "for each other attacking creature you control, " + "create a token that's a copy of that Aura attached to that creature"; } private ThreeDogGalaxyNewsDJTokenEffect(final ThreeDogGalaxyNewsDJTokenEffect effect) { super(effect); - this.permanent = effect.permanent != null ? effect.permanent.copy() : null; + this.sacPermanent = effect.sacPermanent != null ? effect.sacPermanent.copy() : null; } @Override @@ -154,15 +135,15 @@ class ThreeDogGalaxyNewsDJTokenEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - if (permanent == null) { + if (sacPermanent == null) { return false; } - for (Permanent permanent : game.getBattlefield().getActivePermanents( + for (Permanent attacker : game.getBattlefield().getActivePermanents( filter, source.getControllerId(), source, game )) { new CreateTokenCopyTargetEffect() - .setSavedPermanent(permanent) - .setAttachedTo(permanent.getId()) + .setSavedPermanent(sacPermanent) + .setAttachedTo(attacker.getId()) .apply(game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/t/TidehollowSculler.java b/Mage.Sets/src/mage/cards/t/TidehollowSculler.java index 39ac31df872..b8c51248c0e 100644 --- a/Mage.Sets/src/mage/cards/t/TidehollowSculler.java +++ b/Mage.Sets/src/mage/cards/t/TidehollowSculler.java @@ -92,7 +92,7 @@ class TidehollowScullerExileEffect extends OneShotEffect { true, CardUtil.getExileZoneId(game, source.getSourceId(), - source.getSourceObjectZoneChangeCounter()), + source.getStackMomentSourceZCC()), "Tidehollow Sculler"); } } @@ -124,8 +124,8 @@ class TidehollowScullerLeaveEffect extends OneShotEffect { MageObject sourceObject = source.getSourceObject(game); if (controller != null && sourceObject != null) { int zoneChangeCounter = (sourceObject instanceof PermanentToken) - ? source.getSourceObjectZoneChangeCounter() - : source.getSourceObjectZoneChangeCounter() - 1; + ? source.getStackMomentSourceZCC() + : source.getStackMomentSourceZCC() - 1; ExileZone exZone = game.getExile().getExileZone( CardUtil.getExileZoneId(game, source.getSourceId(), zoneChangeCounter)); if (exZone != null) { diff --git a/Mage.Sets/src/mage/cards/t/TizerusCharger.java b/Mage.Sets/src/mage/cards/t/TizerusCharger.java index 729d2e28491..4d9c78e58c3 100644 --- a/Mage.Sets/src/mage/cards/t/TizerusCharger.java +++ b/Mage.Sets/src/mage/cards/t/TizerusCharger.java @@ -82,7 +82,7 @@ class TizerusChargerEffect extends OneShotEffect { SpellAbility spellAbility = (SpellAbility) getValue(EntersBattlefieldEffect.SOURCE_CAST_SPELL_ABILITY); if (!(spellAbility instanceof EscapeAbility) || !spellAbility.getSourceId().equals(source.getSourceId()) - || permanent.getZoneChangeCounter(game) != spellAbility.getSourceObjectZoneChangeCounter()) { + || permanent.getZoneChangeCounter(game) != spellAbility.getStackMomentSourceZCC()) { return false; } List appliedEffects = (ArrayList) this.getValue("appliedEffects"); diff --git a/Mage.Sets/src/mage/cards/t/TocasiaDigSiteMentor.java b/Mage.Sets/src/mage/cards/t/TocasiaDigSiteMentor.java index 6c2c76bd899..51e50cd3a1e 100644 --- a/Mage.Sets/src/mage/cards/t/TocasiaDigSiteMentor.java +++ b/Mage.Sets/src/mage/cards/t/TocasiaDigSiteMentor.java @@ -93,8 +93,8 @@ class TocasiaDigSiteMentorTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 10, game); } diff --git a/Mage.Sets/src/mage/cards/t/ToluzCleverConductor.java b/Mage.Sets/src/mage/cards/t/ToluzCleverConductor.java index c011bfd9f15..7a44c7687b6 100644 --- a/Mage.Sets/src/mage/cards/t/ToluzCleverConductor.java +++ b/Mage.Sets/src/mage/cards/t/ToluzCleverConductor.java @@ -114,7 +114,7 @@ class ToluzCleverConductorEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter() - 1)); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC() - 1)); return player != null && exileZone != null && !exileZone.isEmpty() diff --git a/Mage.Sets/src/mage/cards/t/TombOfTheDuskRose.java b/Mage.Sets/src/mage/cards/t/TombOfTheDuskRose.java index 35cffef463a..46a7ecf9733 100644 --- a/Mage.Sets/src/mage/cards/t/TombOfTheDuskRose.java +++ b/Mage.Sets/src/mage/cards/t/TombOfTheDuskRose.java @@ -1,7 +1,5 @@ package mage.cards.t; -import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -20,10 +18,12 @@ import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class TombOfTheDuskRose extends CardImpl { @@ -60,7 +60,7 @@ class TombOfTheDuskRoseEffect extends OneShotEffect { TombOfTheDuskRoseEffect() { super(Outcome.PutCardInPlay); - this.staticText = "Put a creature card exiled with this permanent onto the battlefield under your control"; + this.staticText = "put a creature card exiled with this permanent onto the battlefield under your control"; } private TombOfTheDuskRoseEffect(final TombOfTheDuskRoseEffect effect) { @@ -75,20 +75,17 @@ class TombOfTheDuskRoseEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - UUID exileId = CardUtil.getCardExileZoneId(game, source); - MageObject sourceObject = source.getSourceObject(game); - if (controller != null && exileId != null && sourceObject != null) { - ExileZone exileZone = game.getExile().getExileZone(exileId); - if (exileZone != null) { - TargetCard targetCard = new TargetCard(Zone.EXILED, StaticFilters.FILTER_CARD_CREATURE); - controller.chooseTarget(outcome, exileZone, targetCard, source, game); - Card card = game.getCard(targetCard.getFirstTarget()); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game); - } - } - return true; + if (controller == null) { + return false; } - return false; + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + if (exileZone == null || exileZone.count(StaticFilters.FILTER_CARD_CREATURE, game) < 1) { + return false; + } + TargetCard targetCard = new TargetCardInExile(StaticFilters.FILTER_CARD_CREATURE, exileZone.getId()); + targetCard.withNotTarget(true); + controller.choose(outcome, targetCard, source, game); + Card card = game.getCard(targetCard.getFirstTarget()); + return card != null && controller.moveCards(card, Zone.BATTLEFIELD, source, game); } } diff --git a/Mage.Sets/src/mage/cards/t/TombstoneCareerCriminal.java b/Mage.Sets/src/mage/cards/t/TombstoneCareerCriminal.java index b38f4a67822..3b8066acb61 100644 --- a/Mage.Sets/src/mage/cards/t/TombstoneCareerCriminal.java +++ b/Mage.Sets/src/mage/cards/t/TombstoneCareerCriminal.java @@ -12,7 +12,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; import mage.target.common.TargetCardInYourGraveyard; import java.util.UUID; @@ -22,8 +21,8 @@ import java.util.UUID; */ public final class TombstoneCareerCriminal extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.VILLAIN); - private static final FilterCard filter2 = new FilterBySubtypeCard(SubType.VILLAIN, "Villain spells"); + private static final FilterCard filter = new FilterCard(SubType.VILLAIN); + private static final FilterCard filter2 = new FilterCard(SubType.VILLAIN, "Villain spells"); public TombstoneCareerCriminal(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); diff --git a/Mage.Sets/src/mage/cards/t/TophTheBlindBandit.java b/Mage.Sets/src/mage/cards/t/TophTheBlindBandit.java new file mode 100644 index 00000000000..ccea918c260 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TophTheBlindBandit.java @@ -0,0 +1,98 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TophTheBlindBandit extends CardImpl { + + public TophTheBlindBandit(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.subtype.add(SubType.ALLY); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // When Toph enters, earthbend 2. + Ability ability = new EntersBattlefieldTriggeredAbility(new EarthbendTargetEffect(2)); + ability.addTarget(new TargetControlledLandPermanent()); + this.addAbility(ability); + + // Toph's power is equal to the number of +1/+1 counters on lands you control. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SetBasePowerSourceEffect(TophTheBlindBanditValue.instance) + ).addHint(TophTheBlindBanditValue.getHint())); + } + + private TophTheBlindBandit(final TophTheBlindBandit card) { + super(card); + } + + @Override + public TophTheBlindBandit copy() { + return new TophTheBlindBandit(this); + } +} + +enum TophTheBlindBanditValue implements DynamicValue { + instance; + private static final Hint hint = new ValueHint("+1/+1 counters on lands 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_LAND, + sourceAbility.getControllerId(), sourceAbility, game + ) + .stream() + .map(permanent -> permanent.getCounters(game)) + .mapToInt(counters -> counters.getCount(CounterType.P1P1)) + .sum(); + } + + @Override + public TophTheBlindBanditValue copy() { + return this; + } + + @Override + public String getMessage() { + return "+1/+1 counters on lands you control"; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TophTheFirstMetalbender.java b/Mage.Sets/src/mage/cards/t/TophTheFirstMetalbender.java new file mode 100644 index 00000000000..4dc2ef8e110 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TophTheFirstMetalbender.java @@ -0,0 +1,88 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TophTheFirstMetalbender extends CardImpl { + + public TophTheFirstMetalbender(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Nontoken artifacts you control are lands in addition to their other types. + this.addAbility(new SimpleStaticAbility(new TophTheFirstMetalbenderEffect())); + + // At the beginning of your end step, earthbend 2. + Ability ability = new BeginningOfEndStepTriggeredAbility(new EarthbendTargetEffect(2)); + ability.addTarget(new TargetControlledLandPermanent()); + this.addAbility(ability); + } + + private TophTheFirstMetalbender(final TophTheFirstMetalbender card) { + super(card); + } + + @Override + public TophTheFirstMetalbender copy() { + return new TophTheFirstMetalbender(this); + } +} + +class TophTheFirstMetalbenderEffect extends ContinuousEffectImpl { + + private static final FilterPermanent filter = new FilterControlledArtifactPermanent(); + + static { + filter.add(TokenPredicate.FALSE); + } + + TophTheFirstMetalbenderEffect() { + super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); + staticText = "nontoken artifacts you control are lands in addition to their other types"; + this.dependendToTypes.add(DependencyType.ArtifactAddingRemoving); + this.dependencyTypes.add(DependencyType.BecomeNonbasicLand); + } + + private TophTheFirstMetalbenderEffect(final TophTheFirstMetalbenderEffect effect) { + super(effect); + } + + @Override + public TophTheFirstMetalbenderEffect copy() { + return new TophTheFirstMetalbenderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + filter, source.getControllerId(), source, game + )) { + permanent.addCardType(game, CardType.LAND); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TopographyTracker.java b/Mage.Sets/src/mage/cards/t/TopographyTracker.java index d404986ae14..50d92e42044 100644 --- a/Mage.Sets/src/mage/cards/t/TopographyTracker.java +++ b/Mage.Sets/src/mage/cards/t/TopographyTracker.java @@ -1,26 +1,26 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.CreateTokenEffect; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.game.Game; import mage.game.events.ExploreEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.MapToken; +import java.util.UUID; + /** - * * @author Grath */ public final class TopographyTracker extends CardImpl { @@ -74,7 +74,7 @@ class TopographyTrackerEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { Permanent permanent = game.getPermanent(event.getTargetId()); - return permanent != null && permanent.isControlledBy(event.getPlayerId()); + return permanent != null && permanent.isControlledBy(source.getControllerId()); } @Override @@ -83,4 +83,4 @@ class TopographyTrackerEffect extends ReplacementEffectImpl { exploreEvent.doubleExplores(); return false; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/t/TorchTheWitness.java b/Mage.Sets/src/mage/cards/t/TorchTheWitness.java index 283c3409be3..da1d98ac6e1 100644 --- a/Mage.Sets/src/mage/cards/t/TorchTheWitness.java +++ b/Mage.Sets/src/mage/cards/t/TorchTheWitness.java @@ -60,8 +60,7 @@ class TorchTheWitnessEffect extends OneShotEffect { if (permanent == null) { return false; } - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - if (lethal < permanent.damage(2 * CardUtil.getSourceCostsTag(game, source, "X", 0), source, game)) { + if (permanent.damageWithExcess(2 * CardUtil.getSourceCostsTag(game, source, "X", 0), source, game) > 0) { InvestigateEffect.doInvestigate(source.getControllerId(), 1, game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/t/TovolarsMagehunter.java b/Mage.Sets/src/mage/cards/t/TovolarsMagehunter.java index 0fcba4dc888..3879bc96f68 100644 --- a/Mage.Sets/src/mage/cards/t/TovolarsMagehunter.java +++ b/Mage.Sets/src/mage/cards/t/TovolarsMagehunter.java @@ -1,17 +1,16 @@ package mage.cards.t; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SpellCastOpponentTriggeredAbility; import mage.abilities.common.WerewolfBackTriggeredAbility; import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.target.targetpointer.FixedTarget; +import mage.filter.StaticFilters; import java.util.UUID; @@ -32,7 +31,10 @@ public final class TovolarsMagehunter extends CardImpl { this.nightCard = true; // Whenever an opponent casts a spell, Tovolar's Magehunter deals 2 damage to that player. - this.addAbility(new TovolarsMagehunterTriggeredAbility()); + this.addAbility(new SpellCastOpponentTriggeredAbility( + Zone.BATTLEFIELD, new DamageTargetEffect(2, true, "that player"), + StaticFilters.FILTER_SPELL_A, false, SetTargetPointer.PLAYER + )); // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Tovolar's Magehunter. this.addAbility(new WerewolfBackTriggeredAbility()); @@ -47,38 +49,3 @@ public final class TovolarsMagehunter extends CardImpl { return new TovolarsMagehunter(this); } } - -class TovolarsMagehunterTriggeredAbility extends TriggeredAbilityImpl { - - TovolarsMagehunterTriggeredAbility() { - super(Zone.BATTLEFIELD, new DamageTargetEffect(2), false); - } - - private TovolarsMagehunterTriggeredAbility(final TovolarsMagehunterTriggeredAbility ability) { - super(ability); - } - - @Override - public TovolarsMagehunterTriggeredAbility copy() { - return new TovolarsMagehunterTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.SPELL_CAST; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (game.getOpponents(controllerId).contains(event.getPlayerId())) { - this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever an opponent casts a spell, {this} deals 2 damage to that player."; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TranquilLandscape.java b/Mage.Sets/src/mage/cards/t/TranquilLandscape.java index 89b06c627ec..99c9104ff89 100644 --- a/Mage.Sets/src/mage/cards/t/TranquilLandscape.java +++ b/Mage.Sets/src/mage/cards/t/TranquilLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class TranquilLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Forest, Plains, or Island card"); + private static final FilterCard filter = new FilterBasicCard("a basic Forest, Plains, or Island card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/t/TreasureMap.java b/Mage.Sets/src/mage/cards/t/TreasureMap.java index 073470aaa2a..224fbd8290d 100644 --- a/Mage.Sets/src/mage/cards/t/TreasureMap.java +++ b/Mage.Sets/src/mage/cards/t/TreasureMap.java @@ -2,22 +2,22 @@ package mage.cards.t; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.RemoveAllCountersSourceEffect; import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.game.permanent.token.TreasureToken; -import mage.players.Player; import java.util.UUID; @@ -26,6 +26,8 @@ import java.util.UUID; */ public final class TreasureMap extends CardImpl { + private static final Condition condition = new SourceHasCounterCondition(CounterType.LANDMARK, 3); + public TreasureMap(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); @@ -33,8 +35,13 @@ public final class TreasureMap extends CardImpl { // {1}, {T}: Scry 1. Put a landmark counter on Treasure Map. Then if there are three or more landmark counters on it, remove those counters, transform Treasure Map, and create three colorless Treasure artifact tokens with "{T}, Sacrifice this artifact: Add one mana of any color." this.addAbility(new TransformAbility()); - Ability ability = new SimpleActivatedAbility(new TreasureMapEffect(), new ManaCostsImpl<>("{1}")); + Ability ability = new SimpleActivatedAbility(new ScryEffect(1, false), new ManaCostsImpl<>("{1}")); ability.addCost(new TapSourceCost()); + ability.addEffect(new AddCountersSourceEffect(CounterType.LANDMARK.createInstance())); + ability.addEffect(new ConditionalOneShotEffect( + new RemoveAllCountersSourceEffect(CounterType.LANDMARK), condition, "Then if there are three or " + + "more landmark counters on it, remove those counters, transform {this}, and create three Treasure tokens" + ).addEffect(new TransformSourceEffect()).addEffect(new CreateTokenEffect(new TreasureToken(), 3))); this.addAbility(ability); } @@ -47,43 +54,3 @@ public final class TreasureMap extends CardImpl { return new TreasureMap(this); } } - -class TreasureMapEffect extends OneShotEffect { - - TreasureMapEffect() { - super(Outcome.Benefit); - this.staticText = "Scry 1. Put a landmark counter on {this}. " - + "Then if there are three or more landmark counters on it, " - + "remove those counters, transform {this}, and create " - + "three Treasure tokens"; - } - - private TreasureMapEffect(final TreasureMapEffect effect) { - super(effect); - } - - @Override - public TreasureMapEffect copy() { - return new TreasureMapEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.scry(1, source, game); - if (permanent != null) { - permanent.addCounters(CounterType.LANDMARK.createInstance(), source.getControllerId(), source, game); - int counters = permanent.getCounters(game).getCount(CounterType.LANDMARK); - if (counters > 2) { - permanent.removeCounters(CounterType.LANDMARK.getName(), counters, source, game); - new TransformSourceEffect().apply(game, source); - new CreateTokenEffect(new TreasureToken(), 3).apply(game, source); - } - return true; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TreeOfPerdition.java b/Mage.Sets/src/mage/cards/t/TreeOfPerdition.java index 07de60bce02..6c7ac347f8c 100644 --- a/Mage.Sets/src/mage/cards/t/TreeOfPerdition.java +++ b/Mage.Sets/src/mage/cards/t/TreeOfPerdition.java @@ -63,7 +63,7 @@ class TreeOfPerditionEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player opponent = game.getPlayer(source.getFirstTarget()); Permanent perm = game.getPermanent(source.getSourceId()); - if (perm == null || opponent == null || !opponent.isLifeTotalCanChange()) { + if (perm == null || opponent == null || !opponent.isLifeTotalCanChange() || !perm.isCreature(game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/TreeOfRedemption.java b/Mage.Sets/src/mage/cards/t/TreeOfRedemption.java index 25a6806d764..9e8cb4c558f 100644 --- a/Mage.Sets/src/mage/cards/t/TreeOfRedemption.java +++ b/Mage.Sets/src/mage/cards/t/TreeOfRedemption.java @@ -59,7 +59,7 @@ class TreeOfRedemptionEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); Permanent perm = game.getPermanent(source.getSourceId()); - if (perm == null || player == null || !player.isLifeTotalCanChange()) { + if (perm == null || player == null || !player.isLifeTotalCanChange() || !perm.isCreature(game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/TriumphOfSaintKatherine.java b/Mage.Sets/src/mage/cards/t/TriumphOfSaintKatherine.java index ea217662dfb..1d66ae330e1 100644 --- a/Mage.Sets/src/mage/cards/t/TriumphOfSaintKatherine.java +++ b/Mage.Sets/src/mage/cards/t/TriumphOfSaintKatherine.java @@ -74,7 +74,7 @@ class TriumphOfSaintKatherineEffect extends OneShotEffect { return false; } Cards cards = new CardsImpl(); - if (card.getZoneChangeCounter(game) == source.getSourceObjectZoneChangeCounter()) { + if (card.getZoneChangeCounter(game) == source.getStackMomentSourceZCC()) { cards.add(card); } cards.addAllCards(player.getLibrary().getTopCards(game, 6)); diff --git a/Mage.Sets/src/mage/cards/t/TromellSeymoursButler.java b/Mage.Sets/src/mage/cards/t/TromellSeymoursButler.java index 3194755eb18..ce1ea4d0d02 100644 --- a/Mage.Sets/src/mage/cards/t/TromellSeymoursButler.java +++ b/Mage.Sets/src/mage/cards/t/TromellSeymoursButler.java @@ -22,6 +22,7 @@ import mage.constants.SuperType; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.EnteredThisTurnPredicate; import mage.filter.predicate.permanent.TokenPredicate; import mage.game.Game; @@ -33,7 +34,7 @@ import java.util.UUID; */ public final class TromellSeymoursButler extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("nontoken creature"); + private static final FilterPermanent filter = new FilterCreaturePermanent("nontoken creature"); static { filter.add(TokenPredicate.FALSE); diff --git a/Mage.Sets/src/mage/cards/t/TundraWall.java b/Mage.Sets/src/mage/cards/t/TundraWall.java new file mode 100644 index 00000000000..374148207c4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TundraWall.java @@ -0,0 +1,36 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.keyword.DefenderAbility; +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 TundraWall extends CardImpl { + + public TundraWall(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.WALL); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + } + + private TundraWall(final TundraWall card) { + super(card); + } + + @Override + public TundraWall copy() { + return new TundraWall(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TurtleDuck.java b/Mage.Sets/src/mage/cards/t/TurtleDuck.java new file mode 100644 index 00000000000..a111f047c40 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TurtleDuck.java @@ -0,0 +1,48 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TurtleDuck extends CardImpl { + + public TurtleDuck(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.TURTLE); + this.subtype.add(SubType.BIRD); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // {3}: Until end of turn, this creature has base power 4 and gains trample. + Ability ability = new SimpleActivatedAbility(new SetBasePowerSourceEffect(4, Duration.EndOfTurn) + .setText("until end of turn, this creature has base power 4"), new GenericManaCost(3)); + ability.addEffect(new GainAbilitySourceEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains trample")); + this.addAbility(ability); + } + + private TurtleDuck(final TurtleDuck card) { + super(card); + } + + @Override + public TurtleDuck copy() { + return new TurtleDuck(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TurtleSeals.java b/Mage.Sets/src/mage/cards/t/TurtleSeals.java new file mode 100644 index 00000000000..ca402fa8aa1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TurtleSeals.java @@ -0,0 +1,37 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TurtleSeals extends CardImpl { + + public TurtleSeals(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.TURTLE); + this.subtype.add(SubType.SEAL); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + } + + private TurtleSeals(final TurtleSeals card) { + super(card); + } + + @Override + public TurtleSeals copy() { + return new TurtleSeals(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TwinbladeInvocation.java b/Mage.Sets/src/mage/cards/t/TwinbladeInvocation.java index 93415a4b48b..1ade18bdba1 100644 --- a/Mage.Sets/src/mage/cards/t/TwinbladeInvocation.java +++ b/Mage.Sets/src/mage/cards/t/TwinbladeInvocation.java @@ -1,11 +1,9 @@ package mage.cards.t; -import mage.abilities.Ability; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.DoubleStrikeAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; @@ -35,8 +33,7 @@ public final class TwinbladeInvocation extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature has double strike. this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect( @@ -44,7 +41,7 @@ public final class TwinbladeInvocation extends CardImpl { ))); // If Twinblade Invocation would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private TwinbladeInvocation(final TwinbladeInvocation card) { diff --git a/Mage.Sets/src/mage/cards/t/TwistedLandscape.java b/Mage.Sets/src/mage/cards/t/TwistedLandscape.java index 39c79522389..ce3ed038b08 100644 --- a/Mage.Sets/src/mage/cards/t/TwistedLandscape.java +++ b/Mage.Sets/src/mage/cards/t/TwistedLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class TwistedLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Swamp, Mountain, or Forest card"); + private static final FilterCard filter = new FilterBasicCard("a basic Swamp, Mountain, or Forest card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/u/UginTheIneffable.java b/Mage.Sets/src/mage/cards/u/UginTheIneffable.java index 848ee849bca..d18b19a832c 100644 --- a/Mage.Sets/src/mage/cards/u/UginTheIneffable.java +++ b/Mage.Sets/src/mage/cards/u/UginTheIneffable.java @@ -110,7 +110,7 @@ class UginTheIneffableEffect extends OneShotEffect { } // exile and look - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); if (player.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName() + " (" + player.getName() + ")")) { card.turnFaceDown(source, game, source.getControllerId()); player.lookAtCards(player.getName() + " - " + card.getIdName() + " - " + CardUtil.sdf.format(System.currentTimeMillis()), card, game); diff --git a/Mage.Sets/src/mage/cards/u/UltimateGreenGoblin.java b/Mage.Sets/src/mage/cards/u/UltimateGreenGoblin.java new file mode 100644 index 00000000000..682ba3002cf --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UltimateGreenGoblin.java @@ -0,0 +1,49 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.abilities.keyword.MayhemAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UltimateGreenGoblin extends CardImpl { + + public UltimateGreenGoblin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B/R}{B/R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // At the beginning of your upkeep, discard a card, then create a Treasure token. + Ability ability = new BeginningOfUpkeepTriggeredAbility(new DiscardControllerEffect(1)); + ability.addEffect(new CreateTokenEffect(new TreasureToken()).concatBy(", then")); + this.addAbility(ability); + + // Mayhem {2}{B/R} + this.addAbility(new MayhemAbility(this, "{2}{B/R}")); + } + + private UltimateGreenGoblin(final UltimateGreenGoblin card) { + super(card); + } + + @Override + public UltimateGreenGoblin copy() { + return new UltimateGreenGoblin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UndeadSprinter.java b/Mage.Sets/src/mage/cards/u/UndeadSprinter.java new file mode 100644 index 00000000000..94bf71690a9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UndeadSprinter.java @@ -0,0 +1,140 @@ +package mage.cards.u; + +import java.util.UUID; + +import mage.MageIdentifier; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.counter.AddCounterEnteringCreatureEffect; +import mage.cards.Card; +import mage.constants.*; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +/** + * + * @author anonymous + */ +public final class UndeadSprinter extends CardImpl { + + public UndeadSprinter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{R}"); + + this.subtype.add(SubType.ZOMBIE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // You may cast this card from your graveyard if a non-Zombie creature died this turn. If you do, this creature enters with a +1/+1 counter on it. + Ability ability = new SimpleStaticAbility(Zone.ALL, new UndeadSprinterEffect()).setIdentifier(MageIdentifier.UndeadSprinterAlternateCast); + ability.addWatcher(new UndeadSprinterCanCastWatcher()); + ability.addWatcher(new UndeadSprinterAlternateCastWatcher()); + this.addAbility(ability); + } + + private UndeadSprinter(final UndeadSprinter card) { + super(card); + } + + @Override + public UndeadSprinter copy() { + return new UndeadSprinter(this); + } +} + +class UndeadSprinterEffect extends AsThoughEffectImpl { + + UndeadSprinterEffect() { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); + this.staticText = "You may cast this card from your graveyard if a non-Zombie creature died this turn"; + } + + private UndeadSprinterEffect(final UndeadSprinterEffect effect) { + super(effect); + } + + @Override + public UndeadSprinterEffect copy() { + return new UndeadSprinterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + if (sourceId.equals(source.getSourceId()) && source.isControlledBy(affectedControllerId)) { + Card card = game.getCard(source.getSourceId()); + Watcher watcher = game.getState().getWatcher(UndeadSprinterCanCastWatcher.class); + if (card != null && watcher != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { + return watcher.conditionMet(); + } + } + return false; + } +} + +class UndeadSprinterCanCastWatcher extends Watcher { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a non-Zombie creature"); + + static { + filter.add(Predicates.not(SubType.ZOMBIE.getPredicate())); + } + + public UndeadSprinterCanCastWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (condition) { + return; + } + if (event.getType() == GameEvent.EventType.ZONE_CHANGE + && ((ZoneChangeEvent) event).isDiesEvent() + && filter.match(((ZoneChangeEvent) event).getTarget(), game)) { + condition = true; + } + } +} + +class UndeadSprinterAlternateCastWatcher extends Watcher { + + UndeadSprinterAlternateCastWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (GameEvent.EventType.SPELL_CAST.equals(event.getType()) + && event.hasApprovingIdentifier(MageIdentifier.UndeadSprinterAlternateCast)) { + Spell target = game.getSpell(event.getTargetId()); + if (target != null) { + game.getState().addEffect(new AddCounterEnteringCreatureEffect(new MageObjectReference(target.getCard(), game), + CounterType.P1P1.createInstance(), Outcome.BoostCreature), + target.getSpellAbility()); + } + } + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnderworldSentinel.java b/Mage.Sets/src/mage/cards/u/UnderworldSentinel.java index 40db7ee5970..293aabd7b23 100644 --- a/Mage.Sets/src/mage/cards/u/UnderworldSentinel.java +++ b/Mage.Sets/src/mage/cards/u/UnderworldSentinel.java @@ -76,7 +76,7 @@ class UnderworldSentinelEffect extends OneShotEffect { return false; } ExileZone exileZone = game.getExile().getExileZone( - CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()) + CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()) ); return exileZone != null && controller.moveCards(exileZone, Zone.BATTLEFIELD, source, game); } diff --git a/Mage.Sets/src/mage/cards/u/UnexplainedAbsence.java b/Mage.Sets/src/mage/cards/u/UnexplainedAbsence.java new file mode 100644 index 00000000000..8de9468f9bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnexplainedAbsence.java @@ -0,0 +1,108 @@ +package mage.cards.u; + +import java.util.*; +import java.util.stream.Collectors; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.ManifestEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +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.Target; +import mage.target.TargetPermanent; +import mage.target.targetadjustment.ForEachPlayerTargetsAdjuster; +import mage.target.targetpointer.EachTargetPointer; + +/** + * + * @author anonymous + */ +public final class UnexplainedAbsence extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("target nonland permanent"); + + static { + filter.add(Predicates.not(CardType.LAND.getPredicate())); + } + + public UnexplainedAbsence(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{W}"); + + // For each player, exile up to one target nonland permanent that player controls. For each permanent exiled this way, its controller cloaks the top card of their library. + this.getSpellAbility().addEffect( + new UnexplainedAbsenceEffect() + .setTargetPointer(new EachTargetPointer()) + .setText("For each player, exile up to one target nonland permanent that player controls. " + + "For each permanent exiled this way, its controller cloaks the top card of their library.") + ); + this.getSpellAbility().addTarget(new TargetPermanent(0, 1, filter)); + this.getSpellAbility().setTargetAdjuster(new ForEachPlayerTargetsAdjuster(false, false)); + } + + private UnexplainedAbsence(final UnexplainedAbsence card) { + super(card); + } + + @Override + public UnexplainedAbsence copy() { + return new UnexplainedAbsence(this); + } +} + +class UnexplainedAbsenceEffect extends OneShotEffect { + + UnexplainedAbsenceEffect() { + super(Outcome.Neutral); + } + + private UnexplainedAbsenceEffect(final UnexplainedAbsenceEffect effect) { + super(effect); + } + + @Override + public UnexplainedAbsenceEffect copy() { + return new UnexplainedAbsenceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Set permanents = source + .getTargets() + .stream() + .map(Target::getTargets) + .flatMap(Collection::stream) + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + // set of controllers for the second part of the effect. + Set controllers = new HashSet<>(); + for (Permanent permanent : permanents) { + Player effectedController = game.getPlayer(permanent.getControllerId()); + + if (effectedController != null) { + controllers.add(effectedController.getId()); + } + } + controller.moveCards(permanents, Zone.EXILED, source, game); + for (UUID controllerId : controllers) { + Player player = game.getPlayer(controllerId); + ManifestEffect.doManifestCards( + game, source, player, + player.getLibrary().getTopCards(game, 1), true + ); + } + return true; + } + +} diff --git a/Mage.Sets/src/mage/cards/u/UnidentifiedHovership.java b/Mage.Sets/src/mage/cards/u/UnidentifiedHovership.java index bd8dc5aa691..d32e31e7827 100644 --- a/Mage.Sets/src/mage/cards/u/UnidentifiedHovership.java +++ b/Mage.Sets/src/mage/cards/u/UnidentifiedHovership.java @@ -88,7 +88,7 @@ class UnidentifiedHovershipEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source, -1)); if (exileZone == null || exileZone.isEmpty()) { return false; } diff --git a/Mage.Sets/src/mage/cards/u/UniversityCampus.java b/Mage.Sets/src/mage/cards/u/UniversityCampus.java new file mode 100644 index 00000000000..e8d1f840e24 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UniversityCampus.java @@ -0,0 +1,46 @@ +package mage.cards.u; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.mana.BlueManaAbility; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UniversityCampus extends CardImpl { + + public UniversityCampus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {W} or {U}. + this.addAbility(new WhiteManaAbility()); + this.addAbility(new BlueManaAbility()); + + // {4}, {T}: Surveil 1. + Ability ability = new SimpleActivatedAbility(new SurveilEffect(1), new GenericManaCost(4)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private UniversityCampus(final UniversityCampus card) { + super(card); + } + + @Override + public UniversityCampus copy() { + return new UniversityCampus(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnleashTheInferno.java b/Mage.Sets/src/mage/cards/u/UnleashTheInferno.java index 33ccf27b203..a2df1217059 100644 --- a/Mage.Sets/src/mage/cards/u/UnleashTheInferno.java +++ b/Mage.Sets/src/mage/cards/u/UnleashTheInferno.java @@ -67,19 +67,18 @@ class UnleashTheInfernoEffect extends OneShotEffect { if (permanent == null) { return false; } - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), 7); - permanent.damage(7, source, game); - int excess = 7 - lethal; - if (excess > 0) { - ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new DestroyTargetEffect(), false); - FilterPermanent filter = new FilterArtifactOrEnchantmentPermanent( - "artifact or enchantment an opponent controls with mana value less than or equal to " + excess - ); - filter.add(TargetController.OPPONENT.getControllerPredicate()); - filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, excess + 1)); - ability.addTarget(new TargetPermanent(filter)); - game.fireReflexiveTriggeredAbility(ability, source); + int excess = permanent.damageWithExcess(7, source, game); + if (excess < 1) { + return true; } + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new DestroyTargetEffect(), false); + FilterPermanent filter = new FilterArtifactOrEnchantmentPermanent( + "artifact or enchantment an opponent controls with mana value less than or equal to " + excess + ); + filter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, excess + 1)); + ability.addTarget(new TargetPermanent(filter)); + game.fireReflexiveTriggeredAbility(ability, source); return true; } } diff --git a/Mage.Sets/src/mage/cards/u/UnstableExperiment.java b/Mage.Sets/src/mage/cards/u/UnstableExperiment.java new file mode 100644 index 00000000000..45e47307181 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnstableExperiment.java @@ -0,0 +1,37 @@ +package mage.cards.u; + +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.keyword.ConniveTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.TargetPlayer; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UnstableExperiment extends CardImpl { + + public UnstableExperiment(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Target player draws a card, then up to one target creature you control connives. + this.getSpellAbility().addEffect(new DrawCardTargetEffect(1)); + this.getSpellAbility().addTarget(new TargetPlayer()); + this.getSpellAbility().addEffect(new ConniveTargetEffect().setTargetPointer(new SecondTargetPointer()).concatBy(", then")); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(0, 1)); + } + + private UnstableExperiment(final UnstableExperiment card) { + super(card); + } + + @Override + public UnstableExperiment copy() { + return new UnstableExperiment(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UrbanRetreat.java b/Mage.Sets/src/mage/cards/u/UrbanRetreat.java new file mode 100644 index 00000000000..f47fcec2a02 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UrbanRetreat.java @@ -0,0 +1,87 @@ +package mage.cards.u; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.costs.common.ReturnToHandChosenControlledPermanentCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.mana.BlueManaAbility; +import mage.abilities.mana.GreenManaAbility; +import mage.abilities.mana.WhiteManaAbility; +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.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UrbanRetreat extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("tapped creature you control"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + public UrbanRetreat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {G}, {W}, or {U}. + this.addAbility(new GreenManaAbility()); + this.addAbility(new WhiteManaAbility()); + this.addAbility(new BlueManaAbility()); + + // {2}, Return a tapped creature you control to its owner's hand: Put this card from your hand onto the battlefield. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility(Zone.HAND, new UrbanRetreatEffect(), new GenericManaCost(2)); + ability.addCost(new ReturnToHandChosenControlledPermanentCost(new TargetControlledPermanent())); + this.addAbility(ability); + } + + private UrbanRetreat(final UrbanRetreat card) { + super(card); + } + + @Override + public UrbanRetreat copy() { + return new UrbanRetreat(this); + } +} + +class UrbanRetreatEffect extends OneShotEffect { + + UrbanRetreatEffect() { + super(Outcome.Benefit); + staticText = "put this card from your hand onto the battlefield"; + } + + private UrbanRetreatEffect(final UrbanRetreatEffect effect) { + super(effect); + } + + @Override + public UrbanRetreatEffect copy() { + return new UrbanRetreatEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = source.getSourceCardIfItStillExists(game); + return player != null && card != null && player.moveCards(card, Zone.BATTLEFIELD, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java b/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java index df9fcd03c86..b30eaba92de 100644 --- a/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java +++ b/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java @@ -6,6 +6,9 @@ import mage.abilities.Ability; import mage.abilities.common.PlayLandOrCastSpellTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCosts; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; @@ -14,7 +17,6 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.game.Controllable; import mage.game.Game; import mage.players.Player; import mage.target.targetpointer.FixedTarget; @@ -142,14 +144,16 @@ class UriangerAugureltPlayEffect extends AsThoughEffectImpl { if (card.isLand(game)) { return true; } - // TODO: This should ideally apply the reduction while the spell is being cast because effects that increase the cost apply first - Optional.ofNullable(source) - .map(Controllable::getControllerId) - .map(game::getPlayer) - .ifPresent(player -> player.setCastSourceIdWithAlternateMana( - card.getId(), CardUtil.reduceCost(card.getManaCost(), 2), - null, MageIdentifier.UriangerAugureltAlternateCast - )); + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + ManaCosts newCost = CardUtil.reduceCost(card.getManaCost(), 2); + if (newCost.isEmpty()) { + newCost.add(new GenericManaCost(0)); + } + controller.setCastSourceIdWithAlternateMana( + card.getId(), newCost, null, MageIdentifier.UriangerAugureltAlternateCast + ); + } return true; } } diff --git a/Mage.Sets/src/mage/cards/u/UroTitanOfNaturesWrath.java b/Mage.Sets/src/mage/cards/u/UroTitanOfNaturesWrath.java index 4007b7a02ec..9d373da06b3 100644 --- a/Mage.Sets/src/mage/cards/u/UroTitanOfNaturesWrath.java +++ b/Mage.Sets/src/mage/cards/u/UroTitanOfNaturesWrath.java @@ -80,7 +80,7 @@ class UroTitanOfNaturesWrathEffect extends OneShotEffect { if (permanent == null) { return false; } - if (EscapeAbility.wasCastedWithEscape(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())) { + if (EscapeAbility.wasCastedWithEscape(game, source.getSourceId(), source.getStackMomentSourceZCC())) { return false; } return permanent.sacrifice(source, game); diff --git a/Mage.Sets/src/mage/cards/v/VATS.java b/Mage.Sets/src/mage/cards/v/VATS.java index ebf4b169f15..057affe65ab 100644 --- a/Mage.Sets/src/mage/cards/v/VATS.java +++ b/Mage.Sets/src/mage/cards/v/VATS.java @@ -62,8 +62,8 @@ class VATSTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (this.getTargets().isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/v/ValakutExploration.java b/Mage.Sets/src/mage/cards/v/ValakutExploration.java index 796840a0131..0a41204bdbb 100644 --- a/Mage.Sets/src/mage/cards/v/ValakutExploration.java +++ b/Mage.Sets/src/mage/cards/v/ValakutExploration.java @@ -97,7 +97,7 @@ class ValakutExplorationExileEffect extends OneShotEffect { } controller.moveCardsToExile( card, source, game, true, CardUtil.getExileZoneId( - game, source.getSourceId(), source.getSourceObjectZoneChangeCounter() + game, source.getSourceId(), source.getStackMomentSourceZCC() ), sourcePermanent.getIdName() ); ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Duration.EndOfGame); @@ -130,7 +130,7 @@ class ValakutExplorationDamageEffect extends OneShotEffect { return false; } ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId( - game, source.getSourceId(), source.getSourceObjectZoneChangeCounter() + game, source.getSourceId(), source.getStackMomentSourceZCC() )); if (exileZone == null) { return false; diff --git a/Mage.Sets/src/mage/cards/v/ValiantRescuer.java b/Mage.Sets/src/mage/cards/v/ValiantRescuer.java index 6c1c956bf92..31a9b48f47c 100644 --- a/Mage.Sets/src/mage/cards/v/ValiantRescuer.java +++ b/Mage.Sets/src/mage/cards/v/ValiantRescuer.java @@ -74,12 +74,11 @@ class ValiantRescuerTriggeredAbility extends TriggeredAbilityImpl { ValiantRescuerWatcher watcher = game.getState().getWatcher(ValiantRescuerWatcher.class); if (watcher == null || !watcher.checkSpell(event.getPlayerId(), event.getSourceId()) - || game.getState().getStack().isEmpty() || !event.getPlayerId().equals(this.getControllerId()) || event.getSourceId().equals(this.getSourceId())) { return false; } - StackObject item = game.getState().getStack().getFirst(); + StackObject item = game.getState().getStack().getFirstOrNull(); return item instanceof StackAbility && item.getStackAbility() instanceof CyclingAbility; } @@ -106,11 +105,10 @@ class ValiantRescuerWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.ACTIVATED_ABILITY - || game.getState().getStack().isEmpty()) { + if (event.getType() != GameEvent.EventType.ACTIVATED_ABILITY) { return; } - StackObject item = game.getState().getStack().getFirst(); + StackObject item = game.getState().getStack().getFirstOrNull(); if (item instanceof StackAbility && item.getStackAbility() instanceof CyclingAbility) { playerMap.computeIfAbsent(event.getPlayerId(), u -> new HashMap<>()); diff --git a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java index 667cebb3c60..fb5f2ea73d9 100644 --- a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java +++ b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java @@ -123,8 +123,7 @@ class ValkiGodOfLiesRevealExileEffect extends OneShotEffect { TargetCard targetToExile = new TargetCard(Zone.HAND, StaticFilters.FILTER_CARD_CREATURE); targetToExile.withChooseHint("card to exile"); targetToExile.withNotTarget(true); - if (opponent.getHand().count(StaticFilters.FILTER_CARD_CREATURE, game) > 0 && - controller.choose(Outcome.Exile, opponent.getHand(), targetToExile, source, game)) { + if (controller.choose(Outcome.Exile, opponent.getHand(), targetToExile, source, game)) { Card targetedCardToExile = game.getCard(targetToExile.getFirstTarget()); if (targetedCardToExile != null && game.getState().getZone(source.getSourceId()) == Zone.BATTLEFIELD) { diff --git a/Mage.Sets/src/mage/cards/v/VancesBlastingCannons.java b/Mage.Sets/src/mage/cards/v/VancesBlastingCannons.java index ca95508a8df..481660bf8b7 100644 --- a/Mage.Sets/src/mage/cards/v/VancesBlastingCannons.java +++ b/Mage.Sets/src/mage/cards/v/VancesBlastingCannons.java @@ -3,21 +3,18 @@ package mage.cards.v; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; import mage.watchers.common.CastSpellLastTurnWatcher; import java.util.UUID; @@ -38,7 +35,7 @@ public final class VancesBlastingCannons extends CardImpl { // Whenever you cast your third spell in a turn, transform Vance's Blasting Cannons. this.addAbility(new TransformAbility()); - this.addAbility(new VancesBlastingCannonsFlipTrigger()); + this.addAbility(new VancesBlastingCannonsTriggeredAbility()); } private VancesBlastingCannons(final VancesBlastingCannons card) { @@ -69,72 +66,36 @@ class VancesBlastingCannonsExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); - if (controller != null && sourcePermanent != null) { - Card card = controller.getLibrary().getFromTop(game); - if (card != null) { - String exileName = sourcePermanent.getIdName() + (card.isLand(game) ? "" : " 0; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VenomEvilUnleashed.java b/Mage.Sets/src/mage/cards/v/VenomEvilUnleashed.java new file mode 100644 index 00000000000..b58c7a5c575 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VenomEvilUnleashed.java @@ -0,0 +1,58 @@ +package mage.cards.v; + +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.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VenomEvilUnleashed extends CardImpl { + + public VenomEvilUnleashed(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SYMBIOTE); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // {2}{B}, Exile this card from your graveyard: Put two +1/+1 counters on target creature. It gains deathtouch until end of turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + Zone.GRAVEYARD, new AddCountersTargetEffect(CounterType.P1P1.createInstance()), new ManaCostsImpl<>("{2}{B}") + ); + ability.addCost(new ExileSourceFromGraveCost()); + ability.addTarget(new TargetCreaturePermanent()); + ability.addEffect(new GainAbilityTargetEffect(DeathtouchAbility.getInstance()) + .setText("It gains deathtouch until end of turn")); + this.addAbility(ability); + } + + private VenomEvilUnleashed(final VenomEvilUnleashed card) { + super(card); + } + + @Override + public VenomEvilUnleashed copy() { + return new VenomEvilUnleashed(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VenomizedCat.java b/Mage.Sets/src/mage/cards/v/VenomizedCat.java new file mode 100644 index 00000000000..7c8f9aac106 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VenomizedCat.java @@ -0,0 +1,43 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VenomizedCat extends CardImpl { + + public VenomizedCat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.SYMBIOTE); + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // When this creature enters, mill two cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(2))); + } + + private VenomizedCat(final VenomizedCat card) { + super(card); + } + + @Override + public VenomizedCat copy() { + return new VenomizedCat(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VibrantCityscape.java b/Mage.Sets/src/mage/cards/v/VibrantCityscape.java new file mode 100644 index 00000000000..f4d1f691025 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VibrantCityscape.java @@ -0,0 +1,40 @@ +package mage.cards.v; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VibrantCityscape extends CardImpl { + + public VibrantCityscape(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. + Ability ability = new SimpleActivatedAbility(new SearchLibraryPutInPlayEffect( + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true + ), new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private VibrantCityscape(final VibrantCityscape card) { + super(card); + } + + @Override + public VibrantCityscape copy() { + return new VibrantCityscape(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VikyaScorchingStalwart.java b/Mage.Sets/src/mage/cards/v/VikyaScorchingStalwart.java index 8fa03b3c406..bd59aeb7c9b 100644 --- a/Mage.Sets/src/mage/cards/v/VikyaScorchingStalwart.java +++ b/Mage.Sets/src/mage/cards/v/VikyaScorchingStalwart.java @@ -14,11 +14,13 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetAnyTarget; +import java.util.Optional; import java.util.UUID; /** @@ -91,14 +93,11 @@ class VikyaScorchingStalwartEffect extends OneShotEffect { if (!permanent.isCreature(game)) { return permanent.damage(amount, source, game) > 0; } - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - permanent.damage(amount, source.getSourceId(), source, game); - if (lethal >= amount) { - return true; - } - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.drawCards(1, source, game); + if (permanent.damageWithExcess(amount, source, game) > 0) { + Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.drawCards(1, source, game)); } return true; } diff --git a/Mage.Sets/src/mage/cards/v/VillainousWrath.java b/Mage.Sets/src/mage/cards/v/VillainousWrath.java new file mode 100644 index 00000000000..a896a4eabda --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VillainousWrath.java @@ -0,0 +1,66 @@ +package mage.cards.v; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VillainousWrath extends CardImpl { + + public VillainousWrath(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{B}"); + + // Target opponent loses life equal to the number of creatures they control. Then destroy all creatures. + this.getSpellAbility().addEffect(new VillainousWrathEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + this.getSpellAbility().addEffect(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES).concatBy("Then")); + } + + private VillainousWrath(final VillainousWrath card) { + super(card); + } + + @Override + public VillainousWrath copy() { + return new VillainousWrath(this); + } +} + +class VillainousWrathEffect extends OneShotEffect { + + VillainousWrathEffect() { + super(Outcome.Benefit); + staticText = "target opponent loses life equal to the number of creatures they control"; + } + + private VillainousWrathEffect(final VillainousWrathEffect effect) { + super(effect); + } + + @Override + public VillainousWrathEffect copy() { + return new VillainousWrathEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + int amount = game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_CREATURE, player.getId(), source, game); + return amount > 0 && player.loseLife(amount, game, source, false) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VindictiveWarden.java b/Mage.Sets/src/mage/cards/v/VindictiveWarden.java new file mode 100644 index 00000000000..ac86ef51529 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VindictiveWarden.java @@ -0,0 +1,50 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.keyword.FirebendingAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VindictiveWarden extends CardImpl { + + public VindictiveWarden(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B/R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // Firebending 1 + this.addAbility(new FirebendingAbility(1)); + + // {3}: This creature deals 1 damage to each opponent. + this.addAbility(new SimpleActivatedAbility( + new DamagePlayersEffect(1, TargetController.OPPONENT), new GenericManaCost(3) + )); + } + + private VindictiveWarden(final VindictiveWarden card) { + super(card); + } + + @Override + public VindictiveWarden copy() { + return new VindictiveWarden(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VolrathsCurse.java b/Mage.Sets/src/mage/cards/v/VolrathsCurse.java index ad01f63f065..e02f19c48e4 100644 --- a/Mage.Sets/src/mage/cards/v/VolrathsCurse.java +++ b/Mage.Sets/src/mage/cards/v/VolrathsCurse.java @@ -176,7 +176,7 @@ class VolrathsCurseIgnoreEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - String key = source.getSourceId().toString() + source.getSourceObjectZoneChangeCounter() + VolrathsCurse.keyString + game.getTurnNum() + ((ActivatedAbilityImpl) source).getActivatorId(); + String key = source.getSourceId().toString() + source.getStackMomentSourceZCC() + VolrathsCurse.keyString + game.getTurnNum() + ((ActivatedAbilityImpl) source).getActivatorId(); game.getState().setValue(key, true); return true; } diff --git a/Mage.Sets/src/mage/cards/v/VoraciousReader.java b/Mage.Sets/src/mage/cards/v/VoraciousReader.java index 55306887a45..9956bc0cc68 100644 --- a/Mage.Sets/src/mage/cards/v/VoraciousReader.java +++ b/Mage.Sets/src/mage/cards/v/VoraciousReader.java @@ -1,7 +1,6 @@ package mage.cards.v; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; @@ -10,26 +9,20 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.predicate.Predicates; +import mage.filter.common.FilterInstantOrSorceryCard; + +import java.util.UUID; /** - * * @author fireshoes */ public final class VoraciousReader extends CardImpl { - private static final FilterCard filter = new FilterCard("Instant and sorcery spells"); - static { - filter.add(Predicates.or( - CardType.INSTANT.getPredicate(), - CardType.SORCERY.getPredicate() - )); - } + private static final FilterCard filter = new FilterInstantOrSorceryCard("Instant and sorcery spells"); public VoraciousReader(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},""); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); this.subtype.add(SubType.ELDRAZI); this.subtype.add(SubType.HOMUNCULUS); this.power = new MageInt(3); diff --git a/Mage.Sets/src/mage/cards/v/VultureSchemingScavenger.java b/Mage.Sets/src/mage/cards/v/VultureSchemingScavenger.java new file mode 100644 index 00000000000..4449374150e --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VultureSchemingScavenger.java @@ -0,0 +1,51 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VultureSchemingScavenger extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.VILLAIN, "Villains"); + + public VultureSchemingScavenger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U/B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(4); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Vulture attacks, other Villains you control gain flying until end of turn. + this.addAbility(new AttacksTriggeredAbility(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.EndOfTurn, filter, true + ))); + } + + private VultureSchemingScavenger(final VultureSchemingScavenger card) { + super(card); + } + + @Override + public VultureSchemingScavenger copy() { + return new VultureSchemingScavenger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/Waildrifter.java b/Mage.Sets/src/mage/cards/w/Waildrifter.java index dec610a203d..321a1d31d12 100644 --- a/Mage.Sets/src/mage/cards/w/Waildrifter.java +++ b/Mage.Sets/src/mage/cards/w/Waildrifter.java @@ -1,8 +1,7 @@ package mage.cards.w; import mage.MageInt; -import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; -import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -30,7 +29,7 @@ public final class Waildrifter extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // If Waildrifter would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); + this.addAbility(DisturbAbility.makeBackAbility()); } private Waildrifter(final Waildrifter card) { diff --git a/Mage.Sets/src/mage/cards/w/WallCrawl.java b/Mage.Sets/src/mage/cards/w/WallCrawl.java new file mode 100644 index 00000000000..ba1278fbfb4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WallCrawl.java @@ -0,0 +1,61 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.combat.CantBeBlockedAllEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.Spider21Token; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WallCrawl extends CardImpl { + + private static final FilterControlledCreaturePermanent filter + = new FilterControlledCreaturePermanent(SubType.SPIDER, "Spiders you control"); + private static final DynamicValue xValue + = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.SPIDER)); + private static final Hint hint = new ValueHint("Spiders you control", xValue); + + public WallCrawl(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); + + // When this enchantment enters, create a 2/1 green Spider creature token with reach, then you gain 1 life for each Spider you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new Spider21Token())); + ability.addEffect(new GainLifeEffect(xValue).concatBy(", then")); + this.addAbility(ability.addHint(hint)); + + // Spiders you control get +1/+1 and can't be blocked by creatures with defender. + ability = new SimpleStaticAbility(new BoostAllEffect( + 1, 1, Duration.WhileControlled, filter, false + )); + ability.addEffect(new CantBeBlockedAllEffect(filter, Duration.WhileControlled) + .setText("and can't be blocked by creatures with defender")); + this.addAbility(ability); + } + + private WallCrawl(final WallCrawl card) { + super(card); + } + + @Override + public WallCrawl copy() { + return new WallCrawl(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WallOfDeceit.java b/Mage.Sets/src/mage/cards/w/WallOfDeceit.java index 1b6dddd9013..0e7fac077b1 100644 --- a/Mage.Sets/src/mage/cards/w/WallOfDeceit.java +++ b/Mage.Sets/src/mage/cards/w/WallOfDeceit.java @@ -14,7 +14,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; @@ -70,7 +69,7 @@ class WallOfDeceitEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (sourcePermanent != null - && source.getSourceObjectZoneChangeCounter() == sourcePermanent.getZoneChangeCounter(game) // in case source was blinked after ability was set to stack + && source.getStackMomentSourceZCC() == sourcePermanent.getZoneChangeCounter(game) // in case source was blinked after ability was set to stack && !sourcePermanent.isFaceDown(game)) { sourcePermanent.setFaceDown(true, game); } diff --git a/Mage.Sets/src/mage/cards/w/WarshipScout.java b/Mage.Sets/src/mage/cards/w/WarshipScout.java new file mode 100644 index 00000000000..3aed4831ebd --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WarshipScout.java @@ -0,0 +1,33 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WarshipScout extends CardImpl { + + public WarshipScout(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + } + + private WarshipScout(final WarshipScout card) { + super(card); + } + + @Override + public WarshipScout copy() { + return new WarshipScout(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WaterWhip.java b/Mage.Sets/src/mage/cards/w/WaterWhip.java new file mode 100644 index 00000000000..7bf6ea5f9fe --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WaterWhip.java @@ -0,0 +1,41 @@ +package mage.cards.w; + +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WaterWhip extends CardImpl { + + public WaterWhip(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}{U}"); + + this.subtype.add(SubType.LESSON); + + // As an additional cost to cast this spell, waterbend {5}. + this.getSpellAbility().addCost(new WaterbendCost(5)); + + // Return up to two target creatures to their owners' hands. Draw two cards. + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2)); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + } + + private WaterWhip(final WaterWhip card) { + super(card); + } + + @Override + public WaterWhip copy() { + return new WaterWhip(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WaterbendingLesson.java b/Mage.Sets/src/mage/cards/w/WaterbendingLesson.java new file mode 100644 index 00000000000..7d41f6b534e --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WaterbendingLesson.java @@ -0,0 +1,40 @@ +package mage.cards.w; + +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +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 WaterbendingLesson extends CardImpl { + + public WaterbendingLesson(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}"); + + this.subtype.add(SubType.LESSON); + + // Draw three cards. Then discard a card unless you waterbend {2}. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(3)); + this.getSpellAbility().addEffect(new DoIfCostPaid( + null, new DiscardControllerEffect(1), + new WaterbendCost(2).setText("waterbend {2} instead of discarding a card") + ).setText("Then discard a card unless you waterbend {2}")); + } + + private WaterbendingLesson(final WaterbendingLesson card) { + super(card); + } + + @Override + public WaterbendingLesson copy() { + return new WaterbendingLesson(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WateryGrasp.java b/Mage.Sets/src/mage/cards/w/WateryGrasp.java new file mode 100644 index 00000000000..288b5062f29 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WateryGrasp.java @@ -0,0 +1,87 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DontUntapInControllersUntapStepEnchantedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WateryGrasp extends CardImpl { + + public WateryGrasp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature doesn't untap during its controller's untap step. + this.addAbility(new SimpleStaticAbility(new DontUntapInControllersUntapStepEnchantedEffect())); + + // Waterbend {5}: Enchanted creature's owner shuffles it into their library. + this.addAbility(new SimpleActivatedAbility(new WateryGraspEffect(), new WaterbendCost(5))); + } + + private WateryGrasp(final WateryGrasp card) { + super(card); + } + + @Override + public WateryGrasp copy() { + return new WateryGrasp(this); + } +} + +class WateryGraspEffect extends OneShotEffect { + + WateryGraspEffect() { + super(Outcome.Benefit); + staticText = "enchanted creature's owner shuffles it into their library"; + } + + private WateryGraspEffect(final WateryGraspEffect effect) { + super(effect); + } + + @Override + public WateryGraspEffect copy() { + return new WateryGraspEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .map(game::getPermanent) + .orElse(null); + if (permanent == null) { + return false; + } + Player player = game.getPlayer(permanent.getOwnerId()); + return player != null && player.shuffleCardsToLibrary(permanent, game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WebOfLifeAndDestiny.java b/Mage.Sets/src/mage/cards/w/WebOfLifeAndDestiny.java new file mode 100644 index 00000000000..ad48b81c379 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WebOfLifeAndDestiny.java @@ -0,0 +1,40 @@ +package mage.cards.w; + +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.ConvokeAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WebOfLifeAndDestiny extends CardImpl { + + public WebOfLifeAndDestiny(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{6}{G}{G}"); + + // Convoke + this.addAbility(new ConvokeAbility()); + + // At the beginning of combat on your turn, look at the top five cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. + this.addAbility(new BeginningOfCombatTriggeredAbility(new LookLibraryAndPickControllerEffect( + 5, 1, StaticFilters.FILTER_CARD_CREATURE, + PutCards.BATTLEFIELD, PutCards.BOTTOM_RANDOM + ))); + } + + private WebOfLifeAndDestiny(final WebOfLifeAndDestiny card) { + super(card); + } + + @Override + public WebOfLifeAndDestiny copy() { + return new WebOfLifeAndDestiny(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WebWarriors.java b/Mage.Sets/src/mage/cards/w/WebWarriors.java new file mode 100644 index 00000000000..3e9c8a289c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WebWarriors.java @@ -0,0 +1,42 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WebWarriors extends CardImpl { + + public WebWarriors(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G/W}"); + + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HERO); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // When this creature enters, put a +1/+1 counter on each other creature you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_OTHER_CONTROLLED_CREATURE + ))); + } + + private WebWarriors(final WebWarriors card) { + super(card); + } + + @Override + public WebWarriors copy() { + return new WebWarriors(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WeightOfConscience.java b/Mage.Sets/src/mage/cards/w/WeightOfConscience.java index 063c4dec9a6..222e01d0130 100644 --- a/Mage.Sets/src/mage/cards/w/WeightOfConscience.java +++ b/Mage.Sets/src/mage/cards/w/WeightOfConscience.java @@ -1,6 +1,5 @@ package mage.cards.w; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; @@ -11,11 +10,13 @@ import mage.abilities.effects.common.combat.CantAttackAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.Predicate; import mage.filter.predicate.mageobject.SharesCreatureTypePredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; @@ -25,7 +26,10 @@ import mage.target.TargetPermanent; import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; /** * @author emerald000 @@ -66,7 +70,6 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { static { filterUntapped.add(TappedPredicate.UNTAPPED); - filterUntapped.add(WeightOfConsciencePredicate.instance); } WeightOfConscienceTarget() { @@ -79,13 +82,15 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = new HashSet<>(); + Player player = game.getPlayer(sourceControllerId); - Set possibleTargets = new HashSet<>(0); if (player == null) { return possibleTargets; } - // Choosing first target + if (this.getTargets().isEmpty()) { + // choosing first target - use any permanent with shared types List permanentList = game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, source, game); if (permanentList.size() < 2) { return possibleTargets; @@ -101,8 +106,8 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { possibleTargets.add(permanent.getId()); } } - } // Choosing second target - else { + } else { + // choosing second target - must have shared type with first target Permanent firstTargetCreature = game.getPermanent(this.getFirstTarget()); if (firstTargetCreature == null) { return possibleTargets; @@ -115,50 +120,8 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { } } } - return possibleTargets; - } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - for (Permanent permanent1 : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, source, game)) { - for (Permanent permanent2 : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, source, game)) { - if (!Objects.equals(permanent1, permanent2) && permanent1.shareCreatureTypes(game, permanent2)) { - return true; - } - } - } - return false; - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - if (!super.canTarget(id, game)) { - return false; - } - Permanent targetPermanent = game.getPermanent(id); - if (targetPermanent == null) { - return false; - } - if (this.getTargets().isEmpty()) { - List permanentList = game.getBattlefield().getActivePermanents(filterUntapped, source.getControllerId(), source, game); - if (permanentList.size() < 2) { - return false; - } - for (Permanent permanent : permanentList) { - if (permanent.isAllCreatureTypes(game)) { - return true; - } - FilterPermanent filter = filterUntapped.copy(); - filter.add(new SharesCreatureTypePredicate(permanent)); - if (game.getBattlefield().count(filter, source.getControllerId(), source, game) > 1) { - return true; - } - } - } else { - Permanent firstTarget = game.getPermanent(this.getTargets().get(0)); - return firstTarget != null && firstTarget.shareCreatureTypes(game, targetPermanent); - } - return false; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -166,17 +129,3 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { return new WeightOfConscienceTarget(this); } } - -enum WeightOfConsciencePredicate implements Predicate { - instance; - - @Override - public boolean apply(MageObject input, Game game) { - return input.isAllCreatureTypes(game) - || input - .getSubtype(game) - .stream() - .map(SubType::getSubTypeSet) - .anyMatch(SubTypeSet.CreatureType::equals); - } -} diff --git a/Mage.Sets/src/mage/cards/w/WerewolfOfAncientHunger.java b/Mage.Sets/src/mage/cards/w/WerewolfOfAncientHunger.java index cb6c03847d3..461ec0d4dc7 100644 --- a/Mage.Sets/src/mage/cards/w/WerewolfOfAncientHunger.java +++ b/Mage.Sets/src/mage/cards/w/WerewolfOfAncientHunger.java @@ -3,7 +3,7 @@ package mage.cards.w; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.WerewolfBackTriggeredAbility; -import mage.abilities.condition.common.TransformedCondition; +import mage.abilities.condition.common.NotTransformedCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.dynamicvalue.common.CardsInAllHandsCount; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; @@ -38,9 +38,13 @@ public final class WerewolfOfAncientHunger extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Werewolf of Ancient Hunger's power and toughness are each equal to the total number of cards in all players' hands. - this.addAbility(new SimpleStaticAbility(Zone.ALL, - new ConditionalContinuousEffect(new SetBasePowerToughnessSourceEffect(CardsInAllHandsCount.instance), - new TransformedCondition(false), "{this}'s power and toughness are each equal to the total number of cards in all players' hands"))); + this.addAbility(new SimpleStaticAbility( + Zone.ALL, + new ConditionalContinuousEffect( + new SetBasePowerToughnessSourceEffect(CardsInAllHandsCount.instance), NotTransformedCondition.instance, + "{this}'s power and toughness are each equal to the total number of cards in all players' hands" + ) + )); // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Werewolf of Ancient Hunger. this.addAbility(new WerewolfBackTriggeredAbility()); diff --git a/Mage.Sets/src/mage/cards/w/WerewolfRansacker.java b/Mage.Sets/src/mage/cards/w/WerewolfRansacker.java index 81e7164a5ce..4b63d6ea51f 100644 --- a/Mage.Sets/src/mage/cards/w/WerewolfRansacker.java +++ b/Mage.Sets/src/mage/cards/w/WerewolfRansacker.java @@ -11,11 +11,12 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetArtifactPermanent; +import java.util.Optional; import java.util.UUID; /** @@ -72,16 +73,18 @@ class WerewolfRansackerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } - Player player = game.getPlayer(permanent.getControllerId()); - permanent.destroy(source, game, false); - if (game.getState().getZone(permanent.getId()) != Zone.GRAVEYARD || player == null) { + permanent.destroy(source, game); + if (game.getState().getZone(permanent.getId()) != Zone.GRAVEYARD) { return true; } - player.damage(3, source.getSourceId(), source, game); + Optional.ofNullable(permanent) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.damage(3, source, game)); return true; } } diff --git a/Mage.Sets/src/mage/cards/w/WhimsOfTheFates.java b/Mage.Sets/src/mage/cards/w/WhimsOfTheFates.java index e246f1e5933..52d8f8d66df 100644 --- a/Mage.Sets/src/mage/cards/w/WhimsOfTheFates.java +++ b/Mage.Sets/src/mage/cards/w/WhimsOfTheFates.java @@ -175,20 +175,17 @@ class TargetSecondPilePermanent extends TargetPermanent { this.firstPile = firstPile; } - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game, boolean flag) { - if (firstPile.contains(id)) { - return false; - } - return super.canTarget(controllerId, id, source, game, flag); + public TargetSecondPilePermanent(final TargetSecondPilePermanent target) { + super(target); + this.firstPile = new HashSet<>(target.firstPile); } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { if (firstPile.contains(id)) { return false; } - return super.canTarget(controllerId, id, source, game); + return super.canTarget(playerId, id, source, game); } @Override @@ -199,4 +196,8 @@ class TargetSecondPilePermanent extends TargetPermanent { return super.canTarget(id, source, game); } + @Override + public TargetSecondPilePermanent copy() { + return new TargetSecondPilePermanent(this); + } } diff --git a/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java b/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java index 74c8976c803..b958fb4edae 100644 --- a/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java +++ b/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java @@ -176,7 +176,7 @@ class WhispersteelDaggerWatcher extends Watcher { return false; } MageObjectReference mor = new MageObjectReference( - source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game + source.getSourceId(), source.getStackMomentSourceZCC(), game ); if (!morMap.containsKey(mor)) { return false; @@ -188,7 +188,7 @@ class WhispersteelDaggerWatcher extends Watcher { void addPlayable(Ability source, UUID ownerId, Game game) { MageObjectReference mor = new MageObjectReference( - source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game + source.getSourceId(), source.getStackMomentSourceZCC(), game ); morMap.computeIfAbsent(mor, m -> new HashMap<>()) .computeIfAbsent(ownerId, m -> new HashMap<>()) diff --git a/Mage.Sets/src/mage/cards/w/WildMagicSorcerer.java b/Mage.Sets/src/mage/cards/w/WildMagicSorcerer.java index f91d13843b2..dc97b1e7937 100644 --- a/Mage.Sets/src/mage/cards/w/WildMagicSorcerer.java +++ b/Mage.Sets/src/mage/cards/w/WildMagicSorcerer.java @@ -2,7 +2,10 @@ package mage.cards.w; import mage.MageInt; import mage.MageObjectReference; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.keyword.CascadeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -11,14 +14,12 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; import mage.game.stack.StackObject; +import mage.players.Player; import mage.watchers.Watcher; + import java.util.HashMap; import java.util.Map; import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.condition.Condition; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.players.Player; /** * @author TheElk801 @@ -35,7 +36,7 @@ public final class WildMagicSorcerer extends CardImpl { // The first spell you cast from exile each turn has cascade. this.addAbility(new SimpleStaticAbility( - new WildMagicSorcererGainCascadeFirstSpellCastFromExileEffect()), + new WildMagicSorcererGainCascadeFirstSpellCastFromExileEffect()), new WildMagicSorcererWatcher()); } @@ -96,12 +97,10 @@ enum FirstSpellCastFromExileEachTurnCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - if (game.getStack().isEmpty()) { - return false; - } WildMagicSorcererWatcher watcher = game.getState().getWatcher(WildMagicSorcererWatcher.class); - StackObject so = game.getStack().getFirst(); - return watcher != null + StackObject so = game.getStack().getFirstOrNull(); + return so != null + && watcher != null && WildMagicSorcererWatcher.checkSpell(so, game); } } diff --git a/Mage.Sets/src/mage/cards/w/WindswiftSlice.java b/Mage.Sets/src/mage/cards/w/WindswiftSlice.java index 0c9751e1c7a..59cb03ca713 100644 --- a/Mage.Sets/src/mage/cards/w/WindswiftSlice.java +++ b/Mage.Sets/src/mage/cards/w/WindswiftSlice.java @@ -1,32 +1,33 @@ package mage.cards.w; -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.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.ElfWarriorToken; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.EachTargetPointer; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; import static mage.filter.StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL; /** - * * @author bwsinger */ public final class WindswiftSlice extends CardImpl { public WindswiftSlice(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); - + // Target creature you control deals damage equal to its power to target creature you don't control. Create a number of 1/1 green Elf Warrior creature tokens equal to the amount of excess damage dealt this way. this.getSpellAbility().addEffect(new WindswiftSliceEffect()); @@ -48,6 +49,7 @@ class WindswiftSliceEffect extends OneShotEffect { WindswiftSliceEffect() { super(Outcome.Benefit); + this.setTargetPointer(new EachTargetPointer()); staticText = "Target creature you control deals damage equal to its power to target " + "creature you don't control. Create a number of 1/1 green Elf Warrior creature " + "tokens equal to the amount of excess damage dealt this way."; @@ -64,27 +66,26 @@ class WindswiftSliceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent myPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - Permanent anotherPermanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - - if (myPermanent == null || anotherPermanent == null) { + List permanents = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (permanents.size() < 2) { return false; } - - int power = myPermanent.getPower().getValue(); + Permanent permanent = permanents.get(0); + int power = permanent.getPower().getValue(); if (power < 1) { return false; } - - int lethal = anotherPermanent.getLethalDamage(myPermanent.getId(), game); - lethal = Math.min(lethal, power); - - anotherPermanent.damage(power, myPermanent.getId(), source, game); - - if (lethal < power) { - new ElfWarriorToken().putOntoBattlefield(power - lethal, game, source, source.getControllerId()); + Permanent creature = permanents.get(1); + int excess = creature.damageWithExcess(power, permanent.getId(), source, game); + if (excess > 0) { + new ElfWarriorToken().putOntoBattlefield(excess, game, source); } - return true; } } diff --git a/Mage.Sets/src/mage/cards/w/Wisecrack.java b/Mage.Sets/src/mage/cards/w/Wisecrack.java new file mode 100644 index 00000000000..5b133dcfa0b --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/Wisecrack.java @@ -0,0 +1,72 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Wisecrack extends CardImpl { + + public Wisecrack(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + // Target creature deals damage equal to its power to itself. If that creature is attacking, Wisecrack deals 2 damage to that creature's controller. + this.getSpellAbility().addEffect(new WisecrackEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private Wisecrack(final Wisecrack card) { + super(card); + } + + @Override + public Wisecrack copy() { + return new Wisecrack(this); + } +} + +class WisecrackEffect extends OneShotEffect { + + WisecrackEffect() { + super(Outcome.Benefit); + staticText = "target creature deals damage equal to its power to itself. " + + "If that creature is attacking, {this} deals 2 damage to that creature's controller"; + } + + private WisecrackEffect(final WisecrackEffect effect) { + super(effect); + } + + @Override + public WisecrackEffect copy() { + return new WisecrackEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + permanent.damage(permanent.getPower().getValue(), permanent.getId(), source, game); + if (permanent.isAttacking()) { + Optional.ofNullable(permanent) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .map(player -> player.damage(2, source, game)); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/w/WithGreatPower.java b/Mage.Sets/src/mage/cards/w/WithGreatPower.java new file mode 100644 index 00000000000..9af1610bde3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WithGreatPower.java @@ -0,0 +1,114 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EnchantAbility; +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.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.AttachedToAttachedPredicate; +import mage.game.Game; +import mage.game.events.DamagePlayerEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WithGreatPower extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("Aura and Equipment attached to it"); + + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate() + )); + filter.add(AttachedToAttachedPredicate.instance); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, 2); + + public WithGreatPower(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature you control + TargetPermanent auraTarget = new TargetPermanent(StaticFilters.FILTER_CONTROLLED_CREATURE); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature gets +2/+2 for each Aura and Equipment attached to it. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(xValue, xValue))); + + // All damage that would be dealt to you is dealt to enchanted creature instead. + this.addAbility(new SimpleStaticAbility(new WithGreatPowerEffect())); + } + + private WithGreatPower(final WithGreatPower card) { + super(card); + } + + @Override + public WithGreatPower copy() { + return new WithGreatPower(this); + } +} + +class WithGreatPowerEffect extends ReplacementEffectImpl { + WithGreatPowerEffect() { + super(Duration.WhileOnBattlefield, Outcome.RedirectDamage); + staticText = "all damage that would be dealt to you is dealt to enchanted creature instead"; + } + + private WithGreatPowerEffect(final WithGreatPowerEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + DamagePlayerEvent damageEvent = (DamagePlayerEvent) event; + Optional.ofNullable(event) + .map(GameEvent::getSourceId) + .map(game::getPermanent) + .map(Permanent::getAttachedTo) + .map(game::getPermanent) + .ifPresent(permanent -> permanent.damage( + damageEvent.getAmount(), event.getSourceId(), source, + game, damageEvent.isCombatDamage(), damageEvent.isPreventable() + )); + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGE_PLAYER; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.isControlledBy(event.getPlayerId()); + } + + @Override + public WithGreatPowerEffect copy() { + return new WithGreatPowerEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WithengarUnbound.java b/Mage.Sets/src/mage/cards/w/WithengarUnbound.java index 032eae161b0..4b170ef2642 100644 --- a/Mage.Sets/src/mage/cards/w/WithengarUnbound.java +++ b/Mage.Sets/src/mage/cards/w/WithengarUnbound.java @@ -1,4 +1,3 @@ - package mage.cards.w; import mage.MageInt; @@ -20,13 +19,12 @@ import mage.game.events.GameEvent; import java.util.UUID; /** - * * @author BetaSteward */ public final class WithengarUnbound extends CardImpl { public WithengarUnbound(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},""); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.DEMON); this.color.setBlack(true); @@ -40,9 +38,9 @@ public final class WithengarUnbound extends CardImpl { this.addAbility(FlyingAbility.getInstance()); this.addAbility(IntimidateAbility.getInstance()); this.addAbility(TrampleAbility.getInstance()); + // Whenever a player loses the game, put thirteen +1/+1 counters on Withengar Unbound. this.addAbility(new WithengarUnboundTriggeredAbility()); - } private WithengarUnbound(final WithengarUnbound card) { @@ -57,8 +55,9 @@ public final class WithengarUnbound extends CardImpl { class WithengarUnboundTriggeredAbility extends TriggeredAbilityImpl { - public WithengarUnboundTriggeredAbility() { + WithengarUnboundTriggeredAbility() { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance(13)), false); + setTriggerPhrase("Whenever a player loses the game, "); } private WithengarUnboundTriggeredAbility(final WithengarUnboundTriggeredAbility ability) { @@ -79,9 +78,4 @@ class WithengarUnboundTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { return true; } - - @Override - public String getRule() { - return "Whenever a player loses the game, put thirteen +1/+1 counters on {this}."; - } } diff --git a/Mage.Sets/src/mage/cards/w/WoebringerDemon.java b/Mage.Sets/src/mage/cards/w/WoebringerDemon.java index 26dbfd48dea..c8fc750d63b 100644 --- a/Mage.Sets/src/mage/cards/w/WoebringerDemon.java +++ b/Mage.Sets/src/mage/cards/w/WoebringerDemon.java @@ -84,7 +84,7 @@ class WoebringerDemonEffect extends OneShotEffect { } } Permanent sourceObject = game.getPermanent(source.getSourceId()); - if (sourceObject != null && sourceObject.getZoneChangeCounter(game) == source.getSourceObjectZoneChangeCounter()) { + if (sourceObject != null && sourceObject.getZoneChangeCounter(game) == source.getStackMomentSourceZCC()) { sourceObject.sacrifice(source, game); } return true; diff --git a/Mage.Sets/src/mage/cards/w/WolfCoveVillager.java b/Mage.Sets/src/mage/cards/w/WolfCoveVillager.java new file mode 100644 index 00000000000..94c9a626062 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WolfCoveVillager.java @@ -0,0 +1,37 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTappedAbility; +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 WolfCoveVillager extends CardImpl { + + public WolfCoveVillager(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // This creature enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + } + + private WolfCoveVillager(final WolfCoveVillager card) { + super(card); + } + + @Override + public WolfCoveVillager copy() { + return new WolfCoveVillager(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WorldgorgerDragon.java b/Mage.Sets/src/mage/cards/w/WorldgorgerDragon.java index ea9d06a53da..b64f0fdec2d 100644 --- a/Mage.Sets/src/mage/cards/w/WorldgorgerDragon.java +++ b/Mage.Sets/src/mage/cards/w/WorldgorgerDragon.java @@ -86,7 +86,7 @@ class WorldgorgerDragonEntersEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (controller != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); if (exileId != null) { Set cardsToExile = new LinkedHashSet<>(); cardsToExile.addAll(game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)); @@ -119,7 +119,7 @@ class WorldgorgerDragonLeavesEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (controller != null && sourceObject != null) { - int zoneChangeCounter = (sourceObject instanceof PermanentToken) ? source.getSourceObjectZoneChangeCounter() : source.getSourceObjectZoneChangeCounter() - 1; + int zoneChangeCounter = (sourceObject instanceof PermanentToken) ? source.getStackMomentSourceZCC() : source.getStackMomentSourceZCC() - 1; ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), zoneChangeCounter)); if (exile != null) { return controller.moveCards(exile.getCards(game), Zone.BATTLEFIELD, source, game, false, false, true, null); diff --git a/Mage.Sets/src/mage/cards/w/WraithViciousVigilante.java b/Mage.Sets/src/mage/cards/w/WraithViciousVigilante.java new file mode 100644 index 00000000000..9f3b15ae9d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WraithViciousVigilante.java @@ -0,0 +1,44 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.keyword.CantBeBlockedSourceAbility; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WraithViciousVigilante extends CardImpl { + + public WraithViciousVigilante(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DETECTIVE); + this.subtype.add(SubType.HERO); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Fear Gas -- Wraith can't be blocked. + this.addAbility(new CantBeBlockedSourceAbility().withFlavorWord("Fear Gas")); + } + + private WraithViciousVigilante(final WraithViciousVigilante card) { + super(card); + } + + @Override + public WraithViciousVigilante copy() { + return new WraithViciousVigilante(this); + } +} diff --git a/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java b/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java index 9c4997e3944..63400e1b2d6 100644 --- a/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java +++ b/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java @@ -93,7 +93,7 @@ class YannikScavengingSentinelEffect extends OneShotEffect { } int power = permanent.getPower().getValue(); new ExileTargetEffect(CardUtil.getExileZoneId( - game, source.getSourceId(), source.getSourceObjectZoneChangeCounter() + game, source.getSourceId(), source.getStackMomentSourceZCC() ), permanent.getIdName()).setTargetPointer(new FixedTarget(permanent, game)).apply(game, source); game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source); if (game.getState().getZone(permanent.getId()) != Zone.BATTLEFIELD) { diff --git a/Mage.Sets/src/mage/cards/y/YasharnImplacableEarth.java b/Mage.Sets/src/mage/cards/y/YasharnImplacableEarth.java index 6f5f5a44238..ee9b54f81e4 100644 --- a/Mage.Sets/src/mage/cards/y/YasharnImplacableEarth.java +++ b/Mage.Sets/src/mage/cards/y/YasharnImplacableEarth.java @@ -1,34 +1,22 @@ package mage.cards.y; -import java.util.Optional; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.assignment.common.SubTypeAssignment; +import mage.abilities.common.CantPayLifeOrSacrificeAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.cards.*; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.target.common.TargetCardInLibrary; import java.util.UUID; -import mage.MageObject; -import mage.abilities.costs.common.PayLifeCost; -import mage.abilities.costs.common.PayVariableLifeCost; -import mage.abilities.costs.common.SacrificeAllCost; -import mage.abilities.costs.common.SacrificeAttachedCost; -import mage.abilities.costs.common.SacrificeAttachmentCost; -import mage.abilities.costs.common.SacrificeSourceCost; -import mage.abilities.costs.common.SacrificeXTargetCost; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; -import mage.filter.Filter; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; /** * @author TheElk801 @@ -52,8 +40,7 @@ public final class YasharnImplacableEarth extends CardImpl { )); // Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities. - Ability ability = new SimpleStaticAbility(new YasharnImplacableEarthEffect()); - this.addAbility(ability); + this.addAbility(new CantPayLifeOrSacrificeAbility(StaticFilters.FILTER_PERMANENTS_NON_LAND)); } private YasharnImplacableEarth(final YasharnImplacableEarth card) { @@ -111,122 +98,3 @@ class YasharnImplacableEarthTarget extends TargetCardInLibrary { return subTypeAssigner.getRoleCount(cards, game) >= cards.size(); } } - -class YasharnImplacableEarthEffect extends ContinuousRuleModifyingEffectImpl { - - YasharnImplacableEarthEffect() { - super(Duration.WhileOnBattlefield, Outcome.Neutral); - staticText = "Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities"; - } - - private YasharnImplacableEarthEffect(final YasharnImplacableEarthEffect effect) { - super(effect); - } - - @Override - public YasharnImplacableEarthEffect copy() { - return new YasharnImplacableEarthEffect(this); - } - - @Override - public String getInfoMessage(Ability source, GameEvent event, Game game) { - MageObject mageObject = game.getObject(source); - if (mageObject != null) { - return "Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities. (" + mageObject.getIdName() + ")."; - } - return null; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ACTIVATE_ABILITY - || event.getType() == GameEvent.EventType.CAST_SPELL; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); - if (event.getType() == GameEvent.EventType.ACTIVATE_ABILITY && permanent == null) { - return false; - } - - boolean canTargetLand = true; - Optional ability = game.getAbility(event.getTargetId(), event.getSourceId()); - if (!ability.isPresent()) { - return false; - } - - for (Cost cost : ability.get().getCosts()) { - if (cost instanceof PayLifeCost - || cost instanceof PayVariableLifeCost) { - return true; // can't pay with life - } - if (cost instanceof SacrificeSourceCost - && !permanent.isLand(game)) { - return true; - } - if (cost instanceof SacrificeTargetCost) { - SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost; - Filter filter = sacrificeCost.getTargets().get(0).getFilter(); - for (Object predicate : filter.getPredicates()) { - if (predicate instanceof CardType.CardTypePredicate) { - if (!predicate.toString().equals("CardType(Land)")) { - canTargetLand = false; - } - } - } - return !canTargetLand; // must be nonland target - } - if (cost instanceof SacrificeAllCost) { - SacrificeAllCost sacrificeAllCost = (SacrificeAllCost) cost; - Filter filter = sacrificeAllCost.getTargets().get(0).getFilter(); - for (Object predicate : filter.getPredicates()) { - if (predicate instanceof CardType.CardTypePredicate) { - if (!predicate.toString().equals("CardType(Land)")) { - canTargetLand = false; - } - } - } - return !canTargetLand; // must be nonland target - } - if (cost instanceof SacrificeAttachedCost) { - SacrificeAttachedCost sacrificeAllCost = (SacrificeAttachedCost) cost; - Filter filter = sacrificeAllCost.getTargets().get(0).getFilter(); - for (Object predicate : filter.getPredicates()) { - if (predicate instanceof CardType.CardTypePredicate) { - if (!predicate.toString().equals("CardType(Land)")) { - canTargetLand = false; - } - } - } - return !canTargetLand; // must be nonland target - } - if (cost instanceof SacrificeAttachmentCost) { - SacrificeAttachmentCost sacrificeAllCost = (SacrificeAttachmentCost) cost; - Filter filter = sacrificeAllCost.getTargets().get(0).getFilter(); - for (Object predicate : filter.getPredicates()) { - if (predicate instanceof CardType.CardTypePredicate) { - if (!predicate.toString().equals("CardType(Land)")) { - canTargetLand = false; - } - } - } - return !canTargetLand; // must be nonland target - } - - if (cost instanceof SacrificeXTargetCost) { - SacrificeXTargetCost sacrificeCost = (SacrificeXTargetCost) cost; - Filter filter = sacrificeCost.getFilter(); - for (Object predicate : filter.getPredicates()) { - if (predicate instanceof CardType.CardTypePredicate) { - if (!predicate.toString().equals("CardType(Land)")) { - canTargetLand = false; - } - } - } - return !canTargetLand; // must be nonland target - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java b/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java index c65013cc80d..f31607ad1d3 100644 --- a/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java +++ b/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java @@ -52,8 +52,7 @@ public final class YorionSkyNomad extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - // When Yorion enters the battlefield, exile any number of other nonland permanents you own - // and control. Return those cards to the battlefield at the beginning of the next end step. + // When Yorion enters the battlefield, exile any number of other nonland permanents you own and control. Return those cards to the battlefield at the beginning of the next end step. this.addAbility(new EntersBattlefieldTriggeredAbility(new OneShotNonTargetEffect( new ExileReturnBattlefieldNextEndStepTargetEffect().setText(ruleText), new TargetPermanent(0, Integer.MAX_VALUE, filter, true)))); diff --git a/Mage.Sets/src/mage/cards/y/YoseiTheMorningStar.java b/Mage.Sets/src/mage/cards/y/YoseiTheMorningStar.java index dab9297934f..1393a2a67bf 100644 --- a/Mage.Sets/src/mage/cards/y/YoseiTheMorningStar.java +++ b/Mage.Sets/src/mage/cards/y/YoseiTheMorningStar.java @@ -69,12 +69,12 @@ class YoseiTheMorningStarTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Player player = game.getPlayer(source.getFirstTarget()); if (player != null) { this.filter = filterTemplate.copy(); this.filter.add(new ControllerIdPredicate(player.getId())); - return super.canTarget(controllerId, id, source, game); + return super.canTarget(playerId, id, source, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/y/YueTheMoonSpirit.java b/Mage.Sets/src/mage/cards/y/YueTheMoonSpirit.java new file mode 100644 index 00000000000..c2a37a0ff2e --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YueTheMoonSpirit.java @@ -0,0 +1,61 @@ +package mage.cards.y; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.effects.common.cost.CastFromHandForFreeEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class YueTheMoonSpirit extends CardImpl { + + private static final FilterCard filter = new FilterCard("a noncreature spell"); + + static { + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + } + + public YueTheMoonSpirit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Waterbend {5}, {T}: You may cast a noncreature spell from your hand without paying its mana cost. + Ability ability = new SimpleActivatedAbility(new CastFromHandForFreeEffect(filter), new WaterbendCost(5)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private YueTheMoonSpirit(final YueTheMoonSpirit card) { + super(card); + } + + @Override + public YueTheMoonSpirit copy() { + return new YueTheMoonSpirit(this); + } +} diff --git a/Mage.Sets/src/mage/cards/y/YunasDecision.java b/Mage.Sets/src/mage/cards/y/YunasDecision.java index a65e84c1b05..c8b11ef013c 100644 --- a/Mage.Sets/src/mage/cards/y/YunasDecision.java +++ b/Mage.Sets/src/mage/cards/y/YunasDecision.java @@ -127,7 +127,7 @@ class YunasDecisionTarget extends TargetCardInHand { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/y/YuyanArchers.java b/Mage.Sets/src/mage/cards/y/YuyanArchers.java new file mode 100644 index 00000000000..ef4bb7af854 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YuyanArchers.java @@ -0,0 +1,46 @@ +package mage.cards.y; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.ReachAbility; +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 YuyanArchers extends CardImpl { + + public YuyanArchers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARCHER); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // When this creature enters, you may discard a card. If you do, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new DiscardCardCost()) + )); + } + + private YuyanArchers(final YuyanArchers card) { + super(card); + } + + @Override + public YuyanArchers copy() { + return new YuyanArchers(this); + } +} diff --git a/Mage.Sets/src/mage/cards/z/ZamWesell.java b/Mage.Sets/src/mage/cards/z/ZamWesell.java index 297c533e777..e2c5ad1166d 100644 --- a/Mage.Sets/src/mage/cards/z/ZamWesell.java +++ b/Mage.Sets/src/mage/cards/z/ZamWesell.java @@ -74,7 +74,7 @@ class ZamWesselEffect extends OneShotEffect { if (targetPlayer != null && you != null) { if (!targetPlayer.getHand().isEmpty()) { TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_CREATURE); - if (you.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { + if (you.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { ContinuousEffect copyEffect = new CopyEffect(Duration.EndOfGame, card.getMainCard(), source.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java b/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java index 31d9612fb30..b1abd5885c2 100644 --- a/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java +++ b/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java @@ -18,6 +18,7 @@ import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.TargetCard; import mage.target.common.TargetCardInHand; import mage.target.common.TargetPlayerOrPlaneswalker; import mage.target.targetpointer.FixedTarget; @@ -83,10 +84,8 @@ class ZaraRenegadeRecruiterEffect extends OneShotEffect { if (controller == null || player == null || player.getHand().isEmpty()) { return false; } - TargetCardInHand targetCard = new TargetCardInHand( - 0, 1, StaticFilters.FILTER_CARD_CREATURE - ); - controller.choose(outcome, player.getHand(), targetCard, source, game); + TargetCard targetCard = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_CREATURE); + controller.choose(Outcome.Detriment, player.getHand(), targetCard, source, game); Card card = game.getCard(targetCard.getFirstTarget()); if (card == null) { return false; diff --git a/Mage.Sets/src/mage/cards/z/ZhaoTheSeethingFlame.java b/Mage.Sets/src/mage/cards/z/ZhaoTheSeethingFlame.java new file mode 100644 index 00000000000..e9978822ea8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZhaoTheSeethingFlame.java @@ -0,0 +1,39 @@ +package mage.cards.z; + +import mage.MageInt; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ZhaoTheSeethingFlame extends CardImpl { + + public ZhaoTheSeethingFlame(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Menace + this.addAbility(new MenaceAbility()); + } + + private ZhaoTheSeethingFlame(final ZhaoTheSeethingFlame card) { + super(card); + } + + @Override + public ZhaoTheSeethingFlame copy() { + return new ZhaoTheSeethingFlame(this); + } +} diff --git a/Mage.Sets/src/mage/cards/z/ZodiarkUmbralGod.java b/Mage.Sets/src/mage/cards/z/ZodiarkUmbralGod.java index e3e13d4075f..c684fa0655d 100644 --- a/Mage.Sets/src/mage/cards/z/ZodiarkUmbralGod.java +++ b/Mage.Sets/src/mage/cards/z/ZodiarkUmbralGod.java @@ -14,7 +14,7 @@ import mage.constants.*; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.CanBeSacrificedPredicate; @@ -66,8 +66,8 @@ public final class ZodiarkUmbralGod extends CardImpl { class ZodiarkUmbralGodEffect extends OneShotEffect { - private static final FilterPermanent filter = new FilterControlledPermanent("non-God creatures you control"); - private static final FilterPermanent filter2 = new FilterControlledPermanent("non-God creatures you control"); + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("non-God creatures you control"); + private static final FilterPermanent filter2 = new FilterControlledCreaturePermanent("non-God creatures you control"); private static final Predicate predicate = Predicates.not(SubType.GOD.getPredicate()); static { diff --git a/Mage.Sets/src/mage/cards/z/ZukoAvatarHunter.java b/Mage.Sets/src/mage/cards/z/ZukoAvatarHunter.java new file mode 100644 index 00000000000..a8867db8960 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZukoAvatarHunter.java @@ -0,0 +1,56 @@ +package mage.cards.z; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.permanent.token.SoldierRedToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ZukoAvatarHunter extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a red spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.RED)); + } + + public ZukoAvatarHunter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever you cast a red spell, create a 2/2 red Soldier creature token. + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new SoldierRedToken()), filter, false + )); + } + + private ZukoAvatarHunter(final ZukoAvatarHunter card) { + super(card); + } + + @Override + public ZukoAvatarHunter copy() { + return new ZukoAvatarHunter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/z/ZukoExiledPrince.java b/Mage.Sets/src/mage/cards/z/ZukoExiledPrince.java new file mode 100644 index 00000000000..e6c4e755d54 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZukoExiledPrince.java @@ -0,0 +1,48 @@ +package mage.cards.z; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.FirebendingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ZukoExiledPrince extends CardImpl { + + public ZukoExiledPrince(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Firebending 3 + this.addAbility(new FirebendingAbility(3)); + + // {3}: Exile the top card of your library. You may play that card this turn. + this.addAbility(new SimpleActivatedAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn), new GenericManaCost(3) + )); + } + + private ZukoExiledPrince(final ZukoExiledPrince card) { + super(card); + } + + @Override + public ZukoExiledPrince copy() { + return new ZukoExiledPrince(this); + } +} diff --git a/Mage.Sets/src/mage/cards/z/ZukosOffense.java b/Mage.Sets/src/mage/cards/z/ZukosOffense.java new file mode 100644 index 00000000000..581a4ce5fd2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZukosOffense.java @@ -0,0 +1,32 @@ +package mage.cards.z; + +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ZukosOffense extends CardImpl { + + public ZukosOffense(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}"); + + // Zuko's Offense deals 2 damage to any target. + this.getSpellAbility().addEffect(new DamageTargetEffect(2)); + this.getSpellAbility().addTarget(new TargetAnyTarget()); + } + + private ZukosOffense(final ZukosOffense card) { + super(card); + } + + @Override + public ZukosOffense copy() { + return new ZukosOffense(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Aetherdrift.java b/Mage.Sets/src/mage/sets/Aetherdrift.java index 2ee9386e3c4..18b97dd34f8 100644 --- a/Mage.Sets/src/mage/sets/Aetherdrift.java +++ b/Mage.Sets/src/mage/sets/Aetherdrift.java @@ -184,12 +184,12 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Fearless Swashbuckler", 481, Rarity.RARE, mage.cards.f.FearlessSwashbuckler.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fearless Swashbuckler", 545, Rarity.RARE, mage.cards.f.FearlessSwashbuckler.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Flood the Engine", 42, Rarity.COMMON, mage.cards.f.FloodTheEngine.class)); - cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forest", 289, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 290, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 291, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 511, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 516, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 511, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Forest", 516, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Foul Roads", 255, Rarity.UNCOMMON, mage.cards.f.FoulRoads.class)); cards.add(new SetCardInfo("Fuel the Flames", 126, Rarity.UNCOMMON, mage.cards.f.FuelTheFlames.class)); cards.add(new SetCardInfo("Full Throttle", 127, Rarity.RARE, mage.cards.f.FullThrottle.class, NON_FULL_USE_VARIOUS)); @@ -248,12 +248,12 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Hulldrifter", 300, Rarity.COMMON, mage.cards.h.Hulldrifter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Interface Ace", 17, Rarity.COMMON, mage.cards.i.InterfaceAce.class)); cards.add(new SetCardInfo("Intimidation Tactics", 92, Rarity.UNCOMMON, mage.cards.i.IntimidationTactics.class)); - cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Island", 280, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 281, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 282, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 508, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 513, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 508, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Island", 513, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Jibbirik Omnivore", 166, Rarity.COMMON, mage.cards.j.JibbirikOmnivore.class)); cards.add(new SetCardInfo("Jungle Hollow", 256, Rarity.COMMON, mage.cards.j.JungleHollow.class)); cards.add(new SetCardInfo("Kalakscion, Hunger Tyrant", 93, Rarity.UNCOMMON, mage.cards.k.KalakscionHungerTyrant.class)); @@ -324,12 +324,12 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Monument to Endurance", 237, Rarity.RARE, mage.cards.m.MonumentToEndurance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Monument to Endurance", 394, Rarity.RARE, mage.cards.m.MonumentToEndurance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Monument to Endurance", 499, Rarity.RARE, mage.cards.m.MonumentToEndurance.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 275, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 275, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Mountain", 286, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 287, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 288, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 510, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 515, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 510, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 515, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Mu Yanling, Wind Rider", 52, Rarity.MYTHIC, mage.cards.m.MuYanlingWindRider.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mu Yanling, Wind Rider", 379, Rarity.MYTHIC, mage.cards.m.MuYanlingWindRider.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mu Yanling, Wind Rider", 399, Rarity.MYTHIC, mage.cards.m.MuYanlingWindRider.class, NON_FULL_USE_VARIOUS)); @@ -359,12 +359,12 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Perilous Snare", 377, Rarity.RARE, mage.cards.p.PerilousSnare.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Perilous Snare", 430, Rarity.RARE, mage.cards.p.PerilousSnare.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pit Automaton", 238, Rarity.UNCOMMON, mage.cards.p.PitAutomaton.class)); - cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Plains", 277, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 278, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 279, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 507, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 512, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 507, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Plains", 512, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Plow Through", 174, Rarity.UNCOMMON, mage.cards.p.PlowThrough.class)); cards.add(new SetCardInfo("Point the Way", 175, Rarity.UNCOMMON, mage.cards.p.PointTheWay.class)); cards.add(new SetCardInfo("Possession Engine", 54, Rarity.RARE, mage.cards.p.PossessionEngine.class, NON_FULL_USE_VARIOUS)); @@ -484,12 +484,12 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Sunbillow Verge", 373, Rarity.RARE, mage.cards.s.SunbillowVerge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sunbillow Verge", 504, Rarity.RARE, mage.cards.s.SunbillowVerge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sundial, Dawn Tyrant", 31, Rarity.UNCOMMON, mage.cards.s.SundialDawnTyrant.class)); - cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Swamp", 283, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 284, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 285, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 509, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 514, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 509, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 514, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Swiftwater Cliffs", 265, Rarity.COMMON, mage.cards.s.SwiftwaterCliffs.class)); cards.add(new SetCardInfo("Swiftwing Assailant", 32, Rarity.COMMON, mage.cards.s.SwiftwingAssailant.class)); cards.add(new SetCardInfo("Syphon Fuel", 108, Rarity.COMMON, mage.cards.s.SyphonFuel.class)); diff --git a/Mage.Sets/src/mage/sets/AmonkhetRemastered.java b/Mage.Sets/src/mage/sets/AmonkhetRemastered.java index 0eb262a06d4..ad31187ba92 100644 --- a/Mage.Sets/src/mage/sets/AmonkhetRemastered.java +++ b/Mage.Sets/src/mage/sets/AmonkhetRemastered.java @@ -124,9 +124,9 @@ public class AmonkhetRemastered extends ExpansionSet { cards.add(new SetCardInfo("Forest", 294, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 295, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 296, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 297, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 297, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forest", 298, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 299, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 299, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forest", 300, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forsake the Worldly", 18, Rarity.COMMON, mage.cards.f.ForsakeTheWorldly.class)); cards.add(new SetCardInfo("Gate to the Afterlife", 271, Rarity.UNCOMMON, mage.cards.g.GateToTheAfterlife.class)); @@ -170,9 +170,9 @@ public class AmonkhetRemastered extends ExpansionSet { cards.add(new SetCardInfo("Island", 305, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 306, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 307, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 308, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 308, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Island", 309, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 310, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 310, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Island", 311, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Jace, Unraveler of Secrets", 65, Rarity.MYTHIC, mage.cards.j.JaceUnravelerOfSecrets.class)); cards.add(new SetCardInfo("Kefnet the Mindful", 66, Rarity.MYTHIC, mage.cards.k.KefnetTheMindful.class)); @@ -202,9 +202,9 @@ public class AmonkhetRemastered extends ExpansionSet { cards.add(new SetCardInfo("Mountain", 312, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 313, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 314, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 315, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 315, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Mountain", 316, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 317, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 317, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Mountain", 318, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mouth // Feed", 202, Rarity.RARE, mage.cards.m.MouthFeed.class)); cards.add(new SetCardInfo("Naga Oracle", 69, Rarity.COMMON, mage.cards.n.NagaOracle.class)); @@ -238,9 +238,9 @@ public class AmonkhetRemastered extends ExpansionSet { cards.add(new SetCardInfo("Plains", 319, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 320, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 321, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 322, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 322, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Plains", 323, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 324, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 324, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Plains", 325, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pouncing Cheetah", 207, Rarity.COMMON, mage.cards.p.PouncingCheetah.class)); cards.add(new SetCardInfo("Prepare // Fight", 251, Rarity.RARE, mage.cards.p.PrepareFight.class)); @@ -313,9 +313,9 @@ public class AmonkhetRemastered extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 332, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 333, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 334, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 335, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 335, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Swamp", 336, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 337, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 337, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Swamp", 338, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sweltering Suns", 176, Rarity.RARE, mage.cards.s.SwelteringSuns.class)); cards.add(new SetCardInfo("Synchronized Strike", 222, Rarity.UNCOMMON, mage.cards.s.SynchronizedStrike.class)); diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 4cfe962a30a..10881e59c8a 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -1,13 +1,18 @@ package mage.sets; 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 AvatarTheLastAirbender extends ExpansionSet { + private static final List unfinished = Arrays.asList("Aang's Iceberg", "Avatar Aang", "Aang, Master of Elements", "Flexible Waterbender", "Geyser Leaper", "Giant Koi", "Katara, Bending Prodigy", "Katara, Water Tribe's Hope", "Waterbending Lesson", "Watery Grasp", "Yue, the Moon Spirit"); private static final AvatarTheLastAirbender instance = new AvatarTheLastAirbender(); public static AvatarTheLastAirbender getInstance() { @@ -18,6 +23,123 @@ public final class AvatarTheLastAirbender extends ExpansionSet { super("Avatar: The Last Airbender", "TLA", ExpansionSet.buildDate(2025, 11, 21), SetType.EXPANSION); this.blockName = "Avatar: The Last Airbender"; // for sorting in GUI this.rotationSet = true; - this.hasBasicLands = false; // temporary + this.hasBasicLands = true; + + cards.add(new SetCardInfo("Aang's Iceberg", 336, Rarity.RARE, mage.cards.a.AangsIceberg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang's Iceberg", 5, Rarity.RARE, mage.cards.a.AangsIceberg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang's Journey", 1, Rarity.COMMON, mage.cards.a.AangsJourney.class)); + cards.add(new SetCardInfo("Aang, Master of Elements", 207, Rarity.MYTHIC, mage.cards.a.AangMasterOfElements.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang, Master of Elements", 363, Rarity.MYTHIC, mage.cards.a.AangMasterOfElements.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang, the Last Airbender", 4, Rarity.UNCOMMON, mage.cards.a.AangTheLastAirbender.class)); + cards.add(new SetCardInfo("Abandon Attachments", 205, Rarity.COMMON, mage.cards.a.AbandonAttachments.class)); + cards.add(new SetCardInfo("Airbending Lesson", 8, Rarity.COMMON, mage.cards.a.AirbendingLesson.class)); + cards.add(new SetCardInfo("Appa, Steadfast Guardian", 10, Rarity.MYTHIC, mage.cards.a.AppaSteadfastGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Appa, Steadfast Guardian", 316, Rarity.MYTHIC, mage.cards.a.AppaSteadfastGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avatar Aang", 207, Rarity.MYTHIC, mage.cards.a.AvatarAang.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avatar Aang", 363, Rarity.MYTHIC, mage.cards.a.AvatarAang.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avatar Enthusiasts", 11, Rarity.COMMON, mage.cards.a.AvatarEnthusiasts.class)); + cards.add(new SetCardInfo("Azula Always Lies", 84, Rarity.COMMON, mage.cards.a.AzulaAlwaysLies.class)); + cards.add(new SetCardInfo("Badgermole", 166, Rarity.COMMON, mage.cards.b.Badgermole.class)); + cards.add(new SetCardInfo("Barrels of Blasting Jelly", 254, Rarity.COMMON, mage.cards.b.BarrelsOfBlastingJelly.class)); + cards.add(new SetCardInfo("Beetle-Headed Merchants", 86, Rarity.COMMON, mage.cards.b.BeetleHeadedMerchants.class)); + cards.add(new SetCardInfo("Bender's Waterskin", 255, Rarity.COMMON, mage.cards.b.BendersWaterskin.class)); + cards.add(new SetCardInfo("Buzzard-Wasp Colony", 88, Rarity.UNCOMMON, mage.cards.b.BuzzardWaspColony.class)); + cards.add(new SetCardInfo("Cat-Gator", 91, Rarity.UNCOMMON, mage.cards.c.CatGator.class)); + cards.add(new SetCardInfo("Cat-Owl", 212, Rarity.COMMON, mage.cards.c.CatOwl.class)); + cards.add(new SetCardInfo("Corrupt Court Official", 92, Rarity.COMMON, mage.cards.c.CorruptCourtOfficial.class)); + cards.add(new SetCardInfo("Dai Li Indoctrination", 93, Rarity.COMMON, mage.cards.d.DaiLiIndoctrination.class)); + cards.add(new SetCardInfo("Deserter's Disciple", 131, Rarity.COMMON, mage.cards.d.DesertersDisciple.class)); + cards.add(new SetCardInfo("Earth Kingdom Soldier", 216, Rarity.COMMON, mage.cards.e.EarthKingdomSoldier.class)); + cards.add(new SetCardInfo("Earth Rumble", 174, Rarity.UNCOMMON, mage.cards.e.EarthRumble.class)); + cards.add(new SetCardInfo("Earth Village Ruffians", 219, Rarity.COMMON, mage.cards.e.EarthVillageRuffians.class)); + cards.add(new SetCardInfo("Earthbending Lesson", 176, Rarity.COMMON, mage.cards.e.EarthbendingLesson.class)); + cards.add(new SetCardInfo("Epic Downfall", 96, Rarity.UNCOMMON, mage.cards.e.EpicDownfall.class)); + cards.add(new SetCardInfo("Fated Firepower", 132, Rarity.MYTHIC, mage.cards.f.FatedFirepower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fated Firepower", 341, Rarity.MYTHIC, mage.cards.f.FatedFirepower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Lord Sozin", 117, Rarity.MYTHIC, mage.cards.f.FireLordSozin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Lord Sozin", 356, Rarity.MYTHIC, mage.cards.f.FireLordSozin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Lord Zuko", 221, Rarity.RARE, mage.cards.f.FireLordZuko.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Lord Zuko", 360, Rarity.RARE, mage.cards.f.FireLordZuko.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Nation Attacks", 133, Rarity.UNCOMMON, mage.cards.f.FireNationAttacks.class)); + cards.add(new SetCardInfo("Fire Nation Engineer", 99, Rarity.UNCOMMON, mage.cards.f.FireNationEngineer.class)); + cards.add(new SetCardInfo("Fire Sages", 136, Rarity.UNCOMMON, mage.cards.f.FireSages.class)); + cards.add(new SetCardInfo("First-Time Flyer", 49, Rarity.COMMON, mage.cards.f.FirstTimeFlyer.class)); + cards.add(new SetCardInfo("Flexible Waterbender", 50, Rarity.COMMON, mage.cards.f.FlexibleWaterbender.class)); + cards.add(new SetCardInfo("Flopsie, Bumi's Buddy", 179, Rarity.UNCOMMON, mage.cards.f.FlopsieBumisBuddy.class)); + cards.add(new SetCardInfo("Forest", 286, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 291, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Geyser Leaper", 52, Rarity.COMMON, mage.cards.g.GeyserLeaper.class)); + cards.add(new SetCardInfo("Giant Koi", 53, Rarity.COMMON, mage.cards.g.GiantKoi.class)); + cards.add(new SetCardInfo("Glider Kids", 21, Rarity.COMMON, mage.cards.g.GliderKids.class)); + cards.add(new SetCardInfo("Hakoda, Selfless Commander", 366, Rarity.RARE, mage.cards.h.HakodaSelflessCommander.class)); + cards.add(new SetCardInfo("Haru, Hidden Talent", 182, Rarity.UNCOMMON, mage.cards.h.HaruHiddenTalent.class)); + cards.add(new SetCardInfo("Heartless Act", 103, Rarity.UNCOMMON, mage.cards.h.HeartlessAct.class)); + cards.add(new SetCardInfo("Hei Bai, Spirit of Balance", 225, Rarity.UNCOMMON, mage.cards.h.HeiBaiSpiritOfBalance.class)); + cards.add(new SetCardInfo("Hog-Monkey", 104, Rarity.COMMON, mage.cards.h.HogMonkey.class)); + cards.add(new SetCardInfo("How to Start a Riot", 140, Rarity.COMMON, mage.cards.h.HowToStartARiot.class)); + cards.add(new SetCardInfo("Iguana Parrot", 56, Rarity.COMMON, mage.cards.i.IguanaParrot.class)); + cards.add(new SetCardInfo("Island", 283, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 288, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("It'll Quench Ya!", 58, Rarity.COMMON, mage.cards.i.ItllQuenchYa.class)); + cards.add(new SetCardInfo("Jeong Jeong's Deserters", 25, Rarity.COMMON, mage.cards.j.JeongJeongsDeserters.class)); + cards.add(new SetCardInfo("Katara, Bending Prodigy", 59, Rarity.UNCOMMON, mage.cards.k.KataraBendingProdigy.class)); + cards.add(new SetCardInfo("Katara, Water Tribe's Hope", 231, Rarity.RARE, mage.cards.k.KataraWaterTribesHope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Katara, Water Tribe's Hope", 351, Rarity.RARE, mage.cards.k.KataraWaterTribesHope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Katara, the Fearless", 230, Rarity.RARE, mage.cards.k.KataraTheFearless.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Katara, the Fearless", 350, Rarity.RARE, mage.cards.k.KataraTheFearless.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Katara, the Fearless", 361, Rarity.RARE, mage.cards.k.KataraTheFearless.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lightning Strike", 146, Rarity.COMMON, mage.cards.l.LightningStrike.class)); + cards.add(new SetCardInfo("Long Feng, Grand Secretariat", 233, Rarity.UNCOMMON, mage.cards.l.LongFengGrandSecretariat.class)); + cards.add(new SetCardInfo("Master Pakku", 63, Rarity.UNCOMMON, mage.cards.m.MasterPakku.class)); + cards.add(new SetCardInfo("Master Piandao", 28, Rarity.UNCOMMON, mage.cards.m.MasterPiandao.class)); + cards.add(new SetCardInfo("Merchant of Many Hats", 110, Rarity.COMMON, mage.cards.m.MerchantOfManyHats.class)); + cards.add(new SetCardInfo("Momo, Friendly Flier", 29, Rarity.RARE, mage.cards.m.MomoFriendlyFlier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Momo, Friendly Flier", 317, Rarity.RARE, mage.cards.m.MomoFriendlyFlier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mongoose Lizard", 148, Rarity.COMMON, mage.cards.m.MongooseLizard.class)); + cards.add(new SetCardInfo("Mountain", 285, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 290, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Ostrich-Horse", 188, Rarity.COMMON, mage.cards.o.OstrichHorse.class)); + cards.add(new SetCardInfo("Otter-Penguin", 67, Rarity.COMMON, mage.cards.o.OtterPenguin.class)); + cards.add(new SetCardInfo("Ozai's Cruelty", 113, Rarity.UNCOMMON, mage.cards.o.OzaisCruelty.class)); + cards.add(new SetCardInfo("Path to Redemption", 31, Rarity.COMMON, mage.cards.p.PathToRedemption.class)); + cards.add(new SetCardInfo("Pillar Launch", 189, Rarity.COMMON, mage.cards.p.PillarLaunch.class)); + cards.add(new SetCardInfo("Plains", 282, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 287, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Pretending Poxbearers", 237, Rarity.COMMON, mage.cards.p.PretendingPoxbearers.class)); + cards.add(new SetCardInfo("Rabaroo Troop", 32, Rarity.COMMON, mage.cards.r.RabarooTroop.class)); + cards.add(new SetCardInfo("Raucous Audience", 190, Rarity.COMMON, mage.cards.r.RaucousAudience.class)); + cards.add(new SetCardInfo("Razor Rings", 33, Rarity.COMMON, mage.cards.r.RazorRings.class)); + cards.add(new SetCardInfo("Rebellious Captives", 191, Rarity.COMMON, mage.cards.r.RebelliousCaptives.class)); + cards.add(new SetCardInfo("Redirect Lightning", 151, Rarity.RARE, mage.cards.r.RedirectLightning.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Redirect Lightning", 343, Rarity.RARE, mage.cards.r.RedirectLightning.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rocky Rebuke", 193, Rarity.COMMON, mage.cards.r.RockyRebuke.class)); + cards.add(new SetCardInfo("Rough Rhino Cavalry", 152, Rarity.COMMON, mage.cards.r.RoughRhinoCavalry.class)); + cards.add(new SetCardInfo("Rowdy Snowballers", 68, Rarity.COMMON, mage.cards.r.RowdySnowballers.class)); + cards.add(new SetCardInfo("Saber-Tooth Moose-Lion", 194, Rarity.COMMON, mage.cards.s.SaberToothMooseLion.class)); + cards.add(new SetCardInfo("Serpent of the Pass", 70, Rarity.UNCOMMON, mage.cards.s.SerpentOfThePass.class)); + cards.add(new SetCardInfo("Sokka's Haiku", 71, Rarity.UNCOMMON, mage.cards.s.SokkasHaiku.class)); + cards.add(new SetCardInfo("Sokka, Bold Boomeranger", 240, Rarity.RARE, mage.cards.s.SokkaBoldBoomeranger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sokka, Bold Boomeranger", 383, Rarity.RARE, mage.cards.s.SokkaBoldBoomeranger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sokka, Lateral Strategist", 241, Rarity.UNCOMMON, mage.cards.s.SokkaLateralStrategist.class)); + cards.add(new SetCardInfo("Southern Air Temple", 36, Rarity.UNCOMMON, mage.cards.s.SouthernAirTemple.class)); + cards.add(new SetCardInfo("Suki, Kyoshi Warrior", 243, Rarity.UNCOMMON, mage.cards.s.SukiKyoshiWarrior.class)); + cards.add(new SetCardInfo("Swamp", 284, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 289, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("The Rise of Sozin", 117, Rarity.MYTHIC, mage.cards.t.TheRiseOfSozin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Rise of Sozin", 356, Rarity.MYTHIC, mage.cards.t.TheRiseOfSozin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Toph, the Blind Bandit", 198, Rarity.UNCOMMON, mage.cards.t.TophTheBlindBandit.class)); + cards.add(new SetCardInfo("Toph, the First Metalbender", 247, Rarity.RARE, mage.cards.t.TophTheFirstMetalbender.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Toph, the First Metalbender", 353, Rarity.RARE, mage.cards.t.TophTheFirstMetalbender.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Toph, the First Metalbender", 362, Rarity.RARE, mage.cards.t.TophTheFirstMetalbender.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Turtle-Duck", 200, Rarity.COMMON, mage.cards.t.TurtleDuck.class)); + cards.add(new SetCardInfo("Vindictive Warden", 249, Rarity.COMMON, mage.cards.v.VindictiveWarden.class)); + cards.add(new SetCardInfo("Waterbending Lesson", 80, Rarity.COMMON, mage.cards.w.WaterbendingLesson.class)); + cards.add(new SetCardInfo("Watery Grasp", 82, Rarity.COMMON, mage.cards.w.WateryGrasp.class)); + cards.add(new SetCardInfo("Yue, the Moon Spirit", 338, Rarity.RARE, mage.cards.y.YueTheMoonSpirit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yue, the Moon Spirit", 83, Rarity.RARE, mage.cards.y.YueTheMoonSpirit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yuyan Archers", 161, Rarity.COMMON, mage.cards.y.YuyanArchers.class)); + cards.add(new SetCardInfo("Zuko, Exiled Prince", 163, Rarity.UNCOMMON, mage.cards.z.ZukoExiledPrince.class)); + + cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); } } diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java new file mode 100644 index 00000000000..a1b357b008b --- /dev/null +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -0,0 +1,131 @@ +package mage.sets; + +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 AvatarTheLastAirbenderEternal extends ExpansionSet { + + private static final List unfinished = Arrays.asList("Water Whip"); + + private static final AvatarTheLastAirbenderEternal instance = new AvatarTheLastAirbenderEternal(); + + public static AvatarTheLastAirbenderEternal getInstance() { + return instance; + } + + private AvatarTheLastAirbenderEternal() { + super("Avatar: The Last Airbender Eternal", "TLE", ExpansionSet.buildDate(2025, 11, 21), SetType.SUPPLEMENTAL); + this.blockName = "Avatar: The Last Airbender"; // for sorting in GUI + this.rotationSet = true; + this.hasBasicLands = true; + + cards.add(new SetCardInfo("Aang's Defense", 211, Rarity.COMMON, mage.cards.a.AangsDefense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang's Defense", 266, Rarity.COMMON, mage.cards.a.AangsDefense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang, Air Nomad", 210, Rarity.RARE, mage.cards.a.AangAirNomad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang, Air Nomad", 265, Rarity.RARE, mage.cards.a.AangAirNomad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang, Airbending Master", 74, Rarity.MYTHIC, mage.cards.a.AangAirbendingMaster.class)); + cards.add(new SetCardInfo("Aardvark Sloth", 212, Rarity.COMMON, mage.cards.a.AardvarkSloth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aardvark Sloth", 267, Rarity.COMMON, mage.cards.a.AardvarkSloth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Allied Teamwork", 213, Rarity.RARE, mage.cards.a.AlliedTeamwork.class)); + cards.add(new SetCardInfo("Appa, Aang's Companion", 214, Rarity.UNCOMMON, mage.cards.a.AppaAangsCompanion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Appa, Aang's Companion", 268, Rarity.UNCOMMON, mage.cards.a.AppaAangsCompanion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bumi, Eclectic Earthbender", 248, Rarity.RARE, mage.cards.b.BumiEclecticEarthbender.class)); + cards.add(new SetCardInfo("Capital Guard", 234, Rarity.COMMON, mage.cards.c.CapitalGuard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Capital Guard", 277, Rarity.COMMON, mage.cards.c.CapitalGuard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deny Entry", 222, Rarity.COMMON, mage.cards.d.DenyEntry.class)); + cards.add(new SetCardInfo("Dragon Moose", 235, Rarity.COMMON, mage.cards.d.DragonMoose.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dragon Moose", 278, Rarity.COMMON, mage.cards.d.DragonMoose.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Earthbending Student", 249, Rarity.UNCOMMON, mage.cards.e.EarthbendingStudent.class)); + cards.add(new SetCardInfo("Eel-Hounds", 250, Rarity.UNCOMMON, mage.cards.e.EelHounds.class)); + cards.add(new SetCardInfo("Elephant-Rat", 228, Rarity.COMMON, mage.cards.e.ElephantRat.class)); + cards.add(new SetCardInfo("Explore", 259, Rarity.COMMON, mage.cards.e.Explore.class)); + cards.add(new SetCardInfo("Explosive Shot", 236, Rarity.COMMON, mage.cards.e.ExplosiveShot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Explosive Shot", 279, Rarity.COMMON, mage.cards.e.ExplosiveShot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Feed the Swarm", 257, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); + cards.add(new SetCardInfo("Fire Nation Ambushers", 229, Rarity.COMMON, mage.cards.f.FireNationAmbushers.class)); + cards.add(new SetCardInfo("Fire Nation Archers", 237, Rarity.RARE, mage.cards.f.FireNationArchers.class)); + cards.add(new SetCardInfo("Fire Nation Sentinels", 230, Rarity.RARE, mage.cards.f.FireNationSentinels.class)); + cards.add(new SetCardInfo("Fire Nation Soldier", 238, Rarity.COMMON, mage.cards.f.FireNationSoldier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Nation Soldier", 280, Rarity.COMMON, mage.cards.f.FireNationSoldier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Nation's Conquest", 239, Rarity.UNCOMMON, mage.cards.f.FireNationsConquest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Nation's Conquest", 281, Rarity.UNCOMMON, mage.cards.f.FireNationsConquest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Flying Dolphin-Fish", 223, Rarity.COMMON, mage.cards.f.FlyingDolphinFish.class)); + cards.add(new SetCardInfo("Force of Negation", 13, Rarity.MYTHIC, mage.cards.f.ForceOfNegation.class)); + cards.add(new SetCardInfo("Frog-Squirrels", 251, Rarity.COMMON, mage.cards.f.FrogSquirrels.class)); + cards.add(new SetCardInfo("Gilacorn", 231, Rarity.COMMON, mage.cards.g.Gilacorn.class)); + cards.add(new SetCardInfo("Hippo-Cows", 252, Rarity.COMMON, mage.cards.h.HippoCows.class)); + cards.add(new SetCardInfo("Iroh, Firebending Instructor", 240, Rarity.UNCOMMON, mage.cards.i.IrohFirebendingInstructor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Iroh, Firebending Instructor", 282, Rarity.UNCOMMON, mage.cards.i.IrohFirebendingInstructor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Katara, Heroic Healer", 215, Rarity.UNCOMMON, mage.cards.k.KataraHeroicHealer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Katara, Heroic Healer", 269, Rarity.UNCOMMON, mage.cards.k.KataraHeroicHealer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Katara, Waterbending Master", 93, Rarity.MYTHIC, mage.cards.k.KataraWaterbendingMaster.class)); + cards.add(new SetCardInfo("Komodo Rhino", 241, Rarity.COMMON, mage.cards.k.KomodoRhino.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Komodo Rhino", 283, Rarity.COMMON, mage.cards.k.KomodoRhino.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kyoshi Warrior Guard", 216, Rarity.COMMON, mage.cards.k.KyoshiWarriorGuard.class)); + cards.add(new SetCardInfo("Lion Vulture", 232, Rarity.RARE, mage.cards.l.LionVulture.class)); + cards.add(new SetCardInfo("Lost in the Spirit World", 224, Rarity.UNCOMMON, mage.cards.l.LostInTheSpiritWorld.class)); + cards.add(new SetCardInfo("Loyal Fire Sage", 242, Rarity.UNCOMMON, mage.cards.l.LoyalFireSage.class)); + cards.add(new SetCardInfo("Match the Odds", 253, Rarity.UNCOMMON, mage.cards.m.MatchTheOdds.class)); + cards.add(new SetCardInfo("Mechanical Glider", 256, Rarity.COMMON, mage.cards.m.MechanicalGlider.class)); + cards.add(new SetCardInfo("Momo, Rambunctious Rascal", 217, Rarity.UNCOMMON, mage.cards.m.MomoRambunctiousRascal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Momo, Rambunctious Rascal", 270, Rarity.UNCOMMON, mage.cards.m.MomoRambunctiousRascal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 289, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 290, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 291, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 292, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 293, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 294, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 295, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 296, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Path to Redemption", 271, Rarity.COMMON, mage.cards.p.PathToRedemption.class)); + cards.add(new SetCardInfo("Plains", 297, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 298, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 299, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 300, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 301, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 302, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 303, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 304, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Purple Pentapus", 233, Rarity.COMMON, mage.cards.p.PurplePentapus.class)); + cards.add(new SetCardInfo("Razor Rings", 272, Rarity.COMMON, mage.cards.r.RazorRings.class)); + cards.add(new SetCardInfo("Roku's Mastery", 243, Rarity.UNCOMMON, mage.cards.r.RokusMastery.class)); + cards.add(new SetCardInfo("Run Amok", 258, Rarity.COMMON, mage.cards.r.RunAmok.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Run Amok", 284, Rarity.COMMON, mage.cards.r.RunAmok.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seismic Tutelage", 254, Rarity.RARE, mage.cards.s.SeismicTutelage.class)); + cards.add(new SetCardInfo("Sledding Otter-Penguin", 218, Rarity.COMMON, mage.cards.s.SleddingOtterPenguin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sledding Otter-Penguin", 273, Rarity.COMMON, mage.cards.s.SleddingOtterPenguin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sokka, Wolf Cove's Protector", 219, Rarity.UNCOMMON, mage.cards.s.SokkaWolfCovesProtector.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sokka, Wolf Cove's Protector", 274, Rarity.UNCOMMON, mage.cards.s.SokkaWolfCovesProtector.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Cabbage Merchant", 134, Rarity.RARE, mage.cards.t.TheCabbageMerchant.class)); + cards.add(new SetCardInfo("The Great Henge", 41, Rarity.MYTHIC, mage.cards.t.TheGreatHenge.class)); + cards.add(new SetCardInfo("The Terror of Serpent's Pass", 225, Rarity.RARE, mage.cards.t.TheTerrorOfSerpentsPass.class)); + cards.add(new SetCardInfo("Thriving Bluff", 260, Rarity.COMMON, mage.cards.t.ThrivingBluff.class)); + cards.add(new SetCardInfo("Thriving Grove", 261, Rarity.COMMON, mage.cards.t.ThrivingGrove.class)); + cards.add(new SetCardInfo("Thriving Heath", 262, Rarity.COMMON, mage.cards.t.ThrivingHeath.class)); + cards.add(new SetCardInfo("Thriving Isle", 263, Rarity.COMMON, mage.cards.t.ThrivingIsle.class)); + cards.add(new SetCardInfo("Thriving Moor", 264, Rarity.COMMON, mage.cards.t.ThrivingMoor.class)); + cards.add(new SetCardInfo("Tundra Wall", 220, Rarity.COMMON, mage.cards.t.TundraWall.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tundra Wall", 275, Rarity.COMMON, mage.cards.t.TundraWall.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Turtle-Seals", 226, Rarity.COMMON, mage.cards.t.TurtleSeals.class)); + cards.add(new SetCardInfo("Warship Scout", 244, Rarity.COMMON, mage.cards.w.WarshipScout.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Warship Scout", 285, Rarity.COMMON, mage.cards.w.WarshipScout.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Water Whip", 227, Rarity.RARE, mage.cards.w.WaterWhip.class)); + cards.add(new SetCardInfo("Wolf Cove Villager", 221, Rarity.COMMON, mage.cards.w.WolfCoveVillager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wolf Cove Villager", 276, Rarity.COMMON, mage.cards.w.WolfCoveVillager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zhao, the Seething Flame", 245, Rarity.UNCOMMON, mage.cards.z.ZhaoTheSeethingFlame.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zhao, the Seething Flame", 286, Rarity.UNCOMMON, mage.cards.z.ZhaoTheSeethingFlame.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zuko's Offense", 247, Rarity.COMMON, mage.cards.z.ZukosOffense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zuko's Offense", 288, Rarity.COMMON, mage.cards.z.ZukosOffense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zuko, Avatar Hunter", 246, Rarity.RARE, mage.cards.z.ZukoAvatarHunter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zuko, Avatar Hunter", 287, Rarity.RARE, mage.cards.z.ZukoAvatarHunter.class, NON_FULL_USE_VARIOUS)); + + cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); + } +} diff --git a/Mage.Sets/src/mage/sets/CowboyBebop.java b/Mage.Sets/src/mage/sets/CowboyBebop.java new file mode 100644 index 00000000000..f87880484e0 --- /dev/null +++ b/Mage.Sets/src/mage/sets/CowboyBebop.java @@ -0,0 +1,29 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/pcbb + */ +public class CowboyBebop extends ExpansionSet { + + private static final CowboyBebop instance = new CowboyBebop(); + + public static CowboyBebop getInstance() { + return instance; + } + + private CowboyBebop() { + super("Cowboy Bebop", "PCBB", ExpansionSet.buildDate(2024, 8, 2), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Disdainful Stroke", 2, Rarity.RARE, mage.cards.d.DisdainfulStroke.class)); + cards.add(new SetCardInfo("Go for the Throat", 3, Rarity.RARE, mage.cards.g.GoForTheThroat.class)); + cards.add(new SetCardInfo("Lightning Strike", 4, Rarity.RARE, mage.cards.l.LightningStrike.class)); + cards.add(new SetCardInfo("Ossification", 1, Rarity.RARE, mage.cards.o.Ossification.class)); + cards.add(new SetCardInfo("Snakeskin Veil", 5, Rarity.RARE, mage.cards.s.SnakeskinVeil.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 019c578519d..476e986740b 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -353,6 +353,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Tyvar, the Pummeler", 353, Rarity.MYTHIC, mage.cards.t.TyvarThePummeler.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tyvar, the Pummeler", 408, Rarity.MYTHIC, mage.cards.t.TyvarThePummeler.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Unable to Scream", 78, Rarity.COMMON, mage.cards.u.UnableToScream.class)); + cards.add(new SetCardInfo("Undead Sprinter", 237, Rarity.RARE, mage.cards.u.UndeadSprinter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Undead Sprinter", 350, Rarity.RARE, mage.cards.u.UndeadSprinter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Under the Skin", 203, Rarity.UNCOMMON, mage.cards.u.UnderTheSkin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Under the Skin", 323, Rarity.UNCOMMON, mage.cards.u.UnderTheSkin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Unidentified Hovership", 305, Rarity.RARE, mage.cards.u.UnidentifiedHovership.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 7a2f0b977f2..353c7fc6d8a 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -29,7 +29,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Arcane Sanctum", 259, Rarity.UNCOMMON, mage.cards.a.ArcaneSanctum.class)); cards.add(new SetCardInfo("Arcane Signet", 92, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); cards.add(new SetCardInfo("Archetype of Imagination", 111, Rarity.UNCOMMON, mage.cards.a.ArchetypeOfImagination.class)); - cards.add(new SetCardInfo("Archon of Cruelty", 371, Rarity.MYTHIC, mage.cards.a.ArchonOfCruelty.class)); + cards.add(new SetCardInfo("Archon of Cruelty", 371, Rarity.MYTHIC, mage.cards.a.ArchonOfCruelty.class, FULL_ART)); cards.add(new SetCardInfo("Arixmethes, Slumbering Isle", 211, Rarity.RARE, mage.cards.a.ArixmethesSlumberingIsle.class)); cards.add(new SetCardInfo("Arvinox, the Mind Flail", 130, Rarity.MYTHIC, mage.cards.a.ArvinoxTheMindFlail.class)); cards.add(new SetCardInfo("Ash Barrens", 260, Rarity.COMMON, mage.cards.a.AshBarrens.class)); @@ -126,7 +126,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Gnarlwood Dryad", 180, Rarity.UNCOMMON, mage.cards.g.GnarlwoodDryad.class)); cards.add(new SetCardInfo("Golgari Rot Farm", 279, Rarity.UNCOMMON, mage.cards.g.GolgariRotFarm.class)); cards.add(new SetCardInfo("Golgari Signet", 246, Rarity.UNCOMMON, mage.cards.g.GolgariSignet.class)); - cards.add(new SetCardInfo("Goryo's Vengeance", 372, Rarity.MYTHIC, mage.cards.g.GoryosVengeance.class)); + cards.add(new SetCardInfo("Goryo's Vengeance", 372, Rarity.MYTHIC, mage.cards.g.GoryosVengeance.class, FULL_ART)); cards.add(new SetCardInfo("Grapple with the Past", 82, Rarity.COMMON, mage.cards.g.GrappleWithThePast.class)); cards.add(new SetCardInfo("Graven Cairns", 280, Rarity.RARE, mage.cards.g.GravenCairns.class)); cards.add(new SetCardInfo("Gray Merchant of Asphodel", 142, Rarity.UNCOMMON, mage.cards.g.GrayMerchantOfAsphodel.class)); @@ -161,7 +161,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Life Insurance", 224, Rarity.RARE, mage.cards.l.LifeInsurance.class)); cards.add(new SetCardInfo("Light Up the Stage", 166, Rarity.UNCOMMON, mage.cards.l.LightUpTheStage.class)); cards.add(new SetCardInfo("Lightning Greaves", 93, Rarity.UNCOMMON, mage.cards.l.LightningGreaves.class)); - cards.add(new SetCardInfo("Living Death", 373, Rarity.MYTHIC, mage.cards.l.LivingDeath.class)); + cards.add(new SetCardInfo("Living Death", 373, Rarity.MYTHIC, mage.cards.l.LivingDeath.class, FULL_ART)); cards.add(new SetCardInfo("Llanowar Wastes", 287, Rarity.RARE, mage.cards.l.LlanowarWastes.class)); cards.add(new SetCardInfo("Mask of Griselbrand", 145, Rarity.RARE, mage.cards.m.MaskOfGriselbrand.class)); cards.add(new SetCardInfo("Massacre Girl", 146, Rarity.RARE, mage.cards.m.MassacreGirl.class)); diff --git a/Mage.Sets/src/mage/sets/EdgeOfEternities.java b/Mage.Sets/src/mage/sets/EdgeOfEternities.java index 59cb657ee78..241e780d155 100644 --- a/Mage.Sets/src/mage/sets/EdgeOfEternities.java +++ b/Mage.Sets/src/mage/sets/EdgeOfEternities.java @@ -312,7 +312,7 @@ public final class EdgeOfEternities extends ExpansionSet { cards.add(new SetCardInfo("Sledge-Class Seedship", 346, Rarity.RARE, mage.cards.s.SledgeClassSeedship.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sothera, the Supervoid", 115, Rarity.MYTHIC, mage.cards.s.SotheraTheSupervoid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sothera, the Supervoid", 360, Rarity.MYTHIC, mage.cards.s.SotheraTheSupervoid.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Sothera, the Supervoid", 382, Rarity.MYTHIC, mage.cards.s.SotheraTheSupervoid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sothera, the Supervoid", 382, Rarity.MYTHIC, mage.cards.s.SotheraTheSupervoid.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Sothera, the Supervoid", 386, Rarity.MYTHIC, mage.cards.s.SotheraTheSupervoid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Space-Time Anomaly", 229, Rarity.RARE, mage.cards.s.SpaceTimeAnomaly.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Space-Time Anomaly", 315, Rarity.RARE, mage.cards.s.SpaceTimeAnomaly.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/EdgeOfEternitiesStellarSights.java b/Mage.Sets/src/mage/sets/EdgeOfEternitiesStellarSights.java index 5079f74d997..6a159997a4d 100644 --- a/Mage.Sets/src/mage/sets/EdgeOfEternitiesStellarSights.java +++ b/Mage.Sets/src/mage/sets/EdgeOfEternitiesStellarSights.java @@ -170,6 +170,7 @@ public final class EdgeOfEternitiesStellarSights extends ExpansionSet { cards.add(new SetCardInfo("Shambling Vent", 38, Rarity.RARE, mage.cards.s.ShamblingVent.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shambling Vent", 83, Rarity.RARE, mage.cards.s.ShamblingVent.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shambling Vent", 128, Rarity.RARE, mage.cards.s.ShamblingVent.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shambling Vent", 173, Rarity.RARE, mage.cards.s.ShamblingVent.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Stirring Wildwood", 39, Rarity.RARE, mage.cards.s.StirringWildwood.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Stirring Wildwood", 84, Rarity.RARE, mage.cards.s.StirringWildwood.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Stirring Wildwood", 129, Rarity.RARE, mage.cards.s.StirringWildwood.class, NON_FULL_USE_VARIOUS)); @@ -177,7 +178,6 @@ public final class EdgeOfEternitiesStellarSights extends ExpansionSet { cards.add(new SetCardInfo("Strip Mine", 40, Rarity.MYTHIC, mage.cards.s.StripMine.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Strip Mine", 85, Rarity.MYTHIC, mage.cards.s.StripMine.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Strip Mine", 130, Rarity.MYTHIC, mage.cards.s.StripMine.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Strip Mine", 173, Rarity.MYTHIC, mage.cards.s.StripMine.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Strip Mine", 175, Rarity.MYTHIC, mage.cards.s.StripMine.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sunken Citadel", 41, Rarity.RARE, mage.cards.s.SunkenCitadel.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sunken Citadel", 86, Rarity.RARE, mage.cards.s.SunkenCitadel.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/FINStandardShowdown.java b/Mage.Sets/src/mage/sets/FINStandardShowdown.java new file mode 100644 index 00000000000..91d220266fd --- /dev/null +++ b/Mage.Sets/src/mage/sets/FINStandardShowdown.java @@ -0,0 +1,26 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/pss5 + */ +public class FINStandardShowdown extends ExpansionSet { + + private static final FINStandardShowdown instance = new FINStandardShowdown(); + + public static FINStandardShowdown getInstance() { + return instance; + } + + private FINStandardShowdown() { + super("FIN Standard Showdown", "PSS5", ExpansionSet.buildDate(2025, 6, 13), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Squall, SeeD Mercenary", 2, Rarity.RARE, mage.cards.s.SquallSeeDMercenary.class)); + cards.add(new SetCardInfo("Ultima", 1, Rarity.RARE, mage.cards.u.Ultima.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/FinalFantasy.java b/Mage.Sets/src/mage/sets/FinalFantasy.java index 6d98c247387..fafcc856776 100644 --- a/Mage.Sets/src/mage/sets/FinalFantasy.java +++ b/Mage.Sets/src/mage/sets/FinalFantasy.java @@ -601,7 +601,7 @@ public final class FinalFantasy extends ExpansionSet { cards.add(new SetCardInfo("Traveling Chocobo", "551b", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Traveling Chocobo", "551c", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Traveling Chocobo", "551d", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Traveling Chocobo", "551f", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveling Chocobo", "551f", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Traveling Chocobo", 210, Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Traveling Chocobo", 406, Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Traveling Chocobo", 551, Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/LoveYourLGS2020.java b/Mage.Sets/src/mage/sets/LoveYourLGS2020.java new file mode 100644 index 00000000000..c7a9e96739b --- /dev/null +++ b/Mage.Sets/src/mage/sets/LoveYourLGS2020.java @@ -0,0 +1,26 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/plg20 + */ +public class LoveYourLGS2020 extends ExpansionSet { + + private static final LoveYourLGS2020 instance = new LoveYourLGS2020(); + + public static LoveYourLGS2020 getInstance() { + return instance; + } + + private LoveYourLGS2020() { + super("Love Your LGS 2020", "PLG20", ExpansionSet.buildDate(2020, 5, 18), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Hangarback Walker", 2, Rarity.RARE, mage.cards.h.HangarbackWalker.class)); + cards.add(new SetCardInfo("Reliquary Tower", 1, Rarity.RARE, mage.cards.r.ReliquaryTower.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/LoveYourLGS2022.java b/Mage.Sets/src/mage/sets/LoveYourLGS2022.java new file mode 100644 index 00000000000..14bf53b4299 --- /dev/null +++ b/Mage.Sets/src/mage/sets/LoveYourLGS2022.java @@ -0,0 +1,26 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/plg22 + */ +public class LoveYourLGS2022 extends ExpansionSet { + + private static final LoveYourLGS2022 instance = new LoveYourLGS2022(); + + public static LoveYourLGS2022 getInstance() { + return instance; + } + + private LoveYourLGS2022() { + super("Love Your LGS 2022", "PLG22", ExpansionSet.buildDate(2022, 7, 1), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Sol Ring", 1, Rarity.RARE, mage.cards.s.SolRing.class, RETRO_ART)); + cards.add(new SetCardInfo("Thought Vessel", 2, Rarity.RARE, mage.cards.t.ThoughtVessel.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/LoveYourLGS2024.java b/Mage.Sets/src/mage/sets/LoveYourLGS2024.java new file mode 100644 index 00000000000..c1dfa7f99b7 --- /dev/null +++ b/Mage.Sets/src/mage/sets/LoveYourLGS2024.java @@ -0,0 +1,29 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/plg24 + */ +public class LoveYourLGS2024 extends ExpansionSet { + + private static final LoveYourLGS2024 instance = new LoveYourLGS2024(); + + public static LoveYourLGS2024 getInstance() { + return instance; + } + + private LoveYourLGS2024() { + super("Love Your LGS 2024", "PLG24", ExpansionSet.buildDate(2024, 8, 6), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Cut Down", "3J", Rarity.RARE, mage.cards.c.CutDown.class)); + cards.add(new SetCardInfo("Lay Down Arms", "1J", Rarity.RARE, mage.cards.l.LayDownArms.class)); + cards.add(new SetCardInfo("Sakura-Tribe Elder", 1, Rarity.RARE, mage.cards.s.SakuraTribeElder.class, FULL_ART)); + cards.add(new SetCardInfo("Sheoldred's Edict", "4J", Rarity.RARE, mage.cards.s.SheoldredsEdict.class)); + cards.add(new SetCardInfo("Sleight of Hand", "2J", Rarity.RARE, mage.cards.s.SleightOfHand.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/LoveYourLGS2025.java b/Mage.Sets/src/mage/sets/LoveYourLGS2025.java new file mode 100644 index 00000000000..5e764d71467 --- /dev/null +++ b/Mage.Sets/src/mage/sets/LoveYourLGS2025.java @@ -0,0 +1,26 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/plg25 + */ +public class LoveYourLGS2025 extends ExpansionSet { + + private static final LoveYourLGS2025 instance = new LoveYourLGS2025(); + + public static LoveYourLGS2025 getInstance() { + return instance; + } + + private LoveYourLGS2025() { + super("Love Your LGS 2025", "PLG25", ExpansionSet.buildDate(2025, 3, 26), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Chromatic Lantern", 1, Rarity.RARE, mage.cards.c.ChromaticLantern.class)); + cards.add(new SetCardInfo("Wastes", 2, Rarity.RARE, mage.cards.w.Wastes.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/MKMStandardShowdown.java b/Mage.Sets/src/mage/sets/MKMStandardShowdown.java new file mode 100644 index 00000000000..13e96e388ac --- /dev/null +++ b/Mage.Sets/src/mage/sets/MKMStandardShowdown.java @@ -0,0 +1,29 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/pss4 + */ +public class MKMStandardShowdown extends ExpansionSet { + + private static final MKMStandardShowdown instance = new MKMStandardShowdown(); + + public static MKMStandardShowdown getInstance() { + return instance; + } + + private MKMStandardShowdown() { + super("MKM Standard Showdown", "PSS4", ExpansionSet.buildDate(2024, 2, 10), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = true; + + cards.add(new SetCardInfo("Forest", 5, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Island", 2, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 4, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Plains", 1, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 3, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + } +} diff --git a/Mage.Sets/src/mage/sets/MTGArenaPromos.java b/Mage.Sets/src/mage/sets/MTGArenaPromos.java index e0d576176eb..0a74ed8705f 100644 --- a/Mage.Sets/src/mage/sets/MTGArenaPromos.java +++ b/Mage.Sets/src/mage/sets/MTGArenaPromos.java @@ -23,56 +23,56 @@ public class MTGArenaPromos extends ExpansionSet { cards.add(new SetCardInfo("Bladewing the Risen", 104, Rarity.RARE, mage.cards.b.BladewingTheRisen.class)); cards.add(new SetCardInfo("Duress", 6, Rarity.COMMON, mage.cards.d.Duress.class)); cards.add(new SetCardInfo("Firemind's Research", 5, Rarity.RARE, mage.cards.f.FiremindsResearch.class)); - cards.add(new SetCardInfo("Forest", 205, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 205, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forest", 210, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 215, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 220, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 225, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 230, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 235, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 240, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 245, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 235, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Forest", 240, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Forest", 245, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Ghalta, Primal Hunger", 7, Rarity.RARE, mage.cards.g.GhaltaPrimalHunger.class)); cards.add(new SetCardInfo("Hanna, Ship's Navigator", 105, Rarity.RARE, mage.cards.h.HannaShipsNavigator.class)); - cards.add(new SetCardInfo("Island", 202, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 202, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Island", 207, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 212, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 217, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 222, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 227, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 232, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 237, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 242, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 232, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Island", 237, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Island", 242, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Llanowar Elves", 4, Rarity.COMMON, mage.cards.l.LlanowarElves.class)); - cards.add(new SetCardInfo("Mountain", 204, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 204, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Mountain", 209, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 214, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 219, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 224, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 229, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 234, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 239, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 244, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 201, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 234, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 239, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 244, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Plains", 201, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Plains", 206, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 211, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 216, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 221, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 226, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 231, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 236, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 241, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 231, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Plains", 236, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Plains", 241, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Ral, Izzet Viceroy", 1, Rarity.MYTHIC, mage.cards.r.RalIzzetViceroy.class)); cards.add(new SetCardInfo("Rhys the Redeemed", 101, Rarity.RARE, mage.cards.r.RhysTheRedeemed.class)); - cards.add(new SetCardInfo("Swamp", 203, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 203, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Swamp", 208, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 213, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 218, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 223, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 228, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 233, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 238, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 243, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 233, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 238, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 243, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Talrand, Sky Summoner", 102, Rarity.RARE, mage.cards.t.TalrandSkySummoner.class)); cards.add(new SetCardInfo("Teferi, Hero of Dominaria", 2, Rarity.MYTHIC, mage.cards.t.TeferiHeroOfDominaria.class)); cards.add(new SetCardInfo("The Gitrog Monster", 103, Rarity.MYTHIC, mage.cards.t.TheGitrogMonster.class)); diff --git a/Mage.Sets/src/mage/sets/MagicFest2019.java b/Mage.Sets/src/mage/sets/MagicFest2019.java index fd2552c3be9..aba996a243c 100644 --- a/Mage.Sets/src/mage/sets/MagicFest2019.java +++ b/Mage.Sets/src/mage/sets/MagicFest2019.java @@ -22,7 +22,7 @@ public final class MagicFest2019 extends ExpansionSet { cards.add(new SetCardInfo("Forest", 6, Rarity.LAND, mage.cards.basiclands.Forest.class)); cards.add(new SetCardInfo("Island", 3, Rarity.LAND, mage.cards.basiclands.Island.class)); - cards.add(new SetCardInfo("Lightning Bolt", 1, Rarity.RARE, mage.cards.l.LightningBolt.class)); + cards.add(new SetCardInfo("Lightning Bolt", 1, Rarity.RARE, mage.cards.l.LightningBolt.class, FULL_ART)); cards.add(new SetCardInfo("Mountain", 5, Rarity.LAND, mage.cards.basiclands.Mountain.class)); cards.add(new SetCardInfo("Plains", 2, Rarity.LAND, mage.cards.basiclands.Plains.class)); cards.add(new SetCardInfo("Sol Ring", 7, Rarity.RARE, mage.cards.s.SolRing.class)); diff --git a/Mage.Sets/src/mage/sets/MagicFest2020.java b/Mage.Sets/src/mage/sets/MagicFest2020.java index eddf156086f..25f30549164 100644 --- a/Mage.Sets/src/mage/sets/MagicFest2020.java +++ b/Mage.Sets/src/mage/sets/MagicFest2020.java @@ -23,7 +23,7 @@ public class MagicFest2020 extends ExpansionSet { cards.add(new SetCardInfo("Forest", 6, Rarity.LAND, mage.cards.basiclands.Forest.class)); cards.add(new SetCardInfo("Island", 3, Rarity.LAND, mage.cards.basiclands.Island.class)); cards.add(new SetCardInfo("Mountain", 5, Rarity.LAND, mage.cards.basiclands.Mountain.class)); - cards.add(new SetCardInfo("Path to Exile", 1, Rarity.RARE, mage.cards.p.PathToExile.class)); + cards.add(new SetCardInfo("Path to Exile", 1, Rarity.RARE, mage.cards.p.PathToExile.class, FULL_ART)); cards.add(new SetCardInfo("Plains", 2, Rarity.LAND, mage.cards.basiclands.Plains.class)); cards.add(new SetCardInfo("Swamp", 4, Rarity.LAND, mage.cards.basiclands.Swamp.class)); } diff --git a/Mage.Sets/src/mage/sets/MagicFest2023.java b/Mage.Sets/src/mage/sets/MagicFest2023.java new file mode 100644 index 00000000000..7d352cfd7d5 --- /dev/null +++ b/Mage.Sets/src/mage/sets/MagicFest2023.java @@ -0,0 +1,29 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/pf23 + * + * @author resech + */ +public class MagicFest2023 extends ExpansionSet { + + private static final MagicFest2023 instance = new MagicFest2023(); + + public static MagicFest2023 getInstance() { + return instance; + } + + private MagicFest2023() { + super("MagicFest 2023", "PF23", ExpansionSet.buildDate(2023, 7, 1), SetType.PROMOTIONAL); + hasBasicLands = false; + + cards.add(new SetCardInfo("Gandalf, Friend of the Shire", 1, Rarity.RARE, mage.cards.g.GandalfFriendOfTheShire.class)); + cards.add(new SetCardInfo("Reliquary Tower", 3, Rarity.RARE, mage.cards.r.ReliquaryTower.class, FULL_ART)); + cards.add(new SetCardInfo("TARDIS", 4, Rarity.RARE, mage.cards.t.TARDIS.class)); + cards.add(new SetCardInfo("Tranquil Thicket", 2, Rarity.RARE, mage.cards.t.TranquilThicket.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/MagicFest2024.java b/Mage.Sets/src/mage/sets/MagicFest2024.java new file mode 100644 index 00000000000..1afd639a404 --- /dev/null +++ b/Mage.Sets/src/mage/sets/MagicFest2024.java @@ -0,0 +1,26 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/pf24 + * + * @author resech + */ +public class MagicFest2024 extends ExpansionSet { + + private static final MagicFest2024 instance = new MagicFest2024(); + + public static MagicFest2024 getInstance() { + return instance; + } + + private MagicFest2024() { + super("MagicFest 2024", "PF24", ExpansionSet.buildDate(2024, 1, 1), SetType.PROMOTIONAL); + hasBasicLands = false; + + cards.add(new SetCardInfo("Counterspell", 1, Rarity.RARE, mage.cards.c.Counterspell.class, FULL_ART)); + } +} diff --git a/Mage.Sets/src/mage/sets/MagicFest2025.java b/Mage.Sets/src/mage/sets/MagicFest2025.java index cbcac3efb35..e623b9d710b 100644 --- a/Mage.Sets/src/mage/sets/MagicFest2025.java +++ b/Mage.Sets/src/mage/sets/MagicFest2025.java @@ -21,10 +21,19 @@ public class MagicFest2025 extends ExpansionSet { super("MagicFest 2025", "PF25", ExpansionSet.buildDate(2025, 2, 3), SetType.PROMOTIONAL); hasBasicLands = false; + cards.add(new SetCardInfo("Arcane Signet", 10, Rarity.RARE, mage.cards.a.ArcaneSignet.class)); cards.add(new SetCardInfo("Avacyn's Pilgrim", "1F", Rarity.RARE, mage.cards.a.AvacynsPilgrim.class, FULL_ART)); + cards.add(new SetCardInfo("Lightning Bolt", 13, Rarity.RARE, mage.cards.l.LightningBolt.class)); cards.add(new SetCardInfo("Ponder", 2, Rarity.RARE, mage.cards.p.Ponder.class)); + cards.add(new SetCardInfo("Rograkh, Son of Rohgahh", 11, Rarity.RARE, mage.cards.r.RograkhSonOfRohgahh.class)); + cards.add(new SetCardInfo("Scourge of Valkas", 14, Rarity.RARE, mage.cards.s.ScourgeOfValkas.class, RETRO_ART)); cards.add(new SetCardInfo("Serra the Benevolent", 1, Rarity.MYTHIC, mage.cards.s.SerraTheBenevolent.class, RETRO_ART)); + cards.add(new SetCardInfo("Sliver Hive", 7, Rarity.RARE, mage.cards.s.SliverHive.class, RETRO_ART)); + cards.add(new SetCardInfo("Swords to Plowshares", 12, Rarity.RARE, mage.cards.s.SwordsToPlowshares.class)); cards.add(new SetCardInfo("The First Sliver", 3, Rarity.MYTHIC, mage.cards.t.TheFirstSliver.class)); - cards.add(new SetCardInfo("Yoshimaru, Ever Faithful", 4, Rarity.MYTHIC, mage.cards.y.YoshimaruEverFaithful.class)); + cards.add(new SetCardInfo("The Ur-Dragon", 15, Rarity.MYTHIC, mage.cards.t.TheUrDragon.class)); + cards.add(new SetCardInfo("Tifa Lockhart", 9, Rarity.RARE, mage.cards.t.TifaLockhart.class)); + cards.add(new SetCardInfo("Ugin, the Spirit Dragon", 6, Rarity.MYTHIC, mage.cards.u.UginTheSpiritDragon.class, RETRO_ART)); + cards.add(new SetCardInfo("Yoshimaru, Ever Faithful", 5, Rarity.MYTHIC, mage.cards.y.YoshimaruEverFaithful.class)); } } diff --git a/Mage.Sets/src/mage/sets/MagicOnlinePromos.java b/Mage.Sets/src/mage/sets/MagicOnlinePromos.java index ff038e56ea6..bcb19295bb5 100644 --- a/Mage.Sets/src/mage/sets/MagicOnlinePromos.java +++ b/Mage.Sets/src/mage/sets/MagicOnlinePromos.java @@ -963,7 +963,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Forest", 279, Rarity.LAND, mage.cards.basiclands.Forest.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 293, Rarity.LAND, mage.cards.basiclands.Forest.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 297, Rarity.LAND, mage.cards.basiclands.Forest.class, RETRO_ART_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 302, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 302, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 31983, Rarity.LAND, mage.cards.basiclands.Forest.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 31991, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 32005, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); @@ -972,7 +972,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Forest", 40050, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 40060, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 40094, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forest", 53875, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 53875, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_ZEN_VARIOUS )); cards.add(new SetCardInfo("Forest", 58261, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 73628, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 81872, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); @@ -1209,7 +1209,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Helm of Kaldra", 31989, Rarity.RARE, mage.cards.h.HelmOfKaldra.class)); cards.add(new SetCardInfo("Helm of Obedience", 65642, Rarity.RARE, mage.cards.h.HelmOfObedience.class)); cards.add(new SetCardInfo("Hengegate Pathway", 88408, Rarity.RARE, mage.cards.h.HengegatePathway.class)); - cards.add(new SetCardInfo("Henzie Toolbox Torre", 99789, Rarity.MYTHIC, mage.cards.h.HenzieToolboxTorre.class)); + cards.add(new SetCardInfo("Henzie \"Toolbox\" Torre", 99789, Rarity.MYTHIC, mage.cards.h.HenzieToolboxTorre.class)); cards.add(new SetCardInfo("Herd Migration", 103470, Rarity.RARE, mage.cards.h.HerdMigration.class)); cards.add(new SetCardInfo("Hermit Druid", 36080, Rarity.RARE, mage.cards.h.HermitDruid.class)); cards.add(new SetCardInfo("Hero of Bladehold", 39646, Rarity.MYTHIC, mage.cards.h.HeroOfBladehold.class)); @@ -1352,7 +1352,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Island", 40052, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 40062, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 40100, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Island", 53881, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 53881, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_ZEN_VARIOUS )); cards.add(new SetCardInfo("Island", 58255, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 73634, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 81846, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); @@ -1486,7 +1486,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Kolvori, God of Kinship", 88348, Rarity.RARE, mage.cards.k.KolvoriGodOfKinship.class)); cards.add(new SetCardInfo("Koma, Cosmos Serpent", 88356, Rarity.MYTHIC, mage.cards.k.KomaCosmosSerpent.class)); cards.add(new SetCardInfo("Komainu Battle Armor", 97995, Rarity.RARE, mage.cards.k.KomainuBattleArmor.class)); - cards.add(new SetCardInfo("Kongming, Sleeping Dragon", 33442, Rarity.RARE, mage.cards.k.KongmingSleepingDragon.class, RETRO_ART)); + cards.add(new SetCardInfo("Kongming, \"Sleeping Dragon\"", 33442, Rarity.RARE, mage.cards.k.KongmingSleepingDragon.class, RETRO_ART)); cards.add(new SetCardInfo("Kor Duelist", 36212, Rarity.UNCOMMON, mage.cards.k.KorDuelist.class)); cards.add(new SetCardInfo("Kor Firewalker", 43574, Rarity.UNCOMMON, mage.cards.k.KorFirewalker.class)); cards.add(new SetCardInfo("Kor Skyfisher", 43548, Rarity.COMMON, mage.cards.k.KorSkyfisher.class)); @@ -1546,7 +1546,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Light Up the Night", 94000, Rarity.RARE, mage.cards.l.LightUpTheNight.class)); cards.add(new SetCardInfo("Light-Paws, Emperor's Voice", 97865, Rarity.RARE, mage.cards.l.LightPawsEmperorsVoice.class)); cards.add(new SetCardInfo("Lightning Bolt", 35932, Rarity.COMMON, mage.cards.l.LightningBolt.class, RETRO_ART_USE_VARIOUS)); - cards.add(new SetCardInfo("Lightning Bolt", 36224, Rarity.COMMON, mage.cards.l.LightningBolt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lightning Bolt", 36224, Rarity.COMMON, mage.cards.l.LightningBolt.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Lightning Bolt", 72886, Rarity.RARE, mage.cards.l.LightningBolt.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Lightning Bolt", 102261, Rarity.UNCOMMON, mage.cards.l.LightningBolt.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lightning Dragon", 32196, Rarity.RARE, mage.cards.l.LightningDragon.class, RETRO_ART)); @@ -1778,7 +1778,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Mountain", 40054, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 40064, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 40096, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 53877, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 53877, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_ZEN_VARIOUS )); cards.add(new SetCardInfo("Mountain", 58259, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 73630, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 81864, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); @@ -2037,7 +2037,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Plains", 40066, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 40098, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 48582, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 53879, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 53879, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_ZEN_VARIOUS )); cards.add(new SetCardInfo("Plains", 58253, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 73626, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 81830, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); @@ -2646,7 +2646,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 40058, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 40068, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 40092, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 53883, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 53883, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_ZEN_VARIOUS )); cards.add(new SetCardInfo("Swamp", 58257, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 73632, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 81856, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); @@ -2756,7 +2756,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Terramorphic Expanse", 86110, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Territorial Kavu", 91339, Rarity.RARE, mage.cards.t.TerritorialKavu.class)); cards.add(new SetCardInfo("Territorial Scythecat", 83704, Rarity.COMMON, mage.cards.t.TerritorialScythecat.class)); - cards.add(new SetCardInfo("Terror", 31483, Rarity.COMMON, mage.cards.t.Terror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terror", 31483, Rarity.COMMON, mage.cards.t.Terror.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Terror", 35948, Rarity.COMMON, mage.cards.t.Terror.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Terror Ballista", 105822, Rarity.RARE, mage.cards.t.TerrorBallista.class)); cards.add(new SetCardInfo("Terror of the Peaks", 81976, Rarity.MYTHIC, mage.cards.t.TerrorOfThePeaks.class)); @@ -3105,7 +3105,7 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Woolly Thoctar", 31449, Rarity.UNCOMMON, mage.cards.w.WoollyThoctar.class)); cards.add(new SetCardInfo("Workshop Warchief", 99715, Rarity.RARE, mage.cards.w.WorkshopWarchief.class)); cards.add(new SetCardInfo("Worship", 77955, Rarity.RARE, mage.cards.w.Worship.class)); - cards.add(new SetCardInfo("Wrath of God", 35048, Rarity.RARE, mage.cards.w.WrathOfGod.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wrath of God", 35048, Rarity.RARE, mage.cards.w.WrathOfGod.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Wrath of God", 82882, Rarity.RARE, mage.cards.w.WrathOfGod.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Wreck Hunter", 105686, Rarity.RARE, mage.cards.w.WreckHunter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Wreck Hunter", 105690, Rarity.RARE, mage.cards.w.WreckHunter.class, RETRO_ART_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/MagicPlayerRewards2008.java b/Mage.Sets/src/mage/sets/MagicPlayerRewards2008.java index 716ec7b3ac2..0aaeec31ee9 100644 --- a/Mage.Sets/src/mage/sets/MagicPlayerRewards2008.java +++ b/Mage.Sets/src/mage/sets/MagicPlayerRewards2008.java @@ -1,8 +1,6 @@ package mage.sets; -import mage.cards.CardGraphicInfo; import mage.cards.ExpansionSet; -import mage.cards.FrameStyle; import mage.constants.Rarity; import mage.constants.SetType; @@ -22,14 +20,12 @@ public class MagicPlayerRewards2008 extends ExpansionSet { this.hasBoosters = false; this.hasBasicLands = false; - final CardGraphicInfo MPR_FULL_ART = new CardGraphicInfo(FrameStyle.MPRP_FULL_ART_BASIC, false); - - cards.add(new SetCardInfo("Corrupt", 7, Rarity.RARE, mage.cards.c.Corrupt.class)); - cards.add(new SetCardInfo("Damnation", 1, Rarity.RARE, mage.cards.d.Damnation.class)); - cards.add(new SetCardInfo("Harmonize", 5, Rarity.RARE, mage.cards.h.Harmonize.class)); - cards.add(new SetCardInfo("Incinerate", 3, Rarity.RARE, mage.cards.i.Incinerate.class)); - cards.add(new SetCardInfo("Mana Tithe", 4, Rarity.RARE, mage.cards.m.ManaTithe.class)); - cards.add(new SetCardInfo("Ponder", 6, Rarity.RARE, mage.cards.p.Ponder.class)); - cards.add(new SetCardInfo("Tidings", 2, Rarity.RARE, mage.cards.t.Tidings.class)); + cards.add(new SetCardInfo("Corrupt", 7, Rarity.RARE, mage.cards.c.Corrupt.class, FULL_ART)); + cards.add(new SetCardInfo("Damnation", 1, Rarity.RARE, mage.cards.d.Damnation.class, FULL_ART)); + cards.add(new SetCardInfo("Harmonize", 5, Rarity.RARE, mage.cards.h.Harmonize.class, FULL_ART)); + cards.add(new SetCardInfo("Incinerate", 3, Rarity.RARE, mage.cards.i.Incinerate.class, FULL_ART)); + cards.add(new SetCardInfo("Mana Tithe", 4, Rarity.RARE, mage.cards.m.ManaTithe.class, FULL_ART)); + cards.add(new SetCardInfo("Ponder", 6, Rarity.RARE, mage.cards.p.Ponder.class, FULL_ART)); + cards.add(new SetCardInfo("Tidings", 2, Rarity.RARE, mage.cards.t.Tidings.class, FULL_ART)); } } diff --git a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java index 8ad64cef5c3..1153a9d5739 100644 --- a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java +++ b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java @@ -1,14 +1,19 @@ package mage.sets; import mage.cards.ExpansionSet; +import mage.cards.s.SilkWebWeaver; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.Arrays; +import java.util.List; + /** * @author TheElk801 */ public final class MarvelsSpiderMan extends ExpansionSet { + private static final List unfinished = Arrays.asList("Eddie Brock", "Gwen Stacy", "Miles Morales", "Norman Osborn", "Peter Parker"); private static final MarvelsSpiderMan instance = new MarvelsSpiderMan(); public static MarvelsSpiderMan getInstance() { @@ -20,50 +25,278 @@ public final class MarvelsSpiderMan extends ExpansionSet { this.blockName = "Marvel's Spider-Man"; // for sorting in GUI this.hasBasicLands = true; + cards.add(new SetCardInfo("Agent Venom", 255, Rarity.RARE, mage.cards.a.AgentVenom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Agent Venom", 49, Rarity.RARE, mage.cards.a.AgentVenom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Alien Symbiosis", 50, Rarity.UNCOMMON, mage.cards.a.AlienSymbiosis.class)); + cards.add(new SetCardInfo("Amazing Acrobatics", 25, Rarity.COMMON, mage.cards.a.AmazingAcrobatics.class)); cards.add(new SetCardInfo("Angry Rabble", 75, Rarity.COMMON, mage.cards.a.AngryRabble.class)); + cards.add(new SetCardInfo("Anti-Venom, Horrifying Healer", 1, Rarity.MYTHIC, mage.cards.a.AntiVenomHorrifyingHealer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anti-Venom, Horrifying Healer", 244, Rarity.MYTHIC, mage.cards.a.AntiVenomHorrifyingHealer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arachne, Psionic Weaver", 2, Rarity.RARE, mage.cards.a.ArachnePsionicWeaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arachne, Psionic Weaver", 245, Rarity.RARE, mage.cards.a.ArachnePsionicWeaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arana, Heart of the Spider", 123, Rarity.RARE, mage.cards.a.AranaHeartOfTheSpider.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arana, Heart of the Spider", 213, Rarity.RARE, mage.cards.a.AranaHeartOfTheSpider.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aunt May", 3, Rarity.UNCOMMON, mage.cards.a.AuntMay.class)); + cards.add(new SetCardInfo("Bagel and Schmear", 161, Rarity.COMMON, mage.cards.b.BagelAndSchmear.class)); cards.add(new SetCardInfo("Beetle, Legacy Criminal", 26, Rarity.COMMON, mage.cards.b.BeetleLegacyCriminal.class)); + cards.add(new SetCardInfo("Behold the Sinister Six!", 221, Rarity.MYTHIC, mage.cards.b.BeholdTheSinisterSix.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Behold the Sinister Six!", 51, Rarity.MYTHIC, mage.cards.b.BeholdTheSinisterSix.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biorganic Carapace", 124, Rarity.RARE, mage.cards.b.BiorganicCarapace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biorganic Carapace", 269, Rarity.RARE, mage.cards.b.BiorganicCarapace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Black Cat, Cunning Thief", 222, Rarity.RARE, mage.cards.b.BlackCatCunningThief.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Black Cat, Cunning Thief", 52, Rarity.RARE, mage.cards.b.BlackCatCunningThief.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Carnage, Crimson Chaos", 125, Rarity.RARE, mage.cards.c.CarnageCrimsonChaos.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Carnage, Crimson Chaos", 227, Rarity.RARE, mage.cards.c.CarnageCrimsonChaos.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chameleon, Master of Disguise", 27, Rarity.UNCOMMON, mage.cards.c.ChameleonMasterOfDisguise.class)); + cards.add(new SetCardInfo("Cheering Crowd", 126, Rarity.RARE, mage.cards.c.CheeringCrowd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cheering Crowd", 270, Rarity.RARE, mage.cards.c.CheeringCrowd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("City Pigeon", 4, Rarity.COMMON, mage.cards.c.CityPigeon.class)); + cards.add(new SetCardInfo("Common Crook", 53, Rarity.COMMON, mage.cards.c.CommonCrook.class)); + cards.add(new SetCardInfo("Cosmic Spider-Man", 127, Rarity.MYTHIC, mage.cards.c.CosmicSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cosmic Spider-Man", 271, Rarity.MYTHIC, mage.cards.c.CosmicSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Costume Closet", 5, Rarity.UNCOMMON, mage.cards.c.CostumeCloset.class)); + cards.add(new SetCardInfo("Daily Bugle Building", 179, Rarity.UNCOMMON, mage.cards.d.DailyBugleBuilding.class)); cards.add(new SetCardInfo("Daily Bugle Reporters", 6, Rarity.COMMON, mage.cards.d.DailyBugleReporters.class)); + cards.add(new SetCardInfo("Damage Control Crew", 99, Rarity.UNCOMMON, mage.cards.d.DamageControlCrew.class)); cards.add(new SetCardInfo("Doc Ock's Henchmen", 30, Rarity.COMMON, mage.cards.d.DocOcksHenchmen.class)); + cards.add(new SetCardInfo("Doc Ock's Tentacles", 162, Rarity.RARE, mage.cards.d.DocOcksTentacles.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Doc Ock's Tentacles", 277, Rarity.RARE, mage.cards.d.DocOcksTentacles.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Doc Ock, Sinister Scientist", 29, Rarity.COMMON, mage.cards.d.DocOckSinisterScientist.class)); + cards.add(new SetCardInfo("Doctor Octopus, Master Planner", 128, Rarity.MYTHIC, mage.cards.d.DoctorOctopusMasterPlanner.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Doctor Octopus, Master Planner", 228, Rarity.MYTHIC, mage.cards.d.DoctorOctopusMasterPlanner.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eddie Brock", 224, Rarity.MYTHIC, mage.cards.e.EddieBrock.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eddie Brock", 233, Rarity.MYTHIC, mage.cards.e.EddieBrock.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eddie Brock", 55, Rarity.MYTHIC, mage.cards.e.EddieBrock.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Eerie Gravestone", 163, Rarity.COMMON, mage.cards.e.EerieGravestone.class)); + cards.add(new SetCardInfo("Electro's Bolt", 77, Rarity.COMMON, mage.cards.e.ElectrosBolt.class)); + cards.add(new SetCardInfo("Electro, Assaulting Battery", 260, Rarity.RARE, mage.cards.e.ElectroAssaultingBattery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Electro, Assaulting Battery", 76, Rarity.RARE, mage.cards.e.ElectroAssaultingBattery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ezekiel Sims, Spider-Totem", 100, Rarity.UNCOMMON, mage.cards.e.EzekielSimsSpiderTotem.class)); + cards.add(new SetCardInfo("Flash Thompson, Spider-Fan", 7, Rarity.UNCOMMON, mage.cards.f.FlashThompsonSpiderFan.class)); cards.add(new SetCardInfo("Flying Octobot", 31, Rarity.UNCOMMON, mage.cards.f.FlyingOctobot.class)); + cards.add(new SetCardInfo("Forest", 193, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 198, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Friendly Neighborhood", 246, Rarity.RARE, mage.cards.f.FriendlyNeighborhood.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Friendly Neighborhood", 8, Rarity.RARE, mage.cards.f.FriendlyNeighborhood.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gallant Citizen", 129, Rarity.COMMON, mage.cards.g.GallantCitizen.class)); + cards.add(new SetCardInfo("Green Goblin, Revenant", 130, Rarity.UNCOMMON, mage.cards.g.GreenGoblinRevenant.class)); + cards.add(new SetCardInfo("Grow Extra Arms", 101, Rarity.COMMON, mage.cards.g.GrowExtraArms.class)); cards.add(new SetCardInfo("Guy in the Chair", 102, Rarity.COMMON, mage.cards.g.GuyInTheChair.class)); + cards.add(new SetCardInfo("Gwen Stacy", 202, Rarity.MYTHIC, mage.cards.g.GwenStacy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gwen Stacy", 209, Rarity.MYTHIC, mage.cards.g.GwenStacy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gwen Stacy", 78, Rarity.MYTHIC, mage.cards.g.GwenStacy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gwenom, Remorseless", 256, Rarity.MYTHIC, mage.cards.g.GwenomRemorseless.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gwenom, Remorseless", 286, Rarity.MYTHIC, mage.cards.g.GwenomRemorseless.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gwenom, Remorseless", 56, Rarity.MYTHIC, mage.cards.g.GwenomRemorseless.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Heroes' Hangout", 79, Rarity.UNCOMMON, mage.cards.h.HeroesHangout.class)); + cards.add(new SetCardInfo("Hide on the Ceiling", 249, Rarity.RARE, mage.cards.h.HideOnTheCeiling.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hide on the Ceiling", 32, Rarity.RARE, mage.cards.h.HideOnTheCeiling.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hobgoblin, Mantled Marauder", 80, Rarity.UNCOMMON, mage.cards.h.HobgoblinMantledMarauder.class)); + cards.add(new SetCardInfo("Hot Dog Cart", 164, Rarity.COMMON, mage.cards.h.HotDogCart.class)); + cards.add(new SetCardInfo("Hydro-Man, Fluid Felon", 250, Rarity.RARE, mage.cards.h.HydroManFluidFelon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hydro-Man, Fluid Felon", 33, Rarity.RARE, mage.cards.h.HydroManFluidFelon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Impostor Syndrome", 251, Rarity.MYTHIC, mage.cards.i.ImpostorSyndrome.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Impostor Syndrome", 34, Rarity.MYTHIC, mage.cards.i.ImpostorSyndrome.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inner Demons Gangsters", 57, Rarity.COMMON, mage.cards.i.InnerDemonsGangsters.class)); + cards.add(new SetCardInfo("Interdimensional Web Watch", 165, Rarity.RARE, mage.cards.i.InterdimensionalWebWatch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Interdimensional Web Watch", 278, Rarity.RARE, mage.cards.i.InterdimensionalWebWatch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Iron Spider, Stark Upgrade", 166, Rarity.RARE, mage.cards.i.IronSpiderStarkUpgrade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Iron Spider, Stark Upgrade", 279, Rarity.RARE, mage.cards.i.IronSpiderStarkUpgrade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 190, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Island", 195, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("J. Jonah Jameson", 261, Rarity.RARE, mage.cards.j.JJonahJameson.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("J. Jonah Jameson", 81, Rarity.RARE, mage.cards.j.JJonahJameson.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jackal, Genius Geneticist", 131, Rarity.RARE, mage.cards.j.JackalGeniusGeneticist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jackal, Genius Geneticist", 272, Rarity.RARE, mage.cards.j.JackalGeniusGeneticist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kapow!", 103, Rarity.COMMON, mage.cards.k.Kapow.class)); + cards.add(new SetCardInfo("Kraven the Hunter", 133, Rarity.RARE, mage.cards.k.KravenTheHunter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kraven the Hunter", 273, Rarity.RARE, mage.cards.k.KravenTheHunter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kraven's Cats", 104, Rarity.COMMON, mage.cards.k.KravensCats.class)); + cards.add(new SetCardInfo("Kraven's Last Hunt", 105, Rarity.RARE, mage.cards.k.KravensLastHunt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kraven's Last Hunt", 226, Rarity.RARE, mage.cards.k.KravensLastHunt.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Kraven, Proud Predator", 132, Rarity.UNCOMMON, mage.cards.k.KravenProudPredator.class)); + cards.add(new SetCardInfo("Lady Octopus, Inspired Inventor", 252, Rarity.RARE, mage.cards.l.LadyOctopusInspiredInventor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lady Octopus, Inspired Inventor", 35, Rarity.RARE, mage.cards.l.LadyOctopusInspiredInventor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Living Brain, Mechanical Marvel", 167, Rarity.UNCOMMON, mage.cards.l.LivingBrainMechanicalMarvel.class)); + cards.add(new SetCardInfo("Lizard, Connors's Curse", 106, Rarity.RARE, mage.cards.l.LizardConnorssCurse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lizard, Connors's Curse", 265, Rarity.RARE, mage.cards.l.LizardConnorssCurse.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lurking Lizards", 107, Rarity.COMMON, mage.cards.l.LurkingLizards.class)); + cards.add(new SetCardInfo("Madame Web, Clairvoyant", 36, Rarity.UNCOMMON, mage.cards.m.MadameWebClairvoyant.class)); + cards.add(new SetCardInfo("Mary Jane Watson", 134, Rarity.RARE, mage.cards.m.MaryJaneWatson.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mary Jane Watson", 229, Rarity.RARE, mage.cards.m.MaryJaneWatson.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Masked Meower", 82, Rarity.COMMON, mage.cards.m.MaskedMeower.class)); + cards.add(new SetCardInfo("Maximum Carnage", 225, Rarity.RARE, mage.cards.m.MaximumCarnage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Maximum Carnage", 83, Rarity.RARE, mage.cards.m.MaximumCarnage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mechanical Mobster", 168, Rarity.COMMON, mage.cards.m.MechanicalMobster.class)); cards.add(new SetCardInfo("Merciless Enforcers", 58, Rarity.COMMON, mage.cards.m.MercilessEnforcers.class)); + cards.add(new SetCardInfo("Miles Morales", 108, Rarity.MYTHIC, mage.cards.m.MilesMorales.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Miles Morales", 200, Rarity.MYTHIC, mage.cards.m.MilesMorales.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Miles Morales", 211, Rarity.MYTHIC, mage.cards.m.MilesMorales.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Miles Morales", 234, Rarity.MYTHIC, mage.cards.m.MilesMorales.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mister Negative", 135, Rarity.MYTHIC, mage.cards.m.MisterNegative.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mister Negative", 274, Rarity.MYTHIC, mage.cards.m.MisterNegative.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mob Lookout", 136, Rarity.COMMON, mage.cards.m.MobLookout.class)); + cards.add(new SetCardInfo("Molten Man, Inferno Incarnate", 84, Rarity.UNCOMMON, mage.cards.m.MoltenManInfernoIncarnate.class)); + cards.add(new SetCardInfo("Morbius the Living Vampire", 137, Rarity.UNCOMMON, mage.cards.m.MorbiusTheLivingVampire.class)); + cards.add(new SetCardInfo("Morlun, Devourer of Spiders", 257, Rarity.RARE, mage.cards.m.MorlunDevourerOfSpiders.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Morlun, Devourer of Spiders", 59, Rarity.RARE, mage.cards.m.MorlunDevourerOfSpiders.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 192, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 197, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Origin of Spider-Man", 9, Rarity.RARE, mage.cards.o.OriginOfSpiderMan.class)); + cards.add(new SetCardInfo("Multiversal Passage", 180, Rarity.RARE, mage.cards.m.MultiversalPassage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Multiversal Passage", 206, Rarity.RARE, mage.cards.m.MultiversalPassage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mysterio's Phantasm", 38, Rarity.COMMON, mage.cards.m.MysteriosPhantasm.class)); + cards.add(new SetCardInfo("Mysterio, Master of Illusion", 253, Rarity.RARE, mage.cards.m.MysterioMasterOfIllusion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mysterio, Master of Illusion", 37, Rarity.RARE, mage.cards.m.MysterioMasterOfIllusion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("News Helicopter", 169, Rarity.COMMON, mage.cards.n.NewsHelicopter.class)); + cards.add(new SetCardInfo("Norman Osborn", 220, Rarity.MYTHIC, mage.cards.n.NormanOsborn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Norman Osborn", 39, Rarity.MYTHIC, mage.cards.n.NormanOsborn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ominous Asylum", 181, Rarity.COMMON, mage.cards.o.OminousAsylum.class)); + cards.add(new SetCardInfo("Origin of Spider-Man", 218, Rarity.RARE, mage.cards.o.OriginOfSpiderMan.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Origin of Spider-Man", 9, Rarity.RARE, mage.cards.o.OriginOfSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Oscorp Industries", 182, Rarity.RARE, mage.cards.o.OscorpIndustries.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Oscorp Industries", 282, Rarity.RARE, mage.cards.o.OscorpIndustries.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Oscorp Research Team", 40, Rarity.COMMON, mage.cards.o.OscorpResearchTeam.class)); + cards.add(new SetCardInfo("Parker Luck", 258, Rarity.RARE, mage.cards.p.ParkerLuck.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Parker Luck", 60, Rarity.RARE, mage.cards.p.ParkerLuck.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Passenger Ferry", 170, Rarity.COMMON, mage.cards.p.PassengerFerry.class)); + cards.add(new SetCardInfo("Peter Parker", 10, Rarity.MYTHIC, mage.cards.p.PeterParker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Peter Parker", 208, Rarity.MYTHIC, mage.cards.p.PeterParker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Peter Parker", 232, Rarity.MYTHIC, mage.cards.p.PeterParker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Peter Parker's Camera", 171, Rarity.RARE, mage.cards.p.PeterParkersCamera.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Peter Parker's Camera", 280, Rarity.RARE, mage.cards.p.PeterParkersCamera.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pictures of Spider-Man", 109, Rarity.UNCOMMON, mage.cards.p.PicturesOfSpiderMan.class)); + cards.add(new SetCardInfo("Plains", 189, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 194, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Prison Break", 61, Rarity.UNCOMMON, mage.cards.p.PrisonBreak.class)); + cards.add(new SetCardInfo("Professional Wrestler", 110, Rarity.COMMON, mage.cards.p.ProfessionalWrestler.class)); + cards.add(new SetCardInfo("Prowler, Clawed Thief", 138, Rarity.UNCOMMON, mage.cards.p.ProwlerClawedThief.class)); + cards.add(new SetCardInfo("Pumpkin Bombardment", 139, Rarity.COMMON, mage.cards.p.PumpkinBombardment.class)); + cards.add(new SetCardInfo("Radioactive Spider", 111, Rarity.RARE, mage.cards.r.RadioactiveSpider.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Radioactive Spider", 212, Rarity.RARE, mage.cards.r.RadioactiveSpider.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Radioactive Spider", 285, Rarity.RARE, mage.cards.r.RadioactiveSpider.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Raging Goblinoids", 85, Rarity.UNCOMMON, mage.cards.r.RagingGoblinoids.class)); + cards.add(new SetCardInfo("Rent Is Due", 11, Rarity.RARE, mage.cards.r.RentIsDue.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rent Is Due", 247, Rarity.RARE, mage.cards.r.RentIsDue.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rhino's Rampage", 141, Rarity.UNCOMMON, mage.cards.r.RhinosRampage.class)); + cards.add(new SetCardInfo("Rhino, Barreling Brute", 140, Rarity.UNCOMMON, mage.cards.r.RhinoBarrelingBrute.class)); cards.add(new SetCardInfo("Risky Research", 62, Rarity.COMMON, mage.cards.r.RiskyResearch.class)); + cards.add(new SetCardInfo("Robotics Mastery", 41, Rarity.UNCOMMON, mage.cards.r.RoboticsMastery.class)); + cards.add(new SetCardInfo("Rocket-Powered Goblin Glider", 172, Rarity.RARE, mage.cards.r.RocketPoweredGoblinGlider.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rocket-Powered Goblin Glider", 281, Rarity.RARE, mage.cards.r.RocketPoweredGoblinGlider.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Romantic Rendezvous", 86, Rarity.COMMON, mage.cards.r.RomanticRendezvous.class)); cards.add(new SetCardInfo("SP//dr, Piloted by Peni", 147, Rarity.UNCOMMON, mage.cards.s.SPDrPilotedByPeni.class)); + cards.add(new SetCardInfo("Sandman's Quicksand", 63, Rarity.UNCOMMON, mage.cards.s.SandmansQuicksand.class)); + cards.add(new SetCardInfo("Sandman, Shifting Scoundrel", 112, Rarity.RARE, mage.cards.s.SandmanShiftingScoundrel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sandman, Shifting Scoundrel", 266, Rarity.RARE, mage.cards.s.SandmanShiftingScoundrel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Savage Mansion", 183, Rarity.COMMON, mage.cards.s.SavageMansion.class)); + cards.add(new SetCardInfo("Scarlet Spider, Ben Reilly", 142, Rarity.RARE, mage.cards.s.ScarletSpiderBenReilly.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scarlet Spider, Ben Reilly", 214, Rarity.RARE, mage.cards.s.ScarletSpiderBenReilly.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scarlet Spider, Kaine", 143, Rarity.UNCOMMON, mage.cards.s.ScarletSpiderKaine.class)); + cards.add(new SetCardInfo("School Daze", 42, Rarity.UNCOMMON, mage.cards.s.SchoolDaze.class)); cards.add(new SetCardInfo("Scorpion's Sting", 65, Rarity.COMMON, mage.cards.s.ScorpionsSting.class)); cards.add(new SetCardInfo("Scorpion, Seething Striker", 64, Rarity.UNCOMMON, mage.cards.s.ScorpionSeethingStriker.class)); cards.add(new SetCardInfo("Scout the City", 113, Rarity.COMMON, mage.cards.s.ScoutTheCity.class)); + cards.add(new SetCardInfo("Secret Identity", 43, Rarity.UNCOMMON, mage.cards.s.SecretIdentity.class)); cards.add(new SetCardInfo("Selfless Police Captain", 12, Rarity.COMMON, mage.cards.s.SelflessPoliceCaptain.class)); + cards.add(new SetCardInfo("Shadow of the Goblin", 262, Rarity.RARE, mage.cards.s.ShadowOfTheGoblin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shadow of the Goblin", 87, Rarity.RARE, mage.cards.s.ShadowOfTheGoblin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shock", 88, Rarity.COMMON, mage.cards.s.Shock.class)); cards.add(new SetCardInfo("Shocker, Unshakable", 89, Rarity.UNCOMMON, mage.cards.s.ShockerUnshakable.class)); + cards.add(new SetCardInfo("Shriek, Treblemaker", 144, Rarity.UNCOMMON, mage.cards.s.ShriekTreblemaker.class)); + cards.add(new SetCardInfo("Silk, Web Weaver", 145, Rarity.RARE, SilkWebWeaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Silk, Web Weaver", 215, Rarity.RARE, SilkWebWeaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Silver Sable, Mercenary Leader", 13, Rarity.UNCOMMON, mage.cards.s.SilverSableMercenaryLeader.class)); + cards.add(new SetCardInfo("Sinister Hideout", 184, Rarity.COMMON, mage.cards.s.SinisterHideout.class)); + cards.add(new SetCardInfo("Skyward Spider", 146, Rarity.COMMON, mage.cards.s.SkywardSpider.class)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 14, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 235, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 236, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 237, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 238, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 239, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 240, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 241, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spectacular Tactics", 15, Rarity.COMMON, mage.cards.s.SpectacularTactics.class)); + cards.add(new SetCardInfo("Spider Manifestation", 148, Rarity.COMMON, mage.cards.s.SpiderManifestation.class)); cards.add(new SetCardInfo("Spider-Bot", 173, Rarity.COMMON, mage.cards.s.SpiderBot.class)); cards.add(new SetCardInfo("Spider-Byte, Web Warden", 44, Rarity.UNCOMMON, mage.cards.s.SpiderByteWebWarden.class)); + cards.add(new SetCardInfo("Spider-Girl, Legacy Hero", 149, Rarity.UNCOMMON, mage.cards.s.SpiderGirlLegacyHero.class)); cards.add(new SetCardInfo("Spider-Gwen, Free Spirit", 90, Rarity.COMMON, mage.cards.s.SpiderGwenFreeSpirit.class)); - cards.add(new SetCardInfo("Spider-Ham, Peter Porker", 114, Rarity.RARE, mage.cards.s.SpiderHamPeterPorker.class)); + cards.add(new SetCardInfo("Spider-Ham, Peter Porker", 114, Rarity.RARE, mage.cards.s.SpiderHamPeterPorker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Ham, Peter Porker", 201, Rarity.RARE, mage.cards.s.SpiderHamPeterPorker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Islanders", 91, Rarity.COMMON, mage.cards.s.SpiderIslanders.class)); + cards.add(new SetCardInfo("Spider-Man 2099", 150, Rarity.RARE, mage.cards.s.SpiderMan2099.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Man 2099", 205, Rarity.RARE, mage.cards.s.SpiderMan2099.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Man 2099", 216, Rarity.RARE, mage.cards.s.SpiderMan2099.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Man India", 151, Rarity.UNCOMMON, mage.cards.s.SpiderManIndia.class)); + cards.add(new SetCardInfo("Spider-Man No More", 45, Rarity.COMMON, mage.cards.s.SpiderManNoMore.class)); + cards.add(new SetCardInfo("Spider-Man Noir", 204, Rarity.UNCOMMON, mage.cards.s.SpiderManNoir.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Man Noir", 67, Rarity.UNCOMMON, mage.cards.s.SpiderManNoir.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spider-Man, Brooklyn Visionary", 115, Rarity.COMMON, mage.cards.s.SpiderManBrooklynVisionary.class)); cards.add(new SetCardInfo("Spider-Man, Web-Slinger", 16, Rarity.COMMON, mage.cards.s.SpiderManWebSlinger.class)); + cards.add(new SetCardInfo("Spider-Mobile", 174, Rarity.UNCOMMON, mage.cards.s.SpiderMobile.class)); + cards.add(new SetCardInfo("Spider-Punk", 207, Rarity.RARE, mage.cards.s.SpiderPunk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Punk", 210, Rarity.RARE, mage.cards.s.SpiderPunk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Punk", 92, Rarity.RARE, mage.cards.s.SpiderPunk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spider-Rex, Daring Dino", 116, Rarity.COMMON, mage.cards.s.SpiderRexDaringDino.class)); + cards.add(new SetCardInfo("Spider-Sense", 254, Rarity.RARE, mage.cards.s.SpiderSense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Sense", 284, Rarity.RARE, mage.cards.s.SpiderSense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Sense", 46, Rarity.RARE, mage.cards.s.SpiderSense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Slayer, Hatred Honed", 175, Rarity.UNCOMMON, mage.cards.s.SpiderSlayerHatredHoned.class)); + cards.add(new SetCardInfo("Spider-Suit", 176, Rarity.UNCOMMON, mage.cards.s.SpiderSuit.class)); + cards.add(new SetCardInfo("Spider-UK", 17, Rarity.UNCOMMON, mage.cards.s.SpiderUK.class)); + cards.add(new SetCardInfo("Spider-Verse", 263, Rarity.MYTHIC, mage.cards.s.SpiderVerse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Verse", 93, Rarity.MYTHIC, mage.cards.s.SpiderVerse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Woman, Stunning Savior", 152, Rarity.RARE, mage.cards.s.SpiderWomanStunningSavior.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Woman, Stunning Savior", 230, Rarity.RARE, mage.cards.s.SpiderWomanStunningSavior.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spiders-Man, Heroic Horde", 117, Rarity.UNCOMMON, mage.cards.s.SpidersManHeroicHorde.class)); + cards.add(new SetCardInfo("Spinneret and Spiderling", 264, Rarity.RARE, mage.cards.s.SpinneretAndSpiderling.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spinneret and Spiderling", 94, Rarity.RARE, mage.cards.s.SpinneretAndSpiderling.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Starling, Aerial Ally", 18, Rarity.COMMON, mage.cards.s.StarlingAerialAlly.class)); + cards.add(new SetCardInfo("Steel Wrecking Ball", 177, Rarity.COMMON, mage.cards.s.SteelWreckingBall.class)); + cards.add(new SetCardInfo("Stegron the Dinosaur Man", 95, Rarity.COMMON, mage.cards.s.StegronTheDinosaurMan.class)); + cards.add(new SetCardInfo("Suburban Sanctuary", 185, Rarity.COMMON, mage.cards.s.SuburbanSanctuary.class)); + cards.add(new SetCardInfo("Subway Train", 178, Rarity.COMMON, mage.cards.s.SubwayTrain.class)); + cards.add(new SetCardInfo("Sudden Strike", 19, Rarity.UNCOMMON, mage.cards.s.SuddenStrike.class)); + cards.add(new SetCardInfo("Sun-Spider, Nimble Webber", 154, Rarity.UNCOMMON, mage.cards.s.SunSpiderNimbleWebber.class)); + cards.add(new SetCardInfo("Supportive Parents", 119, Rarity.UNCOMMON, mage.cards.s.SupportiveParents.class)); + cards.add(new SetCardInfo("Swamp", 191, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 196, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swarm, Being of Bees", 69, Rarity.COMMON, mage.cards.s.SwarmBeingOfBees.class)); + cards.add(new SetCardInfo("Symbiote Spider-Man", 156, Rarity.RARE, mage.cards.s.SymbioteSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Symbiote Spider-Man", 217, Rarity.RARE, mage.cards.s.SymbioteSpiderMan.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Taxi Driver", 97, Rarity.COMMON, mage.cards.t.TaxiDriver.class)); + cards.add(new SetCardInfo("The Clone Saga", 219, Rarity.RARE, mage.cards.t.TheCloneSaga.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Clone Saga", 28, Rarity.RARE, mage.cards.t.TheCloneSaga.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Spot's Portal", 68, Rarity.UNCOMMON, mage.cards.t.TheSpotsPortal.class)); cards.add(new SetCardInfo("Thwip!", 20, Rarity.COMMON, mage.cards.t.Thwip.class)); cards.add(new SetCardInfo("Tombstone, Career Criminal", 70, Rarity.UNCOMMON, mage.cards.t.TombstoneCareerCriminal.class)); + cards.add(new SetCardInfo("Ultimate Green Goblin", 157, Rarity.RARE, mage.cards.u.UltimateGreenGoblin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultimate Green Goblin", 276, Rarity.RARE, mage.cards.u.UltimateGreenGoblin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("University Campus", 186, Rarity.COMMON, mage.cards.u.UniversityCampus.class)); + cards.add(new SetCardInfo("Unstable Experiment", 47, Rarity.COMMON, mage.cards.u.UnstableExperiment.class)); + cards.add(new SetCardInfo("Urban Retreat", 187, Rarity.RARE, mage.cards.u.UrbanRetreat.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Urban Retreat", 283, Rarity.RARE, mage.cards.u.UrbanRetreat.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Venom's Hunger", 73, Rarity.COMMON, mage.cards.v.VenomsHunger.class)); + cards.add(new SetCardInfo("Venom, Evil Unleashed", 71, Rarity.COMMON, mage.cards.v.VenomEvilUnleashed.class)); + cards.add(new SetCardInfo("Venomized Cat", 72, Rarity.COMMON, mage.cards.v.VenomizedCat.class)); + cards.add(new SetCardInfo("Vibrant Cityscape", 188, Rarity.COMMON, mage.cards.v.VibrantCityscape.class)); + cards.add(new SetCardInfo("Villainous Wrath", 259, Rarity.RARE, mage.cards.v.VillainousWrath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Villainous Wrath", 74, Rarity.RARE, mage.cards.v.VillainousWrath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vulture, Scheming Scavenger", 158, Rarity.UNCOMMON, mage.cards.v.VultureSchemingScavenger.class)); + cards.add(new SetCardInfo("Wall Crawl", 121, Rarity.UNCOMMON, mage.cards.w.WallCrawl.class)); cards.add(new SetCardInfo("Web Up", 21, Rarity.COMMON, mage.cards.w.WebUp.class)); + cards.add(new SetCardInfo("Web of Life and Destiny", 122, Rarity.MYTHIC, mage.cards.w.WebOfLifeAndDestiny.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Web of Life and Destiny", 268, Rarity.MYTHIC, mage.cards.w.WebOfLifeAndDestiny.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Web-Warriors", 159, Rarity.UNCOMMON, mage.cards.w.WebWarriors.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Web-Warriors", 203, Rarity.UNCOMMON, mage.cards.w.WebWarriors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Whoosh!", 48, Rarity.COMMON, mage.cards.w.Whoosh.class)); cards.add(new SetCardInfo("Wild Pack Squad", 23, Rarity.COMMON, mage.cards.w.WildPackSquad.class)); + cards.add(new SetCardInfo("Wisecrack", 98, Rarity.UNCOMMON, mage.cards.w.Wisecrack.class)); + cards.add(new SetCardInfo("With Great Power...", 24, Rarity.RARE, mage.cards.w.WithGreatPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("With Great Power...", 248, Rarity.RARE, mage.cards.w.WithGreatPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wraith, Vicious Vigilante", 160, Rarity.UNCOMMON, mage.cards.w.WraithViciousVigilante.class)); + + cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); } } diff --git a/Mage.Sets/src/mage/sets/MarvelsSpiderManEternal.java b/Mage.Sets/src/mage/sets/MarvelsSpiderManEternal.java index 0457ffe02fd..532b1723ad7 100644 --- a/Mage.Sets/src/mage/sets/MarvelsSpiderManEternal.java +++ b/Mage.Sets/src/mage/sets/MarvelsSpiderManEternal.java @@ -22,21 +22,29 @@ public final class MarvelsSpiderManEternal extends ExpansionSet { cards.add(new SetCardInfo("Alchemax Slayer-Bots", 5, Rarity.COMMON, mage.cards.a.AlchemaxSlayerBots.class)); cards.add(new SetCardInfo("Amateur Hero", 1, Rarity.COMMON, mage.cards.a.AmateurHero.class)); + cards.add(new SetCardInfo("Amazing Alliance", 2, Rarity.RARE, mage.cards.a.AmazingAlliance.class)); cards.add(new SetCardInfo("Doc Ock, Evil Inventor", 24, Rarity.RARE, mage.cards.d.DocOckEvilInventor.class)); + cards.add(new SetCardInfo("Double Trouble", 13, Rarity.RARE, mage.cards.d.DoubleTrouble.class)); cards.add(new SetCardInfo("Future Flight", 6, Rarity.RARE, mage.cards.f.FutureFlight.class)); + cards.add(new SetCardInfo("Ghost-Spider, Gwen Stacy", 14, Rarity.MYTHIC, mage.cards.g.GhostSpiderGwenStacy.class)); cards.add(new SetCardInfo("Grasping Tentacles", 21, Rarity.RARE, mage.cards.g.GraspingTentacles.class)); cards.add(new SetCardInfo("Green Goblin, Nemesis", 23, Rarity.RARE, mage.cards.g.GreenGoblinNemesis.class)); cards.add(new SetCardInfo("Grendel, Spawn of Knull", 9, Rarity.UNCOMMON, mage.cards.g.GrendelSpawnOfKnull.class)); + cards.add(new SetCardInfo("Lethal Protection", 10, Rarity.RARE, mage.cards.l.LethalProtection.class)); cards.add(new SetCardInfo("Lyla, Holographic Assistant", 7, Rarity.UNCOMMON, mage.cards.l.LylaHolographicAssistant.class)); cards.add(new SetCardInfo("MJ, Rising Star", 3, Rarity.UNCOMMON, mage.cards.m.MJRisingStar.class)); cards.add(new SetCardInfo("Prowler, Misguided Mentor", 17, Rarity.UNCOMMON, mage.cards.p.ProwlerMisguidedMentor.class)); cards.add(new SetCardInfo("Pumpkin Bombs", 26, Rarity.RARE, mage.cards.p.PumpkinBombs.class)); + cards.add(new SetCardInfo("Rampaging Classmate", 16, Rarity.COMMON, mage.cards.r.RampagingClassmate.class)); cards.add(new SetCardInfo("Sensational Spider-Man", 25, Rarity.RARE, mage.cards.s.SensationalSpiderMan.class)); + cards.add(new SetCardInfo("Spider-Man 2099, Miguel O'Hara", 8, Rarity.MYTHIC, mage.cards.s.SpiderMan2099MiguelOHara.class)); cards.add(new SetCardInfo("Spider-Man, Miles Morales", 18, Rarity.MYTHIC, mage.cards.s.SpiderManMilesMorales.class)); cards.add(new SetCardInfo("Spider-Man, Peter Parker", 4, Rarity.MYTHIC, mage.cards.s.SpiderManPeterParker.class)); cards.add(new SetCardInfo("Symbiote Spawn", 11, Rarity.COMMON, mage.cards.s.SymbioteSpawn.class)); + cards.add(new SetCardInfo("The Mary Janes", 15, Rarity.UNCOMMON, mage.cards.t.TheMaryJanes.class)); cards.add(new SetCardInfo("Twisted Spider-Clone", 19, Rarity.COMMON, mage.cards.t.TwistedSpiderClone.class)); cards.add(new SetCardInfo("Venom Blast", 20, Rarity.RARE, mage.cards.v.VenomBlast.class)); cards.add(new SetCardInfo("Venom, Deadly Devourer", 22, Rarity.RARE, mage.cards.v.VenomDeadlyDevourer.class)); + cards.add(new SetCardInfo("Venom, Eddie Brock", 12, Rarity.MYTHIC, mage.cards.v.VenomEddieBrock.class)); } } diff --git a/Mage.Sets/src/mage/sets/MastersEditionIII.java b/Mage.Sets/src/mage/sets/MastersEditionIII.java index 1743064f236..6a9b8b9978a 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionIII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionIII.java @@ -140,7 +140,7 @@ public final class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Kobold Overlord", 105, Rarity.UNCOMMON, mage.cards.k.KoboldOverlord.class, RETRO_ART)); cards.add(new SetCardInfo("Kobold Taskmaster", 106, Rarity.COMMON, mage.cards.k.KoboldTaskmaster.class, RETRO_ART)); cards.add(new SetCardInfo("Kobolds of Kher Keep", 107, Rarity.COMMON, mage.cards.k.KoboldsOfKherKeep.class, RETRO_ART)); - cards.add(new SetCardInfo("Kongming, Sleeping Dragon", 16, Rarity.RARE, mage.cards.k.KongmingSleepingDragon.class, RETRO_ART)); + cards.add(new SetCardInfo("Kongming, \"Sleeping Dragon\"", 16, Rarity.RARE, mage.cards.k.KongmingSleepingDragon.class, RETRO_ART)); cards.add(new SetCardInfo("Labyrinth Minotaur", 39, Rarity.COMMON, mage.cards.l.LabyrinthMinotaur.class, RETRO_ART)); cards.add(new SetCardInfo("Lady Caleria", 157, Rarity.UNCOMMON, mage.cards.l.LadyCaleria.class, RETRO_ART)); cards.add(new SetCardInfo("Lady Evangela", 158, Rarity.UNCOMMON, mage.cards.l.LadyEvangela.class, RETRO_ART)); diff --git a/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java b/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java index b655cb8f7fc..04b60e4a656 100644 --- a/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java +++ b/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java @@ -24,6 +24,7 @@ public class MediaAndCollaborationPromos extends ExpansionSet { cards.add(new SetCardInfo("Ajani, Mentor of Heroes", "2024-3", Rarity.MYTHIC, mage.cards.a.AjaniMentorOfHeroes.class)); cards.add(new SetCardInfo("Ancestral Mask", "2025-2", Rarity.RARE, mage.cards.a.AncestralMask.class)); + cards.add(new SetCardInfo("Anti-Venom, Horrifying Healer", "2025-15", Rarity.MYTHIC, mage.cards.a.AntiVenomHorrifyingHealer.class)); cards.add(new SetCardInfo("Archangel", "2000-4", Rarity.RARE, mage.cards.a.Archangel.class, RETRO_ART)); cards.add(new SetCardInfo("Ascendant Evincar", "2000-7", Rarity.RARE, mage.cards.a.AscendantEvincar.class, RETRO_ART)); cards.add(new SetCardInfo("Avalanche Riders", "2023-5", Rarity.RARE, mage.cards.a.AvalancheRiders.class)); @@ -32,6 +33,7 @@ public class MediaAndCollaborationPromos extends ExpansionSet { cards.add(new SetCardInfo("Cast Down", "2019-1", Rarity.UNCOMMON, mage.cards.c.CastDown.class)); cards.add(new SetCardInfo("Chandra's Outrage", "2010-3", Rarity.COMMON, mage.cards.c.ChandrasOutrage.class)); cards.add(new SetCardInfo("Chandra's Spitfire", "2010-4", Rarity.UNCOMMON, mage.cards.c.ChandrasSpitfire.class)); + cards.add(new SetCardInfo("Cloud, Planet's Champion", "2025-13", Rarity.MYTHIC, mage.cards.c.CloudPlanetsChampion.class)); cards.add(new SetCardInfo("Counterspell", "2021-1", Rarity.RARE, mage.cards.c.Counterspell.class)); cards.add(new SetCardInfo("Crop Rotation", "2020-7", Rarity.RARE, mage.cards.c.CropRotation.class)); cards.add(new SetCardInfo("Culling the Weak", "2023-8", Rarity.RARE, mage.cards.c.CullingTheWeak.class)); @@ -39,22 +41,27 @@ public class MediaAndCollaborationPromos extends ExpansionSet { cards.add(new SetCardInfo("Dark Ritual", "2020-4", Rarity.RARE, mage.cards.d.DarkRitual.class)); cards.add(new SetCardInfo("Darksteel Juggernaut", "2010-1", Rarity.RARE, mage.cards.d.DarksteelJuggernaut.class)); cards.add(new SetCardInfo("Daxos, Blessed by the Sun", "2020-2", Rarity.UNCOMMON, mage.cards.d.DaxosBlessedByTheSun.class)); - cards.add(new SetCardInfo("Diabolic Edict", "2024-5", Rarity.RARE, mage.cards.d.DiabolicEdict.class)); + cards.add(new SetCardInfo("Diabolic Edict", "2019-2", Rarity.RARE, mage.cards.d.DiabolicEdict.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Diabolic Edict", "2024-5", Rarity.RARE, mage.cards.d.DiabolicEdict.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Disenchant", "2022-1", Rarity.RARE, mage.cards.d.Disenchant.class)); - cards.add(new SetCardInfo("Duress", "2025-7", Rarity.RARE, mage.cards.d.Duress.class)); + cards.add(new SetCardInfo("Duress", "2019-6", Rarity.RARE, mage.cards.d.Duress.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Duress", "2025-7", Rarity.RARE, mage.cards.d.Duress.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fireball", "1995-1", Rarity.COMMON, mage.cards.f.Fireball.class, RETRO_ART)); cards.add(new SetCardInfo("Frantic Search", "2022-4", Rarity.RARE, mage.cards.f.FranticSearch.class)); cards.add(new SetCardInfo("Gingerbrute", "2023-3", Rarity.RARE, mage.cards.g.Gingerbrute.class, RETRO_ART)); cards.add(new SetCardInfo("Gush", "2024-4", Rarity.RARE, mage.cards.g.Gush.class)); cards.add(new SetCardInfo("Harald, King of Skemfar", "2021-3", Rarity.RARE, mage.cards.h.HaraldKingOfSkemfar.class)); cards.add(new SetCardInfo("Heliod's Pilgrim", "2020-6", Rarity.RARE, mage.cards.h.HeliodsPilgrim.class)); + cards.add(new SetCardInfo("Huntmaster of the Fells", "2025-17", Rarity.RARE, mage.cards.h.HuntmasterOfTheFells.class)); cards.add(new SetCardInfo("Hypnotic Sprite", "2019-5", Rarity.RARE, mage.cards.h.HypnoticSprite.class)); + cards.add(new SetCardInfo("Iron Spider, Stark Upgrade", "2025-18", Rarity.RARE, mage.cards.i.IronSpiderStarkUpgrade.class)); cards.add(new SetCardInfo("Jace Beleren", "2009-1", Rarity.MYTHIC, mage.cards.j.JaceBeleren.class)); cards.add(new SetCardInfo("Jace, Memory Adept", "2024-2", Rarity.MYTHIC, mage.cards.j.JaceMemoryAdept.class)); cards.add(new SetCardInfo("Jamuraan Lion", "1996-3", Rarity.COMMON, mage.cards.j.JamuraanLion.class, RETRO_ART)); cards.add(new SetCardInfo("Kuldotha Phoenix", "2010-5", Rarity.RARE, mage.cards.k.KuldothaPhoenix.class)); cards.add(new SetCardInfo("Lava Coil", "2019-4", Rarity.UNCOMMON, mage.cards.l.LavaCoil.class)); cards.add(new SetCardInfo("Lightning Hounds", "2000-1", Rarity.COMMON, mage.cards.l.LightningHounds.class, RETRO_ART)); + cards.add(new SetCardInfo("Lightning, Security Sergeant", "2025-12", Rarity.RARE, mage.cards.l.LightningSecuritySergeant.class)); cards.add(new SetCardInfo("Liliana of the Dark Realms", "2024-8", Rarity.MYTHIC, mage.cards.l.LilianaOfTheDarkRealms.class)); cards.add(new SetCardInfo("Mental Misstep", "2023-1", Rarity.RARE, mage.cards.m.MentalMisstep.class)); cards.add(new SetCardInfo("Nicol Bolas, Planeswalker", "2025-10", Rarity.MYTHIC, mage.cards.n.NicolBolasPlaneswalker.class)); @@ -63,15 +70,19 @@ public class MediaAndCollaborationPromos extends ExpansionSet { cards.add(new SetCardInfo("Phantasmal Dragon", "2011-1", Rarity.UNCOMMON, mage.cards.p.PhantasmalDragon.class)); cards.add(new SetCardInfo("Phyrexian Rager", "2000-5", Rarity.COMMON, mage.cards.p.PhyrexianRager.class, RETRO_ART)); cards.add(new SetCardInfo("Pyromancer's Gauntlet", "2023-6", Rarity.RARE, mage.cards.p.PyromancersGauntlet.class, RETRO_ART)); + cards.add(new SetCardInfo("Ravager of the Fells", "2025-17", Rarity.RARE, mage.cards.r.RavagerOfTheFells.class)); cards.add(new SetCardInfo("Ruin Crab", "2023-4", Rarity.RARE, mage.cards.r.RuinCrab.class, RETRO_ART)); cards.add(new SetCardInfo("Sandbar Crocodile", "1996-1", Rarity.COMMON, mage.cards.s.SandbarCrocodile.class, RETRO_ART)); cards.add(new SetCardInfo("Scent of Cinder", "1999-1", Rarity.COMMON, mage.cards.s.ScentOfCinder.class, RETRO_ART)); + cards.add(new SetCardInfo("Sephiroth, Planet's Heir", "2025-14", Rarity.MYTHIC, mage.cards.s.SephirothPlanetsHeir.class)); cards.add(new SetCardInfo("Shield Wall", "1997-3", Rarity.COMMON, mage.cards.s.ShieldWall.class, RETRO_ART)); cards.add(new SetCardInfo("Shivan Dragon", "2001-2", Rarity.RARE, mage.cards.s.ShivanDragon.class, RETRO_ART)); - cards.add(new SetCardInfo("Shock", "2025-1", Rarity.RARE, mage.cards.s.Shock.class)); + cards.add(new SetCardInfo("Shock", "2019-3", Rarity.RARE, mage.cards.s.Shock.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shock", "2025-1", Rarity.RARE, mage.cards.s.Shock.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shrieking Drake", "1997-2", Rarity.COMMON, mage.cards.s.ShriekingDrake.class, RETRO_ART)); cards.add(new SetCardInfo("Silver Drake", "2000-2", Rarity.COMMON, mage.cards.s.SilverDrake.class, RETRO_ART)); cards.add(new SetCardInfo("Snuff Out", "2024-1", Rarity.RARE, mage.cards.s.SnuffOut.class)); + cards.add(new SetCardInfo("Spectacular Spider-Man", "2025-16", Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class)); cards.add(new SetCardInfo("Spined Wurm", "2001-1", Rarity.COMMON, mage.cards.s.SpinedWurm.class, RETRO_ART)); cards.add(new SetCardInfo("Sprite Dragon", "2020-5", Rarity.RARE, mage.cards.s.SpriteDragon.class)); cards.add(new SetCardInfo("Staggering Insight", "2020-3", Rarity.RARE, mage.cards.s.StaggeringInsight.class)); @@ -79,8 +90,10 @@ public class MediaAndCollaborationPromos extends ExpansionSet { cards.add(new SetCardInfo("Talruum Champion", "1997-1", Rarity.COMMON, mage.cards.t.TalruumChampion.class, RETRO_ART)); cards.add(new SetCardInfo("Tangled Florahedron", "2020-8", Rarity.UNCOMMON, mage.cards.t.TangledFlorahedron.class)); cards.add(new SetCardInfo("Thorn Elemental", "2000-3", Rarity.RARE, mage.cards.t.ThornElemental.class, RETRO_ART)); + cards.add(new SetCardInfo("Ultimecia, Temporal Threat", "2025-11", Rarity.RARE, mage.cards.u.UltimeciaTemporalThreat.class)); cards.add(new SetCardInfo("Usher of the Fallen", "2022-3", Rarity.RARE, mage.cards.u.UsherOfTheFallen.class)); - cards.add(new SetCardInfo("Voltaic Key", "2024-6", Rarity.RARE, mage.cards.v.VoltaicKey.class)); + cards.add(new SetCardInfo("Voltaic Key", "2020-1", Rarity.RARE, mage.cards.v.VoltaicKey.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Voltaic Key", "2024-6", Rarity.RARE, mage.cards.v.VoltaicKey.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Warmonger", "1999-2", Rarity.UNCOMMON, mage.cards.w.Warmonger.class, RETRO_ART)); cards.add(new SetCardInfo("Wild Growth", "2022-2", Rarity.RARE, mage.cards.w.WildGrowth.class)); cards.add(new SetCardInfo("Winged Boots", "2023-7", Rarity.RARE, mage.cards.w.WingedBoots.class)); diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java index 5054600618e..6c51be43bc5 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java @@ -244,6 +244,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Selesnya Sanctuary", 290, Rarity.UNCOMMON, mage.cards.s.SelesnyaSanctuary.class)); cards.add(new SetCardInfo("Selfless Squire", 82, Rarity.RARE, mage.cards.s.SelflessSquire.class)); cards.add(new SetCardInfo("Selvala, Explorer Returned", 218, Rarity.RARE, mage.cards.s.SelvalaExplorerReturned.class)); + cards.add(new SetCardInfo("Serene Sleuth", 14, Rarity.RARE, mage.cards.s.SereneSleuth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Serene Sleuth", 325, Rarity.RARE, mage.cards.s.SereneSleuth.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sevinne's Reclamation", 83, Rarity.RARE, mage.cards.s.SevinnesReclamation.class)); cards.add(new SetCardInfo("Sheltered Thicket", 291, Rarity.RARE, mage.cards.s.ShelteredThicket.class)); cards.add(new SetCardInfo("Shimmer Dragon", 117, Rarity.RARE, mage.cards.s.ShimmerDragon.class)); @@ -311,6 +313,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Twilight Prophet", 143, Rarity.MYTHIC, mage.cards.t.TwilightProphet.class)); cards.add(new SetCardInfo("Ugin's Mastery", 53, Rarity.RARE, mage.cards.u.UginsMastery.class)); cards.add(new SetCardInfo("Ulvenwald Mysteries", 193, Rarity.UNCOMMON, mage.cards.u.UlvenwaldMysteries.class)); + cards.add(new SetCardInfo("Unexplained Absence", 17, Rarity.RARE, mage.cards.u.UnexplainedAbsence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Unexplained Absence", 328, Rarity.RARE, mage.cards.u.UnexplainedAbsence.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Unshakable Tail", 29, Rarity.RARE, mage.cards.u.UnshakableTail.class)); cards.add(new SetCardInfo("Vengeful Ancestor", 163, Rarity.RARE, mage.cards.v.VengefulAncestor.class)); cards.add(new SetCardInfo("Vizier of Many Faces", 123, Rarity.RARE, mage.cards.v.VizierOfManyFaces.class)); diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java index 2623fcab354..eeaeff0a5c7 100644 --- a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java @@ -126,6 +126,7 @@ public final class OutlawsOfThunderJunctionCommander extends ExpansionSet { cards.add(new SetCardInfo("Finale of Promise", 166, Rarity.MYTHIC, mage.cards.f.FinaleOfPromise.class)); cards.add(new SetCardInfo("Finale of Revelation", 97, Rarity.MYTHIC, mage.cards.f.FinaleOfRevelation.class)); cards.add(new SetCardInfo("Flooded Grove", 297, Rarity.RARE, mage.cards.f.FloodedGrove.class)); + cards.add(new SetCardInfo("Forger's Foundry", 14, Rarity.RARE, mage.cards.f.ForgersFoundry.class)); cards.add(new SetCardInfo("Frostboil Snarl", 298, Rarity.RARE, mage.cards.f.FrostboilSnarl.class)); cards.add(new SetCardInfo("Galvanic Iteration", 227, Rarity.RARE, mage.cards.g.GalvanicIteration.class)); cards.add(new SetCardInfo("Genesis Hydra", 193, Rarity.RARE, mage.cards.g.GenesisHydra.class)); diff --git a/Mage.Sets/src/mage/sets/PortalThreeKingdoms.java b/Mage.Sets/src/mage/sets/PortalThreeKingdoms.java index f1f31081167..bdc621429af 100644 --- a/Mage.Sets/src/mage/sets/PortalThreeKingdoms.java +++ b/Mage.Sets/src/mage/sets/PortalThreeKingdoms.java @@ -85,7 +85,7 @@ public final class PortalThreeKingdoms extends ExpansionSet { cards.add(new SetCardInfo("Island", 170, Rarity.LAND, mage.cards.basiclands.Island.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 171, Rarity.LAND, mage.cards.basiclands.Island.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Kongming's Contraptions", 10, Rarity.RARE, mage.cards.k.KongmingsContraptions.class, RETRO_ART)); - cards.add(new SetCardInfo("Kongming, Sleeping Dragon", 9, Rarity.RARE, mage.cards.k.KongmingSleepingDragon.class, RETRO_ART)); + cards.add(new SetCardInfo("Kongming, \"Sleeping Dragon\"", 9, Rarity.RARE, mage.cards.k.KongmingSleepingDragon.class, RETRO_ART)); cards.add(new SetCardInfo("Lady Sun", 45, Rarity.RARE, mage.cards.l.LadySun.class, RETRO_ART)); cards.add(new SetCardInfo("Lady Zhurong, Warrior Queen", 139, Rarity.RARE, mage.cards.l.LadyZhurongWarriorQueen.class, RETRO_ART)); cards.add(new SetCardInfo("Liu Bei, Lord of Shu", 11, Rarity.RARE, mage.cards.l.LiuBeiLordOfShu.class, RETRO_ART)); @@ -106,7 +106,7 @@ public final class PortalThreeKingdoms extends ExpansionSet { cards.add(new SetCardInfo("Mountain Bandit", 117, Rarity.COMMON, mage.cards.m.MountainBandit.class, RETRO_ART)); cards.add(new SetCardInfo("Mystic Denial", 49, Rarity.UNCOMMON, mage.cards.m.MysticDenial.class, RETRO_ART)); cards.add(new SetCardInfo("Overwhelming Forces", 79, Rarity.RARE, mage.cards.o.OverwhelmingForces.class, RETRO_ART)); - cards.add(new SetCardInfo("Pang Tong, Young Phoenix", 14, Rarity.RARE, mage.cards.p.PangTongYoungPhoenix.class, RETRO_ART)); + cards.add(new SetCardInfo("Pang Tong, \"Young Phoenix\"", 14, Rarity.RARE, mage.cards.p.PangTongYoungPhoenix.class, RETRO_ART)); cards.add(new SetCardInfo("Peach Garden Oath", 15, Rarity.UNCOMMON, mage.cards.p.PeachGardenOath.class, RETRO_ART)); cards.add(new SetCardInfo("Plains", 166, Rarity.LAND, mage.cards.basiclands.Plains.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 167, Rarity.LAND, mage.cards.basiclands.Plains.class, RETRO_ART_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/ProTourPromos.java b/Mage.Sets/src/mage/sets/ProTourPromos.java index 5f3ba1eb777..2ae9e4e7546 100644 --- a/Mage.Sets/src/mage/sets/ProTourPromos.java +++ b/Mage.Sets/src/mage/sets/ProTourPromos.java @@ -27,10 +27,12 @@ public final class ProTourPromos extends ExpansionSet { * https://github.com/magefree/mage/pull/6190#issuecomment-582353697 * https://github.com/magefree/mage/pull/6190#issuecomment-582354790 */ + cards.add(new SetCardInfo("Aerith Gainsborough", "2025-3", Rarity.RARE, mage.cards.a.AerithGainsborough.class)); cards.add(new SetCardInfo("Aether Vial", "2020-3", Rarity.RARE, mage.cards.a.AetherVial.class)); cards.add(new SetCardInfo("Ajani Goldmane", 2011, Rarity.MYTHIC, mage.cards.a.AjaniGoldmane.class)); cards.add(new SetCardInfo("Arcbound Ravager", 2019, Rarity.RARE, mage.cards.a.ArcboundRavager.class)); cards.add(new SetCardInfo("Avatar of Woe", 2010, Rarity.RARE, mage.cards.a.AvatarOfWoe.class)); + cards.add(new SetCardInfo("Cloud, Midgar Mercenary", "2025-1", Rarity.MYTHIC, mage.cards.c.CloudMidgarMercenary.class)); cards.add(new SetCardInfo("Cryptic Command", "2020-1", Rarity.RARE, mage.cards.c.CrypticCommand.class)); cards.add(new SetCardInfo("Emrakul, the Aeons Torn", 2017, Rarity.MYTHIC, mage.cards.e.EmrakulTheAeonsTorn.class)); cards.add(new SetCardInfo("Eternal Dragon", 2007, Rarity.RARE, mage.cards.e.EternalDragon.class)); diff --git a/Mage.Sets/src/mage/sets/RegionalChampionshipQualifiers2022.java b/Mage.Sets/src/mage/sets/RegionalChampionshipQualifiers2022.java new file mode 100644 index 00000000000..5a0a537eef1 --- /dev/null +++ b/Mage.Sets/src/mage/sets/RegionalChampionshipQualifiers2022.java @@ -0,0 +1,27 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/prcq + */ +public class RegionalChampionshipQualifiers2022 extends ExpansionSet { + + private static final RegionalChampionshipQualifiers2022 instance = new RegionalChampionshipQualifiers2022(); + + public static RegionalChampionshipQualifiers2022 getInstance() { + return instance; + } + + private RegionalChampionshipQualifiers2022() { + super("Regional Championship Qualifiers 2022", "PRCQ", ExpansionSet.buildDate(2022, 10, 1), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Gideon, Ally of Zendikar", 1, Rarity.MYTHIC, mage.cards.g.GideonAllyOfZendikar.class)); + cards.add(new SetCardInfo("Selfless Spirit", 2, Rarity.RARE, mage.cards.s.SelflessSpirit.class)); + cards.add(new SetCardInfo("Thraben Inspector", 3, Rarity.RARE, mage.cards.t.ThrabenInspector.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/Renaissance.java b/Mage.Sets/src/mage/sets/Renaissance.java new file mode 100644 index 00000000000..52fdb54e148 --- /dev/null +++ b/Mage.Sets/src/mage/sets/Renaissance.java @@ -0,0 +1,152 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * German and French versions of Renaissance + *

+ * https://scryfall.com/sets/ren + * https://mtg.wiki/page/Renaissance + * + * @author JayDi85 + */ +public final class Renaissance extends ExpansionSet { + + private static final Renaissance instance = new Renaissance(); + + public static Renaissance getInstance() { + return instance; + } + + private Renaissance() { + super("Renaissance", "REN", ExpansionSet.buildDate(1995, 8, 1), SetType.SUPPLEMENTAL); + this.hasBasicLands = true; + + this.enableCollectorBooster(Integer.MAX_VALUE, 0, 6, 2, 0); + + cards.add(new SetCardInfo("Abomination", 46, Rarity.UNCOMMON, mage.cards.a.Abomination.class, RETRO_ART)); + cards.add(new SetCardInfo("Alabaster Potion", 2, Rarity.COMMON, mage.cards.a.AlabasterPotion.class, RETRO_ART)); + cards.add(new SetCardInfo("Ali Baba", 71, Rarity.UNCOMMON, mage.cards.a.AliBaba.class, RETRO_ART)); + cards.add(new SetCardInfo("Amrou Kithkin", 3, Rarity.COMMON, mage.cards.a.AmrouKithkin.class, RETRO_ART)); + cards.add(new SetCardInfo("Amulet of Kroog", 99, Rarity.COMMON, mage.cards.a.AmuletOfKroog.class, RETRO_ART)); + cards.add(new SetCardInfo("Angry Mob", 4, Rarity.UNCOMMON, mage.cards.a.AngryMob.class, RETRO_ART)); + cards.add(new SetCardInfo("Apprentice Wizard", 23, Rarity.UNCOMMON, mage.cards.a.ApprenticeWizard.class, RETRO_ART)); + cards.add(new SetCardInfo("Ashes to Ashes", 47, Rarity.COMMON, mage.cards.a.AshesToAshes.class, RETRO_ART)); + cards.add(new SetCardInfo("Ashnod's Battle Gear", 104, Rarity.UNCOMMON, mage.cards.a.AshnodsBattleGear.class, RETRO_ART)); + cards.add(new SetCardInfo("Backfire", 24, Rarity.UNCOMMON, mage.cards.b.Backfire.class, RETRO_ART)); + cards.add(new SetCardInfo("Ball Lightning", 73, Rarity.UNCOMMON, mage.cards.b.BallLightning.class, RETRO_ART)); + cards.add(new SetCardInfo("Battering Ram", 107, Rarity.COMMON, mage.cards.b.BatteringRam.class, RETRO_ART)); + cards.add(new SetCardInfo("Bird Maiden", 74, Rarity.COMMON, mage.cards.b.BirdMaiden.class, RETRO_ART)); + cards.add(new SetCardInfo("Black Mana Battery", 108, Rarity.UNCOMMON, mage.cards.b.BlackManaBattery.class, RETRO_ART)); + cards.add(new SetCardInfo("Blight", 48, Rarity.UNCOMMON, mage.cards.b.Blight.class, RETRO_ART)); + cards.add(new SetCardInfo("Blood Lust", 75, Rarity.UNCOMMON, mage.cards.b.BloodLust.class, RETRO_ART)); + cards.add(new SetCardInfo("Blue Mana Battery", 112, Rarity.UNCOMMON, mage.cards.b.BlueManaBattery.class, RETRO_ART)); + cards.add(new SetCardInfo("Bog Imp", 49, Rarity.COMMON, mage.cards.b.BogImp.class, RETRO_ART)); + cards.add(new SetCardInfo("Brainwash", 6, Rarity.COMMON, mage.cards.b.Brainwash.class, RETRO_ART)); + //cards.add(new SetCardInfo("Bronze Tablet", 115, Rarity.UNCOMMON, mage.cards.b.BronzeTablet.class, RETRO_ART)); + cards.add(new SetCardInfo("Brothers of Fire", 76, Rarity.UNCOMMON, mage.cards.b.BrothersOfFire.class, RETRO_ART)); + cards.add(new SetCardInfo("Carnivorous Plant", 117, Rarity.COMMON, mage.cards.c.CarnivorousPlant.class, RETRO_ART)); + cards.add(new SetCardInfo("Carrion Ants", 51, Rarity.UNCOMMON, mage.cards.c.CarrionAnts.class, RETRO_ART)); + cards.add(new SetCardInfo("Cave People", 78, Rarity.UNCOMMON, mage.cards.c.CavePeople.class, RETRO_ART)); + cards.add(new SetCardInfo("Circle of Protection: Artifacts", 7, Rarity.UNCOMMON, mage.cards.c.CircleOfProtectionArtifacts.class, RETRO_ART)); + cards.add(new SetCardInfo("Clay Statue", 120, Rarity.COMMON, mage.cards.c.ClayStatue.class, RETRO_ART)); + cards.add(new SetCardInfo("Clockwork Avian", 122, Rarity.UNCOMMON, mage.cards.c.ClockworkAvian.class, RETRO_ART)); + cards.add(new SetCardInfo("Colossus of Sardia", 123, Rarity.UNCOMMON, mage.cards.c.ColossusOfSardia.class, RETRO_ART)); + cards.add(new SetCardInfo("Coral Helm", 124, Rarity.UNCOMMON, mage.cards.c.CoralHelm.class, RETRO_ART)); + cards.add(new SetCardInfo("Cosmic Horror", 52, Rarity.UNCOMMON, mage.cards.c.CosmicHorror.class, RETRO_ART)); + cards.add(new SetCardInfo("Crimson Manticore", 80, Rarity.UNCOMMON, mage.cards.c.CrimsonManticore.class, RETRO_ART)); + cards.add(new SetCardInfo("Cursed Rack", 126, Rarity.COMMON, mage.cards.c.CursedRack.class, RETRO_ART)); + cards.add(new SetCardInfo("Cyclopean Mummy", 54, Rarity.COMMON, mage.cards.c.CyclopeanMummy.class, RETRO_ART)); + cards.add(new SetCardInfo("Detonate", 83, Rarity.UNCOMMON, mage.cards.d.Detonate.class, RETRO_ART)); + cards.add(new SetCardInfo("Diabolic Machine", 129, Rarity.UNCOMMON, mage.cards.d.DiabolicMachine.class, RETRO_ART)); + cards.add(new SetCardInfo("Divine Transformation", 8, Rarity.UNCOMMON, mage.cards.d.DivineTransformation.class, RETRO_ART)); + cards.add(new SetCardInfo("Durkwood Boars", 131, Rarity.COMMON, mage.cards.d.DurkwoodBoars.class, RETRO_ART)); + cards.add(new SetCardInfo("Elder Land Wurm", 9, Rarity.UNCOMMON, mage.cards.e.ElderLandWurm.class, RETRO_ART)); + cards.add(new SetCardInfo("Elven Riders", 133, Rarity.UNCOMMON, mage.cards.e.ElvenRiders.class, RETRO_ART)); + cards.add(new SetCardInfo("Energy Tap", 28, Rarity.COMMON, mage.cards.e.EnergyTap.class, RETRO_ART)); + cards.add(new SetCardInfo("Erosion", 29, Rarity.COMMON, mage.cards.e.Erosion.class, RETRO_ART)); + cards.add(new SetCardInfo("Eternal Warrior", 84, Rarity.UNCOMMON, mage.cards.e.EternalWarrior.class, RETRO_ART)); + cards.add(new SetCardInfo("Fellwar Stone", 136, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class, RETRO_ART)); + cards.add(new SetCardInfo("Fissure", 85, Rarity.COMMON, mage.cards.f.Fissure.class, RETRO_ART)); + cards.add(new SetCardInfo("Flood", 30, Rarity.UNCOMMON, mage.cards.f.Flood.class, RETRO_ART)); + cards.add(new SetCardInfo("Fortified Area", 11, Rarity.UNCOMMON, mage.cards.f.FortifiedArea.class, RETRO_ART)); + cards.add(new SetCardInfo("Gaseous Form", 32, Rarity.COMMON, mage.cards.g.GaseousForm.class, RETRO_ART)); + cards.add(new SetCardInfo("Ghost Ship", 33, Rarity.COMMON, mage.cards.g.GhostShip.class, RETRO_ART)); + cards.add(new SetCardInfo("Giant Strength", 86, Rarity.COMMON, mage.cards.g.GiantStrength.class, RETRO_ART)); + cards.add(new SetCardInfo("Giant Tortoise", 34, Rarity.COMMON, mage.cards.g.GiantTortoise.class, RETRO_ART)); + cards.add(new SetCardInfo("Goblin Rock Sled", 87, Rarity.COMMON, mage.cards.g.GoblinRockSled.class, RETRO_ART)); + cards.add(new SetCardInfo("Grapeshot Catapult", 143, Rarity.COMMON, mage.cards.g.GrapeshotCatapult.class, RETRO_ART)); + cards.add(new SetCardInfo("Greed", 56, Rarity.UNCOMMON, mage.cards.g.Greed.class, RETRO_ART)); + cards.add(new SetCardInfo("Green Mana Battery", 145, Rarity.UNCOMMON, mage.cards.g.GreenManaBattery.class, RETRO_ART)); + cards.add(new SetCardInfo("Hurr Jackal", 88, Rarity.COMMON, mage.cards.h.HurrJackal.class, RETRO_ART)); + cards.add(new SetCardInfo("Immolation", 89, Rarity.COMMON, mage.cards.i.Immolation.class, RETRO_ART)); + cards.add(new SetCardInfo("Inferno", 90, Rarity.UNCOMMON, mage.cards.i.Inferno.class, RETRO_ART)); + cards.add(new SetCardInfo("Ironclaw Orcs", 91, Rarity.COMMON, mage.cards.i.IronclawOrcs.class, RETRO_ART)); + cards.add(new SetCardInfo("Junun Efreet", 57, Rarity.UNCOMMON, mage.cards.j.JununEfreet.class, RETRO_ART)); + cards.add(new SetCardInfo("Killer Bees", 146, Rarity.UNCOMMON, mage.cards.k.KillerBees.class, RETRO_ART)); + cards.add(new SetCardInfo("Kismet", 12, Rarity.UNCOMMON, mage.cards.k.Kismet.class, RETRO_ART)); + cards.add(new SetCardInfo("Land Leeches", 147, Rarity.COMMON, mage.cards.l.LandLeeches.class, RETRO_ART)); + cards.add(new SetCardInfo("Land Tax", 13, Rarity.UNCOMMON, mage.cards.l.LandTax.class, RETRO_ART)); + cards.add(new SetCardInfo("Leviathan", 36, Rarity.UNCOMMON, mage.cards.l.Leviathan.class, RETRO_ART)); + cards.add(new SetCardInfo("Lost Soul", 58, Rarity.COMMON, mage.cards.l.LostSoul.class, RETRO_ART)); + cards.add(new SetCardInfo("Mana Clash", 92, Rarity.UNCOMMON, mage.cards.m.ManaClash.class, RETRO_ART)); + cards.add(new SetCardInfo("Marsh Gas", 59, Rarity.COMMON, mage.cards.m.MarshGas.class, RETRO_ART)); + cards.add(new SetCardInfo("Marsh Viper", 149, Rarity.COMMON, mage.cards.m.MarshViper.class, RETRO_ART)); + cards.add(new SetCardInfo("Mind Bomb", 37, Rarity.UNCOMMON, mage.cards.m.MindBomb.class, RETRO_ART)); + cards.add(new SetCardInfo("Mishra's Factory", 187, Rarity.UNCOMMON, mage.cards.m.MishrasFactory.class, RETRO_ART)); + cards.add(new SetCardInfo("Morale", 15, Rarity.COMMON, mage.cards.m.Morale.class, RETRO_ART)); + cards.add(new SetCardInfo("Murk Dwellers", 62, Rarity.COMMON, mage.cards.m.MurkDwellers.class, RETRO_ART)); + cards.add(new SetCardInfo("Nafs Asp", 151, Rarity.COMMON, mage.cards.n.NafsAsp.class, RETRO_ART)); + cards.add(new SetCardInfo("Oasis", 188, Rarity.UNCOMMON, mage.cards.o.Oasis.class, RETRO_ART)); + cards.add(new SetCardInfo("Osai Vultures", 16, Rarity.COMMON, mage.cards.o.OsaiVultures.class, RETRO_ART)); + cards.add(new SetCardInfo("Piety", 17, Rarity.COMMON, mage.cards.p.Piety.class, RETRO_ART)); + cards.add(new SetCardInfo("Pikemen", 18, Rarity.COMMON, mage.cards.p.Pikemen.class, RETRO_ART)); + cards.add(new SetCardInfo("Pit Scorpion", 63, Rarity.COMMON, mage.cards.p.PitScorpion.class, RETRO_ART)); + cards.add(new SetCardInfo("Pradesh Gypsies", 152, Rarity.UNCOMMON, mage.cards.p.PradeshGypsies.class, RETRO_ART)); + cards.add(new SetCardInfo("Psionic Entity", 38, Rarity.UNCOMMON, mage.cards.p.PsionicEntity.class, RETRO_ART)); + cards.add(new SetCardInfo("Pyrotechnics", 93, Rarity.COMMON, mage.cards.p.Pyrotechnics.class, RETRO_ART)); + cards.add(new SetCardInfo("Radjan Spirit", 153, Rarity.UNCOMMON, mage.cards.r.RadjanSpirit.class, RETRO_ART)); + cards.add(new SetCardInfo("Rag Man", 64, Rarity.UNCOMMON, mage.cards.r.RagMan.class, RETRO_ART)); + //cards.add(new SetCardInfo("Rebirth", 154, Rarity.UNCOMMON, mage.cards.r.Rebirth.class, RETRO_ART)); + cards.add(new SetCardInfo("Red Mana Battery", 155, Rarity.UNCOMMON, mage.cards.r.RedManaBattery.class, RETRO_ART)); + cards.add(new SetCardInfo("Relic Bind", 39, Rarity.UNCOMMON, mage.cards.r.RelicBind.class, RETRO_ART)); + cards.add(new SetCardInfo("Sandstorm", 156, Rarity.COMMON, mage.cards.s.Sandstorm.class, RETRO_ART)); + cards.add(new SetCardInfo("Seeker", 19, Rarity.UNCOMMON, mage.cards.s.Seeker.class, RETRO_ART)); + cards.add(new SetCardInfo("Segovian Leviathan", 40, Rarity.UNCOMMON, mage.cards.s.SegovianLeviathan.class, RETRO_ART)); + cards.add(new SetCardInfo("Shapeshifter", 157, Rarity.UNCOMMON, mage.cards.s.Shapeshifter.class, RETRO_ART)); + cards.add(new SetCardInfo("Sindbad", 41, Rarity.UNCOMMON, mage.cards.s.Sindbad.class, RETRO_ART)); + cards.add(new SetCardInfo("Sisters of the Flame", 94, Rarity.UNCOMMON, mage.cards.s.SistersOfTheFlame.class, RETRO_ART)); + cards.add(new SetCardInfo("Spirit Link", 20, Rarity.UNCOMMON, mage.cards.s.SpiritLink.class, RETRO_ART)); + cards.add(new SetCardInfo("Spirit Shackle", 65, Rarity.COMMON, mage.cards.s.SpiritShackle.class, RETRO_ART)); + cards.add(new SetCardInfo("Strip Mine", 189, Rarity.UNCOMMON, mage.cards.s.StripMine.class, RETRO_ART)); + cards.add(new SetCardInfo("Sunken City", 42, Rarity.COMMON, mage.cards.s.SunkenCity.class, RETRO_ART)); + cards.add(new SetCardInfo("Sylvan Library", 158, Rarity.UNCOMMON, mage.cards.s.SylvanLibrary.class, RETRO_ART)); + cards.add(new SetCardInfo("Tawnos's Wand", 159, Rarity.UNCOMMON, mage.cards.t.TawnossWand.class, RETRO_ART)); + cards.add(new SetCardInfo("Tawnos's Weaponry", 160, Rarity.UNCOMMON, mage.cards.t.TawnossWeaponry.class, RETRO_ART)); + //cards.add(new SetCardInfo("Tempest Efreet", 95, Rarity.UNCOMMON, mage.cards.t.TempestEfreet.class, RETRO_ART)); + cards.add(new SetCardInfo("Tetravus", 161, Rarity.UNCOMMON, mage.cards.t.Tetravus.class, RETRO_ART)); + cards.add(new SetCardInfo("The Brute", 96, Rarity.COMMON, mage.cards.t.TheBrute.class, RETRO_ART)); + cards.add(new SetCardInfo("Time Elemental", 43, Rarity.UNCOMMON, mage.cards.t.TimeElemental.class, RETRO_ART)); + cards.add(new SetCardInfo("Triskelion", 162, Rarity.UNCOMMON, mage.cards.t.Triskelion.class, RETRO_ART)); + cards.add(new SetCardInfo("Tundra Wolves", 21, Rarity.COMMON, mage.cards.t.TundraWolves.class, RETRO_ART)); + cards.add(new SetCardInfo("Twiddle", 44, Rarity.COMMON, mage.cards.t.Twiddle.class, RETRO_ART)); + cards.add(new SetCardInfo("Uncle Istvan", 66, Rarity.UNCOMMON, mage.cards.u.UncleIstvan.class, RETRO_ART)); + cards.add(new SetCardInfo("Untamed Wilds", 163, Rarity.UNCOMMON, mage.cards.u.UntamedWilds.class, RETRO_ART)); + cards.add(new SetCardInfo("Urza's Avenger", 164, Rarity.UNCOMMON, mage.cards.u.UrzasAvenger.class, RETRO_ART)); + cards.add(new SetCardInfo("Vampire Bats", 67, Rarity.COMMON, mage.cards.v.VampireBats.class, RETRO_ART)); + cards.add(new SetCardInfo("Venom", 165, Rarity.COMMON, mage.cards.v.Venom.class, RETRO_ART)); + cards.add(new SetCardInfo("Visions", 22, Rarity.UNCOMMON, mage.cards.v.Visions.class, RETRO_ART)); + cards.add(new SetCardInfo("Wall of Dust", 97, Rarity.UNCOMMON, mage.cards.w.WallOfDust.class, RETRO_ART)); + cards.add(new SetCardInfo("Wall of Spears", 166, Rarity.UNCOMMON, mage.cards.w.WallOfSpears.class, RETRO_ART)); + cards.add(new SetCardInfo("Whirling Dervish", 167, Rarity.UNCOMMON, mage.cards.w.WhirlingDervish.class, RETRO_ART)); + cards.add(new SetCardInfo("White Mana Battery", 168, Rarity.UNCOMMON, mage.cards.w.WhiteManaBattery.class, RETRO_ART)); + cards.add(new SetCardInfo("Winds of Change", 98, Rarity.UNCOMMON, mage.cards.w.WindsOfChange.class, RETRO_ART)); + cards.add(new SetCardInfo("Winter Blast", 169, Rarity.UNCOMMON, mage.cards.w.WinterBlast.class, RETRO_ART)); + cards.add(new SetCardInfo("Word of Binding", 68, Rarity.COMMON, mage.cards.w.WordOfBinding.class, RETRO_ART)); + cards.add(new SetCardInfo("Xenic Poltergeist", 69, Rarity.UNCOMMON, mage.cards.x.XenicPoltergeist.class, RETRO_ART)); + cards.add(new SetCardInfo("Yotian Soldier", 170, Rarity.COMMON, mage.cards.y.YotianSoldier.class, RETRO_ART)); + cards.add(new SetCardInfo("Zephyr Falcon", 45, Rarity.COMMON, mage.cards.z.ZephyrFalcon.class, RETRO_ART)); + } +} diff --git a/Mage.Sets/src/mage/sets/Rinascimento.java b/Mage.Sets/src/mage/sets/Rinascimento.java new file mode 100644 index 00000000000..8822934f707 --- /dev/null +++ b/Mage.Sets/src/mage/sets/Rinascimento.java @@ -0,0 +1,99 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * Italian version of Renaissance + *

+ * https://scryfall.com/sets/rin + * https://mtg.wiki/page/Renaissance + * + * @author JayDi85 + */ +public final class Rinascimento extends ExpansionSet { + + private static final Rinascimento instance = new Rinascimento(); + + public static Rinascimento getInstance() { + return instance; + } + + private Rinascimento() { + super("Rinascimento", "RIN", ExpansionSet.buildDate(1995, 8, 1), SetType.SUPPLEMENTAL); + this.hasBasicLands = true; + + this.enableCollectorBooster(Integer.MAX_VALUE, 0, 6, 2, 0); + + cards.add(new SetCardInfo("Abu Ja'far", 1, Rarity.UNCOMMON, mage.cards.a.AbuJafar.class, RETRO_ART)); + cards.add(new SetCardInfo("Aladdin", 70, Rarity.UNCOMMON, mage.cards.a.Aladdin.class, RETRO_ART)); + cards.add(new SetCardInfo("Ali Baba", 71, Rarity.UNCOMMON, mage.cards.a.AliBaba.class, RETRO_ART)); + cards.add(new SetCardInfo("Amulet of Kroog", 99, Rarity.COMMON, mage.cards.a.AmuletOfKroog.class, RETRO_ART)); + cards.add(new SetCardInfo("Argothian Pixies", 100, Rarity.COMMON, mage.cards.a.ArgothianPixies.class, RETRO_ART)); + cards.add(new SetCardInfo("Ashnod's Altar", 101, Rarity.UNCOMMON, mage.cards.a.AshnodsAltar.class, RETRO_ART)); + cards.add(new SetCardInfo("Ashnod's Battle Gear", 102, Rarity.UNCOMMON, mage.cards.a.AshnodsBattleGear.class, RETRO_ART)); + cards.add(new SetCardInfo("Ashnod's Transmogrant", 103, Rarity.UNCOMMON, mage.cards.a.AshnodsTransmogrant.class, RETRO_ART)); + cards.add(new SetCardInfo("Battering Ram", 105, Rarity.COMMON, mage.cards.b.BatteringRam.class, RETRO_ART)); + cards.add(new SetCardInfo("Bird Maiden", 72, Rarity.COMMON, mage.cards.b.BirdMaiden.class, RETRO_ART)); + //cards.add(new SetCardInfo("Bronze Tablet", 106, Rarity.UNCOMMON, mage.cards.b.BronzeTablet.class, RETRO_ART)); + cards.add(new SetCardInfo("Circle of Protection: Artifacts", 5, Rarity.UNCOMMON, mage.cards.c.CircleOfProtectionArtifacts.class, RETRO_ART)); + cards.add(new SetCardInfo("City of Brass", 171, Rarity.UNCOMMON, mage.cards.c.CityOfBrass.class, RETRO_ART)); + cards.add(new SetCardInfo("Clay Statue", 109, Rarity.COMMON, mage.cards.c.ClayStatue.class, RETRO_ART)); + cards.add(new SetCardInfo("Clockwork Avian", 110, Rarity.UNCOMMON, mage.cards.c.ClockworkAvian.class, RETRO_ART)); + cards.add(new SetCardInfo("Colossus of Sardia", 111, Rarity.UNCOMMON, mage.cards.c.ColossusOfSardia.class, RETRO_ART)); + cards.add(new SetCardInfo("Coral Helm", 113, Rarity.UNCOMMON, mage.cards.c.CoralHelm.class, RETRO_ART)); + cards.add(new SetCardInfo("Cuombajj Witches", 50, Rarity.COMMON, mage.cards.c.CuombajjWitches.class, RETRO_ART)); + cards.add(new SetCardInfo("Cursed Rack", 114, Rarity.COMMON, mage.cards.c.CursedRack.class, RETRO_ART)); + cards.add(new SetCardInfo("Cyclone", 116, Rarity.UNCOMMON, mage.cards.c.Cyclone.class, RETRO_ART)); + cards.add(new SetCardInfo("Dandan", 25, Rarity.COMMON, mage.cards.d.Dandan.class, RETRO_ART)); + cards.add(new SetCardInfo("Detonate", 77, Rarity.UNCOMMON, mage.cards.d.Detonate.class, RETRO_ART)); + cards.add(new SetCardInfo("Erhnam Djinn", 118, Rarity.UNCOMMON, mage.cards.e.ErhnamDjinn.class, RETRO_ART)); + cards.add(new SetCardInfo("Feldon's Cane", 119, Rarity.COMMON, mage.cards.f.FeldonsCane.class, RETRO_ART)); + cards.add(new SetCardInfo("Fishliver Oil", 26, Rarity.COMMON, mage.cards.f.FishliverOil.class, RETRO_ART)); + cards.add(new SetCardInfo("Ghazban Ogre", 121, Rarity.COMMON, mage.cards.g.GhazbanOgre.class, RETRO_ART)); + cards.add(new SetCardInfo("Giant Tortoise", 27, Rarity.COMMON, mage.cards.g.GiantTortoise.class, RETRO_ART)); + cards.add(new SetCardInfo("Goblin Artisans", 79, Rarity.UNCOMMON, mage.cards.g.GoblinArtisans.class, RETRO_ART)); + cards.add(new SetCardInfo("Grapeshot Catapult", 125, Rarity.COMMON, mage.cards.g.GrapeshotCatapult.class, RETRO_ART)); + cards.add(new SetCardInfo("Hasran Ogress", 53, Rarity.COMMON, mage.cards.h.HasranOgress.class, RETRO_ART)); + cards.add(new SetCardInfo("Hurr Jackal", 81, Rarity.COMMON, mage.cards.h.HurrJackal.class, RETRO_ART)); + cards.add(new SetCardInfo("Ironclaw Orcs", 82, Rarity.COMMON, mage.cards.i.IronclawOrcs.class, RETRO_ART)); + cards.add(new SetCardInfo("Jalum Tome", 127, Rarity.UNCOMMON, mage.cards.j.JalumTome.class, RETRO_ART)); + //cards.add(new SetCardInfo("Jeweled Bird", 128, Rarity.UNCOMMON, mage.cards.j.JeweledBird.class, RETRO_ART)); + cards.add(new SetCardInfo("Junun Efreet", 55, Rarity.UNCOMMON, mage.cards.j.JununEfreet.class, RETRO_ART)); + cards.add(new SetCardInfo("Metamorphosis", 130, Rarity.COMMON, mage.cards.m.Metamorphosis.class, RETRO_ART)); + cards.add(new SetCardInfo("Mishra's Factory", 172, Rarity.UNCOMMON, mage.cards.m.MishrasFactory.class, RETRO_ART)); + cards.add(new SetCardInfo("Nafs Asp", 132, Rarity.COMMON, mage.cards.n.NafsAsp.class, RETRO_ART)); + cards.add(new SetCardInfo("Oasis", 173, Rarity.UNCOMMON, mage.cards.o.Oasis.class, RETRO_ART)); + cards.add(new SetCardInfo("Obelisk of Undoing", 134, Rarity.UNCOMMON, mage.cards.o.ObeliskOfUndoing.class, RETRO_ART)); + cards.add(new SetCardInfo("Rakalite", 135, Rarity.UNCOMMON, mage.cards.r.Rakalite.class, RETRO_ART)); + cards.add(new SetCardInfo("Repentant Blacksmith", 10, Rarity.UNCOMMON, mage.cards.r.RepentantBlacksmith.class, RETRO_ART)); + cards.add(new SetCardInfo("Sandstorm", 137, Rarity.COMMON, mage.cards.s.Sandstorm.class, RETRO_ART)); + cards.add(new SetCardInfo("Shapeshifter", 138, Rarity.UNCOMMON, mage.cards.s.Shapeshifter.class, RETRO_ART)); + cards.add(new SetCardInfo("Sindbad", 31, Rarity.UNCOMMON, mage.cards.s.Sindbad.class, RETRO_ART)); + cards.add(new SetCardInfo("Strip Mine", 174, Rarity.UNCOMMON, mage.cards.s.StripMine.class, RETRO_ART)); + cards.add(new SetCardInfo("Tawnos's Wand", 139, Rarity.UNCOMMON, mage.cards.t.TawnossWand.class, RETRO_ART)); + cards.add(new SetCardInfo("Tawnos's Weaponry", 140, Rarity.UNCOMMON, mage.cards.t.TawnossWeaponry.class, RETRO_ART)); + cards.add(new SetCardInfo("Tetravus", 141, Rarity.UNCOMMON, mage.cards.t.Tetravus.class, RETRO_ART)); + cards.add(new SetCardInfo("Triskelion", 142, Rarity.UNCOMMON, mage.cards.t.Triskelion.class, RETRO_ART)); + cards.add(new SetCardInfo("Twiddle", 35, Rarity.COMMON, mage.cards.t.Twiddle.class, RETRO_ART)); + cards.add(new SetCardInfo("Urza's Avenger", 144, Rarity.UNCOMMON, mage.cards.u.UrzasAvenger.class, RETRO_ART)); + cards.add(new SetCardInfo("Urza's Mine", 175, Rarity.COMMON, mage.cards.u.UrzasMine.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Mine", 176, Rarity.COMMON, mage.cards.u.UrzasMine.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Mine", 177, Rarity.COMMON, mage.cards.u.UrzasMine.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Mine", 178, Rarity.COMMON, mage.cards.u.UrzasMine.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Power Plant", 179, Rarity.COMMON, mage.cards.u.UrzasPowerPlant.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Power Plant", 180, Rarity.COMMON, mage.cards.u.UrzasPowerPlant.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Power Plant", 181, Rarity.COMMON, mage.cards.u.UrzasPowerPlant.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Power Plant", 182, Rarity.COMMON, mage.cards.u.UrzasPowerPlant.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Tower", 183, Rarity.COMMON, mage.cards.u.UrzasTower.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Tower", 184, Rarity.COMMON, mage.cards.u.UrzasTower.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Tower", 185, Rarity.COMMON, mage.cards.u.UrzasTower.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Urza's Tower", 186, Rarity.COMMON, mage.cards.u.UrzasTower.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Wall of Spears", 148, Rarity.UNCOMMON, mage.cards.w.WallOfSpears.class, RETRO_ART)); + cards.add(new SetCardInfo("War Elephant", 14, Rarity.COMMON, mage.cards.w.WarElephant.class, RETRO_ART)); + cards.add(new SetCardInfo("Xenic Poltergeist", 60, Rarity.UNCOMMON, mage.cards.x.XenicPoltergeist.class, RETRO_ART)); + cards.add(new SetCardInfo("Yawgmoth Demon", 61, Rarity.UNCOMMON, mage.cards.y.YawgmothDemon.class, RETRO_ART)); + cards.add(new SetCardInfo("Yotian Soldier", 150, Rarity.COMMON, mage.cards.y.YotianSoldier.class, RETRO_ART)); + } +} diff --git a/Mage.Sets/src/mage/sets/SecretLairDrop.java b/Mage.Sets/src/mage/sets/SecretLairDrop.java index 02ad3042856..108133868e1 100644 --- a/Mage.Sets/src/mage/sets/SecretLairDrop.java +++ b/Mage.Sets/src/mage/sets/SecretLairDrop.java @@ -76,7 +76,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Narset, Enlightened Master", 53, Rarity.MYTHIC, mage.cards.n.NarsetEnlightenedMaster.class)); cards.add(new SetCardInfo("Oona, Queen of the Fae", 54, Rarity.MYTHIC, mage.cards.o.OonaQueenOfTheFae.class)); cards.add(new SetCardInfo("Saskia the Unyielding", 55, Rarity.MYTHIC, mage.cards.s.SaskiaTheUnyielding.class)); - cards.add(new SetCardInfo("Arcbound Ravager", 56, Rarity.RARE, mage.cards.a.ArcboundRavager.class)); + cards.add(new SetCardInfo("Arcbound Ravager", 56, Rarity.RARE, mage.cards.a.ArcboundRavager.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Darksteel Colossus", 57, Rarity.MYTHIC, mage.cards.d.DarksteelColossus.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Walking Ballista", 58, Rarity.RARE, mage.cards.w.WalkingBallista.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Squire", 59, Rarity.RARE, mage.cards.s.Squire.class)); @@ -158,11 +158,11 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Scavenging Ooze", 135, Rarity.RARE, mage.cards.s.ScavengingOoze.class)); cards.add(new SetCardInfo("The Mimeoplasm", 136, Rarity.MYTHIC, mage.cards.t.TheMimeoplasm.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Voidslime", 137, Rarity.RARE, mage.cards.v.Voidslime.class)); - cards.add(new SetCardInfo("Anguished Unmaking", 138, Rarity.RARE, mage.cards.a.AnguishedUnmaking.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Assassin's Trophy", 139, Rarity.RARE, mage.cards.a.AssassinsTrophy.class)); - cards.add(new SetCardInfo("Decimate", 140, Rarity.RARE, mage.cards.d.Decimate.class)); - cards.add(new SetCardInfo("Dreadbore", 141, Rarity.RARE, mage.cards.d.Dreadbore.class)); - cards.add(new SetCardInfo("Thraximundar", 142, Rarity.MYTHIC, mage.cards.t.Thraximundar.class)); + cards.add(new SetCardInfo("Anguished Unmaking", 138, Rarity.RARE, mage.cards.a.AnguishedUnmaking.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Assassin's Trophy", 139, Rarity.RARE, mage.cards.a.AssassinsTrophy.class, FULL_ART)); + cards.add(new SetCardInfo("Decimate", 140, Rarity.RARE, mage.cards.d.Decimate.class, FULL_ART)); + cards.add(new SetCardInfo("Dreadbore", 141, Rarity.RARE, mage.cards.d.Dreadbore.class, FULL_ART)); + cards.add(new SetCardInfo("Thraximundar", 142, Rarity.MYTHIC, mage.cards.t.Thraximundar.class, FULL_ART)); cards.add(new SetCardInfo("Greymond, Avacyn's Stalwart", 143, Rarity.MYTHIC, mage.cards.g.GreymondAvacynsStalwart.class)); cards.add(new SetCardInfo("Hansk, Slayer Zealot", 144, Rarity.MYTHIC, mage.cards.h.HanskSlayerZealot.class)); cards.add(new SetCardInfo("Gregor, Shrewd Magistrate", 145, Rarity.MYTHIC, mage.cards.g.GregorShrewdMagistrate.class)); @@ -193,11 +193,11 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Birds of Paradise", 176, Rarity.RARE, mage.cards.b.BirdsOfParadise.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Howling Mine", 177, Rarity.RARE, mage.cards.h.HowlingMine.class)); cards.add(new SetCardInfo("Wasteland", 178, Rarity.RARE, mage.cards.w.Wasteland.class)); - cards.add(new SetCardInfo("Wrath of God", 185, Rarity.RARE, mage.cards.w.WrathOfGod.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Preordain", 186, Rarity.RARE, mage.cards.p.Preordain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Decree of Pain", 187, Rarity.RARE, mage.cards.d.DecreeOfPain.class)); - cards.add(new SetCardInfo("Gamble", 188, Rarity.RARE, mage.cards.g.Gamble.class)); - cards.add(new SetCardInfo("Nature's Lore", 189, Rarity.RARE, mage.cards.n.NaturesLore.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wrath of God", 185, Rarity.RARE, mage.cards.w.WrathOfGod.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Preordain", 186, Rarity.RARE, mage.cards.p.Preordain.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Decree of Pain", 187, Rarity.RARE, mage.cards.d.DecreeOfPain.class, FULL_ART)); + cards.add(new SetCardInfo("Gamble", 188, Rarity.RARE, mage.cards.g.Gamble.class, FULL_ART)); + cards.add(new SetCardInfo("Nature's Lore", 189, Rarity.RARE, mage.cards.n.NaturesLore.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Soul-Scar Mage", 190, Rarity.RARE, mage.cards.s.SoulScarMage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dryad of the Ilysian Grove", 191, Rarity.RARE, mage.cards.d.DryadOfTheIlysianGrove.class)); cards.add(new SetCardInfo("Sakura-Tribe Elder", 192, Rarity.RARE, mage.cards.s.SakuraTribeElder.class)); @@ -323,10 +323,10 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Ilharg, the Raze-Boar", 318, Rarity.MYTHIC, mage.cards.i.IlhargTheRazeBoar.class)); cards.add(new SetCardInfo("Protean Hulk", 319, Rarity.RARE, mage.cards.p.ProteanHulk.class)); cards.add(new SetCardInfo("Gishath, Sun's Avatar", 320, Rarity.MYTHIC, mage.cards.g.GishathSunsAvatar.class)); - cards.add(new SetCardInfo("Dismember", 321, Rarity.RARE, mage.cards.d.Dismember.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Blasphemous Act", 322, Rarity.RARE, mage.cards.b.BlasphemousAct.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Beast Within", 323, Rarity.RARE, mage.cards.b.BeastWithin.class)); - cards.add(new SetCardInfo("Grafdigger's Cage", 324, Rarity.RARE, mage.cards.g.GrafdiggersCage.class)); + cards.add(new SetCardInfo("Dismember", 321, Rarity.RARE, mage.cards.d.Dismember.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Blasphemous Act", 322, Rarity.RARE, mage.cards.b.BlasphemousAct.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Beast Within", 323, Rarity.RARE, mage.cards.b.BeastWithin.class, FULL_ART)); + cards.add(new SetCardInfo("Grafdigger's Cage", 324, Rarity.RARE, mage.cards.g.GrafdiggersCage.class, FULL_ART)); cards.add(new SetCardInfo("Snow-Covered Plains", 325, Rarity.LAND, mage.cards.s.SnowCoveredPlains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Snow-Covered Island", 326, Rarity.LAND, mage.cards.s.SnowCoveredIsland.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Snow-Covered Swamp", 327, Rarity.LAND, mage.cards.s.SnowCoveredSwamp.class, FULL_ART_BFZ_VARIOUS)); @@ -742,7 +742,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Colossal Dreadmaw", "740*", Rarity.RARE, mage.cards.c.ColossalDreadmaw.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Chaos Warp", 741, Rarity.RARE, mage.cards.c.ChaosWarp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Chaos Warp", "741*", Rarity.RARE, mage.cards.c.ChaosWarp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Command Tower", 744, Rarity.RARE, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 744, Rarity.RARE, mage.cards.c.CommandTower.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Ajani Goldmane", 745, Rarity.MYTHIC, mage.cards.a.AjaniGoldmane.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ajani Goldmane", "745b", Rarity.MYTHIC, mage.cards.a.AjaniGoldmane.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Jace Beleren", 746, Rarity.MYTHIC, mage.cards.j.JaceBeleren.class, NON_FULL_USE_VARIOUS)); @@ -760,7 +760,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Relentless Rats", 755, Rarity.RARE, mage.cards.r.RelentlessRats.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Relentless Rats", 756, Rarity.RARE, mage.cards.r.RelentlessRats.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Relentless Rats", 757, Rarity.RARE, mage.cards.r.RelentlessRats.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Command Tower", 758, Rarity.RARE, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 758, Rarity.RARE, mage.cards.c.CommandTower.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Skemfar Shadowsage", 759, Rarity.RARE, mage.cards.s.SkemfarShadowsage.class)); cards.add(new SetCardInfo("Elvish Champion", 761, Rarity.RARE, mage.cards.e.ElvishChampion.class)); cards.add(new SetCardInfo("Elvish Vanguard", 762, Rarity.RARE, mage.cards.e.ElvishVanguard.class)); @@ -802,7 +802,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Sonic Screwdriver", 803, Rarity.RARE, mage.cards.s.SonicScrewdriver.class)); cards.add(new SetCardInfo("Beloved Princess", 804, Rarity.RARE, mage.cards.b.BelovedPrincess.class)); cards.add(new SetCardInfo("Elvish Mystic", 805, Rarity.RARE, mage.cards.e.ElvishMystic.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Command Tower", 806, Rarity.RARE, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 806, Rarity.RARE, mage.cards.c.CommandTower.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Chandra, Flamecaller", 807, Rarity.MYTHIC, mage.cards.c.ChandraFlamecaller.class)); cards.add(new SetCardInfo("Snapcaster Mage", 808, Rarity.MYTHIC, mage.cards.s.SnapcasterMage.class)); cards.add(new SetCardInfo("Immerwolf", 809, Rarity.RARE, mage.cards.i.Immerwolf.class)); @@ -812,6 +812,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Seven Dwarves", 814, Rarity.RARE, mage.cards.s.SevenDwarves.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Seven Dwarves", 815, Rarity.RARE, mage.cards.s.SevenDwarves.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Seven Dwarves", 816, Rarity.RARE, mage.cards.s.SevenDwarves.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seven Dwarves", 817, Rarity.RARE, mage.cards.s.SevenDwarves.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Arcane Signet", 820, Rarity.RARE, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Arcane Signet", "820*", Rarity.RARE, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Echo of Eons", 821, Rarity.RARE, mage.cards.e.EchoOfEons.class, RETRO_ART)); @@ -823,6 +824,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Norin the Wary", 827, Rarity.RARE, mage.cards.n.NorinTheWary.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Norin the Wary", "827b", Rarity.RARE, mage.cards.n.NorinTheWary.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Keen Duelist", 828, Rarity.RARE, mage.cards.k.KeenDuelist.class)); + cards.add(new SetCardInfo("Cleaver Skaab", 834, Rarity.RARE, mage.cards.c.CleaverSkaab.class, RETRO_ART)); cards.add(new SetCardInfo("Fatestitcher", 835, Rarity.RARE, mage.cards.f.Fatestitcher.class, RETRO_ART)); cards.add(new SetCardInfo("Undead Alchemist", 836, Rarity.RARE, mage.cards.u.UndeadAlchemist.class, RETRO_ART)); cards.add(new SetCardInfo("Champion of the Perished", 837, Rarity.RARE, mage.cards.c.ChampionOfThePerished.class, RETRO_ART)); @@ -835,13 +837,17 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Headless Rider", 844, Rarity.RARE, mage.cards.h.HeadlessRider.class, RETRO_ART)); cards.add(new SetCardInfo("Liliana's Standard Bearer", 845, Rarity.RARE, mage.cards.l.LilianasStandardBearer.class, RETRO_ART)); cards.add(new SetCardInfo("Mikaeus, the Unhallowed", 846, Rarity.MYTHIC, mage.cards.m.MikaeusTheUnhallowed.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Phyrexian Crusader", 847, Rarity.RARE, mage.cards.p.PhyrexianCrusader.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Pontiff of Blight", 848, Rarity.RARE, mage.cards.p.PontiffOfBlight.class, RETRO_ART)); cards.add(new SetCardInfo("Ravenous Rotbelly", 849, Rarity.RARE, mage.cards.r.RavenousRotbelly.class, RETRO_ART)); cards.add(new SetCardInfo("Relentless Dead", 850, Rarity.MYTHIC, mage.cards.r.RelentlessDead.class, RETRO_ART)); + cards.add(new SetCardInfo("Rot Hulk", 851, Rarity.MYTHIC, mage.cards.r.RotHulk.class, RETRO_ART)); cards.add(new SetCardInfo("Rotting Regisaur", 852, Rarity.RARE, mage.cards.r.RottingRegisaur.class, RETRO_ART)); + cards.add(new SetCardInfo("Stitcher's Supplier", 853, Rarity.RARE, mage.cards.s.StitchersSupplier.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Tomb Tyrant", 854, Rarity.RARE, mage.cards.t.TombTyrant.class, RETRO_ART)); cards.add(new SetCardInfo("Tormod, the Desecrator", 855, Rarity.RARE, mage.cards.t.TormodTheDesecrator.class, RETRO_ART)); cards.add(new SetCardInfo("Vindictive Lich", 856, Rarity.RARE, mage.cards.v.VindictiveLich.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Neheb, Dreadhorde Champion", 857, Rarity.RARE, mage.cards.n.NehebDreadhordeChampion.class, RETRO_ART)); cards.add(new SetCardInfo("Diregraf Captain", 858, Rarity.RARE, mage.cards.d.DiregrafCaptain.class, RETRO_ART)); cards.add(new SetCardInfo("Havengul Lich", 859, Rarity.MYTHIC, mage.cards.h.HavengulLich.class, RETRO_ART)); cards.add(new SetCardInfo("Nekusar, the Mindrazer", 860, Rarity.MYTHIC, mage.cards.n.NekusarTheMindrazer.class, RETRO_ART_USE_VARIOUS)); @@ -874,12 +880,12 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Changeling Outcast", 894, Rarity.RARE, mage.cards.c.ChangelingOutcast.class)); cards.add(new SetCardInfo("Helpful Hunter", 895, Rarity.RARE, mage.cards.h.HelpfulHunter.class)); cards.add(new SetCardInfo("Spirited Companion", 896, Rarity.RARE, mage.cards.s.SpiritedCompanion.class)); - cards.add(new SetCardInfo("The Scarab God", 900, Rarity.MYTHIC, mage.cards.t.TheScarabGod.class)); + cards.add(new SetCardInfo("The Scarab God", 900, Rarity.MYTHIC, mage.cards.t.TheScarabGod.class, FULL_ART)); cards.add(new SetCardInfo("Lightning Bolt", 901, Rarity.RARE, mage.cards.l.LightningBolt.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Deadeye Navigator", 902, Rarity.RARE, mage.cards.d.DeadeyeNavigator.class)); cards.add(new SetCardInfo("The Locust God", 903, Rarity.MYTHIC, mage.cards.t.TheLocustGod.class)); cards.add(new SetCardInfo("The Scorpion God", 904, Rarity.MYTHIC, mage.cards.t.TheScorpionGod.class)); - cards.add(new SetCardInfo("Ignoble Hierarch", 906, Rarity.RARE, mage.cards.i.IgnobleHierarch.class)); + cards.add(new SetCardInfo("Ignoble Hierarch", 906, Rarity.RARE, mage.cards.i.IgnobleHierarch.class, FULL_ART)); cards.add(new SetCardInfo("Seedborn Muse", 907, Rarity.RARE, mage.cards.s.SeedbornMuse.class)); cards.add(new SetCardInfo("Arcane Signet", 908, Rarity.RARE, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gilded Lotus", 909, Rarity.RARE, mage.cards.g.GildedLotus.class, NON_FULL_USE_VARIOUS)); @@ -983,7 +989,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Contagion Engine", 1095, Rarity.RARE, mage.cards.c.ContagionEngine.class)); cards.add(new SetCardInfo("Sword of Truth and Justice", 1096, Rarity.MYTHIC, mage.cards.s.SwordOfTruthAndJustice.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Laboratory Maniac", 1097, Rarity.RARE, mage.cards.l.LaboratoryManiac.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Stitcher's Supplier", 1098, Rarity.RARE, mage.cards.s.StitchersSupplier.class)); + cards.add(new SetCardInfo("Stitcher's Supplier", 1098, Rarity.RARE, mage.cards.s.StitchersSupplier.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Beast Whisperer", 1099, Rarity.RARE, mage.cards.b.BeastWhisperer.class)); cards.add(new SetCardInfo("Vizier of the Menagerie", 1100, Rarity.MYTHIC, mage.cards.v.VizierOfTheMenagerie.class)); cards.add(new SetCardInfo("Wood Elves", 1101, Rarity.RARE, mage.cards.w.WoodElves.class)); @@ -1019,10 +1025,10 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 1132, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Mountain", 1133, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forest", 1134, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); - cards.add(new SetCardInfo("Abundant Growth", 1135, Rarity.RARE, mage.cards.a.AbundantGrowth.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mycoloth", 1136, Rarity.RARE, mage.cards.m.Mycoloth.class)); - cards.add(new SetCardInfo("Ghave, Guru of Spores", 1137, Rarity.MYTHIC, mage.cards.g.GhaveGuruOfSpores.class)); - cards.add(new SetCardInfo("Slimefoot, the Stowaway", 1138, Rarity.RARE, mage.cards.s.SlimefootTheStowaway.class)); + cards.add(new SetCardInfo("Abundant Growth", 1135, Rarity.RARE, mage.cards.a.AbundantGrowth.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Mycoloth", 1136, Rarity.RARE, mage.cards.m.Mycoloth.class, FULL_ART)); + cards.add(new SetCardInfo("Ghave, Guru of Spores", 1137, Rarity.MYTHIC, mage.cards.g.GhaveGuruOfSpores.class, FULL_ART)); + cards.add(new SetCardInfo("Slimefoot, the Stowaway", 1138, Rarity.RARE, mage.cards.s.SlimefootTheStowaway.class, FULL_ART)); cards.add(new SetCardInfo("Elspeth, Sun's Champion", 1140, Rarity.MYTHIC, mage.cards.e.ElspethSunsChampion.class)); cards.add(new SetCardInfo("Narset, Parter of Veils", 1141, Rarity.RARE, mage.cards.n.NarsetParterOfVeils.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Garruk Wildspeaker", 1142, Rarity.RARE, mage.cards.g.GarrukWildspeaker.class, NON_FULL_USE_VARIOUS)); @@ -1082,14 +1088,14 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 1192, Rarity.LAND, mage.cards.basiclands.Swamp.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 1193, Rarity.LAND, mage.cards.basiclands.Mountain.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 1194, Rarity.LAND, mage.cards.basiclands.Forest.class, RETRO_ART_USE_VARIOUS)); - cards.add(new SetCardInfo("Phage the Untouchable", 1195, Rarity.MYTHIC, mage.cards.p.PhageTheUntouchable.class)); - cards.add(new SetCardInfo("Yisan, the Wanderer Bard", 1196, Rarity.RARE, mage.cards.y.YisanTheWandererBard.class)); - cards.add(new SetCardInfo("Alela, Artful Provocateur", 1197, Rarity.MYTHIC, mage.cards.a.AlelaArtfulProvocateur.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Sen Triplets", 1198, Rarity.MYTHIC, mage.cards.s.SenTriplets.class)); - cards.add(new SetCardInfo("Tevesh Szat, Doom of Fools", 1199, Rarity.MYTHIC, mage.cards.t.TeveshSzatDoomOfFools.class)); - cards.add(new SetCardInfo("Godo, Bandit Warlord", 1200, Rarity.RARE, mage.cards.g.GodoBanditWarlord.class)); - cards.add(new SetCardInfo("Jeska, Thrice Reborn", 1201, Rarity.MYTHIC, mage.cards.j.JeskaThriceReborn.class)); - cards.add(new SetCardInfo("Vial Smasher the Fierce", 1202, Rarity.MYTHIC, mage.cards.v.VialSmasherTheFierce.class)); + cards.add(new SetCardInfo("Phage the Untouchable", 1195, Rarity.MYTHIC, mage.cards.p.PhageTheUntouchable.class, FULL_ART)); + cards.add(new SetCardInfo("Yisan, the Wanderer Bard", 1196, Rarity.RARE, mage.cards.y.YisanTheWandererBard.class, FULL_ART)); + cards.add(new SetCardInfo("Alela, Artful Provocateur", 1197, Rarity.MYTHIC, mage.cards.a.AlelaArtfulProvocateur.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Sen Triplets", 1198, Rarity.MYTHIC, mage.cards.s.SenTriplets.class, FULL_ART)); + cards.add(new SetCardInfo("Tevesh Szat, Doom of Fools", 1199, Rarity.MYTHIC, mage.cards.t.TeveshSzatDoomOfFools.class, FULL_ART)); + cards.add(new SetCardInfo("Godo, Bandit Warlord", 1200, Rarity.RARE, mage.cards.g.GodoBanditWarlord.class, FULL_ART)); + cards.add(new SetCardInfo("Jeska, Thrice Reborn", 1201, Rarity.MYTHIC, mage.cards.j.JeskaThriceReborn.class, FULL_ART)); + cards.add(new SetCardInfo("Vial Smasher the Fierce", 1202, Rarity.MYTHIC, mage.cards.v.VialSmasherTheFierce.class, FULL_ART)); cards.add(new SetCardInfo("Blighted Agent", 1203, Rarity.RARE, mage.cards.b.BlightedAgent.class)); cards.add(new SetCardInfo("K'rrik, Son of Yawgmoth", 1204, Rarity.RARE, mage.cards.k.KrrikSonOfYawgmoth.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Glistener Elf", 1205, Rarity.RARE, mage.cards.g.GlistenerElf.class)); @@ -1105,14 +1111,14 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Westvale Abbey", 1212, Rarity.RARE, mage.cards.w.WestvaleAbbey.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ormendahl, Profane Prince", 1212, Rarity.RARE, mage.cards.o.OrmendahlProfanePrince.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Phyrexian Unlife", 1213, Rarity.RARE, mage.cards.p.PhyrexianUnlife.class)); - cards.add(new SetCardInfo("Phyrexian Crusader", 1214, Rarity.RARE, mage.cards.p.PhyrexianCrusader.class)); + cards.add(new SetCardInfo("Phyrexian Crusader", 1214, Rarity.RARE, mage.cards.p.PhyrexianCrusader.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plague Engineer", 1215, Rarity.RARE, mage.cards.p.PlagueEngineer.class)); cards.add(new SetCardInfo("Ertai, the Corrupted", 1216, Rarity.RARE, mage.cards.e.ErtaiTheCorrupted.class)); cards.add(new SetCardInfo("Glissa, the Traitor", 1217, Rarity.MYTHIC, mage.cards.g.GlissaTheTraitor.class)); - cards.add(new SetCardInfo("Eldrazi Conscription", 1218, Rarity.RARE, mage.cards.e.EldraziConscription.class)); - cards.add(new SetCardInfo("Deafening Silence", 1219, Rarity.RARE, mage.cards.d.DeafeningSilence.class)); - cards.add(new SetCardInfo("Counterbalance", 1220, Rarity.RARE, mage.cards.c.Counterbalance.class)); - cards.add(new SetCardInfo("Bruna, Light of Alabaster", 1221, Rarity.MYTHIC, mage.cards.b.BrunaLightOfAlabaster.class)); + cards.add(new SetCardInfo("Eldrazi Conscription", 1218, Rarity.RARE, mage.cards.e.EldraziConscription.class, FULL_ART)); + cards.add(new SetCardInfo("Deafening Silence", 1219, Rarity.RARE, mage.cards.d.DeafeningSilence.class, FULL_ART)); + cards.add(new SetCardInfo("Counterbalance", 1220, Rarity.RARE, mage.cards.c.Counterbalance.class, FULL_ART)); + cards.add(new SetCardInfo("Bruna, Light of Alabaster", 1221, Rarity.MYTHIC, mage.cards.b.BrunaLightOfAlabaster.class, FULL_ART)); cards.add(new SetCardInfo("Hexdrinker", 1222, Rarity.MYTHIC, mage.cards.h.Hexdrinker.class)); cards.add(new SetCardInfo("Lotus Cobra", 1223, Rarity.RARE, mage.cards.l.LotusCobra.class)); cards.add(new SetCardInfo("Seshiro the Anointed", 1224, Rarity.RARE, mage.cards.s.SeshiroTheAnointed.class)); @@ -1195,6 +1201,10 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Gaea's Blessing", 1303, Rarity.RARE, mage.cards.g.GaeasBlessing.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Twilight Prophet", 1304, Rarity.MYTHIC, mage.cards.t.TwilightProphet.class)); cards.add(new SetCardInfo("Worldspine Wurm", 1305, Rarity.MYTHIC, mage.cards.w.WorldspineWurm.class)); + cards.add(new SetCardInfo("Pack Rat", 1307, Rarity.RARE, mage.cards.p.PackRat.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shared Summons", 1308, Rarity.RARE, mage.cards.s.SharedSummons.class)); + cards.add(new SetCardInfo("Sylvan Offering", 1309, Rarity.RARE, mage.cards.s.SylvanOffering.class)); + cards.add(new SetCardInfo("Sliver Legion", 1310, Rarity.MYTHIC, mage.cards.s.SliverLegion.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Goblin Lackey", 1311, Rarity.RARE, mage.cards.g.GoblinLackey.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Goblin Lackey", "1311*", Rarity.RARE, mage.cards.g.GoblinLackey.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Goblin Matron", 1312, Rarity.RARE, mage.cards.g.GoblinMatron.class, NON_FULL_USE_VARIOUS)); @@ -1214,14 +1224,14 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Gilt-Leaf Palace", 1322, Rarity.RARE, mage.cards.g.GiltLeafPalace.class)); cards.add(new SetCardInfo("Secluded Glen", 1323, Rarity.RARE, mage.cards.s.SecludedGlen.class)); cards.add(new SetCardInfo("Wanderwine Hub", 1324, Rarity.RARE, mage.cards.w.WanderwineHub.class)); - cards.add(new SetCardInfo("Estrid's Invocation", 1325, Rarity.RARE, mage.cards.e.EstridsInvocation.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Estrid's Invocation", "1325b", Rarity.RARE, mage.cards.e.EstridsInvocation.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Steely Resolve", 1326, Rarity.RARE, mage.cards.s.SteelyResolve.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Steely Resolve", "1326b", Rarity.RARE, mage.cards.s.SteelyResolve.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Estrid, the Masked", 1327, Rarity.MYTHIC, mage.cards.e.EstridTheMasked.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Estrid, the Masked", "1327b", Rarity.MYTHIC, mage.cards.e.EstridTheMasked.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Tuvasa the Sunlit", 1328, Rarity.MYTHIC, mage.cards.t.TuvasaTheSunlit.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Tuvasa the Sunlit", "1328b", Rarity.MYTHIC, mage.cards.t.TuvasaTheSunlit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Estrid's Invocation", 1325, Rarity.RARE, mage.cards.e.EstridsInvocation.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Estrid's Invocation", "1325b", Rarity.RARE, mage.cards.e.EstridsInvocation.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Steely Resolve", 1326, Rarity.RARE, mage.cards.s.SteelyResolve.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Steely Resolve", "1326b", Rarity.RARE, mage.cards.s.SteelyResolve.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Estrid, the Masked", 1327, Rarity.MYTHIC, mage.cards.e.EstridTheMasked.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Estrid, the Masked", "1327b", Rarity.MYTHIC, mage.cards.e.EstridTheMasked.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Tuvasa the Sunlit", 1328, Rarity.MYTHIC, mage.cards.t.TuvasaTheSunlit.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Tuvasa the Sunlit", "1328b", Rarity.MYTHIC, mage.cards.t.TuvasaTheSunlit.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Gargos, Vicious Watcher", 1329, Rarity.RARE, mage.cards.g.GargosViciousWatcher.class)); cards.add(new SetCardInfo("Primordial Hydra", 1330, Rarity.MYTHIC, mage.cards.p.PrimordialHydra.class)); cards.add(new SetCardInfo("Unbound Flourishing", 1331, Rarity.MYTHIC, mage.cards.u.UnboundFlourishing.class)); @@ -1801,8 +1811,8 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Stranglehold", 1804, Rarity.RARE, mage.cards.s.Stranglehold.class)); cards.add(new SetCardInfo("Thrill of Possibility", 1805, Rarity.RARE, mage.cards.t.ThrillOfPossibility.class)); cards.add(new SetCardInfo("Dolmen Gate", 1806, Rarity.RARE, mage.cards.d.DolmenGate.class)); - cards.add(new SetCardInfo("Kardur, Doomscourge", 1807, Rarity.MYTHIC, mage.cards.k.KardurDoomscourge.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Kardur, Doomscourge", "1807b", Rarity.MYTHIC, mage.cards.k.KardurDoomscourge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kardur, Doomscourge", 1807, Rarity.MYTHIC, mage.cards.k.KardurDoomscourge.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Kardur, Doomscourge", "1807b", Rarity.MYTHIC, mage.cards.k.KardurDoomscourge.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Phyrexian Reclamation", 1808, Rarity.RARE, mage.cards.p.PhyrexianReclamation.class)); cards.add(new SetCardInfo("Varragoth, Bloodsky Sire", 1809, Rarity.RARE, mage.cards.v.VarragothBloodskySire.class)); cards.add(new SetCardInfo("Twinflame", 1810, Rarity.RARE, mage.cards.t.Twinflame.class)); @@ -1946,8 +1956,6 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Reckoner Bankbuster", "1967b", Rarity.RARE, mage.cards.r.ReckonerBankbuster.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Smuggler's Copter", 1968, Rarity.RARE, mage.cards.s.SmugglersCopter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Smuggler's Copter", "1968b", Rarity.RARE, mage.cards.s.SmugglersCopter.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mechtitan Core", 1969, Rarity.COMMON, mage.cards.m.MechtitanCore.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mechtitan Core", "1969b", Rarity.COMMON, mage.cards.m.MechtitanCore.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dragonlord Atarka", 1970, Rarity.MYTHIC, mage.cards.d.DragonlordAtarka.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dragonlord Atarka", "1970b", Rarity.MYTHIC, mage.cards.d.DragonlordAtarka.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dragonlord Dromoka", 1971, Rarity.MYTHIC, mage.cards.d.DragonlordDromoka.class, NON_FULL_USE_VARIOUS)); @@ -1978,9 +1986,9 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Constant Mists", 2026, Rarity.RARE, mage.cards.c.ConstantMists.class)); cards.add(new SetCardInfo("Song of the Dryads", 2027, Rarity.RARE, mage.cards.s.SongOfTheDryads.class)); cards.add(new SetCardInfo("Consecrated Sphinx", 2028, Rarity.MYTHIC, mage.cards.c.ConsecratedSphinx.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Resculpt", 2029, Rarity.RARE, mage.cards.r.Resculpt.class)); - cards.add(new SetCardInfo("Mirage Mirror", 2030, Rarity.RARE, mage.cards.m.MirageMirror.class)); - cards.add(new SetCardInfo("Scion of Draco", 2031, Rarity.MYTHIC, mage.cards.s.ScionOfDraco.class)); + cards.add(new SetCardInfo("Resculpt", 2029, Rarity.RARE, mage.cards.r.Resculpt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mirage Mirror", 2030, Rarity.RARE, mage.cards.m.MirageMirror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scion of Draco", 2031, Rarity.MYTHIC, mage.cards.s.ScionOfDraco.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lava Dart", 2037, Rarity.RARE, mage.cards.l.LavaDart.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Monastery Swiftspear", 2038, Rarity.RARE, mage.cards.m.MonasterySwiftspear.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Soul-Scar Mage", 2039, Rarity.RARE, mage.cards.s.SoulScarMage.class, NON_FULL_USE_VARIOUS)); @@ -2037,6 +2045,16 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Hammer of Nazahn", 2098, Rarity.RARE, mage.cards.h.HammerOfNazahn.class)); cards.add(new SetCardInfo("Lightning Greaves", 2099, Rarity.RARE, mage.cards.l.LightningGreaves.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Weatherlight", 2100, Rarity.MYTHIC, mage.cards.w.Weatherlight.class)); + cards.add(new SetCardInfo("Ethersworn Canonist", 2102, Rarity.RARE, mage.cards.e.EtherswornCanonist.class)); + cards.add(new SetCardInfo("Goblin Engineer", 2103, Rarity.RARE, mage.cards.g.GoblinEngineer.class)); + cards.add(new SetCardInfo("Dance of the Manse", 2104, Rarity.RARE, mage.cards.d.DanceOfTheManse.class)); + cards.add(new SetCardInfo("Arcbound Ravager", 2105, Rarity.RARE, mage.cards.a.ArcboundRavager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Foundry Inspector", 2106, Rarity.RARE, mage.cards.f.FoundryInspector.class)); + cards.add(new SetCardInfo("Reprocess", 2107, Rarity.RARE, mage.cards.r.Reprocess.class)); + cards.add(new SetCardInfo("Aggressive Mining", 2108, Rarity.RARE, mage.cards.a.AggressiveMining.class)); + cards.add(new SetCardInfo("Sylvan Safekeeper", 2109, Rarity.RARE, mage.cards.s.SylvanSafekeeper.class)); + cards.add(new SetCardInfo("Crucible of Worlds", 2110, Rarity.MYTHIC, mage.cards.c.CrucibleOfWorlds.class)); + cards.add(new SetCardInfo("Zuran Orb", 2111, Rarity.RARE, mage.cards.z.ZuranOrb.class)); cards.add(new SetCardInfo("Feed the Swarm", 7001, Rarity.RARE, mage.cards.f.FeedTheSwarm.class)); cards.add(new SetCardInfo("Forge Anew", 7002, Rarity.RARE, mage.cards.f.ForgeAnew.class)); cards.add(new SetCardInfo("Silence", 7003, Rarity.RARE, mage.cards.s.Silence.class, NON_FULL_USE_VARIOUS)); @@ -2049,9 +2067,22 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Counterspell", 7010, Rarity.RARE, mage.cards.c.Counterspell.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dismember", 7011, Rarity.RARE, mage.cards.d.Dismember.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Command Tower", 7012, Rarity.RARE, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Goblin Bombardment", 7022, Rarity.RARE, mage.cards.g.GoblinBombardment.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Consecrated Sphinx", 7023, Rarity.MYTHIC, mage.cards.c.ConsecratedSphinx.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Resculpt", 7024, Rarity.RARE, mage.cards.r.Resculpt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mirage Mirror", 7025, Rarity.RARE, mage.cards.m.MirageMirror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scion of Draco", 7026, Rarity.MYTHIC, mage.cards.s.ScionOfDraco.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tibalt's Trickery", 7027, Rarity.RARE, mage.cards.t.TibaltsTrickery.class)); - cards.add(new SetCardInfo("Minds Aglow", 7028, Rarity.RARE, mage.cards.m.MindsAglow.class)); + cards.add(new SetCardInfo("Minds Aglow", 7028, Rarity.RARE, mage.cards.m.MindsAglow.class, FULL_ART)); cards.add(new SetCardInfo("Command Tower", 7029, Rarity.RARE, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 7030, Rarity.RARE, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Petal", 7031, Rarity.MYTHIC, mage.cards.l.LotusPetal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Petal", 7032, Rarity.MYTHIC, mage.cards.l.LotusPetal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Petal", 7033, Rarity.MYTHIC, mage.cards.l.LotusPetal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Petal", 7034, Rarity.MYTHIC, mage.cards.l.LotusPetal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Petal", 7035, Rarity.MYTHIC, mage.cards.l.LotusPetal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Petal", 7036, Rarity.MYTHIC, mage.cards.l.LotusPetal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Petal", 7037, Rarity.MYTHIC, mage.cards.l.LotusPetal.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Jace, the Mind Sculptor", 8001, Rarity.MYTHIC, mage.cards.j.JaceTheMindSculptor.class)); cards.add(new SetCardInfo("Doom Blade", 9990, Rarity.RARE, mage.cards.d.DoomBlade.class)); cards.add(new SetCardInfo("Massacre", 9991, Rarity.RARE, mage.cards.m.Massacre.class)); diff --git a/Mage.Sets/src/mage/sets/SecretLairShowdown.java b/Mage.Sets/src/mage/sets/SecretLairShowdown.java index dfc3fecd8be..b951d3351fe 100644 --- a/Mage.Sets/src/mage/sets/SecretLairShowdown.java +++ b/Mage.Sets/src/mage/sets/SecretLairShowdown.java @@ -30,7 +30,7 @@ public class SecretLairShowdown extends ExpansionSet { cards.add(new SetCardInfo("Expressive Iteration", 13, Rarity.RARE, mage.cards.e.ExpressiveIteration.class)); cards.add(new SetCardInfo("Fatal Push", 3, Rarity.RARE, mage.cards.f.FatalPush.class)); cards.add(new SetCardInfo("Fauna Shaman", 41, Rarity.RARE, mage.cards.f.FaunaShaman.class)); - cards.add(new SetCardInfo("Force of Despair", 29, Rarity.RARE, mage.cards.f.ForceOfDespair.class)); + cards.add(new SetCardInfo("Force of Despair", 29, Rarity.RARE, mage.cards.f.ForceOfDespair.class, FULL_ART)); cards.add(new SetCardInfo("Garruk Wildspeaker", 42, Rarity.MYTHIC, mage.cards.g.GarrukWildspeaker.class)); cards.add(new SetCardInfo("Goblin Guide", 23, Rarity.RARE, mage.cards.g.GoblinGuide.class)); cards.add(new SetCardInfo("Island", 32, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); @@ -42,21 +42,21 @@ public class SecretLairShowdown extends ExpansionSet { cards.add(new SetCardInfo("Mayhem Devil", 28, Rarity.RARE, mage.cards.m.MayhemDevil.class)); cards.add(new SetCardInfo("Mountain", 34, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Murktide Regent", 17, Rarity.MYTHIC, mage.cards.m.MurktideRegent.class)); - cards.add(new SetCardInfo("Nexus of Fate", 27, Rarity.RARE, mage.cards.n.NexusOfFate.class)); + cards.add(new SetCardInfo("Nexus of Fate", 27, Rarity.RARE, mage.cards.n.NexusOfFate.class, FULL_ART)); cards.add(new SetCardInfo("Plains", 31, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); - cards.add(new SetCardInfo("Ponder", 19, Rarity.RARE, mage.cards.p.Ponder.class)); - cards.add(new SetCardInfo("Prosperous Innkeeper", 40, Rarity.RARE, mage.cards.p.ProsperousInnkeeper.class)); + cards.add(new SetCardInfo("Ponder", 19, Rarity.RARE, mage.cards.p.Ponder.class, FULL_ART)); + cards.add(new SetCardInfo("Prosperous Innkeeper", 40, Rarity.RARE, mage.cards.p.ProsperousInnkeeper.class, FULL_ART)); cards.add(new SetCardInfo("Questing Druid", 38, Rarity.RARE, mage.cards.q.QuestingDruid.class)); cards.add(new SetCardInfo("Ragavan, Nimble Pilferer", 2, Rarity.MYTHIC, mage.cards.r.RagavanNimblePilferer.class)); cards.add(new SetCardInfo("Relentless Rats", 10, Rarity.RARE, mage.cards.r.RelentlessRats.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Relentless Rats", 11, Rarity.RARE, mage.cards.r.RelentlessRats.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Seasoned Pyromancer", 24, Rarity.MYTHIC, mage.cards.s.SeasonedPyromancer.class)); - cards.add(new SetCardInfo("Shoot the Sheriff", 43, Rarity.RARE, mage.cards.s.ShootTheSheriff.class)); - cards.add(new SetCardInfo("Sleight of Hand", 25, Rarity.RARE, mage.cards.s.SleightOfHand.class)); + cards.add(new SetCardInfo("Shoot the Sheriff", 43, Rarity.RARE, mage.cards.s.ShootTheSheriff.class, FULL_ART)); + cards.add(new SetCardInfo("Sleight of Hand", 25, Rarity.RARE, mage.cards.s.SleightOfHand.class, FULL_ART)); cards.add(new SetCardInfo("Spell Pierce", 18, Rarity.RARE, mage.cards.s.SpellPierce.class)); cards.add(new SetCardInfo("Springleaf Drum", 22, Rarity.RARE, mage.cards.s.SpringleafDrum.class)); - cards.add(new SetCardInfo("Sudden Edict", 39, Rarity.RARE, mage.cards.s.SuddenEdict.class)); - cards.add(new SetCardInfo("Supreme Verdict", 26, Rarity.RARE, mage.cards.s.SupremeVerdict.class)); + cards.add(new SetCardInfo("Sudden Edict", 39, Rarity.RARE, mage.cards.s.SuddenEdict.class, FULL_ART)); + cards.add(new SetCardInfo("Supreme Verdict", 26, Rarity.RARE, mage.cards.s.SupremeVerdict.class, FULL_ART)); cards.add(new SetCardInfo("Swamp", 33, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Swords to Plowshares", 20, Rarity.RARE, mage.cards.s.SwordsToPlowshares.class)); cards.add(new SetCardInfo("Ugin, the Spirit Dragon", 6, Rarity.MYTHIC, mage.cards.u.UginTheSpiritDragon.class)); diff --git a/Mage.Sets/src/mage/sets/SpecialGuests.java b/Mage.Sets/src/mage/sets/SpecialGuests.java index 68a36bb95f7..ec2200fbe48 100644 --- a/Mage.Sets/src/mage/sets/SpecialGuests.java +++ b/Mage.Sets/src/mage/sets/SpecialGuests.java @@ -83,7 +83,7 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Lord of the Undead", 88, Rarity.MYTHIC, mage.cards.l.LordOfTheUndead.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lord of the Undead", 98, Rarity.MYTHIC, mage.cards.l.LordOfTheUndead.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Maddening Hex", 70, Rarity.MYTHIC, mage.cards.m.MaddeningHex.class)); - cards.add(new SetCardInfo("Magus of the Moon", 125, Rarity.RARE, mage.cards.m.MagusOfTheMoon.class, FULL_ART)); + cards.add(new SetCardInfo("Magus of the Moon", 125, Rarity.MYTHIC, mage.cards.m.MagusOfTheMoon.class, FULL_ART)); cards.add(new SetCardInfo("Malcolm, Keen-Eyed Navigator", 2, Rarity.UNCOMMON, mage.cards.m.MalcolmKeenEyedNavigator.class)); cards.add(new SetCardInfo("Mana Crypt", "17a", Rarity.MYTHIC, mage.cards.m.ManaCrypt.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mana Crypt", "17b", Rarity.MYTHIC, mage.cards.m.ManaCrypt.class, NON_FULL_USE_VARIOUS)); @@ -127,7 +127,7 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Show and Tell", 21, Rarity.MYTHIC, mage.cards.s.ShowAndTell.class)); cards.add(new SetCardInfo("Skysovereign, Consul Flagship", 103, Rarity.MYTHIC, mage.cards.s.SkysovereignConsulFlagship.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Skysovereign, Consul Flagship", 93, Rarity.MYTHIC, mage.cards.s.SkysovereignConsulFlagship.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Sliver Overlord", 128, Rarity.RARE, mage.cards.s.SliverOverlord.class, FULL_ART)); + cards.add(new SetCardInfo("Sliver Overlord", 128, Rarity.MYTHIC, mage.cards.s.SliverOverlord.class, FULL_ART)); cards.add(new SetCardInfo("Solitude", 44, Rarity.MYTHIC, mage.cards.s.Solitude.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Solitude", 49, Rarity.MYTHIC, mage.cards.s.Solitude.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Soul Warden", 65, Rarity.MYTHIC, mage.cards.s.SoulWarden.class)); diff --git a/Mage.Sets/src/mage/sets/SpotlightSeries.java b/Mage.Sets/src/mage/sets/SpotlightSeries.java new file mode 100644 index 00000000000..92d42cff327 --- /dev/null +++ b/Mage.Sets/src/mage/sets/SpotlightSeries.java @@ -0,0 +1,32 @@ + +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/pspl + * @author ReSech + */ +public final class SpotlightSeries extends ExpansionSet { + + private static final SpotlightSeries instance = new SpotlightSeries(); + + public static SpotlightSeries getInstance() { + return instance; + } + + private SpotlightSeries() { + super("Spotlight Series", "PSPL", ExpansionSet.buildDate(2025, 1, 3), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Cloud, Midgar Mercenary", 5, Rarity.MYTHIC, mage.cards.c.CloudMidgarMercenary.class)); + cards.add(new SetCardInfo("Get Lost", 6, Rarity.RARE, mage.cards.g.GetLost.class)); + cards.add(new SetCardInfo("Kaldra Compleat", 2, Rarity.MYTHIC, mage.cards.k.KaldraCompleat.class)); + cards.add(new SetCardInfo("Sword of Forge and Frontier", 3, Rarity.MYTHIC, mage.cards.s.SwordOfForgeAndFrontier.class)); + cards.add(new SetCardInfo("Terror of the Peaks", 1, Rarity.RARE, mage.cards.t.TerrorOfThePeaks.class)); + } + +} diff --git a/Mage.Sets/src/mage/sets/Starter1999.java b/Mage.Sets/src/mage/sets/Starter1999.java index 05b8fa121e8..923d2a20aef 100644 --- a/Mage.Sets/src/mage/sets/Starter1999.java +++ b/Mage.Sets/src/mage/sets/Starter1999.java @@ -5,7 +5,6 @@ import mage.constants.Rarity; import mage.constants.SetType; /** - * * @author LevelX2 */ public final class Starter1999 extends ExpansionSet { @@ -114,7 +113,7 @@ public final class Starter1999 extends ExpansionSet { cards.add(new SetCardInfo("Man-o'-War", 41, Rarity.UNCOMMON, mage.cards.m.ManOWar.class, RETRO_ART)); cards.add(new SetCardInfo("Merfolk of the Pearl Trident", 42, Rarity.COMMON, mage.cards.m.MerfolkOfThePearlTrident.class, RETRO_ART)); cards.add(new SetCardInfo("Mind Rot", 83, Rarity.COMMON, mage.cards.m.MindRot.class, RETRO_ART)); - cards.add(new SetCardInfo("Mons's Goblin Raiders", 112, Rarity.RARE, mage.cards.m.MonssGoblinRaiders.class, RETRO_ART)); + cards.add(new SetCardInfo("Mons's Goblin Raiders", 112, Rarity.COMMON, mage.cards.m.MonssGoblinRaiders.class, RETRO_ART)); cards.add(new SetCardInfo("Monstrous Growth", 132, Rarity.COMMON, mage.cards.m.MonstrousGrowth.class, RETRO_ART)); cards.add(new SetCardInfo("Moon Sprite", 133, Rarity.UNCOMMON, mage.cards.m.MoonSprite.class, RETRO_ART)); cards.add(new SetCardInfo("Mountain", 166, Rarity.LAND, mage.cards.basiclands.Mountain.class, RETRO_ART_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/StoreChampionships.java b/Mage.Sets/src/mage/sets/StoreChampionships.java new file mode 100644 index 00000000000..b9a6e890afe --- /dev/null +++ b/Mage.Sets/src/mage/sets/StoreChampionships.java @@ -0,0 +1,72 @@ + +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/sch + * @author ReSech + */ +public final class StoreChampionships extends ExpansionSet { + + private static final StoreChampionships instance = new StoreChampionships(); + + public static StoreChampionships getInstance() { + return instance; + } + + private StoreChampionships() { + super("Store Championships", "SCH", ExpansionSet.buildDate(2022, 7, 9), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Aether Channeler", 11, Rarity.RARE, mage.cards.a.AetherChanneler.class)); + cards.add(new SetCardInfo("Angel of Despair", 22, Rarity.RARE, mage.cards.a.AngelOfDespair.class)); + cards.add(new SetCardInfo("Annex Sentry", 7, Rarity.RARE, mage.cards.a.AnnexSentry.class)); + cards.add(new SetCardInfo("Archmage's Charm", 2, Rarity.RARE, mage.cards.a.ArchmagesCharm.class)); + cards.add(new SetCardInfo("Bitter Triumph", 42, Rarity.RARE, mage.cards.b.BitterTriumph.class)); + cards.add(new SetCardInfo("Blazing Rootwalla", 24, Rarity.RARE, mage.cards.b.BlazingRootwalla.class)); + cards.add(new SetCardInfo("Cauldron Familiar", 18, Rarity.RARE, mage.cards.c.CauldronFamiliar.class)); + cards.add(new SetCardInfo("Charming Scoundrel", 37, Rarity.RARE, mage.cards.c.CharmingScoundrel.class)); + cards.add(new SetCardInfo("Chromatic Sphere", 30, Rarity.RARE, mage.cards.c.ChromaticSphere.class)); + cards.add(new SetCardInfo("City of Brass", 41, Rarity.RARE, mage.cards.c.CityOfBrass.class)); + cards.add(new SetCardInfo("Dark Confidant", 3, Rarity.RARE, mage.cards.d.DarkConfidant.class, FULL_ART)); + cards.add(new SetCardInfo("Dark Petition", 19, Rarity.RARE, mage.cards.d.DarkPetition.class)); + cards.add(new SetCardInfo("Dauthi Voidwalker", "23e", Rarity.RARE, mage.cards.d.DauthiVoidwalker.class, FULL_ART)); + cards.add(new SetCardInfo("Death's Shadow", 40, Rarity.RARE, mage.cards.d.DeathsShadow.class)); + cards.add(new SetCardInfo("Deep-Cavern Bat", 33, Rarity.RARE, mage.cards.d.DeepCavernBat.class)); + cards.add(new SetCardInfo("Eidolon of the Great Revel", 14, Rarity.RARE, mage.cards.e.EidolonOfTheGreatRevel.class)); + cards.add(new SetCardInfo("Fable of the Mirror-Breaker", 44, Rarity.RARE, mage.cards.f.FableOfTheMirrorBreaker.class)); + cards.add(new SetCardInfo("Flame Slash", 1, Rarity.RARE, mage.cards.f.FlameSlash.class)); + cards.add(new SetCardInfo("Gifted Aetherborn", 13, Rarity.RARE, mage.cards.g.GiftedAetherborn.class)); + cards.add(new SetCardInfo("Gilded Goose", 5, Rarity.RARE, mage.cards.g.GildedGoose.class)); + cards.add(new SetCardInfo("Gleeful Demolition", 36, Rarity.RARE, mage.cards.g.GleefulDemolition.class)); + cards.add(new SetCardInfo("Goddric, Cloaked Reveler", 38, Rarity.RARE, mage.cards.g.GoddricCloakedReveler.class, FULL_ART)); + cards.add(new SetCardInfo("Hollow One", 25, Rarity.RARE, mage.cards.h.HollowOne.class)); + cards.add(new SetCardInfo("Koth, Fire of Resistance", 9, Rarity.RARE, mage.cards.k.KothFireOfResistance.class)); + cards.add(new SetCardInfo("Lier, Disciple of the Drowned", 20, Rarity.RARE, mage.cards.l.LierDiscipleOfTheDrowned.class, FULL_ART)); + cards.add(new SetCardInfo("Memory Deluge", 8, Rarity.RARE, mage.cards.m.MemoryDeluge.class)); + cards.add(new SetCardInfo("Monastery Swiftspear", 27, Rarity.RARE, mage.cards.m.MonasterySwiftspear.class)); + cards.add(new SetCardInfo("Moonshaker Cavalry", 17, Rarity.MYTHIC, mage.cards.m.MoonshakerCavalry.class, FULL_ART)); + cards.add(new SetCardInfo("Mortify", 21, Rarity.RARE, mage.cards.m.Mortify.class)); + cards.add(new SetCardInfo("Omnath, Locus of Creation", 6, Rarity.MYTHIC, mage.cards.o.OmnathLocusOfCreation.class, FULL_ART)); + cards.add(new SetCardInfo("Preacher of the Schism", 34, Rarity.RARE, mage.cards.p.PreacherOfTheSchism.class)); + cards.add(new SetCardInfo("Preordain", 39, Rarity.RARE, mage.cards.p.Preordain.class)); + cards.add(new SetCardInfo("Reality Smasher", 31, Rarity.RARE, mage.cards.r.RealitySmasher.class)); + cards.add(new SetCardInfo("Reflection of Kiki-Jiki", 44, Rarity.RARE, mage.cards.r.ReflectionOfKikiJiki.class)); + cards.add(new SetCardInfo("Shark Typhoon", 28, Rarity.RARE, mage.cards.s.SharkTyphoon.class)); + cards.add(new SetCardInfo("Slickshot Show-Off", 43, Rarity.RARE, mage.cards.s.SlickshotShowOff.class)); + cards.add(new SetCardInfo("Spell Pierce", 4, Rarity.RARE, mage.cards.s.SpellPierce.class)); + cards.add(new SetCardInfo("Strangle", 10, Rarity.RARE, mage.cards.s.Strangle.class)); + cards.add(new SetCardInfo("Tail Swipe", 15, Rarity.RARE, mage.cards.t.TailSwipe.class)); + cards.add(new SetCardInfo("Thalia and The Gitrog Monster", 12, Rarity.MYTHIC, mage.cards.t.ThaliaAndTheGitrogMonster.class, FULL_ART)); + cards.add(new SetCardInfo("Transcendent Message", 16, Rarity.RARE, mage.cards.t.TranscendentMessage.class)); + cards.add(new SetCardInfo("Urza's Saga", 29, Rarity.RARE, mage.cards.u.UrzasSaga.class, FULL_ART)); + cards.add(new SetCardInfo("Vengevine", 26, Rarity.RARE, mage.cards.v.Vengevine.class, FULL_ART)); + cards.add(new SetCardInfo("Virtue of Persistence", 35, Rarity.MYTHIC, mage.cards.v.VirtueOfPersistence.class)); + cards.add(new SetCardInfo("Void Winnower", 32, Rarity.RARE, mage.cards.v.VoidWinnower.class, FULL_ART)); + } + +} diff --git a/Mage.Sets/src/mage/sets/The30thAnniversaryHistoryPromos.java b/Mage.Sets/src/mage/sets/The30thAnniversaryHistoryPromos.java new file mode 100644 index 00000000000..cf403856707 --- /dev/null +++ b/Mage.Sets/src/mage/sets/The30thAnniversaryHistoryPromos.java @@ -0,0 +1,36 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/p30h + * + * @author resech + */ +public class The30thAnniversaryHistoryPromos extends ExpansionSet { + + private static final The30thAnniversaryHistoryPromos instance = new The30thAnniversaryHistoryPromos(); + + public static The30thAnniversaryHistoryPromos getInstance() { + return instance; + } + + private The30thAnniversaryHistoryPromos() { + super("30th Anniversary History Promos", "P30H", ExpansionSet.buildDate(2022, 9, 9), SetType.PROMOTIONAL); + hasBasicLands = false; + + + cards.add(new SetCardInfo("Llanowar Elves", "5*", Rarity.RARE, mage.cards.l.LlanowarElves.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Llanowar Elves", 5, Rarity.RARE, mage.cards.l.LlanowarElves.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lord of Atlantis", "2*", Rarity.RARE, mage.cards.l.LordOfAtlantis.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Lord of Atlantis", 2, Rarity.RARE, mage.cards.l.LordOfAtlantis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sengir Vampire", "3*", Rarity.RARE, mage.cards.s.SengirVampire.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Sengir Vampire", 3, Rarity.RARE, mage.cards.s.SengirVampire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Serra Angel", "1*", Rarity.RARE, mage.cards.s.SerraAngel.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Serra Angel", 1, Rarity.RARE, mage.cards.s.SerraAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shivan Dragon", "4*", Rarity.RARE, mage.cards.s.ShivanDragon.class, RETRO_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Shivan Dragon", 4, Rarity.RARE, mage.cards.s.ShivanDragon.class, NON_FULL_USE_VARIOUS)); + } +} diff --git a/Mage.Sets/src/mage/sets/TheBrothersWar.java b/Mage.Sets/src/mage/sets/TheBrothersWar.java index 954cf1e1849..c1c78f27745 100644 --- a/Mage.Sets/src/mage/sets/TheBrothersWar.java +++ b/Mage.Sets/src/mage/sets/TheBrothersWar.java @@ -360,7 +360,7 @@ public final class TheBrothersWar extends ExpansionSet { cards.add(new SetCardInfo("Terror Ballista", 290, Rarity.RARE, mage.cards.t.TerrorBallista.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Terror Ballista", 375, Rarity.RARE, mage.cards.t.TerrorBallista.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Fall of Kroog", 133, Rarity.UNCOMMON, mage.cards.t.TheFallOfKroog.class)); - cards.add(new SetCardInfo("The Mightstone and Weakstone", "238a", Rarity.RARE, mage.cards.t.TheMightstoneAndWeakstone.class)); + cards.add(new SetCardInfo("The Mightstone and Weakstone", "238", Rarity.RARE, mage.cards.t.TheMightstoneAndWeakstone.class)); cards.add(new SetCardInfo("The Stasis Coffin", 245, Rarity.RARE, mage.cards.t.TheStasisCoffin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Stasis Coffin", 366, Rarity.RARE, mage.cards.t.TheStasisCoffin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Stone Brain", 247, Rarity.RARE, mage.cards.t.TheStoneBrain.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java index 695b777e50e..93acfb49741 100644 --- a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java +++ b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java @@ -27,7 +27,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Anduril, Flame of the West", 236, Rarity.MYTHIC, mage.cards.a.AndurilFlameOfTheWest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Anduril, Flame of the West", 375, Rarity.MYTHIC, mage.cards.a.AndurilFlameOfTheWest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Anduril, Flame of the West", 687, Rarity.MYTHIC, mage.cards.a.AndurilFlameOfTheWest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Anduril, Flame of the West", 746, Rarity.MYTHIC, mage.cards.a.AndurilFlameOfTheWest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anduril, Flame of the West", 746, Rarity.MYTHIC, mage.cards.a.AndurilFlameOfTheWest.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Anduril, Flame of the West", 786, Rarity.MYTHIC, mage.cards.a.AndurilFlameOfTheWest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aragorn and Arwen, Wed", 287, Rarity.MYTHIC, mage.cards.a.AragornAndArwenWed.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aragorn and Arwen, Wed", 394, Rarity.MYTHIC, mage.cards.a.AragornAndArwenWed.class, NON_FULL_USE_VARIOUS)); @@ -40,7 +40,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Aragorn, the Uniter", 317, Rarity.MYTHIC, mage.cards.a.AragornTheUniter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aragorn, the Uniter", 434, Rarity.MYTHIC, mage.cards.a.AragornTheUniter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aragorn, the Uniter", 643, Rarity.MYTHIC, mage.cards.a.AragornTheUniter.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Aragorn, the Uniter", 741, Rarity.MYTHIC, mage.cards.a.AragornTheUniter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aragorn, the Uniter", 741, Rarity.MYTHIC, mage.cards.a.AragornTheUniter.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Aragorn, the Uniter", 809, Rarity.MYTHIC, mage.cards.a.AragornTheUniter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Arwen Undomiel", 194, Rarity.UNCOMMON, mage.cards.a.ArwenUndomiel.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Arwen Undomiel", 645, Rarity.UNCOMMON, mage.cards.a.ArwenUndomiel.class, NON_FULL_USE_VARIOUS)); @@ -49,7 +49,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Arwen, Mortal Queen", 193, Rarity.MYTHIC, mage.cards.a.ArwenMortalQueen.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Arwen, Mortal Queen", 367, Rarity.MYTHIC, mage.cards.a.ArwenMortalQueen.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Arwen, Mortal Queen", 644, Rarity.MYTHIC, mage.cards.a.ArwenMortalQueen.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Arwen, Mortal Queen", 742, Rarity.MYTHIC, mage.cards.a.ArwenMortalQueen.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arwen, Mortal Queen", 742, Rarity.MYTHIC, mage.cards.a.ArwenMortalQueen.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Arwen, Mortal Queen", 778, Rarity.MYTHIC, mage.cards.a.ArwenMortalQueen.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Assault on Osgiliath", 285, Rarity.RARE, mage.cards.a.AssaultOnOsgiliath.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Assault on Osgiliath", 386, Rarity.RARE, mage.cards.a.AssaultOnOsgiliath.class, NON_FULL_USE_VARIOUS)); @@ -121,7 +121,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Dawn of a New Age", 347, Rarity.MYTHIC, mage.cards.d.DawnOfANewAge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dawn of a New Age", 456, Rarity.MYTHIC, mage.cards.d.DawnOfANewAge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dawn of a New Age", 5, Rarity.MYTHIC, mage.cards.d.DawnOfANewAge.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Dawn of a New Age", 731, Rarity.MYTHIC, mage.cards.d.DawnOfANewAge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dawn of a New Age", 731, Rarity.MYTHIC, mage.cards.d.DawnOfANewAge.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Dawn of a New Age", 758, Rarity.MYTHIC, mage.cards.d.DawnOfANewAge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Deceive the Messenger", 47, Rarity.COMMON, mage.cards.d.DeceiveTheMessenger.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Deceive the Messenger", 498, Rarity.COMMON, mage.cards.d.DeceiveTheMessenger.class, NON_FULL_USE_VARIOUS)); @@ -287,7 +287,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Gandalf the White", 305, Rarity.MYTHIC, mage.cards.g.GandalfTheWhite.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gandalf the White", 442, Rarity.MYTHIC, mage.cards.g.GandalfTheWhite.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gandalf the White", 470, Rarity.MYTHIC, mage.cards.g.GandalfTheWhite.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Gandalf the White", 732, Rarity.MYTHIC, mage.cards.g.GandalfTheWhite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gandalf the White", 732, Rarity.MYTHIC, mage.cards.g.GandalfTheWhite.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Gandalf the White", 797, Rarity.MYTHIC, mage.cards.g.GandalfTheWhite.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gandalf's Sanction", 208, Rarity.UNCOMMON, mage.cards.g.GandalfsSanction.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gandalf's Sanction", 659, Rarity.UNCOMMON, mage.cards.g.GandalfsSanction.class, NON_FULL_USE_VARIOUS)); @@ -318,7 +318,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Glamdring", 239, Rarity.MYTHIC, mage.cards.g.Glamdring.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Glamdring", 376, Rarity.MYTHIC, mage.cards.g.Glamdring.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Glamdring", 690, Rarity.MYTHIC, mage.cards.g.Glamdring.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Glamdring", 747, Rarity.MYTHIC, mage.cards.g.Glamdring.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Glamdring", 747, Rarity.MYTHIC, mage.cards.g.Glamdring.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Glamdring", 787, Rarity.MYTHIC, mage.cards.g.Glamdring.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gloin, Dwarf Emissary", 132, Rarity.RARE, mage.cards.g.GloinDwarfEmissary.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gloin, Dwarf Emissary", 360, Rarity.RARE, mage.cards.g.GloinDwarfEmissary.class, NON_FULL_USE_VARIOUS)); @@ -368,7 +368,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Hew the Entwood", 136, Rarity.MYTHIC, mage.cards.h.HewTheEntwood.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Hew the Entwood", 361, Rarity.MYTHIC, mage.cards.h.HewTheEntwood.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Hew the Entwood", 587, Rarity.MYTHIC, mage.cards.h.HewTheEntwood.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Hew the Entwood", 737, Rarity.MYTHIC, mage.cards.h.HewTheEntwood.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hew the Entwood", 737, Rarity.MYTHIC, mage.cards.h.HewTheEntwood.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Hew the Entwood", 772, Rarity.MYTHIC, mage.cards.h.HewTheEntwood.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Hithlain Knots", 505, Rarity.COMMON, mage.cards.h.HithlainKnots.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Hithlain Knots", 54, Rarity.COMMON, mage.cards.h.HithlainKnots.class, NON_FULL_USE_VARIOUS)); @@ -420,7 +420,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Last March of the Ents", 172, Rarity.MYTHIC, mage.cards.l.LastMarchOfTheEnts.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Last March of the Ents", 418, Rarity.MYTHIC, mage.cards.l.LastMarchOfTheEnts.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Last March of the Ents", 623, Rarity.MYTHIC, mage.cards.l.LastMarchOfTheEnts.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Last March of the Ents", 739, Rarity.MYTHIC, mage.cards.l.LastMarchOfTheEnts.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Last March of the Ents", 739, Rarity.MYTHIC, mage.cards.l.LastMarchOfTheEnts.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Legolas, Counter of Kills", 212, Rarity.UNCOMMON, mage.cards.l.LegolasCounterOfKills.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Legolas, Counter of Kills", 324, Rarity.UNCOMMON, mage.cards.l.LegolasCounterOfKills.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Legolas, Counter of Kills", 663, Rarity.UNCOMMON, mage.cards.l.LegolasCounterOfKills.class, NON_FULL_USE_VARIOUS)); @@ -509,7 +509,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Mount Doom", 258, Rarity.MYTHIC, mage.cards.m.MountDoom.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mount Doom", 343, Rarity.MYTHIC, mage.cards.m.MountDoom.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mount Doom", 709, Rarity.MYTHIC, mage.cards.m.MountDoom.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mount Doom", 750, Rarity.MYTHIC, mage.cards.m.MountDoom.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mount Doom", 750, Rarity.MYTHIC, mage.cards.m.MountDoom.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Mount Doom", 754, Rarity.MYTHIC, mage.cards.m.MountDoom.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 268, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 269, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); @@ -565,7 +565,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Palantir of Orthanc", 247, Rarity.MYTHIC, mage.cards.p.PalantirOfOrthanc.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Palantir of Orthanc", 381, Rarity.MYTHIC, mage.cards.p.PalantirOfOrthanc.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Palantir of Orthanc", 698, Rarity.MYTHIC, mage.cards.p.PalantirOfOrthanc.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Palantir of Orthanc", 749, Rarity.MYTHIC, mage.cards.p.PalantirOfOrthanc.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Palantir of Orthanc", 749, Rarity.MYTHIC, mage.cards.p.PalantirOfOrthanc.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Palantir of Orthanc", 792, Rarity.MYTHIC, mage.cards.p.PalantirOfOrthanc.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pelargir Survivor", 515, Rarity.COMMON, mage.cards.p.PelargirSurvivor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pelargir Survivor", 64, Rarity.COMMON, mage.cards.p.PelargirSurvivor.class, NON_FULL_USE_VARIOUS)); @@ -608,7 +608,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Radagast the Brown", 184, Rarity.MYTHIC, mage.cards.r.RadagastTheBrown.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Radagast the Brown", 365, Rarity.MYTHIC, mage.cards.r.RadagastTheBrown.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Radagast the Brown", 635, Rarity.MYTHIC, mage.cards.r.RadagastTheBrown.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Radagast the Brown", 740, Rarity.MYTHIC, mage.cards.r.RadagastTheBrown.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Radagast the Brown", 740, Rarity.MYTHIC, mage.cards.r.RadagastTheBrown.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Radagast the Brown", 776, Rarity.MYTHIC, mage.cards.r.RadagastTheBrown.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Rally at the Hornburg", 142, Rarity.COMMON, mage.cards.r.RallyAtTheHornburg.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Rally at the Hornburg", 593, Rarity.COMMON, mage.cards.r.RallyAtTheHornburg.class, NON_FULL_USE_VARIOUS)); @@ -664,7 +664,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Saruman of Many Colors", 328, Rarity.MYTHIC, mage.cards.s.SarumanOfManyColors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Saruman of Many Colors", 412, Rarity.MYTHIC, mage.cards.s.SarumanOfManyColors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Saruman of Many Colors", 674, Rarity.MYTHIC, mage.cards.s.SarumanOfManyColors.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Saruman of Many Colors", 743, Rarity.MYTHIC, mage.cards.s.SarumanOfManyColors.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Saruman of Many Colors", 743, Rarity.MYTHIC, mage.cards.s.SarumanOfManyColors.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Saruman of Many Colors", 820, Rarity.MYTHIC, mage.cards.s.SarumanOfManyColors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Saruman the White", 518, Rarity.UNCOMMON, mage.cards.s.SarumanTheWhite.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Saruman the White", 67, Rarity.UNCOMMON, mage.cards.s.SarumanTheWhite.class, NON_FULL_USE_VARIOUS)); @@ -678,7 +678,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Sauron, the Dark Lord", 301, Rarity.MYTHIC, mage.cards.s.SauronTheDarkLord.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sauron, the Dark Lord", 329, Rarity.MYTHIC, mage.cards.s.SauronTheDarkLord.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sauron, the Dark Lord", 675, Rarity.MYTHIC, mage.cards.s.SauronTheDarkLord.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Sauron, the Dark Lord", 744, Rarity.MYTHIC, mage.cards.s.SauronTheDarkLord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sauron, the Dark Lord", 744, Rarity.MYTHIC, mage.cards.s.SauronTheDarkLord.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Sauron, the Dark Lord", 821, Rarity.MYTHIC, mage.cards.s.SauronTheDarkLord.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sauron, the Lidless Eye", 288, Rarity.MYTHIC, mage.cards.s.SauronTheLidlessEye.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sauron, the Lidless Eye", 396, Rarity.MYTHIC, mage.cards.s.SauronTheLidlessEye.class, NON_FULL_USE_VARIOUS)); @@ -695,7 +695,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Shadow of the Enemy", 107, Rarity.MYTHIC, mage.cards.s.ShadowOfTheEnemy.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shadow of the Enemy", 424, Rarity.MYTHIC, mage.cards.s.ShadowOfTheEnemy.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shadow of the Enemy", 558, Rarity.MYTHIC, mage.cards.s.ShadowOfTheEnemy.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Shadow of the Enemy", 735, Rarity.MYTHIC, mage.cards.s.ShadowOfTheEnemy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shadow of the Enemy", 735, Rarity.MYTHIC, mage.cards.s.ShadowOfTheEnemy.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Shadowfax, Lord of Horses", 227, Rarity.UNCOMMON, mage.cards.s.ShadowfaxLordOfHorses.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shadowfax, Lord of Horses", 678, Rarity.UNCOMMON, mage.cards.s.ShadowfaxLordOfHorses.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shagrat, Loot Bearer", 228, Rarity.RARE, mage.cards.s.ShagratLootBearer.class, NON_FULL_USE_VARIOUS)); @@ -740,7 +740,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Spiteful Banditry", 149, Rarity.MYTHIC, mage.cards.s.SpitefulBanditry.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spiteful Banditry", 439, Rarity.MYTHIC, mage.cards.s.SpitefulBanditry.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spiteful Banditry", 600, Rarity.MYTHIC, mage.cards.s.SpitefulBanditry.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Spiteful Banditry", 738, Rarity.MYTHIC, mage.cards.s.SpitefulBanditry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spiteful Banditry", 738, Rarity.MYTHIC, mage.cards.s.SpitefulBanditry.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Stalwarts of Osgiliath", 33, Rarity.COMMON, mage.cards.s.StalwartsOfOsgiliath.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Stalwarts of Osgiliath", 484, Rarity.COMMON, mage.cards.s.StalwartsOfOsgiliath.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Stern Scolding", 522, Rarity.UNCOMMON, mage.cards.s.SternScolding.class, NON_FULL_USE_VARIOUS)); @@ -755,7 +755,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Storm of Saruman", 413, Rarity.MYTHIC, mage.cards.s.StormOfSaruman.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Storm of Saruman", 523, Rarity.MYTHIC, mage.cards.s.StormOfSaruman.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Storm of Saruman", 72, Rarity.MYTHIC, mage.cards.s.StormOfSaruman.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Storm of Saruman", 733, Rarity.MYTHIC, mage.cards.s.StormOfSaruman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Storm of Saruman", 733, Rarity.MYTHIC, mage.cards.s.StormOfSaruman.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Strider, Ranger of the North", 232, Rarity.UNCOMMON, mage.cards.s.StriderRangerOfTheNorth.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Strider, Ranger of the North", 683, Rarity.UNCOMMON, mage.cards.s.StriderRangerOfTheNorth.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Surrounded by Orcs", 524, Rarity.COMMON, mage.cards.s.SurroundedByOrcs.class, NON_FULL_USE_VARIOUS)); @@ -793,7 +793,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("The One Ring", 380, Rarity.MYTHIC, mage.cards.t.TheOneRing.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The One Ring", 451, Rarity.MYTHIC, mage.cards.t.TheOneRing.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The One Ring", 697, Rarity.MYTHIC, mage.cards.t.TheOneRing.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("The One Ring", 748, Rarity.MYTHIC, mage.cards.t.TheOneRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The One Ring", 748, Rarity.MYTHIC, mage.cards.t.TheOneRing.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("The One Ring", 791, Rarity.MYTHIC, mage.cards.t.TheOneRing.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Ring Goes South", 186, Rarity.RARE, mage.cards.t.TheRingGoesSouth.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Ring Goes South", 366, Rarity.RARE, mage.cards.t.TheRingGoesSouth.class, NON_FULL_USE_VARIOUS)); @@ -807,7 +807,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("The Torment of Gollum", 561, Rarity.COMMON, mage.cards.t.TheTormentOfGollum.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Watcher in the Water", 354, Rarity.MYTHIC, mage.cards.t.TheWatcherInTheWater.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Watcher in the Water", 526, Rarity.MYTHIC, mage.cards.t.TheWatcherInTheWater.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("The Watcher in the Water", 734, Rarity.MYTHIC, mage.cards.t.TheWatcherInTheWater.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Watcher in the Water", 734, Rarity.MYTHIC, mage.cards.t.TheWatcherInTheWater.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("The Watcher in the Water", 75, Rarity.MYTHIC, mage.cards.t.TheWatcherInTheWater.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Watcher in the Water", 765, Rarity.MYTHIC, mage.cards.t.TheWatcherInTheWater.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Theoden, King of Rohan", 233, Rarity.UNCOMMON, mage.cards.t.TheodenKingOfRohan.class, NON_FULL_USE_VARIOUS)); @@ -817,7 +817,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Tom Bombadil", 234, Rarity.MYTHIC, mage.cards.t.TomBombadil.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tom Bombadil", 331, Rarity.MYTHIC, mage.cards.t.TomBombadil.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tom Bombadil", 685, Rarity.MYTHIC, mage.cards.t.TomBombadil.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Tom Bombadil", 745, Rarity.MYTHIC, mage.cards.t.TomBombadil.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tom Bombadil", 745, Rarity.MYTHIC, mage.cards.t.TomBombadil.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Tom Bombadil", 823, Rarity.MYTHIC, mage.cards.t.TomBombadil.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Took Reaper", 35, Rarity.COMMON, mage.cards.t.TookReaper.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Took Reaper", 486, Rarity.COMMON, mage.cards.t.TookReaper.class, NON_FULL_USE_VARIOUS)); @@ -847,7 +847,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Witch-king of Angmar", 311, Rarity.MYTHIC, mage.cards.w.WitchKingOfAngmar.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Witch-king of Angmar", 423, Rarity.MYTHIC, mage.cards.w.WitchKingOfAngmar.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Witch-king of Angmar", 565, Rarity.MYTHIC, mage.cards.w.WitchKingOfAngmar.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Witch-king of Angmar", 736, Rarity.MYTHIC, mage.cards.w.WitchKingOfAngmar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Witch-king of Angmar", 736, Rarity.MYTHIC, mage.cards.w.WitchKingOfAngmar.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Witch-king of Angmar", 803, Rarity.MYTHIC, mage.cards.w.WitchKingOfAngmar.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Witch-king, Bringer of Ruin", 293, Rarity.RARE, mage.cards.w.WitchKingBringerOfRuin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Witch-king, Bringer of Ruin", 391, Rarity.RARE, mage.cards.w.WitchKingBringerOfRuin.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/URLConventionPromos.java b/Mage.Sets/src/mage/sets/URLConventionPromos.java index 8f10cb5898c..155a5718d2f 100644 --- a/Mage.Sets/src/mage/sets/URLConventionPromos.java +++ b/Mage.Sets/src/mage/sets/URLConventionPromos.java @@ -23,12 +23,12 @@ public class URLConventionPromos extends ExpansionSet { cards.add(new SetCardInfo("Aeronaut Tinkerer", 8, Rarity.COMMON, mage.cards.a.AeronautTinkerer.class)); cards.add(new SetCardInfo("Bloodthrone Vampire", 3, Rarity.RARE, mage.cards.b.BloodthroneVampire.class)); cards.add(new SetCardInfo("Chandra's Fury", 5, Rarity.RARE, mage.cards.c.ChandrasFury.class)); - cards.add(new SetCardInfo("Kor Skyfisher", 2, Rarity.RARE, mage.cards.k.KorSkyfisher.class)); + cards.add(new SetCardInfo("Counterspell", 2, Rarity.RARE, mage.cards.c.Counterspell.class)); + cards.add(new SetCardInfo("Hylda of the Icy Crown", "2025-1", Rarity.MYTHIC, mage.cards.h.HyldaOfTheIcyCrown.class)); + cards.add(new SetCardInfo("Katara, the Fearless", "2025-3", Rarity.RARE, mage.cards.k.KataraTheFearless.class)); + cards.add(new SetCardInfo("Kor Skyfisher", 23, Rarity.RARE, mage.cards.k.KorSkyfisher.class)); cards.add(new SetCardInfo("Merfolk Mesmerist", 4, Rarity.RARE, mage.cards.m.MerfolkMesmerist.class)); - // Italian-only printing - //cards.add(new SetCardInfo("Relentless Rats", 9, Rarity.RARE, mage.cards.r.RelentlessRats.class)); - // Japanese-only printing - //cards.add(new SetCardInfo("Shepherd of the Lost", "34*", Rarity.UNCOMMON, mage.cards.s.ShepherdOfTheLost.class)); + cards.add(new SetCardInfo("Shepherd of the Lost", "34*", Rarity.UNCOMMON, mage.cards.s.ShepherdOfTheLost.class)); cards.add(new SetCardInfo("Stealer of Secrets", 7, Rarity.RARE, mage.cards.s.StealerOfSecrets.class)); cards.add(new SetCardInfo("Steward of Valeron", 1, Rarity.RARE, mage.cards.s.StewardOfValeron.class)); } diff --git a/Mage.Sets/src/mage/sets/UrzasSaga.java b/Mage.Sets/src/mage/sets/UrzasSaga.java index e394638101f..eb72c5943ad 100644 --- a/Mage.Sets/src/mage/sets/UrzasSaga.java +++ b/Mage.Sets/src/mage/sets/UrzasSaga.java @@ -352,7 +352,7 @@ public final class UrzasSaga extends ExpansionSet { cards.add(new SetCardInfo("Unworthy Dead", "163s", Rarity.COMMON, mage.cards.u.UnworthyDead.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Urza's Armor", 313, Rarity.UNCOMMON, mage.cards.u.UrzasArmor.class, RETRO_ART)); cards.add(new SetCardInfo("Vampiric Embrace", 164, Rarity.UNCOMMON, mage.cards.v.VampiricEmbrace.class, RETRO_ART_USE_VARIOUS)); - cards.add(new SetCardInfo("Vampiric Embrace", "164s", Rarity.UNCOMMON, mage.cards.v.VampiricEmbrace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vampiric Embrace", "164s", Rarity.UNCOMMON, mage.cards.v.VampiricEmbrace.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Vebulid", 165, Rarity.RARE, mage.cards.v.Vebulid.class, RETRO_ART)); cards.add(new SetCardInfo("Veil of Birds", 106, Rarity.COMMON, mage.cards.v.VeilOfBirds.class, RETRO_ART)); cards.add(new SetCardInfo("Veiled Apparition", 107, Rarity.UNCOMMON, mage.cards.v.VeiledApparition.class, RETRO_ART)); diff --git a/Mage.Sets/src/mage/sets/WizardsPlayNetwork2025.java b/Mage.Sets/src/mage/sets/WizardsPlayNetwork2025.java index 94b5e9e2916..5929c33e2fc 100644 --- a/Mage.Sets/src/mage/sets/WizardsPlayNetwork2025.java +++ b/Mage.Sets/src/mage/sets/WizardsPlayNetwork2025.java @@ -20,8 +20,15 @@ public class WizardsPlayNetwork2025 extends ExpansionSet { this.hasBoosters = false; this.hasBasicLands = false; - cards.add(new SetCardInfo("Dragon's Hoard", 2, Rarity.RARE, mage.cards.d.DragonsHoard.class, RETRO_ART)); + cards.add(new SetCardInfo("Culling Ritual", 4, Rarity.RARE, mage.cards.c.CullingRitual.class)); + cards.add(new SetCardInfo("Despark", 2, Rarity.UNCOMMON, mage.cards.d.Despark.class)); + cards.add(new SetCardInfo("Dragon's Hoard", "1p", Rarity.RARE, mage.cards.d.DragonsHoard.class, RETRO_ART)); cards.add(new SetCardInfo("Dragonspeaker Shaman", 3, Rarity.RARE, mage.cards.d.DragonspeakerShaman.class)); + cards.add(new SetCardInfo("Monstrous Rage", 9, Rarity.RARE, mage.cards.m.MonstrousRage.class, RETRO_ART)); + cards.add(new SetCardInfo("Palladium Myr", 6, Rarity.RARE, mage.cards.p.PalladiumMyr.class, RETRO_ART)); cards.add(new SetCardInfo("Rishkar's Expertise", 1, Rarity.RARE, mage.cards.r.RishkarsExpertise.class, RETRO_ART)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 7, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class)); + cards.add(new SetCardInfo("Trinket Mage", 8, Rarity.RARE, mage.cards.t.TrinketMage.class, RETRO_ART)); + cards.add(new SetCardInfo("Zidane, Tantalus Thief", 5, Rarity.RARE, mage.cards.z.ZidaneTantalusThief.class)); } } diff --git a/Mage.Sets/src/mage/sets/YearOfTheDragon2024.java b/Mage.Sets/src/mage/sets/YearOfTheDragon2024.java new file mode 100644 index 00000000000..a3dd5e0c773 --- /dev/null +++ b/Mage.Sets/src/mage/sets/YearOfTheDragon2024.java @@ -0,0 +1,30 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/pl24 + */ +public class YearOfTheDragon2024 extends ExpansionSet { + + private static final YearOfTheDragon2024 instance = new YearOfTheDragon2024(); + + public static YearOfTheDragon2024 getInstance() { + return instance; + } + + private YearOfTheDragon2024() { + super("Year of the Dragon 2024", "PL24", ExpansionSet.buildDate(2024, 2, 8), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = true; + + cards.add(new SetCardInfo("Dragon Tempest", 7, Rarity.RARE, mage.cards.d.DragonTempest.class)); + cards.add(new SetCardInfo("Dragonlord's Servant", 1, Rarity.RARE, mage.cards.d.DragonlordsServant.class)); + cards.add(new SetCardInfo("Korvold, Fae-Cursed King", 6, Rarity.MYTHIC, mage.cards.k.KorvoldFaeCursedKing.class)); + cards.add(new SetCardInfo("Mountain", 5, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Sarkhan Unbroken", 2, Rarity.MYTHIC, mage.cards.s.SarkhanUnbroken.class)); + cards.add(new SetCardInfo("Steel Hellkite", 4, Rarity.RARE, mage.cards.s.SteelHellkite.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/YearOfTheSnake2025.java b/Mage.Sets/src/mage/sets/YearOfTheSnake2025.java new file mode 100644 index 00000000000..1275fd9ecc5 --- /dev/null +++ b/Mage.Sets/src/mage/sets/YearOfTheSnake2025.java @@ -0,0 +1,29 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * https://scryfall.com/sets/pl25 + */ +public class YearOfTheSnake2025 extends ExpansionSet { + + private static final YearOfTheSnake2025 instance = new YearOfTheSnake2025(); + + public static YearOfTheSnake2025 getInstance() { + return instance; + } + + private YearOfTheSnake2025() { + super("Year of the Snake 2025", "PL25", ExpansionSet.buildDate(2025, 2, 14), SetType.PROMOTIONAL); + this.hasBoosters = false; + this.hasBasicLands = true; + + cards.add(new SetCardInfo("Forest", 6, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Kaseto, Orochi Archmage", 5, Rarity.MYTHIC, mage.cards.k.KasetoOrochiArchmage.class)); + cards.add(new SetCardInfo("Lotus Cobra", 3, Rarity.RARE, mage.cards.l.LotusCobra.class)); + cards.add(new SetCardInfo("Sakura-Tribe Elder", 4, Rarity.RARE, mage.cards.s.SakuraTribeElder.class)); + cards.add(new SetCardInfo("Xyris, the Writhing Storm", 1, Rarity.MYTHIC, mage.cards.x.XyrisTheWrithingStorm.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/YearOfTheTiger2022.java b/Mage.Sets/src/mage/sets/YearOfTheTiger2022.java index 48fe95d9167..25672726759 100644 --- a/Mage.Sets/src/mage/sets/YearOfTheTiger2022.java +++ b/Mage.Sets/src/mage/sets/YearOfTheTiger2022.java @@ -21,7 +21,7 @@ public class YearOfTheTiger2022 extends ExpansionSet { this.hasBasicLands = false; cards.add(new SetCardInfo("Herald's Horn", 5, Rarity.RARE, mage.cards.h.HeraldsHorn.class)); - cards.add(new SetCardInfo("Jedit Ojanen", 2, Rarity.RARE, mage.cards.j.JeditOjanen.class)); + cards.add(new SetCardInfo("Jedit Ojanen", 2, Rarity.RARE, mage.cards.j.JeditOjanen.class, FULL_ART)); // cards.add(new SetCardInfo("Snapdax, Apex of the Hunt", 3, Rarity.RARE, mage.cards.s.SnapdaxApexOfTheHunt.class)); cards.add(new SetCardInfo("Temur Sabertooth", 1, Rarity.RARE, mage.cards.t.TemurSabertooth.class)); cards.add(new SetCardInfo("Yuriko, the Tiger's Shadow", 4, Rarity.RARE, mage.cards.y.YurikoTheTigersShadow.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java index a7840516228..07204312358 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java @@ -116,7 +116,7 @@ public class CopyAITest extends CardTestPlayerBaseWithAIHelps { // addCard(Zone.GRAVEYARD, playerB, "Balduvian Bears", 1); // 2/2 - // copy (AI must choose most valueable permanent - own) + // copy (AI must choose most valuable permanent - own) aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationPerformanceAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationPerformanceAITest.java index c8ce253a707..268335e360b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationPerformanceAITest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationPerformanceAITest.java @@ -20,8 +20,9 @@ import java.util.List; *

* TODO: add tests and implement best choice selection on timeout * (AI must make any good/bad choice on timeout with game log - not a skip) + * + * TODO: AI do not support game sims from triggered (it's run, but do not use results) *

- * TODO: AI do not support game simulations for target options in triggered * * @author JayDi85 */ @@ -213,4 +214,30 @@ public class SimulationPerformanceAITest extends CardTestPlayerBaseAI { // 4 damage to x2 bears and 1 damage to damaged bear runManyTargetOptionsInActivate("5 target creatures with one damaged", 5, 3, true, 20); } + + @Test + @Ignore // TODO: enable and fix random error with too many sim nodes (depends on machine performance?) + public void test_ElderDeepFiend_TooManyUpToChoices() { + // bug: game freeze with 100% CPU usage + // https://github.com/magefree/mage/issues/9518 + int cardsCount = 2; // 2+ cards will generate too much target options for simulations + + // Boulderfall deals 5 damage divided as you choose among any number of targets. + // Flash + // Emerge {5}{U}{U} (You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's mana value.) + // When you cast this spell, tap up to four target permanents. + addCard(Zone.HAND, playerA, "Elder Deep-Fiend", cardsCount); // {8} + addCard(Zone.BATTLEFIELD, playerA, "Island", 8 * cardsCount); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 2); + addCard(Zone.BATTLEFIELD, playerB, "Kitesail Corsair", 2); + addCard(Zone.BATTLEFIELD, playerB, "Alpha Tyrranax", 2); + addCard(Zone.BATTLEFIELD, playerB, "Abbey Griffin", 2); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Elder Deep-Fiend", cardsCount); // ai must cast it + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationStabilityAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationStabilityAITest.java index d1aa17af189..4ee5d9fbf8e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationStabilityAITest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationStabilityAITest.java @@ -28,6 +28,8 @@ public class SimulationStabilityAITest extends CardTestPlayerBaseWithAIHelps { @Before public void prepare() { + // WARNING, for some reason java 8 sometime can't compile and run test with updated AI settings, so it's ok to freeze on it + // comment it to enable AI code debug Assert.assertFalse("AI stability tests must be run under release config", ComputerPlayer.COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS); } diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationTriggersAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationTriggersAITest.java new file mode 100644 index 00000000000..a13f26171be --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationTriggersAITest.java @@ -0,0 +1,84 @@ +package org.mage.test.AI.basic; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * Make sure AI can simulate priority with triggers resolve + * + * @author JayDi85 + */ +public class SimulationTriggersAITest extends CardTestPlayerBaseWithAIHelps { + + @Test + @Ignore + // TODO: trigger's target options supported on priority sim, but do not used for some reason + // see addTargetOptions, node.children, ComputerPlayer6->targets, etc + public void test_DeepglowSkate_MustBeSimulated() { + // make sure targets choosing on trigger use same game sims and best results + + // When Deepglow Skate enters the battlefield, double the number of each kind of counter on any number + // of target permanents. + addCard(Zone.HAND, playerA, "Deepglow Skate", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + // + addCard(Zone.BATTLEFIELD, playerA, "Ajani, Adversary of Tyrants", 1); // x4 loyalty + addCard(Zone.BATTLEFIELD, playerA, "Ajani, Caller of the Pride", 1); // x4 loyalty + // + // This creature enters with a -1/-1 counter on it. + addCard(Zone.BATTLEFIELD, playerA, "Bloodied Ghost", 1); // 3/3 + // + // Players can't activate planeswalkers' loyalty abilities. + addCard(Zone.BATTLEFIELD, playerA, "The Immortal Sun", 1); // disable planeswalkers usage by AI + + // AI must cast boost and ignore doubling of -1/-1 counters on own creatures due bad score + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Deepglow Skate", 1); + assertCounterCount(playerA, "Ajani, Adversary of Tyrants", CounterType.LOYALTY, 4 * 2); + assertCounterCount(playerA, "Ajani, Caller of the Pride", CounterType.LOYALTY, 4 * 2); + assertCounterCount(playerA, "Bloodied Ghost", CounterType.M1M1, 1); // make sure AI will not double bad counters + } + + @Test + public void test_DeepglowSkate_PerformanceOnTooManyChoices() { + // bug: game freeze with 100% CPU usage + // https://github.com/magefree/mage/issues/9438 + int cardsCount = 2; // 2+ cards will generate too much target options for simulations + int boostMultiplier = (int) Math.pow(2, cardsCount); + + // When Deepglow Skate enters the battlefield, double the number of each kind of counter on any number + // of target permanents. + addCard(Zone.HAND, playerA, "Deepglow Skate", cardsCount); // {4}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 5 * cardsCount); + // + addCard(Zone.BATTLEFIELD, playerA, "Ajani, Adversary of Tyrants", 1); // x4 loyalty + addCard(Zone.BATTLEFIELD, playerA, "Ajani, Caller of the Pride", 1); // x4 loyalty + addCard(Zone.BATTLEFIELD, playerB, "Ajani Goldmane", 1); // x4 loyalty + addCard(Zone.BATTLEFIELD, playerB, "Ajani, Inspiring Leader", 1); // x5 loyalty + // + // Players can't activate planeswalkers' loyalty abilities. + addCard(Zone.BATTLEFIELD, playerA, "The Immortal Sun", 1); // disable planeswalkers usage by AI + + // AI must cast multiple booster spells and double only own counters and only good + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Deepglow Skate", cardsCount); + assertCounterCount(playerA, "Ajani, Adversary of Tyrants", CounterType.LOYALTY, 4 * boostMultiplier); + assertCounterCount(playerA, "Ajani, Caller of the Pride", CounterType.LOYALTY, 4 * boostMultiplier); + assertCounterCount(playerB, "Ajani Goldmane", CounterType.LOYALTY, 4); + assertCounterCount(playerB, "Ajani, Inspiring Leader", CounterType.LOYALTY, 5); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java index d9bea5640c2..247e30aef40 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java @@ -69,6 +69,7 @@ public class ValakutTheMoltenPinnacleTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scapeshift"); setChoice(playerA, "Forest^Forest^Forest^Forest^Forest^Forest"); // to sac + setChoice(playerA, TestPlayer.CHOICE_SKIP); addTarget(playerA, "Mountain^Mountain^Mountain^Mountain^Mountain^Mountain"); // to search setChoice(playerA, "Whenever a Mountain", 6 - 1); // x6 triggers from valakut addTarget(playerA, playerB, 6); // to deal damage diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BargainTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BargainTest.java index 98b5e6a3b1b..59885e2e729 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BargainTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BargainTest.java @@ -337,4 +337,20 @@ public class BargainTest extends CardTestPlayerBase { assertLife(playerA, 20 + 3); assertTappedCount("Forest", true, 7); } + + @Test + public void testCantBargainWithRestriction() { + setStrictChooseMode(true); + // Players can’t pay life or sacrifice nonland permanents to cast spells or activate abilities. + addCard(Zone.BATTLEFIELD, playerB, "Yasharn, Implacable Earth"); + addCard(Zone.HAND, playerA, glutton); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, relic); + + checkPlayableAbility("restricted by Yasharn", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hamlet Glutton", false); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java index 457ecad62cf..9eed0be4e7a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java @@ -61,32 +61,37 @@ public class CumulativeUpkeepTest extends CardTestPlayerBase { // Whenever Kor Celebrant or another creature you control enters, you gain 1 life. addCard(Zone.HAND, playerB, "Kor Celebrant", 1); // Creature {2}{W} addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); - - addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + // // Cumulative upkeep {2} - // When Illusions of Grandeur enters the battlefield, you gain 20 life. + // At the beginning of your upkeep, put an age counter on this permanent, + // then sacrifice it unless you pay its upkeep cost for each age counter on it. // When Illusions of Grandeur leaves the battlefield, you lose 20 life. addCard(Zone.HAND, playerA, "Illusions of Grandeur"); // Enchantment {3}{U} - - // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + // + // At the beginning of your upkeep, you may exchange control of target nonland permanent you control + // and target nonland permanent an opponent controls with an equal or lesser converted mana cost. addCard(Zone.HAND, playerA, "Puca's Mischief"); // Enchantment {3}{U} - + // turn 1, 2 - prepare cumulative upkeep and gain life triggers castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Illusions of Grandeur"); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Kor Celebrant"); - - // Illusions of Grandeur - CumulativeUpkeepAbility: Cumulative upkeep {2} - setChoice(playerA, true); // Pay {2}? - + + // turn 3 - upkeep trigger and prepare control card + setChoice(playerA, true); // use upkeep cost {2} castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Puca's Mischief"); - - setChoice(playerA, "Cumulative upkeep"); // Triggered list (total 2) which trigger goes first on the stack - addTarget(playerA, "Illusions of Grandeur"); // Own target permanent of Puca's Mischief - addTarget(playerA, "Kor Celebrant"); // Opponent's target permanent of Puca's Mischief - - setChoice(playerA, true); // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. - setChoice(playerA, false); // Pay {2}{2}? + + // turn 5 - multiple upkeep triggers + // first put upkeep, then put change control - so control will be resolved before upkeep cost + setChoice(playerA, "Cumulative upkeep"); + + // resolve change control first + addTarget(playerA, "Illusions of Grandeur"); // own target + addTarget(playerA, "Kor Celebrant"); // opponent target + setChoice(playerA, true); // yes, resolve change control + + // resolve upkeep + setChoice(playerA, false); // no, do not pay upkeep cost checkPermanentCounters("Age counters", 5, PhaseStep.PRECOMBAT_MAIN, playerB, "Illusions of Grandeur", CounterType.AGE, 2); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java index 3ad59bf8d70..15afb526c3a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java @@ -55,7 +55,7 @@ public class DisguiseTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dog Walker using Disguise"); runCode("face up on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { Assert.assertEquals("stack, server - can't find spell", 1, currentGame.getStack().size()); - SpellAbility spellAbility = (SpellAbility) currentGame.getStack().getFirst().getStackAbility(); + SpellAbility spellAbility = (SpellAbility) currentGame.getStack().getFirstOrNull().getStackAbility(); Assert.assertEquals("stack, server - can't find spell", "Cast Dog Walker using Disguise", spellAbility.getName()); CardView spellView = getGameView(playerA).getStack().values().stream().findFirst().orElse(null); Assert.assertNotNull("stack, client: can't find spell", spellView); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisturbTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisturbTest.java index 31c9ba2e104..9bc7356225d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisturbTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisturbTest.java @@ -40,7 +40,7 @@ public class DisturbTest extends CardTestPlayerBase { checkStackObject("on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hook-Haunt Drifter using Disturb", 1); runCode("check stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { // Stack must contain another card side, so spell/card characteristics must be diff from main side (only mana value is same) - Spell spell = (Spell) game.getStack().getFirst(); + Spell spell = (Spell) game.getStack().getFirstOrNull(); Assert.assertEquals("Hook-Haunt Drifter", spell.getName()); Assert.assertEquals(1, spell.getCardType(game).size()); Assert.assertEquals(CardType.CREATURE, spell.getCardType(game).get(0)); @@ -91,7 +91,7 @@ public class DisturbTest extends CardTestPlayerBase { checkStackObject("on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Waildrifter using Disturb", 1); runCode("check stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { // Stack must contain another card side, so spell/card characteristics must be diff from main side (only mana value is same) - Spell spell = (Spell) game.getStack().getFirst(); + Spell spell = (Spell) game.getStack().getFirstOrNull(); Assert.assertEquals("Waildrifter", spell.getName()); Assert.assertEquals(1, spell.getCardType(game).size()); Assert.assertEquals(CardType.CREATURE, spell.getCardType(game).get(0)); @@ -187,7 +187,7 @@ public class DisturbTest extends CardTestPlayerBase { // cast with disturb activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hook-Haunt Drifter using Disturb"); runCode("check stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { - Spell spell = (Spell) game.getStack().getFirst(); + Spell spell = (Spell) game.getStack().getFirstOrNull(); Assert.assertEquals("mana value must be from main side", 2, spell.getManaValue()); Assert.assertEquals("mana cost to pay must be modified", "{U}", spell.getSpellAbility().getManaCostsToPay().getText()); }); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java index ce3c5451784..875883533cc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java @@ -4,6 +4,7 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -27,7 +28,9 @@ public class EscalateTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Savage Alliance", "mode=2Silvercoat Lion"); setModeChoice(playerA, "2"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -52,7 +55,9 @@ public class EscalateTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Collective Defiance", playerB); setModeChoice(playerA, "3"); // deal 3 dmg to opponent + setModeChoice(playerA, TestPlayer.MODE_SKIP); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -78,10 +83,12 @@ public class EscalateTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Collective Defiance"); // {1}{R}{R} sorcery addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Collective Defiance", "mode=2Gaddock Teeg"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Collective Defiance", "mode=2Gaddock Teeg^mode=3targetPlayer=PlayerB"); setModeChoice(playerA, "2"); // deal 4 dmg to target creature (gaddock teeg) setModeChoice(playerA, "3"); // deal 3 dmg to opponent + setModeChoice(playerA, TestPlayer.MODE_SKIP); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -109,8 +116,6 @@ public class EscalateTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Collective Defiance"); // {1}{R}{R} sorcery addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); - setStrictChooseMode(true); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Collective Defiance", "mode=2Wall of Omens"); setModeChoice(playerA, "1"); // opponent discards hand and draws that many setModeChoice(playerA, "2"); // deal 4 dmg to target creature (Wall of Omens) @@ -120,6 +125,8 @@ public class EscalateTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Spell Queller"); addTarget(playerB, "Collective Defiance"); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ForetellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ForetellTest.java index d83cbb867ab..56d21bbca97 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ForetellTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ForetellTest.java @@ -2,6 +2,7 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -81,4 +82,243 @@ public class ForetellTest extends CardTestPlayerBase { assertExileCount(playerA, "Lightning Bolt", 1); // foretold card in exile assertPowerToughness(playerA, "Dream Devourer", 2, 3); // +2 power boost from trigger due to foretell of Lightning Bolt } + + + // Tests needed to check watcher scope issue (see issue #7493 and issue #13774) + + private static final String scornEffigy = "Scorn Effigy"; // {3} 2/3 foretell {0} + private static final String poisonCup = "Poison the Cup"; // {1}{B}{B} instant destroy target creature + // Foretell {1}{B}, if spell was foretold, scry 2 + private static final String flamespeaker = "Flamespeaker Adept"; // {2}{R} 2/3 + // Whenever you scry, gets +2/+0 and first strike until end of turn + private static final String chancemetElves = "Chance-Met Elves"; // {2}{G} 3/2 + // Whenever you scry, gets a +1/+1 counter, triggers once per turn + + private static final String cardE = "Elite Vanguard"; + private static final String cardD = "Devilthorn Fox"; + private static final String cardC = "Canopy Gorger"; + private static final String cardB = "Barbtooth Wurm"; + private static final String cardA = "Alaborn Trooper"; + + private void setupLibrariesEtc() { + // make a library of 5 cards, bottom : E D C B A : top + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, cardE); + addCard(Zone.LIBRARY, playerA, cardD); + addCard(Zone.LIBRARY, playerA, cardC); + addCard(Zone.LIBRARY, playerA, cardB); + addCard(Zone.LIBRARY, playerA, cardA); + removeAllCardsFromLibrary(playerB); + addCard(Zone.LIBRARY, playerB, cardE); + addCard(Zone.LIBRARY, playerB, cardD); + addCard(Zone.LIBRARY, playerB, cardC); + addCard(Zone.LIBRARY, playerB, cardB); + addCard(Zone.LIBRARY, playerB, cardA); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 5); + addCard(Zone.BATTLEFIELD, playerA, flamespeaker); + addCard(Zone.BATTLEFIELD, playerB, chancemetElves); + addCard(Zone.HAND, playerA, scornEffigy); + } + + @Test + public void testForetellWatcherPlayerA() { + setupLibrariesEtc(); + addCard(Zone.HAND, playerA, poisonCup); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scornEffigy); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell"); + checkExileCount("foretold in exile", 2, PhaseStep.PRECOMBAT_MAIN, playerA, poisonCup, 1); + // turn 3, draw card A + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell {1}{B}", chancemetElves); + // foretold, so scry 2 (cards B and C) + addTarget(playerA, cardB); // scrying B bottom (C remains on top) + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, scornEffigy, 2, 3); + assertGraveyardCount(playerA, poisonCup, 1); + assertGraveyardCount(playerB, chancemetElves, 1); + assertPowerToughness(playerA, flamespeaker, 4, 3); + } + + @Test + public void testForetellWatcherPlayerB() { + setupLibrariesEtc(); + addCard(Zone.HAND, playerB, poisonCup); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scornEffigy); + // turn 2, draw card A + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Foretell"); + checkExileCount("foretold in exile", 2, PhaseStep.PRECOMBAT_MAIN, playerB, poisonCup, 1); + // turn 4, draw card B + activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "Foretell {1}{B}", flamespeaker); + // foretold, so scry 2 (cards C and D) + addTarget(playerB, cardD); // scrying D bottom (C remains on top) + + setStrictChooseMode(true); + setStopAt(4, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, scornEffigy, 2, 3); + assertGraveyardCount(playerB, poisonCup, 1); + assertGraveyardCount(playerA, flamespeaker, 1); + assertPowerToughness(playerB, chancemetElves, 4, 3); + } + + @Test + public void testRanar() { + skipInitShuffling(); + String ranar = "Ranar the Ever-Watchful"; // 2WU 2/3 Flying Vigilance + // The first card you foretell each turn costs {0} to foretell. + // Whenever one or more cards are put into exile from your hand or a spell or ability you control exiles + // one or more permanents from the battlefield, create a 1/1 white Spirit creature token with flying. + addCard(Zone.BATTLEFIELD, playerA, ranar); + addCard(Zone.BATTLEFIELD, playerA, "Sage of the Falls"); // may loot on creature ETB + addCard(Zone.HAND, playerA, poisonCup); + addCard(Zone.LIBRARY, playerA, scornEffigy); + addCard(Zone.HAND, playerA, "Wastes"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell"); // poison the cup + setChoice(playerA, true); // yes to loot + setChoice(playerA, "Wastes"); // discard + + checkExileCount("Poison the Cup foretold", 1, PhaseStep.BEGIN_COMBAT, playerA, poisonCup, 1); + checkHandCardCount("scorn effigy drawn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, scornEffigy, 1); + checkPlayableAbility("can't foretell another for free", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell", false); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell"); // scorn effigy + setChoice(playerA, false); // no loot + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Spirit Token", 2); + assertExileCount(playerA, 2); + assertGraveyardCount(playerA, "Wastes", 1); + + } + + @Test + public void testCosmosCharger() { + addCard(Zone.BATTLEFIELD, playerA, "Cosmos Charger"); + // Foretelling cards from your hand costs {1} less and can be done on any player’s turn. + + addCard(Zone.HAND, playerA, scornEffigy); + addCard(Zone.BATTLEFIELD, playerA, "Wastes"); + + activateAbility(2, PhaseStep.UPKEEP, playerA, "Foretell"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, scornEffigy, 1); + } + + @Test + public void testAlrund() { + String alrund = "Alrund, God of the Cosmos"; + // Alrund gets +1/+1 for each card in your hand and each foretold card you own in exile. + + addCard(Zone.BATTLEFIELD, playerA, alrund); // 1/1 + addCard(Zone.HAND, playerA, scornEffigy); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Cadaverous Bloom"); + // Exile a card from your hand: Add {B}{B} or {G}{G}. + + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Exile a card from your hand: Add {B}{B}"); + setChoice(playerA, "Lightning Bolt"); + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Foretell"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHandCount(playerA, 0); + assertExileCount(playerA, scornEffigy, 1); + assertPowerToughness(playerA, alrund, 2, 2); + } + + private static final String valkyrie = "Ethereal Valkyrie"; // 4/4 flying + // Whenever this creature enters or attacks, draw a card, then exile a card from your hand face down. + // It becomes foretold. Its foretell cost is its mana cost reduced by {2}. + + @Test + public void testEtherealValkyrie() { + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + String saga = "Niko Defies Destiny"; + // I - You gain 2 life for each foretold card you own in exile. + // II - Add {W}{U}. Spend this mana only to foretell cards or cast spells that have foretell. + String crab = "Fortress Crab"; // 3U 1/6 + String puma = "Stonework Puma"; // {3} 2/2 + addCard(Zone.BATTLEFIELD, playerA, valkyrie); + addCard(Zone.HAND, playerA, saga); + addCard(Zone.HAND, playerA, crab); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 5); + addCard(Zone.LIBRARY, playerA, "Wastes"); + addCard(Zone.LIBRARY, playerA, puma); + + attack(1, playerA, valkyrie, playerB); + addTarget(playerA, crab); // exile becomes foretold + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, saga); // gain 2 life + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkExileCount("crab foretold", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, crab, 1); + checkPlayableAbility("can't cast foretold same turn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell", false); + + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, puma); + + activateAbility(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 22); + assertLife(playerB, 16); + assertCounterCount(saga, CounterType.LORE, 2); + assertPowerToughness(playerA, crab, 1, 6); + assertPowerToughness(playerA, valkyrie, 4, 4); + assertPowerToughness(playerA, puma, 2, 2); + assertHandCount(playerA, 1); + assertHandCount(playerA, "Wastes", 1); + assertTappedCount("Tundra", true, 5); + } + + @Test + public void testForetoldNotForetell() { + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Wastes"); + addCard(Zone.LIBRARY, playerA, "Darksteel Citadel"); + addCard(Zone.BATTLEFIELD, playerA, valkyrie); + addCard(Zone.BATTLEFIELD, playerA, "Dream Devourer"); + addCard(Zone.HAND, playerA, "Papercraft Decoy"); + + attack(1, playerA, valkyrie, playerB); + addTarget(playerA, "Papercraft Decoy"); // exile becomes foretold + + checkPT("Dream Devourer not boosted", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Dream Devourer", 0, 3); + checkPlayableAbility("Can't cast this turn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell", false); + checkHandCardCount("card drawn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Darksteel Citadel", 1); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 16); + assertPowerToughness(playerA, "Papercraft Decoy", 2, 1); + assertPowerToughness(playerA, "Dream Devourer", 0, 3); + assertHandCount(playerA, 2); + } + + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithAnyNumberModesAbilityTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithAnyNumberModesAbilityTest.java index d42dd419e14..63bf73b6070 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithAnyNumberModesAbilityTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithAnyNumberModesAbilityTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -50,6 +51,7 @@ public class KickerWithAnyNumberModesAbilityTest extends CardTestPlayerBase { setChoice(playerA, true); // use kicker setModeChoice(playerA, "2"); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, playerA); // gain x life addTarget(playerA, "Balduvian Bears"); // get counters @@ -79,6 +81,7 @@ public class KickerWithAnyNumberModesAbilityTest extends CardTestPlayerBase { setChoice(playerA, true); // use kicker setModeChoice(playerA, "2"); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, playerA); // gain x life addTarget(playerA, "Balduvian Bears"); // get counters @@ -108,6 +111,7 @@ public class KickerWithAnyNumberModesAbilityTest extends CardTestPlayerBase { setChoice(playerA, true); // use kicker setModeChoice(playerA, "2"); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, playerA); // gain x life addTarget(playerA, "Balduvian Bears"); // get counters @@ -139,6 +143,7 @@ public class KickerWithAnyNumberModesAbilityTest extends CardTestPlayerBase { setChoice(playerA, true); // use kicker setModeChoice(playerA, "2"); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, playerA); // gain x life addTarget(playerA, "Balduvian Bears"); // get counters diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/LandfallTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/LandfallTest.java index fd5b5811759..f52e53f7474 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/LandfallTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/LandfallTest.java @@ -1,15 +1,12 @@ - package org.mage.test.cards.abilities.keywords; import mage.abilities.keyword.IntimidateAbility; import mage.constants.PhaseStep; import mage.constants.Zone; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class LandfallTest extends CardTestPlayerBase { @@ -26,9 +23,12 @@ public class LandfallTest extends CardTestPlayerBase { playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rest for the Weary"); + addTarget(playerA, playerA); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Rest for the Weary"); + addTarget(playerA, playerA); + setStrictChooseMode(true); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); @@ -43,7 +43,6 @@ public class LandfallTest extends CardTestPlayerBase { * If you Hive Mind an opponent's Rest for the Weary and redirect its target * to yourself when it's not your turn, the game spits out this message and * rolls back to before Rest for the Weary was cast. - * */ @Test public void testHiveMind() { @@ -57,15 +56,17 @@ public class LandfallTest extends CardTestPlayerBase { // Landfall - If you had a land enter the battlefield under your control this turn, that player gains 8 life instead. addCard(Zone.HAND, playerA, "Rest for the Weary", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rest for the Weary"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rest for the Weary", playerA); + setChoice(playerB, true); // change target of copied spell + addTarget(playerB, playerB); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertGraveyardCount(playerA, "Rest for the Weary", 1); assertLife(playerA, 24); assertLife(playerB, 24); - } @Test @@ -76,6 +77,7 @@ public class LandfallTest extends CardTestPlayerBase { playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -104,10 +106,14 @@ public class LandfallTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + // prepare landfall playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Searing Blaze"); + addTarget(playerA, playerB); + addTarget(playerA, "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -133,6 +139,7 @@ public class LandfallTest extends CardTestPlayerBase { attack(2, playerB, "Silvercoat Lion"); castSpell(2, PhaseStep.DECLARE_ATTACKERS, playerB, "Groundswell", "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_COMBAT); execute(); @@ -179,6 +186,7 @@ public class LandfallTest extends CardTestPlayerBase { attack(2, playerB, "Silvercoat Lion"); castSpell(2, PhaseStep.DECLARE_ATTACKERS, playerB, "Groundswell", "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MayhemTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MayhemTest.java new file mode 100644 index 00000000000..79bfd41042b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MayhemTest.java @@ -0,0 +1,89 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class MayhemTest extends CardTestPlayerBase { + + private static final String islanders = "Spider-Islanders"; + + @Test + public void testCastRegular() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.HAND, playerA, islanders); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, islanders); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, islanders, 1); + } + + private static final String imp = "Putrid Imp"; + + @Test + public void testCastDiscarded() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, imp); + addCard(Zone.HAND, playerA, islanders); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard"); + setChoice(playerA, islanders); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, islanders + " with Mayhem"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, islanders, 1); + } + + @Test + public void testCantCastGraveyard() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.GRAVEYARD, playerA, islanders); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, islanders + " with Mayhem"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + try { + execute(); + } catch (Throwable e) { + Assert.assertEquals( + "Should fail to be able to cast " + islanders + " with mayhem as it wasn't discarded this turn", + "Can't find ability to activate command: Cast " + islanders + " with Mayhem", e.getMessage() + ); + } + } + + @Test + public void testCantCastDiscardedPreviously() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, imp); + addCard(Zone.HAND, playerA, islanders); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard"); + setChoice(playerA, islanders); + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, islanders + " with Mayhem"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + try { + execute(); + } catch (Throwable e) { + Assert.assertEquals( + "Should fail to be able to cast " + islanders + " with mayhem as it wasn't discarded this turn", + "Can't find ability to activate command: Cast " + islanders + " with Mayhem", e.getMessage() + ); + } + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java index 26edf9e5db8..fc9123a883e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java @@ -16,6 +16,7 @@ import mage.filter.predicate.mageobject.*; import mage.game.permanent.Permanent; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -319,6 +320,7 @@ public class PrototypeTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, epiphany); setModeChoice(playerA, "3"); // Return target nonland permanent to its owner's hand. setModeChoice(playerA, "4"); // Create a token that's a copy of target creature you control. + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, automaton); addTarget(playerA, automaton); @@ -340,6 +342,7 @@ public class PrototypeTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, epiphany); setModeChoice(playerA, "3"); // Return target nonland permanent to its owner's hand. setModeChoice(playerA, "4"); // Create a token that's a copy of target creature you control. + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, automaton); addTarget(playerA, automaton); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java index 33051479103..ab6a016413a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java @@ -8,6 +8,7 @@ import mage.game.permanent.Permanent; import mage.watchers.common.SaddledMountWatcher; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -90,6 +91,7 @@ public class SaddleTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); setChoice(playerA, bear); // to saddle cost + setChoice(playerA, TestPlayer.CHOICE_SKIP); attack(1, playerA, possum, playerB); setChoice(playerA, bear); // to return @@ -116,6 +118,7 @@ public class SaddleTest extends CardTestPlayerBase { // turn 1 - saddle x2 and trigger on attack activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); setChoice(playerA, bear + "^" + lion); + setChoice(playerA, TestPlayer.CHOICE_SKIP); attack(1, playerA, possum, playerB); setChoice(playerA, elf); // to return (try to choose a wrong creature, so game must not allow to choose it) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java index d15f224a4a2..97c0b402a5b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -24,8 +25,9 @@ public class SpreeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, bear, 1); addCard(Zone.HAND, playerA, accident); - setModeChoice(playerA, "1"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); + setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -41,8 +43,9 @@ public class SpreeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 1); addCard(Zone.HAND, playerA, accident); - setModeChoice(playerA, "2"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident); + setModeChoice(playerA, "2"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -58,9 +61,10 @@ public class SpreeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, bear, 1); addCard(Zone.HAND, playerA, accident); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); setModeChoice(playerA, "1"); setModeChoice(playerA, "2"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -80,9 +84,10 @@ public class SpreeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, electromancer, 1); addCard(Zone.HAND, playerA, accident); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); setModeChoice(playerA, "1"); setModeChoice(playerA, "2"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java index ad94dfaed49..04417cc619c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java @@ -1,6 +1,5 @@ package org.mage.test.cards.abilities.keywords; -import mage.cards.s.SpringOfEternalPeace; import mage.constants.CardType; import mage.constants.PhaseStep; import mage.constants.Zone; @@ -384,7 +383,6 @@ public class TransformTest extends CardTestPlayerBase { assertPowerToughness(playerA, "Huntmaster of the Fells", 2, 2); assertTappedCount("Plains", true, 2); assertTappedCount("Wastes", true, 1); - } @Test @@ -413,8 +411,32 @@ public class TransformTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Ravager of the Fells", 0); assertPermanentCount(playerA, "Huntmaster of the Fells", 1); assertPowerToughness(playerA, "Huntmaster of the Fells", 2, 2); + } + @Test + public void testWildsongHowlerTrigger() { + // The only Daybound/Nightbound card with a Transforms trigger on the back side + removeAllCardsFromLibrary(playerA); + addCard(Zone.HAND, playerA, "Howlpack Piper", 2); // Creature {2}{R}{G} + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 50); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Howlpack Piper"); + setChoice(playerA, true); //Transform trigger + addTarget(playerA, "Silvercoat Lion"); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Howlpack Piper"); + setChoice(playerA, true); //ETB trigger + addTarget(playerA, "Silvercoat Lion"); + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + setStrictChooseMode(true); + + execute(); + + assertPermanentCount(playerA, "Wildsong Howler", 2); + assertPermanentCount(playerA, "Howlpack Piper", 0); // They should be both transformed + assertHandCount(playerA, "Silvercoat Lion", 3); + assertHandCount(playerA, 3); //The two Silvercoat Lions from triggers and 1 from natural card draw } /** diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java index 0475b151603..95f1e8ceb06 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java @@ -5,22 +5,31 @@ import mage.constants.Zone; import org.junit.Test; import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; + /** - * * @author notgreat */ public class OneShotNonTargetTest extends CardTestPlayerBase { + @Test public void YorionChooseAfterTriggerTest() { + // When Yorion enters the battlefield, exile any number of other nonland permanents you own and control. + // Return those cards to the battlefield at the beginning of the next end step. addCard(Zone.HAND, playerA, "Yorion, Sky Nomad"); addCard(Zone.BATTLEFIELD, playerA, "Plains", 7); + // + // When Resolute Reinforcements enters the battlefield, create a 1/1 white Soldier creature token. addCard(Zone.HAND, playerA, "Resolute Reinforcements"); + // prepare yori and put trigger on stack castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Yorion, Sky Nomad"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); checkPermanentCount("Yorion on battlefield", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Yorion, Sky Nomad", 1); + + // prepare another create before yori trigger resolve castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Resolute Reinforcements"); - setChoice(playerA, "Resolute Reinforcements"); + setChoice(playerA, "Resolute Reinforcements"); // 1 of 2 target for yori trigger + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -30,6 +39,7 @@ public class OneShotNonTargetTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Resolute Reinforcements", 1); assertTappedCount("Plains", true, 7); } + @Test public void NonTargetAdjusterTest() { addCard(Zone.HAND, playerA, "Temporal Firestorm"); @@ -52,21 +62,30 @@ public class OneShotNonTargetTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Python", 0); assertGraveyardCount(playerA, "Watchwolf", 1); } + @Test public void ModeSelectionTest() { - addCard(Zone.HAND, playerA, "SOLDIER Military Program"); + // At the beginning of combat on your turn, choose one. If you control a commander, you may choose both instead. + // * Create a 1/1 white Soldier creature token. + // * Put a +1/+1 counter on each of up to two Soldiers you control. + addCard(Zone.HAND, playerA, "SOLDIER Military Program"); // {2}{W} addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); - addCard(Zone.BATTLEFIELD, playerA, "Squire", 1); + addCard(Zone.BATTLEFIELD, playerA, "Squire", 1); // 1/2 + // prepare mode ability castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "SOLDIER Military Program"); + + // turn 1 combat - put counter setModeChoice(playerA, "2"); setChoice(playerA, "Squire"); - setChoice(playerA, TestPlayer.CHOICE_SKIP); + // turn 3 combat - create token setModeChoice(playerA, "1"); + // turn 5 combat - put counter setModeChoice(playerA, "2"); - setChoice(playerA, "Squire^Soldier Token"); + setChoice(playerA, "Squire"); + setChoice(playerA, "Soldier Token"); setStrictChooseMode(true); setStopAt(5, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AddCardSubtypeAllEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AddCardSubtypeAllEffectTest.java new file mode 100644 index 00000000000..6b52fd0918c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AddCardSubtypeAllEffectTest.java @@ -0,0 +1,55 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class AddCardSubtypeAllEffectTest extends CardTestPlayerBase { + + /* + Kudo, King Among Bears + {G}{W} + Legendary Creature — Bear + Other creatures have base power and toughness 2/2 and are Bears in addition to their other types. + 2/2 + */ + private static final String kudo = "Kudo, King Among Bears"; + + /* + Fugitive Wizard + {U} + Creature — Human Wizard + 1/1 + */ + private static final String fugitive = "Fugitive Wizard"; + + @Test + public void testAddCardSubtypeAllEffect() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, fugitive, 3); + addCard(Zone.BATTLEFIELD, playerB, fugitive, 3); + addCard(Zone.BATTLEFIELD, playerA, kudo); + addCard(Zone.BATTLEFIELD, playerA, "Savannah", 2); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { + if (permanent.getName().equals(fugitive)) { + assertTrue(permanent.hasSubtype(SubType.BEAR, currentGame)); + assertTrue(permanent.hasSubtype(SubType.WIZARD, currentGame)); + assertTrue(permanent.hasSubtype(SubType.HUMAN, currentGame)); + assertEquals(2, permanent.getPower().getModifiedBaseValue()); + assertEquals(2, permanent.getToughness().getModifiedBaseValue()); + } + } + + assertSubtype(kudo, SubType.BEAR); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AngelOfJubilationTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AngelOfJubilationTest.java index 2655cdda368..54852c869d3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AngelOfJubilationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AngelOfJubilationTest.java @@ -1,7 +1,13 @@ package org.mage.test.cards.continuous; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.permanent.token.FoodToken; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -18,12 +24,14 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class AngelOfJubilationTest extends CardTestPlayerBase { + public static final String angelOfJubilation = "Angel of Jubilation"; + /** * Tests boosting other non black creatures */ @Test public void testBoost() { - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerA, "Devout Chaplain"); addCard(Zone.BATTLEFIELD, playerA, "Corpse Traders"); @@ -33,7 +41,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); - assertPowerToughness(playerA, "Angel of Jubilation", 3, 3); + assertPowerToughness(playerA, angelOfJubilation, 3, 3); assertPowerToughness(playerA, "Devout Chaplain", 3, 3); assertPowerToughness(playerA, "Corpse Traders", 3, 3); } @@ -43,14 +51,14 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { */ @Test public void testNoBoostOnBattlefieldLeave() { - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerA, "Devout Chaplain"); addCard(Zone.BATTLEFIELD, playerA, "Corpse Traders"); addCard(Zone.HAND, playerA, "Lightning Bolt"); addCard(Zone.BATTLEFIELD, playerA, "Mountain"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Angel of Jubilation"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", angelOfJubilation); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -58,14 +66,14 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); - assertPermanentCount(playerA, "Angel of Jubilation", 0); + assertPermanentCount(playerA, angelOfJubilation, 0); assertPowerToughness(playerA, "Devout Chaplain", 2, 2); assertPowerToughness(playerA, "Corpse Traders", 3, 3); } @Test public void testOpponentCantSacrificeCreatures() { - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk"); addCard(Zone.BATTLEFIELD, playerB, "Corpse Traders"); @@ -81,7 +89,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { @Test public void testOpponentCanSacrificeNonCreaturePermanents() { - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerA, "Savannah Lions"); addCard(Zone.BATTLEFIELD, playerB, "Barrin, Master Wizard"); addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk"); @@ -89,20 +97,20 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Food Chain"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}, Sacrifice a permanent: Return target creature to its owner's hand."); - addTarget(playerB, "Angel of Jubilation"); // return to hand + addTarget(playerB, angelOfJubilation); // return to hand setChoice(playerB, "Food Chain"); // sacrifice cost setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, "Angel of Jubilation", 0); + assertPermanentCount(playerA, angelOfJubilation, 0); assertPermanentCount(playerB, "Food Chain", 0); } @Test public void testOpponentCantSacrificeCreaturesAsPartOfPermanentsOptions() { - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerB, "Barrin, Master Wizard"); addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk"); addCard(Zone.BATTLEFIELD, playerB, "Llanowar Elves", 2); @@ -115,13 +123,13 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, "Angel of Jubilation", 1); + assertPermanentCount(playerA, angelOfJubilation, 1); assertPermanentCount(playerB, "Nantuko Husk", 1); } @Test public void testOpponentCantSacrificeAll() { - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk"); addCard(Zone.BATTLEFIELD, playerB, "Corpse Traders"); addCard(Zone.HAND, playerB, "Soulblast"); @@ -142,7 +150,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { @Test public void testOpponentCantSacrificeCreatureSource() { - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerB, "Children of Korlis"); checkPlayableAbility("Can't sac", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Sacrifice", false); @@ -155,7 +163,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { @Test public void testOpponentCanSacrificeAllLands() { - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerB, "Tomb of Urami"); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4); @@ -169,7 +177,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { @Test public void testOpponentCanSacrificeNonCreatureSource() { - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerA, "Tundra"); addCard(Zone.BATTLEFIELD, playerB, "Wasteland"); @@ -195,7 +203,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { setStrictChooseMode(true); // Other nonblack creatures you control get +1/+1. // Players can't pay life or sacrifice creatures to cast spells or activate abilities - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // Indestructible @@ -234,7 +242,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { setStrictChooseMode(true); // Other nonblack creatures you control get +1/+1. // Players can't pay life or sacrifice creatures to cast spells or activate abilities - addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation"); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); // Pay 7 life: Draw seven cards. addCard(Zone.BATTLEFIELD, playerB, "Griselbrand"); @@ -244,4 +252,90 @@ public class AngelOfJubilationTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); } + + @Test + public void testCanSacrificeTriggeredAbility() { + /* + Unscrupulous Contractor + {2}{B} + Creature — Human Assassin + When this creature enters, you may sacrifice a creature. When you do, target player draws two cards and loses 2 life. + Plot {2}{B} + 3/2 + */ + String contractor = "Unscrupulous Contractor"; + /* + Bear Cub + {1}{G} + Creature - Bear + 2/2 + */ + String cub = "Bear Cub"; + + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); + addCard(Zone.HAND, playerA, contractor); + addCard(Zone.HAND, playerB, contractor); + addCard(Zone.BATTLEFIELD, playerA, cub); + addCard(Zone.BATTLEFIELD, playerB, cub); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, contractor); + setChoice(playerA, true); + setChoice(playerA, cub); + addTarget(playerA, playerA); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, contractor); + setChoice(playerB, true); + setChoice(playerB, cub); + addTarget(playerB, playerB); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, 2); + assertLife(playerA, 20 - 2); + assertGraveyardCount(playerA, cub, 1); + + assertHandCount(playerB, 1 + 2); //draw + contractor effect + assertLife(playerB, 20 - 2); + assertGraveyardCount(playerB, cub, 1); + } + + @Test + public void canSacToMondrakWithArtifacts() { + setStrictChooseMode(true); + //Mondrak, Glory Dominus + //{2}{W}{W} + //Legendary Creature — Phyrexian Horror + //If one or more tokens would be created under your control, twice that many of those tokens are created instead. + //{1}{W/P}{W/P}, Sacrifice two other artifacts and/or creatures: Put an indestructible counter on Mondrak. + String mondrak = "Mondrak, Glory Dominus"; + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new CreateTokenEffect(new FoodToken(), 2), + new ManaCostsImpl<>("") + ); + addCustomCardWithAbility("Token-maker", playerA, ability); + + addCard(Zone.BATTLEFIELD, playerA, mondrak); + addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation); + addCard(Zone.BATTLEFIELD, playerA, "Bear Cub", 2); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + + checkPlayableAbility("Can't activate Mondrak", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W/P}{W/P}, Sacrifice", false); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "create two"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W/P}{W/P}, Sacrifice"); + setChoice(playerA, "Food Token", 2); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertCounterCount(playerA, mondrak, CounterType.INDESTRUCTIBLE, 1); + assertPermanentCount(playerA, "Bear Cub", 2); + assertPermanentCount(playerA, "Food Token", 2); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesColorEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesColorEffectTest.java new file mode 100644 index 00000000000..0002250c017 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesColorEffectTest.java @@ -0,0 +1,69 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class BecomesColorEffectTest extends CardTestPlayerBase { + + /* + Ancient Kavu + {3}{R} + Creature — Kavu + {2}: This creature becomes colorless until end of turn. + Those with the ability to change their nature survived Phyrexia’s biological attacks. Everything else died. + 3/3 + */ + String kavu = "Ancient Kavu"; + /* + Alchor's Tomb + {4} + Artifact + {2}, {T}: Target permanent you control becomes the color of your choice. (This effect lasts indefinitely.) + */ + String alchorsTomb = "Alchor's Tomb"; + /* + + */ + + @Test + public void testBecomesColorSource() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, kavu); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + checkColor("Ancient Kavu is red", 1, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "R", true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: {this}"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkColor("Ancient Kavu is colorless", 1, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "C", true); + checkColor("Ancient Kavu is red again", 2, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "R", true); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + } + + @Test + public void testBecomesColorTarget() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, kavu); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, alchorsTomb); + + checkColor("Ancient Kavu is red", 1, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "R", true); + // make Ancient Kavu green + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}: Target permanent", kavu); + setChoice(playerA, "Green"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkColor("Ancient Kavu is green", 1, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "G", true); + checkColor("Ancient Kavu is still green the following turn", 2, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "G", true); + // activate Ancient Kavu's ability to override green color until end of turn + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: {this}"); + checkColor("Ancient Kavu is colorless", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, kavu, "C", true); + // next turn it should be green again + checkColor("Ancient Kavu is green again", 4, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "G", true); + + setStopAt(4, PhaseStep.BEGIN_COMBAT); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureEffectTest.java new file mode 100644 index 00000000000..9b9cff0f81f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureEffectTest.java @@ -0,0 +1,87 @@ +package org.mage.test.cards.continuous; + +import mage.ObjectColor; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.filter.predicate.mageobject.ToughnessPredicate; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import java.util.Collections; + +public class BecomesCreatureEffectTest extends CardTestPlayerBase { + /* + Ambush Commander + {3}{G}{G} + Creature — Elf + Forests you control are 1/1 green Elf creatures that are still lands. + {1}{G}, Sacrifice an Elf: Target creature gets +3/+3 until end of turn. + 2/2 + */ + String ambushCommander = "Ambush Commander"; + /* + Dryad Arbor + Land Creature — Forest Dryad + 1/1 + */ + String dryadArbor = "Dryad Arbor"; + /* + Frogify + {1}{U} + Enchantment — Aura + Enchant creature + Enchanted creature loses all abilities and is a blue Frog creature with base power and toughness 1/1. + (It loses all other card types and creature types.) + */ + String frogify = "Frogify"; + @Test + public void testBecomesCreatureAllEffect() { + FilterPermanent filter = new FilterPermanent(); + filter.add(CardType.CREATURE.getPredicate()); + filter.add(SubType.ELF.getPredicate()); + filter.add(new PowerPredicate(ComparisonType.EQUAL_TO, 1)); + filter.add(new ToughnessPredicate(ComparisonType.EQUAL_TO, 1)); + + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, dryadArbor); + addCard(Zone.HAND, playerA, ambushCommander); + + runCode("Check forests are not 1/1 Elves", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + int numElves = game.getBattlefield().getActivePermanents(filter, player.getId(), game).size(); + Assert.assertEquals("No 1/1 elves should be present", 0, numElves); + }); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ambushCommander); + runCode("Check forests are 1/1 Elves", 1, PhaseStep.BEGIN_COMBAT, playerA, (info, player, game) -> { + int numElves = game.getBattlefield().getActivePermanents(filter, player.getId(), game).size(); + // 5 forests + dryad arbor + Assert.assertEquals("There should be 6 1/1 elves present", 6, numElves); + }); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void testBecomesCreatureAttachedEffect() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, dryadArbor); + addCard(Zone.HAND, playerA, frogify); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, frogify, dryadArbor); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAbilities(playerA, dryadArbor, Collections.emptyList()); + assertPowerToughness(playerA, dryadArbor, 1, 1); + assertType(dryadArbor, CardType.CREATURE, SubType.FROG); + assertNotSubtype(dryadArbor, SubType.DRYAD); + assertNotType(dryadArbor, CardType.LAND); + assertColor(playerA, dryadArbor, ObjectColor.BLUE, true); + assertColor(playerA, dryadArbor, ObjectColor.GREEN, false); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureIfVehicleEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureIfVehicleEffectTest.java new file mode 100644 index 00000000000..a95936d743a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureIfVehicleEffectTest.java @@ -0,0 +1,45 @@ +package org.mage.test.cards.continuous; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class BecomesCreatureIfVehicleEffectTest extends CardTestPlayerBase { + + /* + Aerial Modification + {4}{W} + Enchantment — Aura + Enchant creature or Vehicle + As long as enchanted permanent is a Vehicle, it’s a creature in addition to its other types. + Enchanted creature gets +2/+2 and has flying. + */ + String aerialMod = "Aerial Modification"; + /* + Goliath Truck + {4} + Artifact — Vehicle + Stowage — Whenever this Vehicle attacks, put two +1/+1 counters on another target attacking creature. + Crew 2 (Tap any number of creatures you control with total power 2 or more: This Vehicle becomes an artifact creature until end of turn.) + 4/4 + */ + String goliathTruck = "Goliath Truck"; + + @Test + public void testBecomesCreatureIfVehicleEffect() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, goliathTruck); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.HAND, playerA, aerialMod); + + checkType("Goliath Truck is not a creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, goliathTruck, CardType.CREATURE, false); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aerialMod, goliathTruck); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertType(goliathTruck, CardType.CREATURE, true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java index 47aa71944e6..c59ff09ec30 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.continuous; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -22,7 +23,9 @@ public class OracleEnVecNextTurnTest extends CardTestPlayerBase { // 1 - activate activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target opponent", playerB); - setChoice(playerB, "Balduvian Bears^Angelic Wall"); + setChoice(playerB, "Balduvian Bears"); + setChoice(playerB, "Angelic Wall"); + setChoice(playerB, TestPlayer.CHOICE_SKIP); // checkLife("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, 20); checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java index 6abb3d2a9ac..f1ae5bc4a96 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java @@ -235,7 +235,7 @@ public class TakeControlWhileSearchingLibraryTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Buried Alive"); setChoice(playerB, true); // continue after new control addTarget(playerB, "Balduvian Bears"); - addTarget(playerB, TestPlayer.TARGET_SKIP); + //addTarget(playerB, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); checkGraveyardCount("after grave a", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 0); checkGraveyardCount("after grave b", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Balduvian Bears", 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java index 1d6ba250fcb..7cec4f73b4f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java @@ -23,7 +23,6 @@ public class RemoveCounterCostTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, Remove two +1/+1 counters"); setChoice(playerA, "Novijen Sages"); // counters to remove - setChoice(playerA, TestPlayer.CHOICE_SKIP); setChoice(playerA, "X=2"); // counters to remove setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java index 10f2bc5eb3c..bf0e4d40959 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java @@ -427,18 +427,20 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase { String savageBeating = "Savage Beating"; skipInitShuffling(); - addCard(Zone.LIBRARY, playerA, savageBeating, 2); + addCard(Zone.LIBRARY, playerA, savageBeating, 2); // to free cast addCard(Zone.HAND, playerA, jeleva); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); addCard(Zone.BATTLEFIELD, playerA, "Island", 3); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // prepare and exile cards from libs castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, jeleva); + // attack and use free cast from exile attack(3, playerA, jeleva); - setChoice(playerA, true); // opt to use Jeleva ability - setChoice(playerA, savageBeating); // choose to cast Savage Beating for free - setChoice(playerA, false); // opt not to pay entwine cost + setChoice(playerA, savageBeating); // to free cast + setChoice(playerA, true); // confirm free cast + setChoice(playerA, false); // ignore entwine cost setModeChoice(playerA, "1"); // use first mode of Savage Beating granting double strike setStopAt(3, PhaseStep.END_COMBAT); @@ -448,6 +450,5 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase { assertTapped(jeleva, true); assertLife(playerB, 18); assertAbility(playerA, jeleva, DoubleStrikeAbility.getInstance(), true); - } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java index a229bb39472..257cae4f572 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java @@ -945,7 +945,7 @@ public class ModalDoubleFacedCardsTest extends CardTestPlayerBase { // cast as copy of mdf card castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zam Wesell"); addTarget(playerA, playerB); // target opponent - addTarget(playerA, "Akoum Warrior"); // creature card to copy + setChoice(playerA, "Akoum Warrior"); // creature card to copy waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/dungeons/DungeonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/dungeons/DungeonTest.java index 548ecea0a7c..126f783ade5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/dungeons/DungeonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/dungeons/DungeonTest.java @@ -20,7 +20,7 @@ import java.util.stream.Stream; */ public class DungeonTest extends CardTestPlayerBase { - private static final String TOMB_OF_ANNIHILATION = "Tomb of Annihilation"; + private static final String TOMB_OF_ANNIHILATION = "Tomb of Annihilation"; // TODO: miss test private static final String LOST_MINE_OF_PHANDELVER = "Lost Mine of Phandelver"; private static final String DUNGEON_OF_THE_MAD_MAGE = "Dungeon of the Mad Mage"; private static final String FLAMESPEAKER_ADEPT = "Flamespeaker Adept"; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemsTest.java index a1587ff240e..5260c13070f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemsTest.java @@ -157,4 +157,29 @@ public class EmblemsTest extends CardTestPlayerBase { assertHandCount(playerA, 0); } + + @Test + public void testJayaFieryNegotiator() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Jaya, Fiery Negotiator"); + addCard(Zone.HAND, playerA, "Wrenn's Resolve"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.LIBRARY, playerA, "Lightning Bolt", 2); + skipInitShuffling(); + + addCounters(1, PhaseStep.UPKEEP, playerA, "Jaya, Fiery Negotiator", CounterType.LOYALTY, 6); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-8: You get an emblem"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wrenn's Resolve"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + setChoice(playerA, false, 2); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, 6 - 1); + assertLife(playerB, 20 - 3 * 3); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java index 9ea9a2ee029..c9dcc5a9b48 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java @@ -4,6 +4,7 @@ package org.mage.test.cards.modal; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -25,7 +26,7 @@ public class OneOrBothTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); setModeChoice(playerA, "1"); - setModeChoice(playerA, null); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -47,7 +48,7 @@ public class OneOrBothTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); setModeChoice(playerA, "2"); - setModeChoice(playerA, null); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java index 8d65219d629..dd02ce056d6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java @@ -6,6 +6,7 @@ import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -39,7 +40,7 @@ public class OneOrMoreTest extends CardTestPlayerBase { addTarget(playerA, "Silvercoat Lion"); // for 3 addTarget(playerA, "Silvercoat Lion"); // for 4 addTarget(playerA, playerB); // for 5 - setModeChoice(playerA, null); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); @@ -76,7 +77,7 @@ public class OneOrMoreTest extends CardTestPlayerBase { addTarget(playerA, "Silvercoat Lion"); // for 3 addTarget(playerA, "Silvercoat Lion"); // for 4 addTarget(playerA, playerB); // for 5 - setModeChoice(playerA, null); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/PawPrintsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/PawPrintsTest.java index b302b9cea0d..1e7f01f5e0c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/modal/PawPrintsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/PawPrintsTest.java @@ -3,7 +3,9 @@ package org.mage.test.cards.modal; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; +import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -61,25 +63,17 @@ public class PawPrintsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Season of Gathering"); setModeChoice(playerA, "1"); setModeChoice(playerA, "2"); - setModeChoice(playerA, "3"); // Will be unused + setModeChoice(playerA, "3"); // no more paws for mode 3, see exception below addTarget(playerA, "Memnite"); // for 1 setChoice(playerA, "Enchantment"); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - // Add one more counter from Hardened Scales, which was still on the battlefield when the counter placing effect triggered - assertPowerToughness(playerA, "Memnite", 3, 3); - assertCounterCount("Memnite", CounterType.P1P1, 2); - - // But not anymore... - assertPermanentCount(playerA, "Hardened Scales", 0); - assertGraveyardCount(playerA, "Hardened Scales", 1); - - // Draw effect didnt trigger - assertHandCount(playerA, 0); - + try { + execute(); + } catch (AssertionError e) { + Assert.assertTrue("mode 3 must not be able to choose due total paws cost", e.getMessage().contains("Can't use mode: 3")); + } } @Test diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/rules/AttachmentTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/rules/AttachmentTest.java index 7da50462f5d..8f2c735588f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/rules/AttachmentTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/rules/AttachmentTest.java @@ -282,9 +282,9 @@ public class AttachmentTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Island", 3); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Show and Tell"); - setChoice(playerA, true); - setChoice(playerB, false); + setChoice(playerA, true); // yes, put from hand to battle addTarget(playerA, "Aether Tunnel"); + setChoice(playerB, false); // no, do not put setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/_5ed/AnHavvaConstableTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/_5ed/AnHavvaConstableTest.java new file mode 100644 index 00000000000..d6485265308 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/_5ed/AnHavvaConstableTest.java @@ -0,0 +1,51 @@ +package org.mage.test.cards.single._5ed; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AnHavvaConstableTest extends CardTestPlayerBase { + + /* + An-Havva Constable + {1}{G}{G} + Creature — Human + An-Havva Constable’s toughness is equal to 1 plus the number of green creatures on the battlefield. + 2/1+* + */ + private static final String constable = "An-Havva Constable"; + /* + Bear Cub + {1}{G} + Creature — Bear + 2/2 + */ + private static final String cub = "Bear Cub"; + @Test + public void testAnHavva() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, constable); + addCard(Zone.HAND, playerA, constable); + addCard(Zone.BATTLEFIELD, playerA, cub, 2); + addCard(Zone.BATTLEFIELD, playerB, cub, 3); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + checkPT("An-Havva Constable has toughness 7", 1, PhaseStep.PRECOMBAT_MAIN, playerA, constable, 2, 1 + 6); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", cub); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPT("An-Havva Constable has toughness 6", 1, PhaseStep.PRECOMBAT_MAIN, playerA, constable, 2, 1 + 5); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + playerA.getHand().getCards(currentGame).stream() + .filter(card -> card.getName().equals(constable)) + .findFirst(). + ifPresent(card -> Assert.assertEquals(6, card.getToughness().getValue())); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/acr/AdrestiaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/acr/AdrestiaTest.java new file mode 100644 index 00000000000..63c3f4618bf --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/acr/AdrestiaTest.java @@ -0,0 +1,58 @@ +package org.mage.test.cards.single.acr; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AdrestiaTest extends CardTestPlayerBase { + + /* + Adrestia + {3} + Legendary Artifact — Vehicle + + Islandwalk (This creature can’t be blocked as long as defending player controls an Island.) + + Whenever Adrestia attacks, if an Assassin crewed it this turn, draw a card. Adrestia becomes an Assassin in addition to its other types until end of turn. + + Crew 1 + */ + private static final String adrestia = "Adrestia"; + /* + Nekrataal + {2}{B}{B} + Creature — Human Assassin + + First strike + + When this creature enters, destroy target nonartifact, nonblack creature. That creature can’t be regenerated. + + 2/1 + */ + private static final String nekrataal = "Nekrataal"; + + @Test + public void testAdrestia() { + setStrictChooseMode(true); + + + addCard(Zone.BATTLEFIELD, playerA, adrestia); + addCard(Zone.BATTLEFIELD, playerA, nekrataal); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Crew"); + setChoice(playerA, nekrataal); + attack(1, playerA, adrestia); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertSubtype(adrestia, SubType.VEHICLE); + assertType(adrestia, CardType.CREATURE, true); + assertType(adrestia, CardType.ARTIFACT, true); + assertSubtype(adrestia, SubType.ASSASSIN); + assertHandCount(playerA, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java index e425d65dc53..5245a558120 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java @@ -50,6 +50,9 @@ public class MantleOfTheAncientsTest extends CardTestPlayerBase { addTarget(playerA, "Gate Smasher^Aether Tunnel"); addTarget(playerA, TestPlayer.TARGET_SKIP); + + showBattlefield("after", 1, PhaseStep.END_TURN, playerA); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java index 844ca5cfb0a..b8140e785d9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java @@ -314,15 +314,14 @@ public class ShareTheSpoilsTest extends CardTestCommander4Players { // Cast an adventure card from hand castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Dizzying Swoop"); addTarget(playerA, "Prosper, Tome-Bound"); - addTarget(playerA, TestPlayer.TARGET_SKIP); // tap 1 of 2 targets + //addTarget(playerA, TestPlayer.TARGET_SKIP); // only 1 creature, so tap 1 of 2, no need in skip waitStackResolved(5, PhaseStep.PRECOMBAT_MAIN); // Make sure the creature card can't be played from exile since there isn't the {W}{W} for it checkPlayableAbility("creature cast", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Ardenvale Tactician", false); - setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); - setStrictChooseMode(true); + setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); execute(); // 1 exiled with Share the Spoils diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java index bbc8a10ac93..de0b7d560c5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java @@ -189,9 +189,9 @@ public class ApproachOfTheSecondSunTest extends CardTestPlayerBase { // first may have been cast from anywhere. castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Finale of Promise", true); setChoice(playerA, "X=7"); // each with mana value X or less + addTarget(playerA, TARGET_SKIP); // skip instant target + addTarget(playerA, "Approach of the Second Sun"); // use sorcery target setChoice(playerA, "Yes"); // You may cast - addTarget(playerA, TARGET_SKIP); // up to one target instant card - addTarget(playerA, "Approach of the Second Sun"); // and/or up to one target sorcery card from your graveyard checkLife("Approach of the Second Sun cast from graveyard gains life", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 27); checkLibraryCount("Approach of the Second Sun cast from graveyard goes to library", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Approach of the Second Sun", 1); @@ -200,9 +200,9 @@ public class ApproachOfTheSecondSunTest extends CardTestPlayerBase { // The second Approach of the Second Sun that you cast must be cast from your hand, castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Finale of Promise", true); setChoice(playerA, "X=7"); // each with mana value X or less - setChoice(playerA, "Yes"); // You may cast addTarget(playerA, TARGET_SKIP); // up to one target instant card addTarget(playerA, "Approach of the Second Sun"); // and/or up to one target sorcery card from your graveyard + setChoice(playerA, "Yes"); // You may cast checkLife("Approach of the Second Sun cast from graveyard gains life", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 34); checkLibraryCount("Approach of the Second Sun cast from graveyard goes to library", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Approach of the Second Sun", 2); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/DarkImpostorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/DarkImpostorTest.java new file mode 100644 index 00000000000..6be3f478b97 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/DarkImpostorTest.java @@ -0,0 +1,53 @@ +package org.mage.test.cards.single.avr; + +import mage.abilities.common.SimpleActivatedAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class DarkImpostorTest extends CardTestPlayerBase { + + /* + Dark Impostor + {2}{B} + Creature — Vampire Assassin + + {4}{B}{B}: Exile target creature and put a +1/+1 counter on this creature. + + This creature has all activated abilities of all creature cards exiled with it. + 2/2 + */ + public static final String darkImposter = "Dark Impostor"; + /* + Deathrite Shaman + {B/G} + Creature — Elf Shaman + + {T}: Exile target land card from a graveyard. Add one mana of any color. + + {B}, {T}: Exile target instant or sorcery card from a graveyard. Each opponent loses 2 life. + + {G}, {T}: Exile target creature card from a graveyard. You gain 2 life. + + 1/2 + */ + public static final String deathriteShaman = "Deathrite Shaman"; + + @Test + public void testDarkImpostor() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, darkImposter); + addCard(Zone.BATTLEFIELD, playerA, deathriteShaman); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{B}{B}"); + addTarget(playerA, deathriteShaman); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAbilityCount(playerA, darkImposter, SimpleActivatedAbility.class, 4); // own ability + 3 other from deathrite + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java index cbaf8d3479d..41701e21958 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java @@ -4,6 +4,7 @@ package org.mage.test.cards.single.bfz; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -27,7 +28,7 @@ public class BrutalExpulsionTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", "mode=2Augmenting Automaton"); setModeChoice(playerA, "2"); - setModeChoice(playerA, null); // ignore last one mode + setModeChoice(playerA, TestPlayer.MODE_SKIP); // ignore last one mode //addTarget(playerA, "mode=2Augmenting Automaton"); // doesn't work with mode setStopAt(1, PhaseStep.END_COMBAT); @@ -56,7 +57,7 @@ public class BrutalExpulsionTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", "mode=2Kiora, the Crashing Wave"); setModeChoice(playerA, "2"); - setModeChoice(playerA, null); // ignore last one mode + setModeChoice(playerA, TestPlayer.MODE_SKIP); // ignore last one mode setStopAt(1, PhaseStep.END_COMBAT); execute(); @@ -116,7 +117,7 @@ public class BrutalExpulsionTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", "mode=2Gideon, Ally of Zendikar"); setModeChoice(playerA, "2"); - setModeChoice(playerA, null); // ignore last one mode + setModeChoice(playerA, TestPlayer.MODE_SKIP); // ignore last one mode castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shock", "Gideon, Ally of Zendikar"); setStopAt(1, PhaseStep.END_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/big/EsotericDuplicatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/big/EsotericDuplicatorTest.java new file mode 100644 index 00000000000..269092e77bb --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/big/EsotericDuplicatorTest.java @@ -0,0 +1,51 @@ +package org.mage.test.cards.single.big; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class EsotericDuplicatorTest extends CardTestPlayerBase { + + /* + Esoteric Duplicator + {2}{U} + Artifact — Clue + + Whenever you sacrifice this artifact or another artifact, you may pay {2}. If you do, at the beginning of the next end step, create a token that’s a copy of that artifact. + + {2}, Sacrifice this artifact: Draw a card. + */ + private static final String esotericDuplicator = "Esoteric Duplicator"; + + /* + Sculpting Steel + {3} + Artifact + You may have Sculpting Steel enter the battlefield as a copy of any artifact on the battlefield. + */ + private static final String sculptingSteel = "Sculpting Steel"; + + @Test + public void testEsotericDuplicator() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, esotericDuplicator); + addCard(Zone.HAND, playerA, sculptingSteel); + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sculptingSteel); + setChoice(playerA, true); + setChoice(playerA, esotericDuplicator); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, Sacrifice"); // Sacrifice duplicator + setChoice(playerA, true); // duplicate + + + setStopAt(2, PhaseStep.UPKEEP); + execute(); + + assertPermanentCount(playerA, esotericDuplicator, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/ActOfAuthorityEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/ActOfAuthorityEffectTest.java new file mode 100644 index 00000000000..05ce5bdb0d4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/ActOfAuthorityEffectTest.java @@ -0,0 +1,34 @@ +package org.mage.test.cards.single.c13; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class ActOfAuthorityEffectTest extends CardTestPlayerBase { + /* + Act of Authority + {1}{W}{W} + Enchantment + When this enchantment enters, you may exile target artifact or enchantment. + At the beginning of your upkeep, you may exile target artifact or enchantment. If you do, its controller gains control of this enchantment. + */ + private static final String actOfAuthority = "Act of Authority"; + + @Test + public void testActOfAuthority() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, actOfAuthority); + addCard(Zone.BATTLEFIELD, playerB, actOfAuthority + "@actB"); + + setChoice(playerA, true); // upkeep + addTarget(playerA, "@actB"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, actOfAuthority, 1); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java index fe28717b08c..a15c9f866aa 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java @@ -27,8 +27,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // attack and cast first time (free) attack(1, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Grizzly Bears"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(1, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Grizzly Bears", 1); checkPermanentTapped("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Forest", true, 0); @@ -40,8 +40,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 3 - second cast (1x tax) attack(3, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Grizzly Bears"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(3, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Grizzly Bears", 1); checkPermanentTapped("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Forest", true, 2); // 1x tax @@ -53,8 +53,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 5 - third cast (2x tax) attack(5, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Grizzly Bears"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(5, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Grizzly Bears", 1); checkPermanentTapped("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Forest", true, 2 * 2); // 2x tax @@ -85,8 +85,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // attack and cast first time (free) attack(1, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Akoum Warrior"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(1, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Akoum Warrior", 1); checkPermanentTapped("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Mountain", true, 0); @@ -98,8 +98,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 3 - second cast (1x tax) attack(3, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Akoum Warrior"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(3, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Akoum Warrior", 1); checkPermanentTapped("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Mountain", true, 2); // 1x tax @@ -111,8 +111,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 5 - third cast (2x tax) attack(5, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Akoum Warrior"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(5, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Akoum Warrior", 1); checkPermanentTapped("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Mountain", true, 2 * 2); // 2x tax @@ -144,8 +144,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // attack and cast first time (free) attack(1, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Birgi, God of Storytelling"); // commander choice + setChoice(playerA, true); // cast commander setChoice(playerA, "Cast Birgi, God of Storytelling"); // spell choice waitStackResolved(1, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Birgi, God of Storytelling", 1); @@ -158,8 +158,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 3 - second cast, LEFT side (1x tax) attack(3, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Birgi, God of Storytelling"); // commander choice + setChoice(playerA, true); // cast commander setChoice(playerA, "Cast Birgi, God of Storytelling"); // spell choice waitStackResolved(3, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Birgi, God of Storytelling", 1); @@ -172,8 +172,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 5 - third cast, RIGHT side (2x tax) attack(5, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Birgi, God of Storytelling"); // commander choice + setChoice(playerA, true); // cast commander setChoice(playerA, "Cast Harnfel, Horn of Bounty"); // spell choice waitStackResolved(5, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Harnfel, Horn of Bounty", 1); @@ -186,8 +186,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 7 - fourth cast, RIGHT side (3x tax) attack(7, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Birgi, God of Storytelling"); // commander choice + setChoice(playerA, true); // cast commander setChoice(playerA, "Cast Harnfel, Horn of Bounty"); // spell choice waitStackResolved(7, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 4", 7, PhaseStep.COMBAT_DAMAGE, playerA, "Harnfel, Horn of Bounty", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/RavenousSlimeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/RavenousSlimeTest.java new file mode 100644 index 00000000000..aa91658d7e1 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/RavenousSlimeTest.java @@ -0,0 +1,36 @@ + +package org.mage.test.cards.single.c18; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author notgreat + */ +public class RavenousSlimeTest extends CardTestPlayerBase { + + @Test + public void testRavenousSlime() { + addCustomEffect_TargetDestroy(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Ravenous Slime"); + addCard(Zone.BATTLEFIELD, playerB, "Runeclaw Bear"); + addCard(Zone.BATTLEFIELD, playerB, "Centaur Courser"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Runeclaw Bear"); + checkPT("Ravenous Slime ate Runeclaw Bear", 1, PhaseStep.BEGIN_COMBAT, playerA, "Ravenous Slime", 3, 3); + + attack(1, playerA, "Ravenous Slime"); + block(1, playerB, "Centaur Courser", "Ravenous Slime"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertExileCount(playerB, "Runeclaw Bear", 1); + assertExileCount(playerB, "Centaur Courser", 1); + assertGraveyardCount(playerA, "Ravenous Slime", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/clu/SludgeTitanTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/clu/SludgeTitanTest.java index 9b22b8e5954..67d4275faee 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/clu/SludgeTitanTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/clu/SludgeTitanTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.single.clu; import mage.constants.PhaseStep; @@ -6,6 +5,7 @@ import mage.constants.Zone; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -41,7 +41,6 @@ public class SludgeTitanTest extends CardTestPlayerBase { addCard(Zone.LIBRARY, playerA, divination, 5); attack(1, playerA, titan, playerB); - setChoice(playerA, playerA.CHOICE_SKIP); setStopAt(1, PhaseStep.DECLARE_BLOCKERS); execute(); @@ -49,29 +48,6 @@ public class SludgeTitanTest extends CardTestPlayerBase { assertGraveyardCount(playerA, divination, 5); } - @Test - public void testNoValidChoiceInvalid() { - setStrictChooseMode(true); - skipInitShuffling(); - - addCard(Zone.BATTLEFIELD, playerA, titan); - addCard(Zone.LIBRARY, playerA, divination, 5); - - attack(1, playerA, titan, playerB); - setChoice(playerA, divination); // invalid, titan doesn't allow for choosing sorcery - - setStopAt(1, PhaseStep.DECLARE_BLOCKERS); - - try { - execute(); - Assert.fail("must throw exception on execute"); - } catch (Throwable e) { - if (!e.getMessage().startsWith("Missing CHOICE def")) { - Assert.fail("Unexpected exception " + e.getMessage()); - } - } - } - @Test public void testCreatureOnly_ChooseNone() { setStrictChooseMode(true); @@ -81,7 +57,7 @@ public class SludgeTitanTest extends CardTestPlayerBase { addCard(Zone.LIBRARY, playerA, piker, 5); attack(1, playerA, titan, playerB); - setChoice(playerA, playerA.CHOICE_SKIP); + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStopAt(1, PhaseStep.DECLARE_BLOCKERS); execute(); @@ -207,6 +183,8 @@ public class SludgeTitanTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, titan); addCard(Zone.LIBRARY, playerA, brownscale, 5); + // must able to select only 1 creature, so after first choice it must auto-finish + // if you see miss choice here then something broken in TestPlayer's choose method attack(1, playerA, titan, playerB); setChoice(playerA, brownscale); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/AatchikEmeraldRadianTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/AatchikEmeraldRadianTest.java new file mode 100644 index 00000000000..bb03167ce2a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/AatchikEmeraldRadianTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.dft; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AatchikEmeraldRadianTest extends CardTestPlayerBase { + + /* + Aatchik, Emerald Radian + {3}{B}{B}{G} + Legendary Creature — Insect Druid + When Aatchik enters, create a 1/1 green Insect creature token for each artifact and/or creature card in your graveyard. + Whenever another Insect you control dies, put a +1/+1 counter on Aatchik. Each opponent loses 1 life. + 3/3 + */ + private static final String aatchik = "Aatchik, Emerald Radian"; + + /* + Springheart Nantuko + {1}{G} + Enchantment Creature — Insect Monk + Bestow {1}{G} + Enchanted creature gets +1/+1. + Landfall — Whenever a land you control enters, you may pay {1}{G} if this permanent is attached to a creature you control. + If you do, create a token that’s a copy of that creature. If you didn’t create a token this way, create a 1/1 green Insect creature token. + 1/1 + */ + private static final String nantuko = "Springheart Nantuko"; + + @Test + public void testOpponentCreatingTokens() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, aatchik); + addCard(Zone.BATTLEFIELD, playerB, aatchik); + addCard(Zone.GRAVEYARD, playerA, aatchik); + addCard(Zone.GRAVEYARD, playerB, aatchik); + addCard(Zone.BATTLEFIELD, playerA, "Bayou", 9); + addCard(Zone.HAND, playerA, nantuko); + addCard(Zone.HAND, playerA, "Bayou"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nantuko + " using bestow"); + addTarget(playerA, aatchik); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bayou"); + setChoice(playerA, true); + setChoice(playerA, aatchik); + setChoice(playerA, "When {this} enters"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTokenCount(playerB, "Insect Token", 0); + assertTokenCount(playerA, "Insect Token", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dis/ExperimentKrajTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dis/ExperimentKrajTest.java new file mode 100644 index 00000000000..2a0e364821b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dis/ExperimentKrajTest.java @@ -0,0 +1,77 @@ +package org.mage.test.cards.single.dis; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.UntapAllControllerEffect; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import static org.junit.Assert.assertEquals; + +public class ExperimentKrajTest extends CardTestPlayerBase { + + /* + Experiment Kraj + {2}{G}{G}{U}{U} + Legendary Creature — Ooze Mutant + + Experiment Kraj has all activated abilities of each other creature with a +1/+1 counter on it. + + {T}: Put a +1/+1 counter on target creature. + */ + private static final String experimentKraj = "Experiment Kraj"; + /* + Stoneforge Mystic + {1}{W} + Creature — Kor Artificer + + When this creature enters, you may search your library for an Equipment card, reveal it, put it into your hand, then shuffle. + + {1}{W}, {T}: You may put an Equipment card from your hand onto the battlefield. + */ + private static final String stoneforgeMystic = "Stoneforge Mystic"; + /* + Noble Hierarch + {G} + Creature — Human Druid + + Exalted (Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.) + + {T}: Add {G}, {W}, or {U}. + */ + private static final String nobleHierarch = "Noble Hierarch"; + + @Test + public void testExperimentKraj() { + setStrictChooseMode(true); + + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_A_CREATURE), + new ManaCostsImpl<>("") + ); + addCustomCardWithAbility("Untap creatures", playerA, ability); + + addCard(Zone.BATTLEFIELD, playerA, experimentKraj); + addCard(Zone.BATTLEFIELD, playerA, stoneforgeMystic); + addCard(Zone.BATTLEFIELD, playerB, nobleHierarch); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Put a +1/+1", stoneforgeMystic); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Put a +1/+1", nobleHierarch); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertEquals("Kraj should have 5 activated abilities", 5, getPermanent(experimentKraj).getAbilities(currentGame) + .stream() + .filter(Ability::isActivatedAbility) + .count()); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/KarnsSylexTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/KarnsSylexTest.java index 5ad1afd3b6f..3f76a1a73b6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/KarnsSylexTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/KarnsSylexTest.java @@ -2,7 +2,6 @@ package org.mage.test.cards.single.dmu; import mage.constants.PhaseStep; import mage.constants.Zone; -import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -76,4 +75,26 @@ public class KarnsSylexTest extends CardTestPlayerBase { assertLife(playerB, 20 - 3); assertGraveyardCount(playerA, "Lightning Bolt", 1); } + + /** + * Test that it does not work with mana abilities, e.g. Thran Portal, with Yasharn, Implacable Earth. + */ + @Test + public void blockedManaAbilitiesWithYasharn() { + addCard(Zone.HAND, playerA, "Thran Portal"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, karnsSylex); + addCard(Zone.BATTLEFIELD, playerA, "Yasharn, Implacable Earth"); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thran Portal"); + setChoice(playerA, "Thran"); + setChoice(playerA, "Mountain"); + + checkPlayableAbility("restricted by Yasharn", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertLife(playerA, 20); + assertLife(playerB, 20); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/ThranPortalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/ThranPortalTest.java index 214a6985c81..881f45d1e0d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/ThranPortalTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/ThranPortalTest.java @@ -5,8 +5,6 @@ import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; -import java.lang.annotation.Target; - /** * {@link mage.cards.t.ThranPortal Thran Portal} * Land Gate diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dsk/UnidentifiedHovershipTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dsk/UnidentifiedHovershipTest.java new file mode 100644 index 00000000000..b7ae92e0d6d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dsk/UnidentifiedHovershipTest.java @@ -0,0 +1,67 @@ +package org.mage.test.cards.single.dsk; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class UnidentifiedHovershipTest extends CardTestPlayerBase { + + /* + Unidentified Hovership + {1}{W}{W} + Artifact - Vehicle + Flying + When this Vehicle enters, exile up to one target creature with toughness 5 or less. + When this Vehicle leaves the battlefield, the exiled card's owner manifests dread. + Crew 1 + 2/2 + */ + private static final String unidentifiedHovership = "Unidentified Hovership"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + /* + Naturalize + {1}{G} + Instant + Destroy target artifact or enchantment. + */ + private static final String naturalize = "Naturalize"; + + @Test + public void testUnidentifiedHovership() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, unidentifiedHovership); + addCard(Zone.HAND, playerB, naturalize); + addCard(Zone.BATTLEFIELD, playerB, bearCub); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, unidentifiedHovership); + addTarget(playerA, bearCub); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, naturalize, unidentifiedHovership); + setChoice(playerB, "Mountain"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, 2 + 1); // forests + face down + assertGraveyardCount(playerA, unidentifiedHovership, 1); + assertGraveyardCount(playerB, 1 + 1); // naturalize + manifest + assertExileCount(playerB, bearCub, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java index 8f5316c9411..31a24c63200 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java @@ -78,7 +78,7 @@ public class TamiyoFieldResearcherTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Bronze Sable"); - addTarget(playerA, TestPlayer.TARGET_SKIP); + //addTarget(playerA, TestPlayer.TARGET_SKIP); // there are only 1 creature, so choose 1 of 2, no need in skip attack(1, playerA, "Bronze Sable"); @@ -240,7 +240,7 @@ public class TamiyoFieldResearcherTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Bronze Sable"); - addTarget(playerA, TestPlayer.TARGET_SKIP); + //addTarget(playerA, TestPlayer.TARGET_SKIP); // there are only 1 creature, so choose 1 of 2, no need in skip attack(2, playerB, "Bronze Sable"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/UriangerAugureltTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/UriangerAugureltTest.java new file mode 100644 index 00000000000..cf66bcd37fd --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/UriangerAugureltTest.java @@ -0,0 +1,75 @@ +package org.mage.test.cards.single.fic; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.UntapAllControllerEffect; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class UriangerAugureltTest extends CardTestPlayerBase { + + private static final String urianger = "Urianger Augurelt"; + private static final String arcaneSignet = "Arcane Signet"; + private static final String howlingMine = "Howling Mine"; + private static final String thoughtVessel = "Thought Vessel"; + private static final String benalishKnight = "Benalish Knight"; + + @Test + public void uriangerAugureltTest() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_A_CREATURE), + new ManaCostsImpl<>("") + ); + addCustomCardWithAbility("Untap creatures", playerA, ability); + + addCard(Zone.BATTLEFIELD, playerA, urianger); + addCard(Zone.LIBRARY, playerA, "Plains", 3); + addCard(Zone.LIBRARY, playerA, arcaneSignet); + addCard(Zone.LIBRARY, playerA, howlingMine); + addCard(Zone.LIBRARY, playerA, thoughtVessel); + addCard(Zone.LIBRARY, playerA, benalishKnight); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Draw Arcanum"); + setChoice(playerA, true, 4); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Draw Arcanum"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Draw Arcanum"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Draw Arcanum"); + + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Arcanum"); + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, benalishKnight, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, arcaneSignet, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, howlingMine, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, thoughtVessel, true); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, benalishKnight, 1); + assertPermanentCount(playerA, arcaneSignet, 1); + assertPermanentCount(playerA, howlingMine, 1); + assertPermanentCount(playerA, thoughtVessel, 1); + assertLife(playerA, 20 + 2 * 4); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/AettirAndPriwenTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/AettirAndPriwenTest.java new file mode 100644 index 00000000000..c5bd9d4e334 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/AettirAndPriwenTest.java @@ -0,0 +1,66 @@ +package org.mage.test.cards.single.fin; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AettirAndPriwenTest extends CardTestPlayerBase { + + /* + Aettir and Priwen + {6} + Legendary Artifact — Equipment + Equipped creature has base power and toughness X/X, where X is your life total. + Equip {5} + */ + private static final String aettir = "Aettir and Priwen"; + /* + Bear Cub + {1}{G} + Creature — Bear + 2/2 + */ + private static final String cub = "Bear Cub"; + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + public static final String bolt = "Lightning Bolt"; + + @Test + public void testAettirAndPriwen() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, aettir); + addCard(Zone.BATTLEFIELD, playerA, cub); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.HAND, playerA, bolt, 2); + + checkPowerToughness(2, cub, 1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}: Equip"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPowerToughness(20, cub, 1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bolt, playerA); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkPowerToughness(20 - 3, cub, 1, PhaseStep.POSTCOMBAT_MAIN); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bolt, playerA); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkPowerToughness(20 - 3 * 2, cub, 1, PhaseStep.POSTCOMBAT_MAIN); + + checkPowerToughness(20 - 3 * 2, cub, 2, PhaseStep.PRECOMBAT_MAIN); + } + + void checkPowerToughness(int xValue, String name, int turnNum, PhaseStep phaseStep) { + runCode("Checking P/T is " + xValue, turnNum, phaseStep, playerA, (info, player, game) -> { + Permanent permanent = getPermanent(name, player); + Assert.assertEquals(xValue, permanent.getPower().getModifiedBaseValue()); + Assert.assertEquals(xValue, permanent.getToughness().getModifiedBaseValue()); + }); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/GarnetPrincessOfAlexandriaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/GarnetPrincessOfAlexandriaTest.java new file mode 100644 index 00000000000..9f308e91789 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/GarnetPrincessOfAlexandriaTest.java @@ -0,0 +1,91 @@ +package org.mage.test.cards.single.fin; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class GarnetPrincessOfAlexandriaTest extends CardTestPlayerBase { + + @Test + public void test_Targets_0() { + // 2/2 + // Whenever Garnet attacks, you may remove a lore counter from each of any number of Sagas you control. + // Put a +1/+1 counter on Garnet for each lore counter removed this way. + addCard(Zone.BATTLEFIELD, playerA, "Garnet, Princess of Alexandria"); + + // nothing to select after attack + attack(1, playerA, "Garnet, Princess of Alexandria", playerB); + //setChoice(playerA, TestPlayer.CHOICE_SKIP); // no valid choices, so do not show choose dialog + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 2); + } + + @Test + public void test_Targets_1() { + // 2/2 + // Whenever Garnet attacks, you may remove a lore counter from each of any number of Sagas you control. + // Put a +1/+1 counter on Garnet for each lore counter removed this way. + addCard(Zone.BATTLEFIELD, playerA, "Garnet, Princess of Alexandria"); + // + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + // I, II, III, IV -- Stampede! -- Other creatures you control get +1/+0 until end of turn. + addCard(Zone.HAND, playerA, "Summon: Choco/Mog"); // {2}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + + // prepare x1 saga + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summon: Choco/Mog"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // can select 1 target only + attack(1, playerA, "Garnet, Princess of Alexandria", playerB); + setChoice(playerA, "Summon: Choco/Mog"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + execute(); + + // 2/2 attack + 1/1 saga's boost + 1/0 counter remove boost + assertCounterCount(playerA, "Summon: Choco/Mog", CounterType.LORE, 0); + assertLife(playerB, 20 - 2 - 1 - 1); + } + + @Test + public void test_Targets_2() { + // 2/2 + // Whenever Garnet attacks, you may remove a lore counter from each of any number of Sagas you control. + // Put a +1/+1 counter on Garnet for each lore counter removed this way. + addCard(Zone.BATTLEFIELD, playerA, "Garnet, Princess of Alexandria"); + // + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + // I, II, III, IV -- Stampede! -- Other creatures you control get +1/+0 until end of turn. + addCard(Zone.HAND, playerA, "Summon: Choco/Mog", 2); // {2}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3 * 2); + + // prepare x2 sagas + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summon: Choco/Mog"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summon: Choco/Mog"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // can select 2 targets + attack(1, playerA, "Garnet, Princess of Alexandria", playerB); + setChoice(playerA, "Summon: Choco/Mog", 2); // x2 targets + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + execute(); + + // 2/2 attack + x2 1/1 saga's boost + x2 1/0 counter remove boost + assertCounterCount(playerA, "Summon: Choco/Mog", CounterType.LORE, 0); + assertLife(playerB, 20 - 2 - 2 - 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java index 66824c5bb01..be2d8afaacb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java @@ -27,13 +27,13 @@ public class WandOfVertebraeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, wandOfVertebrae); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.GRAVEYARD, playerA, lavaCoil); - - setStrictChooseMode(true); + addCard(Zone.GRAVEYARD, playerA, "Grizzly Bears", 10); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}"); addTarget(playerA, lavaCoil); addTarget(playerA, TestPlayer.TARGET_SKIP); // must choose 1 of 5 + setStrictChooseMode(true); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java index 8d8353cf3a1..cb5d923bdd2 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java @@ -20,9 +20,9 @@ public class DiluvianPrimordialTest extends CardTestPlayerBase { */ private static final String primordial = "Diluvian Primordial"; - // Bug: NPE on casting Valakut Awakening @Test public void test_MDFC() { + // possible bug: NPE on casting Valakut Awakening setStrictChooseMode(true); addCard(Zone.HAND, playerA, primordial); @@ -32,7 +32,7 @@ public class DiluvianPrimordialTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, primordial); addTarget(playerA, "Valakut Awakening"); setChoice(playerA, true); // Yes to "You may" - setChoice(playerA, TestPlayer.CHOICE_SKIP); // No choice for Awakening's effect + //setChoice(playerA, TestPlayer.CHOICE_SKIP); // no need in skip, cause no valid choices for Awakening's effect setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/kld/AnimationModuleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/kld/AnimationModuleTest.java new file mode 100644 index 00000000000..ba91018396a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/kld/AnimationModuleTest.java @@ -0,0 +1,80 @@ +package org.mage.test.cards.single.kld; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.target.common.TargetPermanentOrPlayer; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class AnimationModuleTest extends CardTestPlayerBase { + + /* + Animation Module + {1} + Artifact + + Whenever one or more +1/+1 counters are put on a permanent you control, you may pay {1}. If you do, create a 1/1 colorless Servo artifact creature token. + + {3}, {T}: Choose a counter on target permanent or player. Give that permanent or player another counter of that kind. + */ + private static final String animationModule = "Animation Module"; + + @Test + public void testGivePermanentCounter() { + setStrictChooseMode(true); + + Ability ability = new SimpleActivatedAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + new ManaCostsImpl<>("") + ); + ability.addTarget(new TargetPermanentOrPlayer(1)); + addCustomCardWithAbility("add counter", playerA, ability); + + addCard(Zone.BATTLEFIELD, playerA, animationModule); + addCard(Zone.BATTLEFIELD, playerA, "Island", 10); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "put", animationModule); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + setChoice(playerA, true); //pay to create servo + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{3}, {T}: Choose a counter", animationModule); + setChoice(playerA, true); //pay to create servo + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, animationModule, CounterType.P1P1, 2); + assertPermanentCount(playerA, "Servo Token", 2); + } + + @Test + public void testGivePlayerCounter() { + setStrictChooseMode(true); + + Ability ability = new SimpleActivatedAbility( + new AddCountersTargetEffect(CounterType.ENERGY.createInstance()), + new ManaCostsImpl<>("") + ); + ability.addTarget(new TargetPermanentOrPlayer(1)); + addCustomCardWithAbility("add counter", playerA, ability); + + addCard(Zone.BATTLEFIELD, playerA, animationModule); + addCard(Zone.BATTLEFIELD, playerA, "Island", 10); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "put", playerA); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{3}, {T}: Choose a counter", playerA); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, CounterType.ENERGY, 2); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lcc/RipplesOfPotentialTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lcc/RipplesOfPotentialTest.java index 15473da96c3..fc073e3f4b8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/lcc/RipplesOfPotentialTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lcc/RipplesOfPotentialTest.java @@ -4,6 +4,7 @@ import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -32,9 +33,10 @@ public class RipplesOfPotentialTest extends CardTestPlayerBase { setChoice(playerA, "Blast Zone^Arcbound Javelineer^Chandra, Pyromaster^Atraxa's Skitterfang"); // Phase out choice setChoice(playerA, "Blast Zone^Arcbound Javelineer^Chandra, Pyromaster"); + setChoice(playerA, TestPlayer.CHOICE_SKIP); // keep Atraxa's Skitterfang + setChoice(playerA, false); // Atraxa's Skitterfang ability - do not remove oil setStrictChooseMode(true); - setChoice(playerA, false); // Atraxa's Skitterfang ability setStopAt(1, PhaseStep.END_TURN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/AbuelosAwakeningTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/AbuelosAwakeningTest.java new file mode 100644 index 00000000000..569db29afcf --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/AbuelosAwakeningTest.java @@ -0,0 +1,78 @@ +package org.mage.test.cards.single.lci; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AbuelosAwakeningTest extends CardTestPlayerBase { + + /* + Abuelo's Awakening + {X}{3}{W} + Sorcery + Return target artifact or non-Aura enchantment card from your graveyard to the battlefield with X additional +1/+1 counters on it. + It’s a 1/1 Spirit creature with flying in addition to its other types. + */ + public static final String abuelosAwakening = "Abuelo's Awakening"; + /* + Talisman of Progress + {2} + Artifact + {T}: Add {C}. + {T}: Add {W} or {U}. This artifact deals 1 damage to you. + */ + public static final String talisman = "Talisman of Progress"; + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + public static final String bolt = "Lightning Bolt"; + + @Test + public void testAbuelosAwakening() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerA, talisman); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.HAND, playerA, abuelosAwakening); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, abuelosAwakening, talisman); + setChoiceAmount(playerA, 2); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, talisman, CounterType.P1P1, 2); + assertType(talisman, CardType.CREATURE, true); + assertSubtype(talisman, SubType.SPIRIT); + assertBasePowerToughness(playerA, talisman, 1, 1); + } + + @Test + public void testAbuelosAwakeningDies() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerA, talisman); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.HAND, playerA, abuelosAwakening); + addCard(Zone.HAND, playerB, bolt); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, abuelosAwakening, talisman); + setChoiceAmount(playerA, 2); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, bolt, talisman); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, talisman, 0); + assertGraveyardCount(playerA, talisman, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DireBlunderbussTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DireBlunderbussTest.java new file mode 100644 index 00000000000..2acd66fb4de --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DireBlunderbussTest.java @@ -0,0 +1,44 @@ +package org.mage.test.cards.single.lci; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class DireBlunderbussTest extends CardTestPlayerBase { + + /* + Dire Blunderbuss + Color Indicator: RedArtifact — Equipment + + Equipped creature gets +3/+0 and has “Whenever this creature attacks, you may sacrifice an artifact other than Dire Blunderbuss. When you do, this creature deals damage equal to its power to target creature.” + + Equip {1} + */ + private static final String direBlunderBuss = "Dire Blunderbuss"; + + @Test + public void DireBlunderbussTest() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, direBlunderBuss); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears@bearsB"); + addCard(Zone.BATTLEFIELD, playerA, "Tormod's Crypt"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Balduvian Bears"); + + attack(1, playerA, "Balduvian Bears"); + setChoice(playerA, true); + setChoice(playerA, "Tormod's Crypt"); + addTarget(playerA, "@bearsB"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, "Balduvian Bears", 1); + assertLife(playerB, 20 - 2 - 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/EatenByPiranhasTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/EatenByPiranhasTest.java new file mode 100644 index 00000000000..55692a80fe0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/EatenByPiranhasTest.java @@ -0,0 +1,44 @@ +package org.mage.test.cards.single.lci; + +import mage.ObjectColor; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class EatenByPiranhasTest extends CardTestPlayerBase { + + /* + Eaten by Piranhas + {1}{U} + Enchantment — Aura + + Flash (You may cast this spell any time you could cast an instant.) + + Enchant creature + + Enchanted creature loses all abilities and is a black Skeleton creature with base power and toughness 1/1. (It loses all other colors, card types, and creature types.) + */ + private static final String eatenByPiranhas = "Eaten by Piranhas"; + + @Test + public void testEatenByPiranhas() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerB, eatenByPiranhas); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, eatenByPiranhas); + addTarget(playerB, "Balduvian Bears"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertType("Balduvian Bears", CardType.CREATURE, SubType.SKELETON); + assertPowerToughness(playerA, "Balduvian Bears", 1, 1); + assertColor(playerA, "Balduvian Bears", ObjectColor.BLACK, true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/leg/AlAbarasCarpetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/leg/AlAbarasCarpetTest.java new file mode 100644 index 00000000000..7e548cb5dde --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/leg/AlAbarasCarpetTest.java @@ -0,0 +1,55 @@ +package org.mage.test.cards.single.leg; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AlAbarasCarpetTest extends CardTestPlayerBase { + + /* + Al-abara's Carpet + {5} + Artifact + + {5}, {T}: Prevent all damage that would be dealt to you this turn by attacking creatures without flying. + */ + private static final String alabarasCarpet = "Al-abara's Carpet"; + + /* + Storm Crow + {1}{U} + Creature — Bird + + Flying (This creature can’t be blocked except by creatures with flying or reach.) + + 1/2 + */ + private static final String stormCrow = "Storm Crow"; + /* + Balduvian Bears + {1}{G} + Creature — Bear + + 2/2 + */ + private static final String balduvianBears = "Balduvian Bears"; + @Test + public void testCarpet() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, alabarasCarpet); + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + addCard(Zone.BATTLEFIELD, playerB, balduvianBears); + addCard(Zone.BATTLEFIELD, playerB, stormCrow); + + attack(2, playerB, balduvianBears); + attack(2, playerB, stormCrow); + activateAbility(2, PhaseStep.DECLARE_ATTACKERS, playerA, "{5}, {T}"); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 - 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/BreakingOfTheFellowshipTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/BreakingOfTheFellowshipTest.java new file mode 100644 index 00000000000..6abe5a0d95a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/BreakingOfTheFellowshipTest.java @@ -0,0 +1,83 @@ +package org.mage.test.cards.single.ltr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ + +public class BreakingOfTheFellowshipTest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_PossibleTargets() { + // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. + addCard(Zone.HAND, playerA, "Breaking of the Fellowship"); // {1}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // + addCard(Zone.HAND, playerB, "Grizzly Bears"); // 2/2, {1}{G} + addCard(Zone.HAND, playerB, "Spectral Bears"); // 3/3, {1}{G} + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2 * 2); + + // turn 1 - no targets, can't play + checkPlayableAbility("no targets - can't cast", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Breaking of the Fellowship", false); + + // turn 3 - 1 of 2 targets, can't play + castSpell(3 - 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Grizzly Bears"); + checkPlayableAbility("1 of 2 targets - can't cast", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Breaking of the Fellowship", false); + + // turn 5 - 2 of 2 targets, can play + castSpell(5 - 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Spectral Bears"); + checkPlayableAbility("2 of 2 targets - can cast", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Breaking of the Fellowship", true); + + castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Breaking of the Fellowship", "Grizzly Bears^Spectral Bears"); + + setStrictChooseMode(true); + setStopAt(5, PhaseStep.END_TURN); + execute(); + + assertDamageReceived(playerB, "Spectral Bears", 2); + } + + @Test + public void test_Normal_AI() { + // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. + addCard(Zone.HAND, playerA, "Breaking of the Fellowship"); // {1}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears"); // 3/3 + + // AI must choose 3/3 to kill 2/2 + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, "Grizzly Bears", 1); + } + + @Test + public void test_Protection_AI() { + // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. + addCard(Zone.HAND, playerA, "Breaking of the Fellowship"); // {1}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears"); // 3/3 + addCard(Zone.BATTLEFIELD, playerB, "Lavinia of the Tenth"); // 4/4, Protection from red + + // can't choose 4/4 to kill 3/3 due protection from red + // AI must choose 3/3 to kill 2/2 + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, "Grizzly Bears", 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/GlamdringTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/GlamdringTest.java index 030bdfe7d6b..a469a2d31c6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/GlamdringTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/GlamdringTest.java @@ -2,6 +2,7 @@ package org.mage.test.cards.single.ltr; import mage.constants.PhaseStep; import mage.constants.Zone; +import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -29,14 +30,16 @@ public class GlamdringTest extends CardTestPlayerBase { attack(1, playerA, "Blur Sliver"); setChoice(playerA, "In Garruk's Wake"); // 9 mana, so we shouldn't be able to choose it - setChoice(playerA, "Yes"); try { setStopAt(1, PhaseStep.FIRST_COMBAT_DAMAGE); execute(); } catch (AssertionError e) { - assert(e.getMessage().contains("Missing CHOICE def for turn 1, step FIRST_COMBAT_DAMAGE, PlayerA")); + Assert.assertTrue( + "catch wrong exception: " + e.getMessage(), + e.getMessage().contains("Found wrong choice command") && e.getMessage().contains("In Garruk's Wake") + ); return; } fail("Was able to pick [[In Garruk's Wake]] but it costs more than 2"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AlpineMoonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AlpineMoonTest.java new file mode 100644 index 00000000000..6f1399f9e03 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AlpineMoonTest.java @@ -0,0 +1,63 @@ +package org.mage.test.cards.single.m19; + +import mage.abilities.mana.AnyColorManaAbility; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AlpineMoonTest extends CardTestPlayerBase { + /* + Alpine Moon + {R} + Enchantment + As this enchantment enters, choose a nonbasic land card name. + Lands your opponents control with the chosen name lose all land types and abilities, and they gain “{T}: Add one mana of any color.” + */ + private static final String alpine = "Alpine Moon"; + /* + Urborg, Tomb of Yawgmoth + Legendary Land + Each land is a Swamp in addition to its other land types. + */ + private static final String urborg = "Urborg, Tomb of Yawgmoth"; + + @Test + public void testAlpineMoonAfterUrborg() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, urborg); + addCard(Zone.BATTLEFIELD, playerB, alpine); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + + setChoice(playerB, urborg); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertNotSubtype(urborg, SubType.SWAMP); + assertSubtype("Mountain", SubType.SWAMP); + assertSubtype("Island", SubType.SWAMP); + assertAbility(playerA, urborg, new AnyColorManaAbility(), true); + } + + @Test + public void testAlpineMoonBeforeUrborg() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerB, urborg); + addCard(Zone.BATTLEFIELD, playerA, alpine); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerB, "Island"); + + setChoice(playerA, urborg); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertNotSubtype(urborg, SubType.SWAMP); + assertSubtype("Mountain", SubType.SWAMP); + assertSubtype("Island", SubType.SWAMP); + assertAbility(playerB, urborg, new AnyColorManaAbility(), true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/EnthrallingHoldTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/EnthrallingHoldTest.java index 1d135208a16..a1dd9da6925 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/EnthrallingHoldTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/EnthrallingHoldTest.java @@ -10,6 +10,14 @@ public class EnthrallingHoldTest extends CardTestPlayerBase { @Test public void testTappedTarget_untapped_doesNotFizzle() { // Traxos, Scourge of Kroog enters the battlefield tapped and doesn't untap during your untap step. + + // The middle ability of Enthralling Hold affects only the choice of target as the spell is cast. + // If the creature becomes untapped before the spell resolves, it still resolves. If a player is + // allowed to change the spell's target while it's on the stack, they may choose an untapped + // creature. If you put Enthralling Hold onto the battlefield without casting it, you may attach + // it to an untapped creature. + // (2020-06-23) + addCard(Zone.BATTLEFIELD, playerB, "Traxos, Scourge of Kroog"); addCard(Zone.BATTLEFIELD, playerA, "Island", 6); @@ -26,10 +34,11 @@ public class EnthrallingHoldTest extends CardTestPlayerBase { */ addCard(Zone.HAND, playerA, "Twiddle"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Enthralling Hold", "Traxos, Scourge of Kroog"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Twiddle", "Traxos, Scourge of Kroog"); - - setChoice(playerA, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Enthralling Hold"); + addTarget(playerA, "Traxos, Scourge of Kroog"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Twiddle"); + addTarget(playerA, "Traxos, Scourge of Kroog"); + setChoice(playerA, true); // untap traxos setStrictChooseMode(true); setStopAt(1, PhaseStep.END_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m3c/BarrowgoyfTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m3c/BarrowgoyfTest.java index e561f89d04e..755799fee58 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/m3c/BarrowgoyfTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m3c/BarrowgoyfTest.java @@ -100,7 +100,6 @@ public class BarrowgoyfTest extends CardTestPlayerBase { attack(1, playerA, barrowgoyf, playerB); setChoice(playerA, true); - setChoice(playerA, TestPlayer.CHOICE_SKIP); // decide to not return. There was no choice anyway. setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mat/NahiriForgedInFuryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mat/NahiriForgedInFuryTest.java new file mode 100644 index 00000000000..39cce6a4160 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mat/NahiriForgedInFuryTest.java @@ -0,0 +1,98 @@ +package org.mage.test.cards.single.mat; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +/** + * @author correl + */ +public class NahiriForgedInFuryTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.n.NahiriForgedInFury} {4}{R}{W} + * Legendary Creature - Kor Artificer + * + * Affinity for Equipment + * + * Whenever an equipped creature you control attacks, exile the top card of + * your library. You may play that card this turn. You may cast Equipment + * spells this way without paying their mana costs. + */ + private static final String nahiri = "Nahiri, Forged in Fury"; + + private final String boots = "Swiftfoot Boots"; + private final String cleaver = "The Reaver Cleaver"; + private final String giant = "Hill Giant"; + private final String greaves = "Lightning Greaves"; + private final String lions = "Savannah Lions"; + private final String sword = "Sword of Feast and Famine"; + + @Test + public void test_CostReducedByEquipment() { + // Nahiri in hand, four equipment in play, and enough to pay RW + addCard(Zone.HAND, playerA, nahiri); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, boots); + addCard(Zone.BATTLEFIELD, playerA, cleaver); + addCard(Zone.BATTLEFIELD, playerA, greaves); + addCard(Zone.BATTLEFIELD, playerA, sword); + + // Cast for RW (Reduced by 4) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nahiri); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_EquippedAttackTriggers() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, nahiri); + addCard(Zone.BATTLEFIELD, playerA, lions); + addCard(Zone.BATTLEFIELD, playerA, giant); + addCard(Zone.BATTLEFIELD, playerA, boots); + addCard(Zone.BATTLEFIELD, playerA, greaves); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {0}", lions); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {1}", giant); + + // Attack with three creatures, two of which are equipped + attack(1, playerA, nahiri); + attack(1, playerA, lions); + attack(1, playerA, giant); + + // Order the 2 Nahiri triggers + setChoice(playerA, "Whenever an equipped creature you control attacks"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + // Triggered twice, exiling two cards + assertExileCount(playerA, 2); + } + + @Test + public void test_CanCastExiledEquipmentForFree() { + addCard(Zone.BATTLEFIELD, playerA, nahiri); + addCard(Zone.BATTLEFIELD, playerA, greaves); + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, sword); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {0}", nahiri); + + // Attack with one equipped creature, exiling the sword + attack(1, playerA, nahiri); + + // Cast sword for free + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, sword); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/AeveProgenitorOozeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/AeveProgenitorOozeTest.java new file mode 100644 index 00000000000..f60dc9fa1e6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/AeveProgenitorOozeTest.java @@ -0,0 +1,60 @@ +package org.mage.test.cards.single.mh2; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AeveProgenitorOozeTest extends CardTestPlayerBase { + + /* + Aeve, Progenitor Ooze + {2}{G}{G}{G} + Legendary Creature — Ooze + Storm (When you cast this spell, copy it for each spell cast before it this turn. Copies become tokens.) + Aeve isn’t legendary if it’s a token. + Aeve enters with a +1/+1 counter on it for each other Ooze you control. + 2/2 + */ + private static final String aeve = "Aeve, Progenitor Ooze"; + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + public static final String bolt = "Lightning Bolt"; + + @Test + public void testAeve() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, aeve); + addCard(Zone.HAND, playerA, bolt); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, playerB); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aeve); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, aeve, 2); + assertTokenCount(playerA, aeve, 1); + for (Permanent permanent : currentGame.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, playerA.getId(), currentGame)) { + if (permanent.getName().equals(aeve)) { + if (permanent instanceof PermanentToken) { + Assert.assertEquals(0, permanent.getCounters(currentGame).getCount(CounterType.P1P1)); + } else { + Assert.assertEquals(1, permanent.getCounters(currentGame).getCount(CounterType.P1P1)); + } + } + } + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/IzzetGeneratoriumTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/IzzetGeneratoriumTest.java index 4141d0e485d..2eb0d77ac76 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/IzzetGeneratoriumTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/IzzetGeneratoriumTest.java @@ -6,6 +6,7 @@ import mage.counters.CounterType; import mage.players.Player; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -82,6 +83,7 @@ public class IzzetGeneratoriumTest extends CardTestPlayerBase { checkPlayableAbility("1: condition not met before losing counters", 2, PhaseStep.UPKEEP, playerA, "{T}: Draw", false); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Final Act"); setModeChoice(playerB, "5"); // each opponent loses all counters. + setModeChoice(playerB, TestPlayer.MODE_SKIP); waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN); runCode("energy counter is 0", 2, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> checkEnergyCount(info, player, 0)); checkPlayableAbility("2: condition met after losing counters", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw", true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java index 658445c4daa..1edd03bd76b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java @@ -183,6 +183,7 @@ public class NethergoyfTest extends CardTestPlayerBaseWithAIHelps { // The same is true for permanent spells you control and nonland permanent cards you own that aren’t on the battlefield. addCard(Zone.HAND, playerA, "Encroaching Mycosynth"); + // AI must be able to choose good targets combination aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); setStopAt(1, PhaseStep.BEGIN_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/PhyrexianDreadnoughtTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/PhyrexianDreadnoughtTest.java new file mode 100644 index 00000000000..db3d1766d7a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/PhyrexianDreadnoughtTest.java @@ -0,0 +1,59 @@ +package org.mage.test.cards.single.mir; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.player.TestPlayer; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class PhyrexianDreadnoughtTest extends CardTestPlayerBase { + + /* + Phyrexian Dreadnought + {1} + Artifact Creature — Phyrexian Dreadnought + + Trample + + When this creature enters, sacrifice it unless you sacrifice any number of creatures with total power 12 or greater. + + 12/12 + */ + private static final String phyrexianDreadnought = "Phyrexian Dreadnought"; + + @Test + public void testPhyrexianDreadnoughtCanPay() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, phyrexianDreadnought); + addCard(Zone.BATTLEFIELD, playerA, phyrexianDreadnought + "@sacTarget"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, phyrexianDreadnought); + setChoice(playerA, true); + setChoice(playerA, "@sacTarget"); + setChoice(playerA, TestPlayer.CHOICE_SKIP); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, phyrexianDreadnought, 1); + assertGraveyardCount(playerA, phyrexianDreadnought, 1); + } + + @Test + public void testPhyrexianDreadnoughtCantPay() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, phyrexianDreadnought); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, phyrexianDreadnought); + + setStopAt(1, PhaseStep.END_TURN); + setChoice(playerA, false); + execute(); + + assertGraveyardCount(playerA, phyrexianDreadnought, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkc/SereneSleuthTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkc/SereneSleuthTest.java new file mode 100644 index 00000000000..ab7c20aae05 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkc/SereneSleuthTest.java @@ -0,0 +1,93 @@ +package org.mage.test.cards.single.mkc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import static org.junit.Assert.assertTrue; + +/** + * + * @author Jmlundeen + */ +public class SereneSleuthTest extends CardTestPlayerBase { + + /* + Serene Sleuth + {1}{W} + Creature - Human Detective + When Serene Sleuth enters the battlefield, investigate. + At the beginning of combat on your turn, investigate for each goaded creature you control. Then each creature you control is no longer goaded. + 2/2 + */ + private static final String sereneSleuth = "Serene Sleuth"; + + /* + Baeloth Barrityl, Entertainer + {4}{R} + Legendary Creature - Elf Shaman + Creatures your opponents control with power less than Baeloth Barrityl's power are goaded. + Whenever a goaded attacking or blocking creature dies, you create a Treasure token. + Choose a Background + 2/5 + */ + private static final String baelothBarritylEntertainer = "Baeloth Barrityl, Entertainer"; + + /* + Fugitive Wizard + {U} + Creature - Human Wizard + + 1/1 + */ + private static final String fugitiveWizard = "Fugitive Wizard"; + + /* + Cloudshift + {W} + Instant + Exile target creature you control, then return that card to the battlefield under your control. + */ + private static final String cloudshift = "Cloudshift"; + + @Test + public void testSereneSleuth() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, baelothBarritylEntertainer); + addCard(Zone.BATTLEFIELD, playerA, fugitiveWizard); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.HAND, playerA, sereneSleuth); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sereneSleuth); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Clue Token", 1 + 1); + assertTrue("fugitive wizard is not goaded", getPermanent(fugitiveWizard).getGoadingPlayers().isEmpty()); + } + + @Test + public void testSereneSleuthReGoaded() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, baelothBarritylEntertainer); + addCard(Zone.BATTLEFIELD, playerA, fugitiveWizard); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Plains"); + addCard(Zone.HAND, playerA, sereneSleuth); + addCard(Zone.HAND, playerB, cloudshift); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sereneSleuth); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, cloudshift, baelothBarritylEntertainer); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Clue Token", 1 + 1 + 1); + assertTrue("fugitive wizard is goaded", getPermanent(fugitiveWizard).getGoadingPlayers().isEmpty()); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java index 20cc546706d..119db90c629 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java @@ -1,9 +1,13 @@ package org.mage.test.cards.single.mkm; +import mage.abilities.Mode; +import mage.abilities.effects.common.ExileAllEffect; import mage.abilities.keyword.FlyingAbility; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.filter.StaticFilters; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -21,6 +25,11 @@ public class KayaSpiritsJusticeTest extends CardTestPlayerBase { addCard(Zone.GRAVEYARD, playerA, "Fyndhorn Elves"); addCard(Zone.HAND, playerA, "Thraben Inspector"); addCard(Zone.HAND, playerA, "Astrid Peth"); + // Choose one or more — + // • Exile all artifacts. + // • Exile all creatures. + // • Exile all enchantments. + // • Exile all graveyards. addCard(Zone.HAND, playerA, "Farewell"); // Creates a Clue token @@ -35,6 +44,7 @@ public class KayaSpiritsJusticeTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Farewell"); setModeChoice(playerA, "2"); setModeChoice(playerA, "4"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); // Kaya's first ability triggers twice, so choose which is put on the stack: diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mom/InvasionOfFioraTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mom/InvasionOfFioraTest.java index e312c636ce9..88a9e75abd7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mom/InvasionOfFioraTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mom/InvasionOfFioraTest.java @@ -1,6 +1,7 @@ package org.mage.test.cards.single.mom; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; import mage.constants.PhaseStep; @@ -47,6 +48,7 @@ public class InvasionOfFioraTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, invasion); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); checkPermanentCount("Battle on battlefield", 1, PhaseStep.PRECOMBAT_MAIN, playerA, invasion, 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/neo/EaterOfVirtueTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/neo/EaterOfVirtueTest.java new file mode 100644 index 00000000000..6db049ab18b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/neo/EaterOfVirtueTest.java @@ -0,0 +1,54 @@ +package org.mage.test.cards.single.neo; + +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import java.util.Arrays; + + +public class EaterOfVirtueTest extends CardTestPlayerBase { + + /* + Eater of Virtue + {1} + Legendary Artifact — Equipment + + Whenever equipped creature dies, exile it. + + Equipped creature gets +2/+0. + + As long as a card exiled with Eater of Virtue has flying, equipped creature has flying. + The same is true for first strike, double strike, deathtouch, haste, hexproof, indestructible, lifelink, menace, protection, reach, trample, and vigilance. + + Equip {1} + */ + public static final String eaterOfVirtue = "Eater of Virtue"; + + @Test + public void testEaterOfVirtue() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, eaterOfVirtue); + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + addCard(Zone.BATTLEFIELD, playerA, "Adult Gold Dragon"); // Flying, Lifelink, Haste + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears"); + addCard(Zone.HAND, playerB, "Doom Blade"); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Adult Gold Dragon"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Adult Gold Dragon"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Equip", "Balduvian Bears"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAbilities(playerA, "Balduvian Bears", Arrays.asList(FlyingAbility.getInstance(), LifelinkAbility.getInstance(), HasteAbility.getInstance())); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/one/EncroachingMycosynthTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/one/EncroachingMycosynthTest.java new file mode 100644 index 00000000000..8646657de6b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/one/EncroachingMycosynthTest.java @@ -0,0 +1,58 @@ +package org.mage.test.cards.single.one; + +import mage.cards.Card; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommanderDuelBase; + +import java.util.List; + +import static org.junit.Assert.assertTrue; + + +public class EncroachingMycosynthTest extends CardTestCommanderDuelBase { + + /* + Encroaching Mycosynth + {3}{U} + Artifact + + Nonland permanents you control are artifacts in addition to their other types. + The same is true for permanent spells you control and nonland permanent cards you own that aren’t on the battlefield. + */ + private static final String encroachingMycosynth = "Encroaching Mycosynth"; + private static final String balduvianBears = "Balduvian Bears"; + @Test + public void testEncroachingMycosynth() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerA, balduvianBears); + addCard(Zone.HAND, playerA, balduvianBears, 2); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, encroachingMycosynth); + addCard(Zone.EXILED, playerA, balduvianBears); + addCard(Zone.LIBRARY, playerA, balduvianBears); + addCard(Zone.COMMAND, playerA, balduvianBears); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, balduvianBears); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertType(balduvianBears, CardType.ARTIFACT, true); + List cards = getHandCards(playerA); + cards.addAll(getLibraryCards(playerA)); + cards.addAll(getCommandCards(playerA)); + cards.addAll(getExiledCards(playerA)); + cards.addAll(getLibraryCards(playerA)); + for (Card card : cards) { + if (!card.isLand(currentGame)) { + assertTrue(card.getCardType(currentGame).contains(CardType.ARTIFACT)); + } + } + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/one/NahirisSacrificeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/one/NahirisSacrificeTest.java new file mode 100644 index 00000000000..24087ff3541 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/one/NahirisSacrificeTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.one; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class NahirisSacrificeTest extends CardTestPlayerBase { + + /* + Nahiri's Sacrifice + {1}{R} + Sorcery + + As an additional cost to cast this spell, sacrifice an artifact or creature with mana value X. + + Nahiri’s Sacrifice deals X damage divided as you choose among any number of target creatures. + */ + private static final String nahirisSacrifice = "Nahiri's Sacrifice"; + private static final String balduvianBears = "Balduvian Bears"; + @Test + public void testNahirisSacrifice() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, nahirisSacrifice); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerB, balduvianBears + "@bearsB"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nahirisSacrifice, "@bearsB"); + setChoice(playerA, "X=2"); + setChoice(playerA, balduvianBears); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, balduvianBears, 1); + assertGraveyardCount(playerA, nahirisSacrifice, 1); + } + + @Test + public void testNahirisSacrificePrevented() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, nahirisSacrifice); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerB, balduvianBears + "@bearsB"); + addCard(Zone.BATTLEFIELD, playerB, "Yasharn, Implacable Earth"); + + checkPlayableAbility("Can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + nahirisSacrifice, false); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/ElsewhereFlaskTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/ElsewhereFlaskTest.java new file mode 100644 index 00000000000..40699057a95 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/ElsewhereFlaskTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.shm; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class ElsewhereFlaskTest extends CardTestPlayerBase { + + /* + Elsewhere Flask + {2} + Artifact + + When this artifact enters, draw a card. + + Sacrifice this artifact: Choose a basic land type. Each land you control becomes that type until end of turn. + */ + private static final String elsewhereFlask = "Elsewhere Flask"; + + @Test + public void testElsewhereFlask() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, elsewhereFlask); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice"); + setChoice(playerA, "Swamp"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertType("Island", CardType.LAND, SubType.SWAMP); + assertType("Forest", CardType.LAND, SubType.SWAMP); + } + + @Test + public void testElsewhereFlask2() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, elsewhereFlask); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice"); + setChoice(playerA, "Swamp"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertType("Island", CardType.LAND, SubType.ISLAND); + assertType("Forest", CardType.LAND, SubType.FOREST); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/AlienSymbiosisTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/AlienSymbiosisTest.java new file mode 100644 index 00000000000..93dc068027d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/AlienSymbiosisTest.java @@ -0,0 +1,53 @@ +package org.mage.test.cards.single.spm; + +import mage.abilities.keyword.MenaceAbility; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class AlienSymbiosisTest extends CardTestPlayerBase { + + /* + Alien Symbiosis + {1}{B} + Enchantment - Aura + Enchant creature + Enchanted creature gets +1/+1, has menace, and is a Symbiote in addition to its other types. + You may cast this card from your graveyard by discarding a card in addition to paying its other costs. + */ + private static final String alienSymbiosis = "Alien Symbiosis"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + @Test + public void testAlienSymbiosisCastFromGrave() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerA, alienSymbiosis); + addCard(Zone.HAND, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.BATTLEFIELD, playerA, bearCub); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alienSymbiosis, bearCub); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPowerToughness(playerA, bearCub, 3, 3); + assertAbilityCount(playerA, bearCub, MenaceAbility.class, 1); + assertSubtype(bearCub, SubType.SYMBIOTE); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ArachnePsionicWeaverTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ArachnePsionicWeaverTest.java new file mode 100644 index 00000000000..a210814861c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ArachnePsionicWeaverTest.java @@ -0,0 +1,53 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class ArachnePsionicWeaverTest extends CardTestPlayerBase { + + /* + Arachne, Psionic Weaver + {2}{W} + Legendary Creature - Spider Human Hero + Web-slinging {W} + As Arachne enters, look at target opponent's hand, then choose a noncreature card type. + Spells of the chosen type cost {1} more to cast. + 3/3 + */ + private static final String arachnePsionicWeaver = "Arachne, Psionic Weaver"; + + /* + Tormod's Crypt + {0} + Artifact + {tap}, Sacrifice Tormod's Crypt: Exile all cards from target player's graveyard. + */ + private static final String tormodsCrypt = "Tormod's Crypt"; + + @Test + public void testArachnePsionicWeaver() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, arachnePsionicWeaver); + addCard(Zone.HAND, playerA, tormodsCrypt); + addCard(Zone.HAND, playerB, tormodsCrypt); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerB, "Plains"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, arachnePsionicWeaver); + setChoice(playerA, CardType.ARTIFACT.toString()); + + checkPlayableAbility("Player A can't cast Tormod's", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + tormodsCrypt, false); + + checkPlayableAbility("Player B can cast Tormod's", 2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast " + tormodsCrypt, true); + setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + execute(); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/BlackCatCunningThiefTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/BlackCatCunningThiefTest.java new file mode 100644 index 00000000000..5af97ba84dc --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/BlackCatCunningThiefTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class BlackCatCunningThiefTest extends CardTestPlayerBase { + + /* + Black Cat, Cunning Thief + {3}{B}{B} + Legendary Creature - Human Rogue Villain + When Black Cat enters, look at the top nine cards of target opponent's library, exile two of them face down, then put the rest on the bottom of their library in a random order. You may play the exiled cards for as long as they remain exiled. Mana of any type can be spent to cast spells this way. + 2/3 + */ + private static final String blackCatCunningThief = "Black Cat, Cunning Thief"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + /* + Fugitive Wizard + {U} + Creature - Human Wizard + + 1/1 + */ + private static final String fugitiveWizard = "Fugitive Wizard"; + + @Test + public void testBlackCatCunningThief() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.HAND, playerA, blackCatCunningThief); + addCard(Zone.LIBRARY, playerB, bearCub); + addCard(Zone.LIBRARY, playerB, fugitiveWizard); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, blackCatCunningThief); + addTarget(playerA, playerB); + setChoice(playerA, bearCub); + setChoice(playerA, fugitiveWizard); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bearCub, true); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, fugitiveWizard); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, bearCub, 1); + assertPermanentCount(playerA, fugitiveWizard, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/CarnageCrimsonChaosTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/CarnageCrimsonChaosTest.java new file mode 100644 index 00000000000..0c9cd8dcf45 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/CarnageCrimsonChaosTest.java @@ -0,0 +1,60 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class CarnageCrimsonChaosTest extends CardTestPlayerBase { + + /* + Carnage, Crimson Chaos + {2}{B}{R} + Legendary Creature - Symbiote Villain + Trample + When Carnage enters, return target creature card with mana value 3 or less from your graveyard to the battlefield. It gains "This creature attacks each combat if able" and "When this creature deals combat damage to a player, sacrifice it." + Mayhem {B}{R} + 4/3 + */ + private static final String carnageCrimsonChaos = "Carnage, Crimson Chaos"; + + /* + Concordant Crossroads + {G} + World Enchantment + All creatures have haste. + */ + private static final String concordantCrossroads = "Concordant Crossroads"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + @Test + public void testCarnageCrimsonChaos() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, concordantCrossroads); + addCard(Zone.HAND, playerA, carnageCrimsonChaos); + addCard(Zone.GRAVEYARD, playerA, bearCub); + addCard(Zone.BATTLEFIELD, playerA, "Badlands", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, carnageCrimsonChaos); + addTarget(playerA, bearCub); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, bearCub, 1); + assertLife(playerB, 20 - 2); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/CheeringCrowdTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/CheeringCrowdTest.java new file mode 100644 index 00000000000..b8a551de14d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/CheeringCrowdTest.java @@ -0,0 +1,49 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +/** + * + * @author Jmlundeen + */ +public class CheeringCrowdTest extends CardTestCommander4Players { + + /* + Cheering Crowd + {1}{R/G} + Creature - Human Citizen + At the beginning of each player's first main phase, that player may put a +1/+1 counter on this creature. If they do, they add {C} for each counter on it. + 2/2 + */ + private static final String cheeringCrowd = "Cheering Crowd"; + + @Test + public void testCheeringCrowd() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, cheeringCrowd); + + setChoice(playerA, true); + setChoice(playerB, true); + setChoice(playerC, true); + setChoice(playerD, true); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkManaPool("PlayerA should have 1 Mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "C", 1); + waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN, playerD); + checkManaPool("PlayerD should have 2 Mana", 2, PhaseStep.PRECOMBAT_MAIN, playerD, "C", 2); + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN, playerC); + checkManaPool("PlayerC should have 3 Mana", 3, PhaseStep.PRECOMBAT_MAIN, playerC, "C", 3); + waitStackResolved(4, PhaseStep.PRECOMBAT_MAIN, playerB); + checkManaPool("PlayerB should have 4 Mana", 4, PhaseStep.PRECOMBAT_MAIN, playerB, "C", 4); + + setStopAt(4, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertCounterCount(playerA, cheeringCrowd, CounterType.P1P1, 4); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ElectroAssaultingBatteryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ElectroAssaultingBatteryTest.java new file mode 100644 index 00000000000..a8a039f54da --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ElectroAssaultingBatteryTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class ElectroAssaultingBatteryTest extends CardTestPlayerBase { + + /* + Electro, Assaulting Battery + {1}{R}{R} + Legendary Creature - Human Villain + Flying + You don't lose unspent red mana as steps and phases end. + Whenever you cast an instant or sorcery spell, add {R}. + When Electro leaves the battlefield, you may pay {X}. When you do, he deals X damage to target player. + 2/3 + */ + private static final String electroAssaultingBattery = "Electro, Assaulting Battery"; + + /* + Pyretic Ritual + {1}{R} + Instant + Add {R}{R}{R}. + */ + private static final String pyreticRitual = "Pyretic Ritual"; + + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + private static final String lightningBolt = "Lightning Bolt"; + + + @Test + public void testElectroAssaultingBattery() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, electroAssaultingBattery); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + addCard(Zone.HAND, playerA, pyreticRitual); + addCard(Zone.HAND, playerB, lightningBolt); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, pyreticRitual); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, lightningBolt, electroAssaultingBattery); + setChoice(playerA, true); + setChoiceAmount(playerA, 4); // 3 from ritual + 1 from electro + addTarget(playerA, playerB); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 4); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/GwenomRemorselessTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/GwenomRemorselessTest.java new file mode 100644 index 00000000000..76702d639ff --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/GwenomRemorselessTest.java @@ -0,0 +1,59 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class GwenomRemorselessTest extends CardTestPlayerBase { + + /* + Gwenom, Remorseless + {3}{B}{B} + Legendary Creature - Symbiote Spider Hero + Deathtouch, lifelink + Whenever Gwenom attacks, until end of turn you may look at the top card of your library any time and you may play cards from the top of your library. If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost. + 4/4 + */ + private static final String gwenomRemorseless = "Gwenom, Remorseless"; + + /* + Bear Cub + {1}{G} + Creature - Bear + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + @Test + public void testGwenomRemorseless() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, gwenomRemorseless); + addCard(Zone.LIBRARY, playerA, "Island"); + addCard(Zone.LIBRARY, playerA, bearCub, 2); + addCard(Zone.LIBRARY, playerA, "Forest"); + + attack(1, playerA, gwenomRemorseless); + playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Forest"); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bearCub); + + // effect ends at end of turn + checkPlayableAbility("Can't play top card", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Island", false); + + setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, bearCub, 1); + assertPermanentCount(playerA, gwenomRemorseless, 1); + assertPermanentCount(playerA, "Forest", 1); + assertLife(playerB, 20 - 4); + assertLife(playerA, 20 + 4 - 2); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/HydroManFluidFelonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/HydroManFluidFelonTest.java new file mode 100644 index 00000000000..8be9cf8912b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/HydroManFluidFelonTest.java @@ -0,0 +1,55 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class HydroManFluidFelonTest extends CardTestPlayerBase { + + /* + Hydro-Man, Fluid Felon + {U}{U} + Legendary Creature - Elemental Villain + Whenever you cast a blue spell, if Hydro-Man is a creature, he gets +1/+1 until end of turn. + At the beginning of your end step, untap Hydro-Man. Until your next turn, he becomes a land and gains "{T}: Add {U}." + 2/2 + */ + private static final String hydroManFluidFelon = "Hydro-Man, Fluid Felon"; + + /* + Fugitive Wizard + {U} + Creature - Human Wizard + + 1/1 + */ + private static final String fugitiveWizard = "Fugitive Wizard"; + + @Test + public void testHydroManFluidFelon() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, fugitiveWizard); + addCard(Zone.BATTLEFIELD, playerA, hydroManFluidFelon); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, fugitiveWizard, true); + + checkPT("Hydro-Man is boosted", 1, PhaseStep.PRECOMBAT_MAIN, playerA, hydroManFluidFelon, 3, 3); + + attack(1, playerA, hydroManFluidFelon); + + setStopAt(2, PhaseStep.UPKEEP); + execute(); + + assertType(hydroManFluidFelon, CardType.LAND, true); + assertNotType(hydroManFluidFelon, CardType.CREATURE); + assertTapped(hydroManFluidFelon, false); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/InterdimensionalWebWatchTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/InterdimensionalWebWatchTest.java new file mode 100644 index 00000000000..45e97878bfa --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/InterdimensionalWebWatchTest.java @@ -0,0 +1,78 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class InterdimensionalWebWatchTest extends CardTestPlayerBase { + + /* + Interdimensional Web Watch + {4} + Artifact + When this artifact enters, exile the top two cards of your library. Until the end of your next turn, you may play those cards. + {T}: Add two mana in any combination of colors. Spend this mana only to cast spells from exile. + */ + private static final String interdimensionalWebWatch = "Interdimensional Web Watch"; + + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + private static final String lightningBolt = "Lightning Bolt"; + + /* + Fugitive Wizard + {U} + Creature - Human Wizard + + 1/1 + */ + private static final String fugitiveWizard = "Fugitive Wizard"; + + /* + Shock + {R} + Instant + Shock deals 2 damage to any target. + */ + private static final String shock = "Shock"; + + @Test + public void testInterdimensionalWebWatch() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, lightningBolt); + addCard(Zone.LIBRARY, playerA, fugitiveWizard); + addCard(Zone.HAND, playerA, interdimensionalWebWatch); + addCard(Zone.HAND, playerA, shock); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, interdimensionalWebWatch, true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add two"); + setChoiceAmount(playerA, 0, 1, 0, 1, 0); // Add {U}{R} + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkPlayableAbility("Can't cast shock from hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, + "Cast " + shock, false); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, true); + addTarget(playerA, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, fugitiveWizard); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 3); + assertPermanentCount(playerA, fugitiveWizard, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/JackalGeniusGeneticistTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/JackalGeniusGeneticistTest.java new file mode 100644 index 00000000000..8cabc98289f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/JackalGeniusGeneticistTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class JackalGeniusGeneticistTest extends CardTestPlayerBase { + + /* + Jackal, Genius Geneticist + {G}{U} + Legendary Creature - Human Scientist Villain + Trample + Whenever you cast a creature spell with mana value equal to Jackal's power, copy that spell, except the copy isn't legendary. Then put a +1/+1 counter on Jackal. + 1/1 + */ + private static final String jackalGeniusGeneticist = "Jackal, Genius Geneticist"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + /* + Ragavan, Nimble Pilferer + {R} + Legendary Creature - Monkey Pirate + Whenever Ragavan, Nimble Pilferer deals combat damage to a player, create a Treasure token and exile the top card of that player's library. Until end of turn, you may cast that card. + Dash {1}{R} + 2/1 + */ + private static final String ragavanNimblePilferer = "Ragavan, Nimble Pilferer"; + + @Test + public void testJackalGeniusGeneticist() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, jackalGeniusGeneticist); + addCard(Zone.BATTLEFIELD, playerA, "Taiga", 3); + addCard(Zone.HAND, playerA, ragavanNimblePilferer); + addCard(Zone.HAND, playerA, bearCub); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ragavanNimblePilferer); + setChoice(playerA, "Cast with no"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bearCub); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, ragavanNimblePilferer, 2); + assertPermanentCount(playerA, bearCub, 2); + assertCounterCount(playerA, jackalGeniusGeneticist, CounterType.P1P1, 2); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/LadyOctopusInspiredInventorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/LadyOctopusInspiredInventorTest.java new file mode 100644 index 00000000000..77d38c3fb6b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/LadyOctopusInspiredInventorTest.java @@ -0,0 +1,122 @@ +package org.mage.test.cards.single.spm; + +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.UntapAllControllerEffect; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class LadyOctopusInspiredInventorTest extends CardTestPlayerBase { + + /* + Lady Octopus, Inspired Inventor + {U} + Legendary Creature - Human Scientist Villain + Whenever you draw your first or second card each turn, put an ingenuity counter on Lady Octopus. + {T}: You may cast an artifact spell from your hand with mana value less than or equal to the number of ingenuity counters on Lady Octopus without paying its mana cost. + */ + private static final String ladyOctopusInspiredInventor = "Lady Octopus, Inspired Inventor"; + + /* + Aether Vial + {1} + Artifact + At the beginning of your upkeep, you may put a charge counter on Aether Vial. + {T}: You may put a creature card with converted mana cost equal to the number of charge counters on ther Vial from your hand onto the battlefield. + */ + private static final String aetherVial = "Aether Vial"; + + /* + Tormod's Crypt + {0} + Artifact + {tap}, Sacrifice Tormod's Crypt: Exile all cards from target player's graveyard. + */ + private static final String tormodsCrypt = "Tormod's Crypt"; + + /* + Howling Mine + {2} + Artifact + At the beginning of each player's draw step, if Howling Mine is untapped, that player draws an additional card. + */ + private static final String howlingMine = "Howling Mine"; + + @Test + public void testLadyOctopusInspiredInventor() { + setStrictChooseMode(true); + + addCustomCardWithAbility("untap all creatures", playerA, new SimpleActivatedAbility( + new UntapAllControllerEffect(new FilterCreaturePermanent()), + new ManaCostsImpl<>("") + )); + addCustomCardWithAbility("draw a card", playerA, new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(1), + new ManaCostsImpl<>("") + )); + addCard(Zone.BATTLEFIELD, playerA, ladyOctopusInspiredInventor); + addCard(Zone.HAND, playerA, aetherVial); + addCard(Zone.HAND, playerA, tormodsCrypt); + addCard(Zone.HAND, playerA, howlingMine); + + activateDrawCardAndUntap(); // Tormod's crypt + activateDrawCardAndUntap(); // Aether Vial + activateDrawCardAndUntap(); // Howling Mine + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, ladyOctopusInspiredInventor, CounterType.INGENUITY, 2); + assertHandCount(playerA, 3); + } + + private void activateDrawCardAndUntap() { + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: You may cast"); + setChoice(playerA, true); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "draw a"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + } + + @Test + public void testLadyOctopusInspiredInventorChoose() { + setStrictChooseMode(true); + + addCustomCardWithAbility("draw a card", playerA, new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(3), + new ManaCostsImpl<>("") + )); + addCard(Zone.BATTLEFIELD, playerA, ladyOctopusInspiredInventor); + addCard(Zone.HAND, playerA, aetherVial); + addCard(Zone.HAND, playerA, tormodsCrypt); + addCard(Zone.HAND, playerA, howlingMine); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "draw "); + setChoice(playerA, "Whenever you draw your first"); // trigger stack + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: You may cast"); + setChoice(playerA, tormodsCrypt); + setChoice(playerA, true); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, ladyOctopusInspiredInventor, CounterType.INGENUITY, 2); + assertHandCount(playerA, 3 + 2); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/MaximumCarnageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/MaximumCarnageTest.java new file mode 100644 index 00000000000..7807ecc4f90 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/MaximumCarnageTest.java @@ -0,0 +1,69 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +import static org.junit.Assert.assertTrue; + +/** + * + * @author Jmlundeen + */ +public class MaximumCarnageTest extends CardTestCommander4Players { + + /* + Maximum Carnage + {4}{R} + Enchantment - Saga + (As this Saga enters step and after your draw step, add a lore counter. Sacrifice after III.) + I -- Until your next turn, each creature attacks each combat if able and attacks a player other than you if able. + II -- Add {R}{R}{R}. + III -- This Saga deals 5 damage to each opponent. + */ + private static final String maximumCarnage = "Maximum Carnage"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + @Test + public void testMaximumCarnage() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, maximumCarnage); + addCard(Zone.BATTLEFIELD, playerB, bearCub); + addCard(Zone.BATTLEFIELD, playerD, bearCub); + + addTarget(playerD, playerC); // must attack + addTarget(playerB, playerD); // must attack + + setStopAt(4, PhaseStep.END_TURN); + execute(); + } + + @Test + public void testMaximumCarnageCantAttackController() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, maximumCarnage); + addCard(Zone.BATTLEFIELD, playerB, bearCub); + addCard(Zone.BATTLEFIELD, playerD, bearCub); + + addTarget(playerD, playerA); // must attack + + setStopAt(4, PhaseStep.END_TURN); + try { + execute(); + } catch (AssertionError e) { + assertTrue("Shouldn't be able to attack playerA", + e.getMessage().contains("[targetPlayer=PlayerA], but not used")); + } + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/MisterNegativeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/MisterNegativeTest.java new file mode 100644 index 00000000000..029e1298138 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/MisterNegativeTest.java @@ -0,0 +1,62 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Jmlundeen + */ +public class MisterNegativeTest extends CardTestPlayerBase { + + /* + Mister Negative + {5}{W}{B} + Legendary Creature - Human Villain + Vigilance, lifelink + When Mister Negative enters, you may exchange your life total with target opponent. If you lose life this way, draw that many cards. + 5/5 + */ + private static final String misterNegative = "Mister Negative"; + + @Test + public void testMisterNegative() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, misterNegative); + addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 7); + setLife(playerB, 15); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, misterNegative); + addTarget(playerA, playerB); + setChoice(playerA, true); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertHandCount(playerA, 5); + assertLife(playerA, 15); + assertLife(playerB, 20); + } + + @Test + public void testMisterNegativeNoDraw() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, misterNegative); + addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 7); + setLife(playerB, 21); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, misterNegative); + addTarget(playerA, playerB); + setChoice(playerA, true); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertHandCount(playerA, 0); + assertLife(playerA, 21); + assertLife(playerB, 20); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/MultiversalPassageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/MultiversalPassageTest.java new file mode 100644 index 00000000000..c23f342462d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/MultiversalPassageTest.java @@ -0,0 +1,40 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class MultiversalPassageTest extends CardTestPlayerBase { + + /* + Multiversal Passage + + Land + As this land enters, choose a basic land type. Then you may pay 2 life. If you don't, it enters tapped. + This land is the chosen type. + */ + private static final String multiversalPassage = "Multiversal Passage"; + + @Test + public void testMultiversalPassage() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, multiversalPassage); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, multiversalPassage); + setChoice(playerA, "Swamp"); + setChoice(playerA, true); // untapped + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertSubtype(multiversalPassage, SubType.SWAMP); + assertLife(playerA, 20 - 2); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/OscorpIndustriesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/OscorpIndustriesTest.java new file mode 100644 index 00000000000..364cc480059 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/OscorpIndustriesTest.java @@ -0,0 +1,52 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class OscorpIndustriesTest extends CardTestPlayerBase { + + /* + Oscorp Industries + + Land + This land enters tapped. + When this land enters from a graveyard, you lose 2 life. + {T}: Add {U}, {B}, or {R}. + Mayhem + */ + private static final String oscorpIndustries = "Oscorp Industries"; + + /* + Thought Courier + {1}{U} + Creature - Human Wizard + {tap}: Draw a card, then discard a card. + 1/1 + */ + private static final String thoughtCourier = "Thought Courier"; + + @Test + public void testOscorpIndustries() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, oscorpIndustries); + addCard(Zone.BATTLEFIELD, playerA, thoughtCourier); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw"); + setChoice(playerA, oscorpIndustries); + + playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, oscorpIndustries + " with Mayhem"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 - 2); + assertPermanentCount(playerA, oscorpIndustries, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ParkerLuckTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ParkerLuckTest.java new file mode 100644 index 00000000000..0e1bb20db49 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ParkerLuckTest.java @@ -0,0 +1,79 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +/** + * + * @author Jmlundeen + */ +public class ParkerLuckTest extends CardTestCommander4Players { + + /* + Parker Luck + {2}{B} + Enchantment + At the beginning of your end step, two target players each reveal the top card of their library. They each lose life equal to the mana value of the card revealed by the other player. Then they each put the card they revealed into their hand. + */ + private static final String parkerLuck = "Parker Luck"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + /* + Fugitive Wizard + {U} + Creature - Human Wizard + + 1/1 + */ + private static final String fugitiveWizard = "Fugitive Wizard"; + + @Test + public void testParkerLuck() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, parkerLuck); + addCard(Zone.LIBRARY, playerD, bearCub); + addCard(Zone.LIBRARY, playerC, fugitiveWizard); + + addTarget(playerA, playerC); + addTarget(playerA, playerD); + + setStopAt(1, PhaseStep.CLEANUP); + execute(); + + assertLife(playerC, 20 - 2); + assertLife(playerD, 20 - 1); + assertHandCount(playerC, 1); + assertHandCount(playerD, 1); + } + + @Test + public void testParkerLuckOneLibraryEmpty() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerC); + + addCard(Zone.BATTLEFIELD, playerA, parkerLuck); + addCard(Zone.LIBRARY, playerD, bearCub); + + addTarget(playerA, playerC); + addTarget(playerA, playerD); + + setStopAt(1, PhaseStep.CLEANUP); + execute(); + + assertLife(playerC, 20 - 2); + assertHandCount(playerD, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/PeterParkerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/PeterParkerTest.java new file mode 100644 index 00000000000..38eeace4a37 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/PeterParkerTest.java @@ -0,0 +1,115 @@ +package org.mage.test.cards.single.spm; + +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.TapAllEffect; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class PeterParkerTest extends CardTestPlayerBase { + + /* + Peter Parker + {1}{W} + Legendary Creature - Human Scientist Hero + When Peter Parker enters, create a 2/1 green Spider creature token with reach. + {1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery. + Amazing Spider-Man + {1}{G}{W}{U} + Legendary Creature - Spider Human Hero + Vigilance, reach + Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}. + 4/4 + */ + private static final String peterParker = "Peter Parker"; + + + /* + Absolute Virtue + {6}{W}{U} + Legendary Creature - Avatar Warrior + This spell can't be countered. + Flying + You have protection from each of your opponents. + 8/8 + */ + private static final String absoluteVirtue = "Absolute Virtue"; + + /* + Adelbert Steiner + {1}{W} + Legendary Creature - Human Knight + Lifelink + Adelbert Steiner gets +1/+1 for each Equipment you control. + 2/1 + */ + private static final String adelbertSteiner = "Adelbert Steiner"; + + /* + Chainer, Nightmare Adept + {2}{B}{R} + Legendary Creature - Human Minion + Discard a card: You may cast a creature card from your graveyard this turn. Activate this ability only once each turn. + Whenever a nontoken creature enters the battlefield under your control, if you didn't cast it from your hand, it gains haste until your next turn. + 3/2 + */ + private static final String chainerNightmareAdept = "Chainer, Nightmare Adept"; + + /* + Balduvian Bears + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String balduvianBears = "Balduvian Bears"; + + @Test + @Ignore("Enable after MDFC rework") + public void testAmazingSpiderMan() { + setStrictChooseMode(true); + + addCustomCardWithAbility("tap all creatures", playerA, new SimpleActivatedAbility( + new TapAllEffect(new FilterCreaturePermanent(SubType.BEAR, "bears")), + new ManaCostsImpl<>("") + )); + + addCard(Zone.HAND, playerA, peterParker); + addCard(Zone.BATTLEFIELD, playerA, chainerNightmareAdept); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears,2); + addCard(Zone.HAND, playerA, adelbertSteiner, 2); + addCard(Zone.GRAVEYARD, playerA, absoluteVirtue); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 8); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Amazing Spider-Man", true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "tap all"); // tap bears, addCard command isn't working to set tapped + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, adelbertSteiner + " with Web-slinging", true); + setChoice(playerA, balduvianBears); // return to hand + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard a card"); + setChoice(playerA, adelbertSteiner); // discard + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkPlayableAbility("Bear does not have web-slinging", 1, PhaseStep.PRECOMBAT_MAIN, playerA, + "Cast " + balduvianBears + " with Web-slinging", false); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, absoluteVirtue + " with Web-slinging"); + setChoice(playerA, balduvianBears); // return to hand + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/RhinosRampageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/RhinosRampageTest.java new file mode 100644 index 00000000000..c9fdc258521 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/RhinosRampageTest.java @@ -0,0 +1,92 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class RhinosRampageTest extends CardTestPlayerBase { + + /* + Rhino's Rampage + {R/G} + Sorcery + Target creature you control gets +1/+0 until end of turn. It fights target creature an opponent controls. + When excess damage is dealt to the creature an opponent controls this way, destroy up to one target noncreature artifact with mana value 3 or less. + */ + private static final String rhinosRampage = "Rhino's Rampage"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + /* + Fugitive Wizard + {U} + Creature - Human Wizard + + 1/1 + */ + private static final String fugitiveWizard = "Fugitive Wizard"; + + /* + Tormod's Crypt + {0} + Artifact + {T}, Sacrifice Tormod's Crypt: Exile all cards from target player's graveyard. + */ + private static final String tormodsCrypt = "Tormod's Crypt"; + + @Test + public void testRhinosRampageExcess() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, rhinosRampage); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, bearCub); + addCard(Zone.BATTLEFIELD, playerB, fugitiveWizard); + addCard(Zone.BATTLEFIELD, playerB, tormodsCrypt); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rhinosRampage); + addTarget(playerA, bearCub); + addTarget(playerA, fugitiveWizard); + addTarget(playerA, tormodsCrypt); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, fugitiveWizard, 1); + assertGraveyardCount(playerB, tormodsCrypt, 1); + } + + @Test + public void testRhinosRampageNoExcess() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, rhinosRampage); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, fugitiveWizard); + addCard(Zone.BATTLEFIELD, playerB, bearCub); + addCard(Zone.BATTLEFIELD, playerB, tormodsCrypt); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rhinosRampage); + addTarget(playerA, fugitiveWizard); + addTarget(playerA, bearCub); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, fugitiveWizard, 1); + assertGraveyardCount(playerB, bearCub, 1); + assertPermanentCount(playerB, tormodsCrypt, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/SandmansQuicksandTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/SandmansQuicksandTest.java new file mode 100644 index 00000000000..5f861550f74 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/SandmansQuicksandTest.java @@ -0,0 +1,81 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class SandmansQuicksandTest extends CardTestPlayerBase { + + /* + Sandman's Quicksand + {1}{B}{B} + Sorcery + Mayhem {3}{B} + All creatures get -2/-2 until end of turn. If this spell's mayhem cost was paid, creatures your opponents control get -2/-2 until end of turn instead. + */ + private static final String sandmansQuicksand = "Sandman's Quicksand"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + /* + Thought Courier + {1}{U} + Creature - Human Wizard + {tap}: Draw a card, then discard a card. + 1/1 + */ + private static final String thoughtCourier = "Thought Courier"; + + @Test + public void testSandmansQuicksand() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, sandmansQuicksand); + addCard(Zone.BATTLEFIELD, playerA, bearCub); + addCard(Zone.BATTLEFIELD, playerB, bearCub); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sandmansQuicksand); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, bearCub, 1); + assertGraveyardCount(playerB, bearCub, 1); + } + + @Test + public void testSandmansQuicksandMayhem() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, sandmansQuicksand); + addCard(Zone.BATTLEFIELD, playerA, bearCub); + addCard(Zone.BATTLEFIELD, playerA, thoughtCourier); + addCard(Zone.BATTLEFIELD, playerB, bearCub); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw"); + setChoice(playerA, sandmansQuicksand); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sandmansQuicksand + " with Mayhem"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, bearCub, 0); + assertGraveyardCount(playerB, bearCub, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ScarletSpiderBenReillyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ScarletSpiderBenReillyTest.java new file mode 100644 index 00000000000..c2e7afe3c97 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ScarletSpiderBenReillyTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.single.spm; + +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.TapAllEffect; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class ScarletSpiderBenReillyTest extends CardTestPlayerBase { + + /* + Scarlet Spider, Ben Reilly + {1}{R}{G} + Legendary Creature - Spider Human Hero + Web-slinging {R}{G} + Trample + Sensational Save -- If Scarlet Spider was cast using web-slinging, he enters with X +1/+1 counters on him, where X is the mana value of the returned creature. + 4/3 + */ + private static final String scarletSpiderBenReilly = "Scarlet Spider, Ben Reilly"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + @Test + public void testScarletSpiderBenReilly() { + setStrictChooseMode(true); + + addCustomCardWithAbility("tap all creatures", playerA, new SimpleActivatedAbility( + new TapAllEffect(new FilterCreaturePermanent(SubType.BEAR, "bears")), + new ManaCostsImpl<>("") + )); + + addCard(Zone.HAND, playerA, scarletSpiderBenReilly); + addCard(Zone.BATTLEFIELD, playerA, bearCub, 1); + addCard(Zone.BATTLEFIELD, playerA, "Taiga", 3); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "tap all"); // tap bears, addCard command isn't working to set tapped + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scarletSpiderBenReilly + " with Web-slinging"); + setChoice(playerA, bearCub); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, scarletSpiderBenReilly, CounterType.P1P1, 2); + assertHandCount(playerA, bearCub, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ShadowOfTheGoblinTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ShadowOfTheGoblinTest.java new file mode 100644 index 00000000000..50312827c11 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/ShadowOfTheGoblinTest.java @@ -0,0 +1,123 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class ShadowOfTheGoblinTest extends CardTestPlayerBase { + + /* + Shadow of the Goblin + {1}{R} + Enchantment + Unreliable Visions -- At the beginning of your first main phase, discard a card. If you do, draw a card. + Undying Vengeance -- Whenever you play a land or cast a spell from anywhere other than your hand, this enchantment deals 1 damage to each opponent. + */ + private static final String shadowOfTheGoblin = "Shadow of the Goblin"; + + /* + Party Thrasher + {1}{R} + Creature - Lizard Wizard + Noncreature spells you cast from exile have convoke. + At the beginning of your precombat main phase, you may discard a card. If you do, exile the top two cards of your library, then choose one of them. You may play that card this turn. + 1/4 + */ + private static final String partyThrasher = "Party Thrasher"; + + /* + Misthollow Griffin + {2}{U}{U} + Creature - Griffin + Flying + You may cast Misthollow Griffin from exile. + 3/3 + */ + private static final String misthollowGriffin = "Misthollow Griffin"; + + /* + Glarb, Calamity's Augur + {B}{G}{U} + Legendary Creature - Frog Wizard Noble + Deathtouch + You may look at the top card of your library any time. + You may play lands and cast spells with mana value 4 or greater from the top of your library. + {T}: Surveil 2. + 2/4 + */ + private static final String glarbCalamitysAugur = "Glarb, Calamity's Augur"; + + @Test + public void testShadowOfTheGoblinFromHand() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, shadowOfTheGoblin); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.HAND, playerA, "Mountain"); + addCard(Zone.HAND, playerA, partyThrasher); + + setChoice(playerA, false); // shadow trigger + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, partyThrasher); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertLife(playerB, 20); // no trigger + } + + @Test + public void testShadowOfTheGoblinFromExile() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, shadowOfTheGoblin); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, partyThrasher); + addCard(Zone.HAND, playerA, "Mountain"); + addCard(Zone.EXILED, playerA, misthollowGriffin); + + setChoice(playerA, "Unreliable Visions"); + setChoice(playerA, true); // discard + setChoice(playerA, "Mountain", 2); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, misthollowGriffin); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 1 - 1); // land + spell from exile + } + + @Test + public void testShadowOfTheGoblinFromLibrary() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, shadowOfTheGoblin); + addCard(Zone.BATTLEFIELD, playerA, glarbCalamitysAugur); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.LIBRARY, playerA, misthollowGriffin); + addCard(Zone.LIBRARY, playerA, "Island"); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, misthollowGriffin); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 1 - 1); // land + spell from library + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/SpiderPunkTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/SpiderPunkTest.java new file mode 100644 index 00000000000..ee04a8fb83c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/SpiderPunkTest.java @@ -0,0 +1,91 @@ +package org.mage.test.cards.single.spm; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.RiotAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.target.TargetPlayer; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class SpiderPunkTest extends CardTestPlayerBase { + + /* + Spider-Punk + {1}{R} + Legendary Creature - Spider Human Hero + Riot + Other Spiders you control have riot. + Spells and abilities can't be countered. + Damage can't be prevented. + 2/1 + */ + private static final String spiderPunk = "Spider-Punk"; + + /* + Counterspell + {U}{U} + Instant + Counter target spell. + */ + private static final String counterspell = "Counterspell"; + + /* + Disallow + {1}{U}{U} + Instant + Counter target spell, activated ability, or triggered ability. + */ + private static final String disallow = "Disallow"; + + /* + Giant Spider + {3}{G} + Creature - Spider + Reach (This creature can block creatures with flying.) + 2/4 + */ + private static final String giantSpider = "Giant Spider"; + + + @Test + public void testSpiderPunk() { + setStrictChooseMode(true); + + Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(2), new TapSourceCost()); + ability.addTarget(new TargetPlayer(1)); + addCustomCardWithAbility("{T} deal damage", playerA, ability); + addCard(Zone.BATTLEFIELD, playerA, spiderPunk); + setChoice(playerA, "No"); // Haste - Spider-Punk + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.BATTLEFIELD, playerB, "Island", 5); + addCard(Zone.HAND, playerA, giantSpider); + addCard(Zone.HAND, playerB, counterspell); + addCard(Zone.HAND, playerB, disallow); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, giantSpider); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, counterspell, giantSpider); + + setChoice(playerA, "No"); // Haste + + attack(1, playerA, giantSpider); + attack(1, playerA, spiderPunk); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: {this} deals 2 damage", playerB); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, disallow); + addTarget(playerB, "stack ability"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 2 - 2 - 2); + assertAbilityCount(playerA, giantSpider, RiotAbility.class, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/SpiderVerseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/SpiderVerseTest.java new file mode 100644 index 00000000000..91bdd0de6e5 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/SpiderVerseTest.java @@ -0,0 +1,81 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +/** + * + * @author Jmlundeen + */ +public class SpiderVerseTest extends CardTestCommander4Players { + + /* + Spider-Verse + {3}{R}{R} + Enchantment + The "legend rule" doesn't apply to Spiders you control. + Whenever you cast a spell from anywhere other than your hand, you may copy it. If you do, you may choose new targets for the copy. If the copy is a permanent spell, it gains haste. Do this only once each turn. + */ + private static final String spiderVerse = "Spider-Verse"; + + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + private static final String lightningBolt = "Lightning Bolt"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + /* + Ashiok, Nightmare Muse + {3}{U}{B} + Legendary Planeswalker - Ashiok + + +1: Create a 2/3 blue and black Nightmare creature token with "Whenever this creature attacks or blocks, each opponent exiles the top two cards of their library." + -3: Return target nonland permanent to its owner's hand, then that player exiles a card from their hand. + -7: You may cast up to three face-up cards your opponents own from exile without paying their mana costs. + */ + private static final String ashiokNightmareMuse = "Ashiok, Nightmare Muse"; + + @Test + public void testSpiderVerse() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, spiderVerse); + addCard(Zone.BATTLEFIELD, playerA, ashiokNightmareMuse); + addCard(Zone.EXILED, playerB, lightningBolt); + addCard(Zone.EXILED, playerB, bearCub); + + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, ashiokNightmareMuse, CounterType.LOYALTY, 9); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-7"); + setChoice(playerA, lightningBolt); + setChoice(playerA, true); + addTarget(playerA, playerD); + setChoice(playerA, false, 2); + setChoice(playerA, true, 2); // use spider-verse / new target + addTarget(playerA, playerB); + + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "-7"); + setChoice(playerA, true, 2); // ashiok + spider-verse + attack(5, playerA, bearCub, playerC); // copy has haste + + setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 3); + assertLife(playerD, 20 - 3); + assertLife(playerC, 20 - 2); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/TheCloneSagaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/TheCloneSagaTest.java new file mode 100644 index 00000000000..9e0af202b8a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/TheCloneSagaTest.java @@ -0,0 +1,64 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.player.TestPlayer; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class TheCloneSagaTest extends CardTestPlayerBase { + + /* + The Clone Saga + {3}{U} + Enchantment - Saga + (As this Saga enters step, add a lore counter. Sacrifice after III.) + I -- Surveil 3. + II -- When you next cast a creature spell this turn, copy it, except the copy isn't legendary. + III -- Choose a card name. Whenever a creature with the chosen name deals combat damage to a player this turn, draw a card. + */ + private static final String theCloneSaga = "The Clone Saga"; + + /* + Ragavan, Nimble Pilferer + {R} + Legendary Creature - Monkey Pirate + Whenever Ragavan, Nimble Pilferer deals combat damage to a player, create a Treasure token and exile the top card of that player's library. Until end of turn, you may cast that card. + Dash {1}{R} + 2/1 + */ + private static final String ragavanNimblePilferer = "Ragavan, Nimble Pilferer"; + + @Test + public void testTheCloneSaga() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, theCloneSaga); + addCard(Zone.HAND, playerA, ragavanNimblePilferer); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + addTarget(playerA, TestPlayer.TARGET_SKIP); + setChoice(playerA, "Mountain", 2); + + setChoice(playerA, ragavanNimblePilferer); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ragavanNimblePilferer); + setChoice(playerA, "Cast with no alternative cost"); + + attack(3, playerA, ragavanNimblePilferer); + attack(3, playerA, ragavanNimblePilferer); + setChoice(playerA, "Whenever a creature with the chosen name", 2); + setChoice(playerA, "Whenever {this} deals"); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 2 - 2); + assertHandCount(playerA, 1 + 1 + 1); // 1 draw + 2 triggers + assertPermanentCount(playerA, "Treasure Token", 2); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdc/ParapetThrasherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdc/ParapetThrasherTest.java new file mode 100644 index 00000000000..fa6a6233b77 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdc/ParapetThrasherTest.java @@ -0,0 +1,89 @@ +package org.mage.test.cards.single.tdc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +/** + * + * @author Jmlundeen + */ +public class ParapetThrasherTest extends CardTestCommander4Players { + + /* + Parapet Thrasher + {2}{R}{R} + Creature - Dragon + Flying + Whenever one or more Dragons you control deal combat damage to an opponent, choose one that hasn't been chosen this turn -- + * Destroy target artifact that opponent controls. + * This creature deals 4 damage to each other opponent. + * Exile the top card of your library. You may play it this turn. + 4/3 + */ + private static final String parapetThrasher = "Parapet Thrasher"; + + /* + Fountain of Youth + {0} + Artifact + {2}, {tap}: You gain 1 life. + */ + private static final String fountainOfYouth = "Fountain of Youth"; + + @Test + public void testParapetThrasherMode1() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, parapetThrasher); + addCard(Zone.BATTLEFIELD, playerB, fountainOfYouth); + + attack(1, playerA, parapetThrasher, playerB); + setModeChoice(playerA, "1"); + addTarget(playerA, fountainOfYouth); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, fountainOfYouth, 1); + } + + @Test + public void testParapetThrasherMode2() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, parapetThrasher); + + attack(1, playerA, parapetThrasher); + setModeChoice(playerA, "2"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 4); + assertLife(playerC, 20 - 4); + assertLife(playerD, 20 - 4); + } + + @Test + public void testParapetThrasherMode3() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, parapetThrasher); + addCard(Zone.LIBRARY, playerA, "Island"); + addCard(Zone.LIBRARY, playerA, "Mountain"); + + attack(1, playerA, parapetThrasher); + setModeChoice(playerA, "3"); + + playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Island"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, 0); + assertPermanentCount(playerA, "Island", 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 index 98c2cbee505..a636b0be12e 100644 --- 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 @@ -8,10 +8,41 @@ import org.mage.test.serverside.base.CardTestPlayerBase; public class MistriseVillageTest extends CardTestPlayerBase { + /* + Mistrise Village + Land + This land enters tapped unless you control a Mountain or a Forest. + {T}: Add {U}. + {U}, {T}: The next spell you cast this turn can’t be countered. + */ 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"; + private static final String COUNTERSPELL = "Counterspell"; + private static final String BEAR_CUB = "Bear Cub"; + private static final String BALDUVIAN_BEARS = "Balduvian Bears"; + /* + Force of Negation + {1}{U}{U} + Instant + If it’s not your turn, you may exile a blue card from your hand rather than pay this spell’s mana cost. + Counter target noncreature spell. If that spell is countered this way, exile it instead of putting it into its owner’s graveyard. + */ + public static final String FORCE_OF_NEGATION = "Force of Negation"; + /* + Narset, Parter of Veils + {1}{U}{U} + Legendary Planeswalker — Narset + Each opponent can’t draw more than one card each turn. + −2: Look at the top four cards of your library. You may reveal a noncreature, nonland card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. + */ + public static final String NARSET_PARTER_OF_VEILS = "Narset, Parter of Veils"; + /* + Aether Spellbomb + {1} + Artifact + {U}, Sacrifice this artifact: Return target creature to its owner’s hand. + {1}, Sacrifice this artifact: Draw a card. + */ + public static final String AETHER_SPELLBOMB = "Aether Spellbomb"; @Test public void testCounter() { @@ -21,22 +52,166 @@ public class MistriseVillageTest extends CardTestPlayerBase { 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); + addCard(Zone.HAND, playerB, COUNTERSPELL, 2); + addCard(Zone.HAND, playerA, BEAR_CUB); + addCard(Zone.HAND, playerA, BALDUVIAN_BEARS); - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise 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); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, BEAR_CUB, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, COUNTERSPELL, BEAR_CUB, BEAR_CUB); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, BALDUVIAN_BEARS, true); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, COUNTERSPELL, BALDUVIAN_BEARS, BALDUVIAN_BEARS); setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, CUB, 1); - assertGraveyardCount(playerA, BEARS, 1); - assertGraveyardCount(playerB, COUNTER, 2); + assertPermanentCount(playerA, BEAR_CUB, 1); + assertGraveyardCount(playerA, BALDUVIAN_BEARS, 1); + assertGraveyardCount(playerB, COUNTERSPELL, 2); } -} \ No newline at end of file + + @Test + public void testCastCounterAfterCastingSpell() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, MISTRISE); + addCard(Zone.BATTLEFIELD, playerA, "Island", 8); + addCard(Zone.HAND, playerA, NARSET_PARTER_OF_VEILS); + addCard(Zone.HAND, playerA, AETHER_SPELLBOMB); + addCard(Zone.BATTLEFIELD, playerB, "Island", 3); + addCard(Zone.HAND, playerB, FORCE_OF_NEGATION); + addCard(Zone.LIBRARY, playerA, "Stock Up"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, NARSET_PARTER_OF_VEILS); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2: "); // Narset ability + setChoice(playerA, true); + addTarget(playerA, "Stock Up"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Stock Up"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, FORCE_OF_NEGATION, "Stock Up"); + + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, AETHER_SPELLBOMB); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, NARSET_PARTER_OF_VEILS, 1); + assertPermanentCount(playerA, AETHER_SPELLBOMB, 1); + assertGraveyardCount(playerB, FORCE_OF_NEGATION, 1); + assertExileCount(playerA, "Stock Up", 1); + } + + @Test + public void testMultipleMistrise() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, MISTRISE,2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 8); + addCard(Zone.HAND, playerA, NARSET_PARTER_OF_VEILS); + addCard(Zone.BATTLEFIELD, playerB, "Island", 6); + addCard(Zone.HAND, playerB, FORCE_OF_NEGATION, 2); + addCard(Zone.LIBRARY, playerA, AETHER_SPELLBOMB); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 2); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, NARSET_PARTER_OF_VEILS); + checkPlayableAbility("cast force", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Force", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, FORCE_OF_NEGATION, NARSET_PARTER_OF_VEILS); // Fail to counter + setChoice(playerB, "Cast with no alternative cost"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2: "); // Narset ability + setChoice(playerA, true); + addTarget(playerA, AETHER_SPELLBOMB); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, AETHER_SPELLBOMB); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, FORCE_OF_NEGATION, AETHER_SPELLBOMB); // Successful counter + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, NARSET_PARTER_OF_VEILS, 1); + assertGraveyardCount(playerB, FORCE_OF_NEGATION, 2); + assertExileCount(playerA, AETHER_SPELLBOMB, 1); + } + + @Test + public void testMultipleMistriseNotConsecutive() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, MISTRISE,2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 8); + addCard(Zone.HAND, playerA, NARSET_PARTER_OF_VEILS); + addCard(Zone.BATTLEFIELD, playerB, "Island", 6); + addCard(Zone.HAND, playerB, FORCE_OF_NEGATION, 2); + addCard(Zone.LIBRARY, playerA, AETHER_SPELLBOMB); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, NARSET_PARTER_OF_VEILS); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, FORCE_OF_NEGATION, NARSET_PARTER_OF_VEILS); // Fail to counter + setChoice(playerB, "Cast with no alternative cost"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2: "); // Narset ability + setChoice(playerA, true); + addTarget(playerA, AETHER_SPELLBOMB); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, 1); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, AETHER_SPELLBOMB); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, FORCE_OF_NEGATION, AETHER_SPELLBOMB); // Successful counter + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, NARSET_PARTER_OF_VEILS, 1); + assertGraveyardCount(playerB, FORCE_OF_NEGATION, 2); + assertPermanentCount(playerA, AETHER_SPELLBOMB, 1); + } + + @Test + public void testCastingMultipleSpells() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, MISTRISE,2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 13); + addCard(Zone.HAND, playerA, NARSET_PARTER_OF_VEILS); + addCard(Zone.HAND, playerA, "Hieroglyphic Illumination"); + addCard(Zone.BATTLEFIELD, playerB, "Island", 6); + addCard(Zone.HAND, playerB, FORCE_OF_NEGATION, 2); + addCard(Zone.LIBRARY, playerA, AETHER_SPELLBOMB); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 2); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, NARSET_PARTER_OF_VEILS); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hieroglyphic Illumination"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, FORCE_OF_NEGATION, NARSET_PARTER_OF_VEILS); // Fail to counter + setChoice(playerB, "Cast with no alternative cost"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, AETHER_SPELLBOMB); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, FORCE_OF_NEGATION, AETHER_SPELLBOMB); // Successful counter + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, NARSET_PARTER_OF_VEILS, 1); + assertGraveyardCount(playerB, FORCE_OF_NEGATION, 2); + assertExileCount(playerA, AETHER_SPELLBOMB, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/StalwartSuccessorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/StalwartSuccessorTest.java new file mode 100644 index 00000000000..343dcf899fc --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/StalwartSuccessorTest.java @@ -0,0 +1,48 @@ +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 StalwartSuccessorTest extends CardTestPlayerBase { + + /* + Stalwart Successor + {1}{B}{G} + Creature — Human Warrior + + Menace (This creature can’t be blocked except by two or more creatures.) + + 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. + + 3/2 + */ + private static final String stalwartSuccessor = "Stalwart Successor"; + /* + Purestrain Genestealer + {2}{G} + Creature — Tyranid + + This creature enters with two +1/+1 counters on it. + + Vanguard Species — Whenever this creature attacks, you may remove a +1/+1 counter from it. If you do, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. + + 1/1 + */ + private static final String purestrainGenestealer = "Purestrain Genestealer"; + @Test + public void testStalwartSuccessor() { + + addCard(Zone.BATTLEFIELD, playerA, stalwartSuccessor); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerA, purestrainGenestealer); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, purestrainGenestealer); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, purestrainGenestealer, CounterType.P1P1, 2 + 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/AlurenTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/AlurenTest.java new file mode 100644 index 00000000000..b1ded1979b0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/AlurenTest.java @@ -0,0 +1,45 @@ +package org.mage.test.cards.single.tmp; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AlurenTest extends CardTestPlayerBase { + + /* + Aluren + {2}{G}{G} + Enchantment + Any player may cast creature spells with mana value 3 or less without paying their mana costs and as though they had flash. + */ + private static final String aluren = "Aluren"; + + /* + Bear Cub + {1}{G} + Creature — Bear + 2/2 + */ + private static final String cub = "Bear Cub"; + + @Test + public void testAluren() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, aluren); + addCard(Zone.HAND, playerA, cub); + addCard(Zone.HAND, playerB, cub); + + castSpell(1, PhaseStep.UPKEEP, playerA, cub); + setChoice(playerA, "Cast without paying its mana cost"); + + castSpell(1, PhaseStep.END_TURN, playerB, cub); + setChoice(playerB, "Cast without paying its mana cost"); + + setStopAt(2, PhaseStep.UPKEEP); + execute(); + + assertPermanentCount(playerA, cub, 1); + assertPermanentCount(playerB, cub, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/ExcavatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/ExcavatorTest.java new file mode 100644 index 00000000000..7ee1daedf6e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/ExcavatorTest.java @@ -0,0 +1,54 @@ +package org.mage.test.cards.single.tmp; + +import mage.abilities.keyword.LandwalkAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class ExcavatorTest extends CardTestPlayerBase { + + /* + Excavator + {2} + Artifact + + {T}, Sacrifice a basic land: Target creature gains landwalk of each of the land types of the sacrificed land until end of turn. + (It can’t be blocked as long as defending player controls a land of any of those types.) + */ + public static final String excavator = "Excavator"; + + /* + Leyline of the Guildpact + {G/W}{G/U}{B/G}{R/G} + Enchantment + + If this card is in your opening hand, you may begin the game with it on the battlefield. + + Each nonland permanent you control is all colors. + + Lands you control are every basic land type in addition to their other types. + */ + public static final String leylineOfTheGuildpact = "Leyline of the Guildpact"; + + @Test + @Ignore("Failing because permanent LKI does not save MageObjectAttribute values") + public void testExcavator() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, excavator); + addCard(Zone.BATTLEFIELD, playerA, leylineOfTheGuildpact); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}"); + setChoice(playerA, "Island"); + addTarget(playerA, "Balduvian Bears"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertAbilityCount(playerA, "Balduvian Bears", LandwalkAbility.class, 5); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/who/CrackInTimeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/CrackInTimeTest.java index a835773568d..558d9e8969c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/who/CrackInTimeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/CrackInTimeTest.java @@ -19,21 +19,29 @@ public class CrackInTimeTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Crack in Time"); // {3}{W} addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1); - // exile + // exile on etb castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Crack in Time"); addTarget(playerA, "Balduvian Bears"); // exile on etb - waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - checkExileCount("on exile", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkExileCount("on etb Balduvian", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + checkExileCount("on etb Grizzly", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Grizzly Bears", 0); + + // exile on next precombat main + addTarget(playerA, "Grizzly Bears"); // exile on etb + checkExileCount("on main Balduvian", 3, PhaseStep.BEGIN_COMBAT, playerB, "Balduvian Bears", 1); + checkExileCount("on main Grizzly", 3, PhaseStep.BEGIN_COMBAT, playerB, "Grizzly Bears", 1); // destroy and return - activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "target destroy", "Crack in Time"); - waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); - checkExileCount("on return", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 0); - checkPermanentCount("on return", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); + activateAbility(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "target destroy", "Crack in Time"); setStrictChooseMode(true); - setStopAt(1, PhaseStep.END_TURN); + setStopAt(3, PhaseStep.END_TURN); execute(); + + assertExileCount(playerB, "Balduvian Bears", 0); + assertPermanentCount(playerB, "Balduvian Bears", 1); + assertExileCount(playerB, "Grizzly Bears", 0); + assertPermanentCount(playerB, "Grizzly Bears", 1); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/who/EverybodyLivesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/EverybodyLivesTest.java new file mode 100644 index 00000000000..c1cd4693912 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/EverybodyLivesTest.java @@ -0,0 +1,95 @@ +package org.mage.test.cards.single.who; + +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.LoseGameSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeAllPlayersEffect; +import mage.abilities.effects.common.WinGameSourceControllerEffect; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +public class EverybodyLivesTest extends CardTestCommander4Players { + + /* + Everybody Lives! + {1}{W} + Instant + + All creatures gain hexproof and indestructible until end of turn. Players gain hexproof until end of turn. + Players can’t lose life this turn and players can’t lose the game or win the game this turn. + */ + private static final String everybodyLives = "Everybody Lives!"; + + @Test + public void testEverybodyLivesCantLoseLifeAndHexproof() { + setStrictChooseMode(true); + + addCustomCardWithAbility("lose life effect", playerA, new SimpleActivatedAbility( + new LoseLifeAllPlayersEffect(20), + new ManaCostsImpl<>("")) + ); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.HAND, playerA, everybodyLives); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, everybodyLives, true); + checkPlayableAbility("Can't cast lightning bolt", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "each player"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + assertLife(playerC, 20); + assertLife(playerD, 20); + } + + @Test + public void testEverybodyLivesCantLoseGame() { + setStrictChooseMode(true); + + addCustomCardWithAbility("lose game effect", playerA, new SimpleActivatedAbility( + new LoseGameSourceControllerEffect(), + new ManaCostsImpl<>("")) + ); + addCard(Zone.HAND, playerA, everybodyLives); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, everybodyLives, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "you lose"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHasNotLostTheGame(playerA); + + } + + @Test + public void testEverybodyLivesCantWinGame() { + setStrictChooseMode(true); + + addCustomCardWithAbility("win game effect", playerA, new SimpleActivatedAbility( + new WinGameSourceControllerEffect(), + new ManaCostsImpl<>("")) + ); + addCard(Zone.HAND, playerA, everybodyLives); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, everybodyLives, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "you win"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHasNotWonTheGame(playerA); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/znr/YasharnImplacableEarthTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/znr/YasharnImplacableEarthTest.java index 2daabf2511d..a17b75ab343 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/znr/YasharnImplacableEarthTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/znr/YasharnImplacableEarthTest.java @@ -1,9 +1,16 @@ package org.mage.test.cards.single.znr; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlyingAbility; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.permanent.token.FoodToken; +import mage.game.permanent.token.TreasureToken; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -23,21 +30,26 @@ public class YasharnImplacableEarthTest extends CardTestPlayerBase { * Test that players can't pay life to cast a spell. */ @Test - @Ignore public void cantPayLifeToCast() { // {W}{B} // As an additional cost to cast this spell, pay 5 life or sacrifice a creature or enchantment. // Destroy target creature. addCard(Zone.HAND, playerA, "Final Payment"); + //{4}{B/P}{B/P}{B/P} + //Legendary Creature — Phyrexian Horror Minion + //2/2 + //Lifelink + //For each {B} in a cost, you may pay 2 life rather than pay that mana. + //Whenever you cast a black spell, put a +1/+1 counter on K'rrik. + addCard(Zone.HAND, playerA, "K'rrik, Son of Yawgmoth"); addCard(Zone.BATTLEFIELD, playerA, yasharn); - addCard(Zone.BATTLEFIELD, playerA, "Swamp"); - addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); setStrictChooseMode(true); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Final Payment", yasharn); - setChoice(playerA, "No"); - setChoice(playerA, "Silvercoat Lion"); + checkPlayableAbility("Can't cast Final Payment", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Final Payment", false); + checkPlayableAbility("Can't cast K'rrik", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast K'rrik, Son of Yawgmoth", false); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -71,22 +83,20 @@ public class YasharnImplacableEarthTest extends CardTestPlayerBase { * Test that players can't sacrifice a nonland permanent to cast a spell. */ @Test - @Ignore public void cantSacrificeNonlandToCast() { // {1}{B} // As an additional cost to cast this spell, sacrifice an artifact or creature. // Draw two cards and create a Treasure token. addCard(Zone.HAND, playerA, "Deadly Dispute"); addCard(Zone.BATTLEFIELD, playerA, yasharn); + addCard(Zone.BATTLEFIELD, playerA, "Bear Cub"); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); setStrictChooseMode(true); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deadly Dispute"); - setChoice(playerA, yasharn); + checkPlayableAbility("Can't cast Deadly Dispute", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Deadly Dispute", false); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); - assertPermanentCount(playerA, "Treasure Token", 0); } /** @@ -117,9 +127,13 @@ public class YasharnImplacableEarthTest extends CardTestPlayerBase { */ @Test public void canSacrificeLandToCast() { + //Crop Rotation + //{G} + //As an additional cost to cast this spell, sacrifice a land. + //Search your library for a land card, put that card onto the battlefield, then shuffle. + addCard(Zone.HAND, playerA, "Crop Rotation"); addCard(Zone.BATTLEFIELD, playerA, yasharn); addCard(Zone.BATTLEFIELD, playerA, "Forest"); - addCard(Zone.HAND, playerA, "Crop Rotation"); addCard(Zone.LIBRARY, playerA, "Mountain"); setStrictChooseMode(true); @@ -156,4 +170,159 @@ public class YasharnImplacableEarthTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Island", 1); assertGraveyardCount(playerA, "Evolving Wilds", 1); } + + /** + * Test that a player cannot sacrifice artifacts or creatures to activate abilities + */ + @Test + public void cantSacToMondrakWithArtifacts() { + setStrictChooseMode(true); + //Mondrak, Glory Dominus + //{2}{W}{W} + //Legendary Creature — Phyrexian Horror + //If one or more tokens would be created under your control, twice that many of those tokens are created instead. + //{1}{W/P}{W/P}, Sacrifice two other artifacts and/or creatures: Put an indestructible counter on Mondrak. + String mondrak = "Mondrak, Glory Dominus"; + + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new CreateTokenEffect(new TreasureToken(), 2), + new ManaCostsImpl<>("") + ); + ability.addEffect(new CreateTokenEffect(new FoodToken(), 2)); + + addCustomCardWithAbility("Token-maker", playerA, ability); + addCard(Zone.BATTLEFIELD, playerA, mondrak); + addCard(Zone.BATTLEFIELD, playerA, yasharn); + addCard(Zone.BATTLEFIELD, playerA, "Bear Cub", 2); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + + checkPlayableAbility("Can't activate Mondrak with creatures", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W/P}{W/P}, Sacrifice", false); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "create two"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkPlayableAbility("Can't activate Mondrak with creatures or artifacts", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W/P}{W/P}, Sacrifice", false); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Bear Cub", 2); + assertPermanentCount(playerA, "Treasure Token", 4); + assertPermanentCount(playerA, "Food Token", 4); + } + + @Test + public void canSacrificeTriggeredAbility() { + /* + Unscrupulous Contractor + {2}{B} + Creature — Human Assassin + When this creature enters, you may sacrifice a creature. When you do, target player draws two cards and loses 2 life. + Plot {2}{B} + 3/2 + */ + String contractor = "Unscrupulous Contractor"; + /* + Bear Cub + {1}{G} + Creature - Bear + 2/2 + */ + String cub = "Bear Cub"; + + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, yasharn); + addCard(Zone.HAND, playerA, contractor); + addCard(Zone.HAND, playerB, contractor); + addCard(Zone.BATTLEFIELD, playerA, cub); + addCard(Zone.BATTLEFIELD, playerB, cub); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, contractor); + setChoice(playerA, true); + setChoice(playerA, cub); + addTarget(playerA, playerA); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, contractor); + setChoice(playerB, true); + setChoice(playerB, cub); + addTarget(playerB, playerB); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, 2); + assertLife(playerA, 20 - 2); + assertGraveyardCount(playerA, cub, 1); + + assertHandCount(playerB, 1 + 2); //draw + contractor effect + assertLife(playerB, 20 - 2); + assertGraveyardCount(playerB, cub, 1); + } + + @Test + public void canPayLifeForTriggeredAbility() { + /* + Arrogant Poet + {1}{B} + Creature — Human Warlock + Whenever this creature attacks, you may pay 2 life. If you do, it gains flying until end of turn. + 2/1 + */ + String poet = "Arrogant Poet"; + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, poet); + addCard(Zone.BATTLEFIELD, playerA, yasharn); + + attack(1, playerA, poet); + setChoice(playerA, true); // pay 2 life + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 2); // combat damage + assertLife(playerA, 20 - 2); // paid life + assertAbility(playerA, poet, FlyingAbility.getInstance(), true); + } + + @Test + public void canSacWithGrist() { + /* + Grist, the Hunger Tide + {1}{B}{G} + Legendary Planeswalker — Grist + As long as Grist isn’t on the battlefield, it’s a 1/1 Insect creature in addition to its other types. + +1: Create a 1/1 black and green Insect creature token, then mill a card. If an Insect card was milled this way, put a loyalty counter on Grist and repeat this process. + −2: You may sacrifice a creature. When you do, destroy target creature or planeswalker. + −5: Each opponent loses life equal to the number of creature cards in your graveyard. + Loyalty: 3 + */ + String grist = "Grist, the Hunger Tide"; + /* + Bear Cub + {1}{G} + Creature - Bear + 2/2 + */ + String cub = "Bear Cub"; + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, grist); + addCard(Zone.BATTLEFIELD, playerA, yasharn); + addCard(Zone.BATTLEFIELD, playerA, cub); + addCard(Zone.BATTLEFIELD, playerB, grist + "@gristB"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2:"); + setChoice(playerA, true); + setChoice(playerA, cub); + addTarget(playerA, "@gristB"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(grist, CounterType.LOYALTY, 1); + assertGraveyardCount(playerB, grist, 1); + assertGraveyardCount(playerA, cub, 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java index 7681ef14fd4..2863182baec 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java @@ -54,11 +54,10 @@ public class TargetsSelectionBaseTest extends CardTestPlayerBaseWithAIHelps { checkHandCardCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", availableCardsCount); checkExileCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", 0); + // cause minTarget = 0, so ability can be activated at any time + checkPlayableAbility("can activate on any targets", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, true); if (availableCardsCount > 0) { - checkPlayableAbility("can activate on non-zero targets", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, true); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText); - } else { - checkPlayableAbility("can't activate on zero targets", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, false); } if (chooseCardsCount > 0) { @@ -74,7 +73,8 @@ public class TargetsSelectionBaseTest extends CardTestPlayerBaseWithAIHelps { // - 2 of 3 - yes // - 3 of 3 - no, it's auto-finish on last select // - 3 of 5 - no, it's auto-finish on last select - if (chooseCardsCount < maxTarget) { + int maxPossibleChoose = Math.min(availableCardsCount, maxTarget); + if (chooseCardsCount < maxPossibleChoose) { addTarget(playerA, TestPlayer.TARGET_SKIP); } } else { @@ -128,20 +128,18 @@ public class TargetsSelectionBaseTest extends CardTestPlayerBaseWithAIHelps { targetCards.add("Swamp"); }); setChoice(playerA, String.join("^", targetCards)); - } else { - // need skip - // on 0 cards there are must be dialog with done button anyway - - // end selection: - // - x of 0 - yes - // - 1 of 3 - yes - // - 2 of 3 - yes - // - 3 of 3 - no, it's auto-finish on last select - // - 3 of 5 - no, it's auto-finish on last select - if (chooseCardsCount < maxTarget) { - setChoice(playerA, TestPlayer.CHOICE_SKIP); - } + } + // choose skip + // end selection condition: + // - x of 0 - yes + // - 1 of 3 - yes + // - 2 of 3 - yes + // - 3 of 3 - no, it's auto-finish on last select + // - 3 of 5 - no, it's auto-finish on last select and nothing to choose + int canSelectCount = Math.min(maxTarget, availableCardsCount); + if (canSelectCount > 0 && chooseCardsCount < canSelectCount) { + setChoice(playerA, TestPlayer.CHOICE_SKIP); } if (DEBUG_ENABLE_DETAIL_LOGS) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesTargetTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesTargetTriggerTest.java index cdaed27a013..47f8a695997 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesTargetTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesTargetTriggerTest.java @@ -7,6 +7,7 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.Filter; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -478,6 +479,7 @@ public class BecomesTargetTriggerTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hostility); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, protector); setStrictChooseMode(true); @@ -502,6 +504,7 @@ public class BecomesTargetTriggerTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hostility); setModeChoice(playerA, "2"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, dragon); setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/DrawNCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/DrawNCardsTest.java new file mode 100644 index 00000000000..1838e9e39b7 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/DrawNCardsTest.java @@ -0,0 +1,32 @@ +package org.mage.test.cards.triggers; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class DrawNCardsTest extends CardTestPlayerBase { + + private static final String snacker = "Sneaky Snacker"; + private static final String mists = "Reach Through Mists"; + private static final String looting = "Faithless Looting"; + + @Test + public void testSnacker() { + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 1 + 1); + addCard(Zone.GRAVEYARD, playerA, snacker); + addCard(Zone.HAND, playerA, mists); + addCard(Zone.HAND, playerA, looting); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, mists); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, looting); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(snacker, true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java index 2217cb4ee82..2837a7e6380 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java @@ -24,7 +24,6 @@ public class EnterLeaveBattlefieldExileTargetTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Angel of Serenity"); addTarget(playerA, "Silvercoat Lion^Pillarfield Ox"); - addTarget(playerA, TestPlayer.TARGET_SKIP); setChoice(playerA, true); setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/dialogs/TestableDialogsTest.java b/Mage.Tests/src/test/java/org/mage/test/dialogs/TestableDialogsTest.java index 7a41de3d740..406f8580e8b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/dialogs/TestableDialogsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/dialogs/TestableDialogsTest.java @@ -6,6 +6,7 @@ import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.continuous.PlayAdditionalLandsAllEffect; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.util.ConsoleUtil; import mage.utils.testers.TestableDialog; import mage.utils.testers.TestableDialogsRunner; import org.junit.Assert; @@ -107,7 +108,7 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { @Test @Ignore // debug only - run single dialog by reg number public void test_RunSingle_Debugging() { - int needRegNumber = 557; + int needRegNumber = 5; prepareCards(); @@ -233,15 +234,15 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { if (resAssert == null) { totalUnknown++; status = "?"; - coloredTexts.put("?", asYellow("?")); + coloredTexts.put("?", ConsoleUtil.asYellow("?")); } else if (resAssert.isEmpty()) { totalGood++; status = "OK"; - coloredTexts.put("OK", asGreen("OK")); + coloredTexts.put("OK", ConsoleUtil.asGreen("OK")); } else { totalBad++; status = "FAIL"; - coloredTexts.put("FAIL", asRed("FAIL")); + coloredTexts.put("FAIL", ConsoleUtil.asRed("FAIL")); assertError = resAssert; } if (!assertError.isEmpty()) { @@ -256,8 +257,8 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { // print dialog error if (!assertError.isEmpty()) { coloredTexts.clear(); - coloredTexts.put(resAssert, asRed(resAssert)); - coloredTexts.put(resDebugSource, asRed(resDebugSource)); + coloredTexts.put(resAssert, ConsoleUtil.asRed(resAssert)); + coloredTexts.put(resDebugSource, ConsoleUtil.asRed(resDebugSource)); String badAssert = getColoredRow(totalsRightFormat, coloredTexts, resAssert); String badDebugSource = getColoredRow(totalsRightFormat, coloredTexts, resDebugSource); if (firstBadDialog == null) { @@ -283,15 +284,15 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { String badStats = String.format("%d bad", totalBad); String unknownStats = String.format("%d unknown", totalUnknown); coloredTexts.clear(); - coloredTexts.put(goodStats, String.format("%s good", asGreen(String.valueOf(totalGood)))); - coloredTexts.put(badStats, String.format("%s bad", asRed(String.valueOf(totalBad)))); - coloredTexts.put(unknownStats, String.format("%s unknown", asYellow(String.valueOf(totalUnknown)))); + coloredTexts.put(goodStats, String.format("%s good", ConsoleUtil.asGreen(String.valueOf(totalGood)))); + coloredTexts.put(badStats, String.format("%s bad", ConsoleUtil.asRed(String.valueOf(totalBad)))); + coloredTexts.put(unknownStats, String.format("%s unknown", ConsoleUtil.asYellow(String.valueOf(totalUnknown)))); System.out.print(getColoredRow(totalsLeftFormat, coloredTexts, String.format("Total results: %s, %s, %s", goodStats, badStats, unknownStats))); // first error for fast access in big list if (totalDialogs > 1 && firstBadDialog != null) { System.out.println(horizontalBorder); - System.out.print(getColoredRow(totalsRightFormat, coloredTexts, "First bad dialog: " + firstBadDialog.getRegNumber())); + System.out.print(getColoredRow(totalsRightFormat, coloredTexts, "First bad dialog: " + firstBadDialog.getRegNumber() + " (debug it by test_RunSingle_Debugging)")); System.out.print(getColoredRow(totalsRightFormat, coloredTexts, firstBadDialog.getName() + " - " + firstBadDialog.getDescription())); System.out.print(firstBadAssert); System.out.print(firstBadDebugSource); @@ -313,16 +314,4 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps { } return line; } - - private String asRed(String text) { - return "\u001B[31m" + text + "\u001B[0m"; - } - - private String asGreen(String text) { - return "\u001B[32m" + text + "\u001B[0m"; - } - - private String asYellow(String text) { - return "\u001B[33m" + text + "\u001B[0m"; - } } \ No newline at end of file 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 ca3a223ea3b..b60def4c78a 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 @@ -16,6 +16,7 @@ import mage.cards.Cards; import mage.cards.CardsImpl; import mage.cards.decks.Deck; import mage.choices.Choice; +import mage.collectors.DataCollectorServices; import mage.constants.*; import mage.counters.Counter; import mage.counters.CounterType; @@ -41,6 +42,7 @@ import mage.game.stack.StackAbility; import mage.game.stack.StackObject; import mage.game.tournament.Tournament; import mage.player.ai.ComputerPlayer; +import mage.player.ai.PossibleTargetsSelector; import mage.players.*; import mage.players.net.UserData; import mage.target.*; @@ -51,7 +53,6 @@ import mage.util.RandomUtil; import mage.watchers.common.AttackedOrBlockedThisCombatWatcher; import org.apache.log4j.Logger; import org.junit.Assert; -import static org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.*; import java.io.Serializable; import java.util.*; @@ -59,6 +60,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.*; + /** * Basic implementation of testable player * @@ -72,6 +75,7 @@ public class TestPlayer implements Player { public static final String TARGET_SKIP = "[target_skip]"; // stop/skip targeting public static final String CHOICE_SKIP = "[choice_skip]"; // stop/skip choice + public static final String MODE_SKIP = "[mode_skip]"; // stop/skip mode selection public static final String CHOICE_NORMAL_COST = "Cast with no alternative cost: "; // when there is the possibility for an alternative cost, use the normal cost instead. public static final String MANA_CANCEL = "[mana_cancel]"; // cancel payment public static final String SKIP_FAILED_COMMAND = "[skip_failed_command]"; // skip next command in player's queue (can remove cast commands after try to activate) @@ -101,7 +105,10 @@ public class TestPlayer implements Player { private final List choices = new ArrayList<>(); // choices stack for choice private final List targets = new ArrayList<>(); // targets stack for choose (it's uses on empty direct target by cast command) private final Map aliases = new HashMap<>(); // aliases for game objects/players (use it for cards with same name to save and use) - private final List modesSet = new ArrayList<>(); + private final List modes = new ArrayList<>(); + + // debug only: ignore all next commands so choose dialog will raise error with full info + private boolean isSkipAllNextChooseCommands = false; private final ComputerPlayer computerPlayer; // real player @@ -144,11 +151,12 @@ public class TestPlayer implements Player { this.choices.addAll(testPlayer.choices); this.targets.addAll(testPlayer.targets); this.aliases.putAll(testPlayer.aliases); - this.modesSet.addAll(testPlayer.modesSet); + this.modes.addAll(testPlayer.modes); this.computerPlayer = testPlayer.computerPlayer.copy(); if (testPlayer.groupsForTargetHandling != null) { this.groupsForTargetHandling = testPlayer.groupsForTargetHandling.clone(); } + this.isSkipAllNextChooseCommands = testPlayer.isSkipAllNextChooseCommands; this.strictChooseMode = testPlayer.strictChooseMode; } @@ -160,6 +168,10 @@ public class TestPlayer implements Player { Assert.assertNotEquals("Choice can't be empty", "", choice); choice = EmptyNames.replaceTestCommandByObjectName(choice); + if (this.isSkipAllNextChooseCommands) { + return; + } + choices.add(choice); } @@ -184,7 +196,10 @@ public class TestPlayer implements Player { } public void addModeChoice(String mode) { - modesSet.add(mode); + if (this.isSkipAllNextChooseCommands) { + return; + } + modes.add(mode); } public void addTarget(String target) { @@ -194,6 +209,10 @@ public class TestPlayer implements Player { target = EmptyNames.replaceTestCommandByObjectName(target); + if (this.isSkipAllNextChooseCommands) { + return; + } + targets.add(target); } @@ -315,11 +334,8 @@ public class TestPlayer implements Player { return true; } else if (groups[2].startsWith("spellOnTopOfStack=")) { String spellOnTopOFStack = groups[2].substring(18); - if (!game.getStack().isEmpty()) { - StackObject stackObject = game.getStack().getFirst(); - return stackObject != null && stackObject.getStackAbility().toString().contains(spellOnTopOFStack); - } - return false; + StackObject stackObject = game.getStack().getFirstOrNull(); + return stackObject != null && stackObject.getStackAbility().toString().contains(spellOnTopOFStack); } else if (groups[2].startsWith("manaInPool=")) { String manaInPool = groups[2].substring(11); int amountOfMana = Integer.parseInt(manaInPool); @@ -366,6 +382,10 @@ public class TestPlayer implements Player { return true; // targetAmount have to be set by setTargetAmount in the test script } ability.getTargets().get(0).addTarget(player.getId(), ability, game); + + String reason = CardUtil.getSourceLogName(game, "by cast/activate, ability: ", ability, "", ""); + DataCollectorServices.getInstance().onTestsTargetUse(game, player, target, reason); + targetsSet++; break; } @@ -464,9 +484,12 @@ public class TestPlayer implements Player { int index = 0; int targetsSet = 0; for (String targetName : targetList) { + + // choose target for specific mode Mode selectedMode; if (targetName.startsWith("mode=")) { - int modeNr = Integer.parseInt(targetName.substring(5, 6)); + String modeStr = targetName.substring(5, 6); + int modeNr = Integer.parseInt(modeStr); if (modeNr == 0 || modeNr > (ability.getModes().isMayChooseSameModeMoreThanOnce() ? ability.getModes().getSelectedModes().size() : ability.getModes().size())) { throw new UnsupportedOperationException("Given mode number (" + modeNr + ") not available for " + ability.toString()); } @@ -489,18 +512,23 @@ public class TestPlayer implements Player { if (index >= selectedMode.getTargets().size()) { break; // this can happen if targets should be set but can't be used because of hexproof e.g. } + + // choose player Target currentTarget = selectedMode.getTargets().get(index); if (targetName.startsWith("targetPlayer=")) { target = targetName.substring(targetName.indexOf("targetPlayer=") + 13); for (Player player : game.getPlayers().values()) { if (player.getName().equals(target)) { currentTarget.addTarget(player.getId(), ability, game); + String reason = CardUtil.getSourceLogName(game, "by cast/activate, ability: ", ability, "", ""); + DataCollectorServices.getInstance().onTestsTargetUse(game, this, target, reason); index++; targetsSet++; break; } } } else { + // choose object boolean originOnly = false; boolean copyOnly = false; if (targetName.endsWith("]")) { @@ -551,6 +579,10 @@ public class TestPlayer implements Player { currentTarget.addTarget(id, ability, game); targetsSet++; } + + String reason = CardUtil.getSourceLogName(game, "by cast/activate, ability: ", ability, "", ""); + DataCollectorServices.getInstance().onTestsTargetUse(game, this, targetName, reason); + if (currentTarget.getTargets().size() == currentTarget.getMaxNumberOfTargets()) { index++; } @@ -628,7 +660,7 @@ public class TestPlayer implements Player { // skip failed command if (!choices.isEmpty() && choices.get(0).equals(SKIP_FAILED_COMMAND)) { actions.remove(action); - choices.remove(0); + choicesRemoveCurrent(game, "on priority"); return true; } } @@ -1267,7 +1299,7 @@ public class TestPlayer implements Player { .map(c -> (((c instanceof PermanentToken) ? "[T] " : "[C] ") + c.getIdName() + (c.isCopy() ? " [copy of " + c.getCopyFrom().getId().toString().substring(0, 3) + "]" : "") - + " class " + c.getMainCard().getClass().getSimpleName() + "" + + " class " + getSimpleClassName(c.getMainCard().getClass()) + "" + " - " + c.getPower().getValue() + "/" + c.getToughness().getValue() + (c.isPlaneswalker(game) ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "") + ", " + (c.isTapped() ? "Tapped" : "Untapped") @@ -1307,7 +1339,7 @@ public class TestPlayer implements Player { + (a.toString().startsWith("Cast ") ? "[" + a.getManaCostsToPay().getText() + "] -> " : "") // printed cost, not modified + (a.toString().length() > 0 ? CardUtil.substring(a.toString(), 40, "...") - : a.getClass().getSimpleName()) + : getSimpleClassName(a.getClass())) )) .sorted() .collect(Collectors.toList()); @@ -2091,13 +2123,13 @@ public class TestPlayer implements Player { } private String getInfo(MageObject o) { - return "Object: " + (o != null ? o.getClass().getSimpleName() + ": " + o.getName() : "null"); + return "Object: " + (o != null ? getSimpleClassName(o.getClass()) + ": " + o.getName() : "null"); } private String getInfo(Ability o, Game game) { if (o != null) { MageObject object = o.getSourceObject(game); - return "Ability: " + (object == null ? "" : object.getIdName() + " - " + o.getClass().getSimpleName() + ": " + o.getRule()); + return "Ability: " + (object == null ? "" : object.getIdName() + " - " + getSimpleClassName(o.getClass()) + ": " + o.getRule()); } return "Ability: null"; } @@ -2109,8 +2141,8 @@ public class TestPlayer implements Player { UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId()); Set possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards); - return "Target: selected " + target.getSize() + ", possible " + possibleTargets.size() - + ", " + target.getClass().getSimpleName() + ": " + target.getMessage(game); + return "Target: selected " + target.getSize() + ", more possible " + possibleTargets.size() + + ", " + getSimpleClassName(target.getClass()) + ": " + target.getMessage(game); } private void assertAliasSupportInChoices(boolean methodSupportAliases) { @@ -2166,6 +2198,13 @@ public class TestPlayer implements Player { }); printEnd(); } + if (choiceType.equals("mode")) { + printStart(game, "Unused modes"); + if (!modes.isEmpty()) { + System.out.println(String.join("\n", modes)); + } + printEnd(); + } Assert.fail("Missing " + choiceType.toUpperCase(Locale.ENGLISH) + " def for" + " turn " + game.getTurnNum() + ", step " + (game.getStep() != null ? game.getTurnStepType().name() : "not started") @@ -2176,23 +2215,8 @@ public class TestPlayer implements Player { @Override public Mode chooseMode(Modes modes, Ability source, Game game) { - if (!modesSet.isEmpty() && modes.getMaxModes(game, source) > modes.getSelectedModes().size()) { - // set mode to null to select less than maximum modes if multiple modes are allowed - if (modesSet.get(0) == null) { - modesSet.remove(0); - return null; - } - int needMode = Integer.parseInt(modesSet.get(0)); - int i = 1; - for (Mode mode : modes.getAvailableModes(source, game)) { - if (i == needMode) { - modesSet.remove(0); - return mode; - } - i++; - } - } - if (modes.getMinModes() <= modes.getSelectedModes().size()) { + if (modes.getSelectedModes().size() >= modes.getMaxModes(game, source)) { + // TODO: no needs here cause min/max mode must be checked by parent code? try to remove it from here return null; } @@ -2207,6 +2231,27 @@ public class TestPlayer implements Player { i++; } + if (!this.modes.isEmpty()) { + + // skip modes + if (tryToSkipSelection(game, null, this.modes, MODE_SKIP)) { + return null; + } + + String needModeStr = this.modes.get(0); + int needModeNumber = Integer.parseInt(needModeStr); + i = 1; + for (Mode mode : modes.getAvailableModes(source, game)) { + if (i == needModeNumber) { + modesRemoveCurrent(game, mode); + return mode; + } + i++; + } + + Assert.fail("Can't use mode: " + needModeNumber + "\n" + getInfo(source, game) + modesInfo); + } + this.chooseStrictModeFailed("mode", game, getInfo(source, game) + modesInfo); return computerPlayer.chooseMode(modes, source, game); } @@ -2219,9 +2264,8 @@ public class TestPlayer implements Player { String possibleChoice = choices.get(0); // skip choices - if (possibleChoice.equals(CHOICE_SKIP)) { - choices.remove(0); - return false; // false - stop to choose + if (tryToSkipSelection(game, null, choices, CHOICE_SKIP)) { + return false; } if (choice.setChoiceByAnswers(choices, true)) { @@ -2254,7 +2298,7 @@ public class TestPlayer implements Player { int index = 0; for (Map.Entry entry : effectsMap.entrySet()) { if (entry.getValue().startsWith(choice)) { - choices.remove(0); + choicesRemoveCurrent(game, "on choose replacements"); // TODO: add short lists? return index; } index++; @@ -2269,219 +2313,7 @@ public class TestPlayer implements Player { @Override public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map options) { - - // choose itself for starting player all the time - if (target.getMessage(game).equals("Select a starting player")) { - target.add(this.getId(), game); - return true; - } - - UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId()); - - // TODO: warning, some cards call player.choose methods instead target.choose, see #8254 - // most use cases - discard and other cost with choice like that method - // must migrate all choices.remove(xxx) to choices.remove(0), takeMaxTargetsPerChoose can help to find it - - boolean isAddedSomething = false; // must return true on any changes in targets, so game can ask next choose dialog until finish - - assertAliasSupportInChoices(true); - if (!choices.isEmpty()) { - - // skip choices - if (tryToSkipSelection(choices, CHOICE_SKIP)) { - Assert.assertTrue("found skip choice, but it require more choices, needs " - + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", - target.getTargets().size() >= target.getMinNumberOfTargets()); - return false; // false - stop to choose - } - - // TODO: Allow to choose a player with TargetPermanentOrPlayer - if ((target.getOriginalTarget() instanceof TargetPermanent) - || (target.getOriginalTarget() instanceof TargetPermanentOrPlayer)) { // player target not implemented yet - FilterPermanent filterPermanent; - if (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) { - filterPermanent = ((TargetPermanentOrPlayer) target.getOriginalTarget()).getFilterPermanent(); - } else { - filterPermanent = ((TargetPermanent) target.getOriginalTarget()).getFilter(); - } - while (!choices.isEmpty()) { // TODO: remove cycle after main commits - if (tryToSkipSelection(choices, CHOICE_SKIP)) { - return false; // stop dialog - } - String choiceRecord = choices.get(0); - String[] targetList = choiceRecord.split("\\^"); - isAddedSomething = false; - for (String targetName : targetList) { - boolean originOnly = false; - boolean copyOnly = false; - if (targetName.endsWith("]")) { - if (targetName.endsWith("[no copy]")) { - originOnly = true; - targetName = targetName.substring(0, targetName.length() - 9); - } - if (targetName.endsWith("[only copy]")) { - copyOnly = true; - targetName = targetName.substring(0, targetName.length() - 11); - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, abilityControllerId, source, game)) { - if (target.contains(permanent.getId())) { - continue; - } - if (hasObjectTargetNameOrAlias(permanent, targetName)) { - if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { - if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) { - target.add(permanent.getId(), game); - isAddedSomething = true; - break; - } - } - } else if ((permanent.getName() + '-' + permanent.getExpansionSetCode()).equals(targetName)) { // TODO: remove search by exp code? - if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { - if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) { - target.add(permanent.getId(), game); - isAddedSomething = true; - break; - } - } - } - } - } - - try { - if (isAddedSomething) { - if (target.isChoiceCompleted(abilityControllerId, source, game)) { - return true; - } - } else { - failOnLastBadChoice(game, source, target, choiceRecord, "invalid target or miss skip command"); - } - } finally { - choices.remove(0); - } - return isAddedSomething; - } // choices - } - - if (target instanceof TargetPlayer) { - while (!choices.isEmpty()) { // TODO: remove cycle after main commits - if (tryToSkipSelection(choices, CHOICE_SKIP)) { - return false; // stop dialog - } - String choiceRecord = choices.get(0); - isAddedSomething = false; - for (Player player : game.getPlayers().values()) { - if (player.getName().equals(choiceRecord)) { - if (target.canTarget(abilityControllerId, player.getId(), source, game) && !target.contains(player.getId())) { - target.add(player.getId(), game); - isAddedSomething = true; - } - } - } - - try { - if (isAddedSomething) { - if (target.isChoiceCompleted(abilityControllerId, source, game)) { - return true; - } - } else { - failOnLastBadChoice(game, source, target, choiceRecord, "invalid target or miss skip command"); - } - } finally { - choices.remove(0); - } - return isAddedSomething; - } // while choices - } - - // TODO: add same choices fixes for other target types (one choice must uses only one time for one target) - if (target.getOriginalTarget() instanceof TargetCard) { - // one choice per target - // only unique targets - //TargetCard targetFull = ((TargetCard) target); - - for (String choiceRecord : new ArrayList<>(choices)) { // TODO: remove cycle after main commits - if (tryToSkipSelection(choices, CHOICE_SKIP)) { - return false; // stop dialog - } - isAddedSomething = false; - String[] possibleChoices = choiceRecord.split("\\^"); - - for (String possibleChoice : possibleChoices) { - Set possibleCards = target.possibleTargets(abilityControllerId, source, game); - for (UUID targetId : possibleCards) { - MageObject targetObject = game.getCard(targetId); - if (hasObjectTargetNameOrAlias(targetObject, possibleChoice)) { - if (target.canTarget(targetObject.getId(), game) && !target.contains(targetObject.getId())) { - target.add(targetObject.getId(), game); - isAddedSomething = true; - break; - } - } - } - } - try { - if (isAddedSomething) { - if (target.isChoiceCompleted(abilityControllerId, source, game)) { - return true; - } - } else { - failOnLastBadChoice(game, source, target, choiceRecord, "invalid target or miss skip command"); - } - } finally { - choices.remove(0); - } - return isAddedSomething; - } // for choices - } - - if (target.getOriginalTarget() instanceof TargetSource) { - TargetSource t = ((TargetSource) target.getOriginalTarget()); - Set possibleTargets = t.possibleTargets(abilityControllerId, source, game); - // TODO: enable choices.get first instead all - for (String choiceRecord : new ArrayList<>(choices)) { // TODO: remove cycle after main commits - if (tryToSkipSelection(choices, CHOICE_SKIP)) { - return false; // stop dialog - } - String[] targetList = choiceRecord.split("\\^"); - isAddedSomething = false; - for (String targetName : targetList) { - for (UUID targetId : possibleTargets) { - MageObject targetObject = game.getObject(targetId); - if (targetObject != null) { - if (hasObjectTargetNameOrAlias(targetObject, targetName)) { - if (t.canTarget(targetObject.getId(), game) && !target.contains(targetObject.getId())) { - target.add(targetObject.getId(), game); - isAddedSomething = true; - break; - } - } - } - } - } - try { - if (isAddedSomething) { - if (target.isChoiceCompleted(abilityControllerId, source, game)) { - return true; - } - } else { - failOnLastBadChoice(game, source, target, choiceRecord, "invalid target or miss skip command"); - } - } finally { - choices.remove(choiceRecord); - } - return isAddedSomething; - } // for choices - } - - // TODO: enable fail checks and fix tests - if (!target.getTargetName().equals("starting player")) { - assertWrongChoiceUsage(choices.size() > 0 ? choices.get(0) : "empty list"); - } - } - - this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, source, game, null)); - return computerPlayer.choose(outcome, target, source, game, options); + return makeChoose(outcome, target, source, game, null); } private void checkTargetDefinitionMarksSupport(Target needTarget, String targetDefinition, String canSupportChars) { @@ -2502,7 +2334,7 @@ public class TestPlayer implements Player { // how to fix: change target definition for addTarget in test's code or update choose from targets implementation in TestPlayer if ((foundMulti && !canMulti) || (foundSpecialStart && !canSpecialStart) || (foundSpecialClose && !canSpecialClose) || (foundEquals && !canEquals)) { Assert.fail(this.getName() + " - Targets list was setup by addTarget with " + targets + ", but target definition [" + targetDefinition + "]" - + " is not supported by [" + canSupportChars + "] for target class " + needTarget.getClass().getSimpleName()); + + " is not supported by [" + canSupportChars + "] for target class " + getSimpleClassName(needTarget.getClass())); } } @@ -2514,12 +2346,11 @@ public class TestPlayer implements Player { if (!targets.isEmpty()) { // skip targets - if (targets.get(0).equals(TARGET_SKIP)) { + if (tryToSkipSelection(game, target, targets, TARGET_SKIP)) { Assert.assertTrue("found skip target, but it require more targets, needs " + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); - targets.remove(0); - return false; // false - stop to choose + return target.getSize() > 0 && target.isChosen(game); } Set targetCardZonesChecked = new HashSet<>(); // control miss implementation @@ -2538,7 +2369,7 @@ public class TestPlayer implements Player { && target.canTarget(abilityControllerId, player.getId(), source, game) && !target.contains(player.getId())) { target.addTarget(player.getId(), source, game); - targets.remove(targetDefinition); + targetsRemoveCurrent(targetDefinition, game, "on choose target - player"); return true; } } @@ -2590,7 +2421,7 @@ public class TestPlayer implements Player { } } if (targetFound) { - targets.remove(targetDefinition); + targetsRemoveCurrent(targetDefinition, game, "on choose target - permanent"); return true; } } @@ -2618,7 +2449,7 @@ public class TestPlayer implements Player { } } if (targetFound) { - targets.remove(targetDefinition); + targetsRemoveCurrent(targetDefinition, game, "on choose target - hand"); return true; } } @@ -2637,7 +2468,7 @@ public class TestPlayer implements Player { } if (filter == null) { Assert.fail("Unsupported exile target filter in TestPlayer: " - + target.getOriginalTarget().getClass().getCanonicalName()); + + getSimpleClassName(target.getOriginalTarget().getClass())); } for (String targetDefinition : targets.stream().limit(takeMaxTargetsPerChoose).collect(Collectors.toList())) { @@ -2656,7 +2487,7 @@ public class TestPlayer implements Player { } } if (targetFound) { - targets.remove(targetDefinition); + targetsRemoveCurrent(targetDefinition, game, "on choose target - exile"); return true; } } @@ -2681,7 +2512,7 @@ public class TestPlayer implements Player { } } if (targetFound) { - targets.remove(targetDefinition); + targetsRemoveCurrent(targetDefinition, game, "on choose target - card in battlefield"); return true; } } @@ -2735,7 +2566,7 @@ public class TestPlayer implements Player { } } if (targetFound) { - targets.remove(targetDefinition); + targetsRemoveCurrent(targetDefinition, game, "on choose target - graveyard"); return true; } } @@ -2762,7 +2593,7 @@ public class TestPlayer implements Player { } } if (targetFound) { - targets.remove(targetDefinition); + targetsRemoveCurrent(targetDefinition, game, "on choose target - stack"); return true; } } @@ -2772,13 +2603,13 @@ public class TestPlayer implements Player { if (target.getOriginalTarget() instanceof TargetCardInLibrary || (target.getOriginalTarget() instanceof TargetCard && target.getOriginalTarget().getZone() == Zone.LIBRARY)) { // user don't have access to library, so it must be targeted through list/revealed cards - Assert.fail("Library zone is private, you must target through cards list, e.g. revealed: " + target.getOriginalTarget().getClass().getCanonicalName()); + Assert.fail("Library zone is private, you must target through cards list, e.g. revealed: " + getSimpleClassName(target.getOriginalTarget().getClass())); } // uninplemented TargetCard's zone if (target.getOriginalTarget() instanceof TargetCard && !targetCardZonesChecked.contains(target.getOriginalTarget().getZone())) { Assert.fail("Found unimplemented TargetCard's zone or TargetCard's extented class: " - + target.getOriginalTarget().getClass().getCanonicalName() + + getSimpleClassName(target.getOriginalTarget().getClass()) + ", zone " + target.getOriginalTarget().getZone() + ", from " + (source == null ? "unknown source" : source.getSourceObject(game))); } @@ -2793,14 +2624,14 @@ public class TestPlayer implements Player { if (source != null) { message = this.getName() + " - Targets list was setup by addTarget with " + targets + ", but not used" + "\nCard: " + source.getSourceObject(game) - + "\nAbility: " + source.getClass().getSimpleName() + " (" + source.getRule() + ")" - + "\nTarget: selected " + target.getSize() + ", possible " + possibleTargets.size() + ", " + target.getClass().getSimpleName() + " (" + target.getMessage(game) + ")" + + "\nAbility: " + getSimpleClassName(source.getClass()) + " (" + source.getRule() + ")" + + "\nTarget: selected " + target.getSize() + ", more possible " + possibleTargets.size() + ", " + getSimpleClassName(target.getClass()) + " (" + target.getMessage(game) + ")" + "\nYou must implement target class support in TestPlayer, \"filter instanceof\", or setup good targets"; } else { message = this.getName() + " - Targets list was setup by addTarget with " + targets + ", but not used" + "\nCard: unknown source" + "\nAbility: unknown source" - + "\nTarget: selected " + target.getSize() + ", possible " + possibleTargets.size() + ", " + target.getClass().getSimpleName() + " (" + target.getMessage(game) + ")" + + "\nTarget: selected " + target.getSize() + ", more possible " + possibleTargets.size() + ", " + getSimpleClassName(target.getClass()) + " (" + target.getMessage(game) + ")" + "\nYou must implement target class support in TestPlayer, \"filter instanceof\", or setup good targets"; } Assert.fail(message); @@ -2816,13 +2647,13 @@ public class TestPlayer implements Player { assertAliasSupportInTargets(false); if (!targets.isEmpty()) { + // skip targets - if (targets.get(0).equals(TARGET_SKIP)) { + if (tryToSkipSelection(game, target, targets, TARGET_SKIP)) { Assert.assertTrue("found skip target, but it require more targets, needs " + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); - targets.remove(0); - return false; // false - stop to choose + return target.getSize() > 0 && target.isChosen(game); } for (String targetDefinition : targets.stream().limit(takeMaxTargetsPerChoose).collect(Collectors.toList())) { String[] targetList = targetDefinition.split("\\^"); @@ -2839,7 +2670,7 @@ public class TestPlayer implements Player { } } if (targetFound) { - targets.remove(targetDefinition); + targetsRemoveCurrent(targetDefinition, game, "on choose target from cards"); return true; } } @@ -2861,7 +2692,7 @@ public class TestPlayer implements Player { for (TriggeredAbility ability : abilities) { if (ability.toString().startsWith(choice)) { - choices.remove(0); + choicesRemoveCurrent(game, "on choose triggers"); // TODO: add short lists? return ability; } } @@ -2890,11 +2721,11 @@ public class TestPlayer implements Player { String choice = choices.get(0); if (choice.equals("No")) { - choices.remove(0); + choicesRemoveCurrent(game, source); return false; } if (choice.equals("Yes")) { - choices.remove(0); + choicesRemoveCurrent(game, source); return true; } @@ -2921,7 +2752,7 @@ public class TestPlayer implements Player { if (choices.get(0).startsWith("X=")) { int xValue = Integer.parseInt(choices.get(0).substring(2)); assertXMinMaxValue(game, source, xValue, min, max); - choices.remove(0); + choicesRemoveCurrent(game, source); return xValue; } } @@ -2960,7 +2791,7 @@ public class TestPlayer implements Player { if (!choices.isEmpty()) { if (choices.get(0).startsWith("X=")) { int xValue = Integer.parseInt(choices.get(0).substring(2)); - choices.remove(0); + choicesRemoveCurrent(game, source); return xValue; } } @@ -2986,17 +2817,17 @@ public class TestPlayer implements Player { // must fill all possible choices or skip it for (int i = 0; i < messages.size(); i++) { if (!choices.isEmpty()) { + // skip + if (tryToSkipSelection(game, null, choices, CHOICE_SKIP)) { + break; + } + // normal choice if (choices.get(0).startsWith("X=")) { answer.set(i, Integer.parseInt(choices.get(0).substring(2))); - choices.remove(0); + choicesRemoveCurrent(game, "on multi amount"); // TODO: add info? continue; } - // skip - if (choices.get(0).equals(CHOICE_SKIP)) { - choices.remove(0); - break; - } } Assert.fail(String.format("Missing choice in multi amount: %s (pos %d - %s)", type.getHeader(), i, messages)); } @@ -3054,7 +2885,7 @@ public class TestPlayer implements Player { @Override public void restore(Player player) { if (!(player instanceof TestPlayer)) { - throw new IllegalArgumentException("Wrong code usage: can't restore from player class " + player.getClass().getName()); + throw new IllegalArgumentException("Wrong code usage: can't restore from player class " + getSimpleClassName(player.getClass())); } // no rollback for test player metadata (modesSet, actions, choices, targets, aliases, etc) @@ -3799,10 +3630,10 @@ public class TestPlayer implements Player { if (!choices.isEmpty()) { String nextResult = choices.get(0); if (nextResult.equals(FLIPCOIN_RESULT_TRUE)) { - choices.remove(0); + choicesRemoveCurrent(game, "on flip coin"); return true; } else if (nextResult.equals(FLIPCOIN_RESULT_FALSE)) { - choices.remove(0); + choicesRemoveCurrent(game, "on flip coin"); return false; } } @@ -3823,7 +3654,7 @@ public class TestPlayer implements Player { if (!choices.isEmpty()) { String nextResult = choices.get(0); if (nextResult.startsWith(DIE_ROLL)) { - choices.remove(0); + choicesRemoveCurrent(game, "on roll die"); return Integer.parseInt(nextResult.substring(DIE_ROLL.length())); } } @@ -3929,13 +3760,13 @@ public class TestPlayer implements Player { } @Override - public PayLifeCostLevel getPayLifeCostLevel() { - return computerPlayer.getPayLifeCostLevel(); + public EnumSet getPayLifeCostRestrictions() { + return computerPlayer.getPayLifeCostRestrictions(); } @Override - public void setPayLifeCostLevel(PayLifeCostLevel payLifeCostLevel) { - computerPlayer.setPayLifeCostLevel(payLifeCostLevel); + public void addPayLifeCostRestriction(PayLifeCostRestriction payLifeCostRestriction) { + computerPlayer.addPayLifeCostRestriction(payLifeCostRestriction); } @Override @@ -3943,16 +3774,6 @@ public class TestPlayer implements Player { return computerPlayer.canPaySacrificeCost(permanent, source, controllerId, game); } - @Override - public FilterPermanent getSacrificeCostFilter() { - return computerPlayer.getSacrificeCostFilter(); - } - - @Override - public void setCanPaySacrificeCostFilter(FilterPermanent permanent) { - computerPlayer.setCanPaySacrificeCostFilter(permanent); - } - @Override public boolean canLoseByZeroOrLessLife() { return computerPlayer.canLoseByZeroOrLessLife(); @@ -4266,62 +4087,143 @@ public class TestPlayer implements Player { return choose(outcome, target, source, game, null); } - private boolean tryToSkipSelection(List selections, String selectionMark) { - if (!selections.isEmpty() && selections.get(0).equals(selectionMark)) { + public void skipAllNextChooseCommands() { + this.isSkipAllNextChooseCommands = true; + } + + public boolean isSkipAllNextChooseCommands() { + return this.isSkipAllNextChooseCommands; + } + + private boolean tryToSkipSelection(Game game, Target target, List selections, String skipCommand) { + if (!selections.isEmpty() && selections.get(0).equals(skipCommand)) { + + // mark target as skip, so choose dialog can be stopped on partly selection + if (target != null) { + if (target.getMaxNumberOfTargets() > target.getMinNumberOfTargets()) { + target.setSkipChoice(true); + } else { + Assert.fail("Wrong skip command found - it can be used with up to targets, but used in " + target); + } + } + selections.remove(0); + DataCollectorServices.getInstance().onTestsChoiceUse(game, this, skipCommand, "by skip command"); + return true; } return false; } - @Override - public boolean choose(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { - assertAliasSupportInChoices(false); - if (!choices.isEmpty()) { + private boolean makeChoose(Outcome outcome, Target target, Ability source, Game game, Cards fromCards) { + assertAliasSupportInChoices(true); + // choose itself for starting player all the time + if (target.getMessage(game).equals("Select a starting player")) { + target.add(this.getId(), game); + return true; + } + + + UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId()); + + PossibleTargetsSelector possibleTargetsSelector = new PossibleTargetsSelector(outcome, target, abilityControllerId, source, game); + possibleTargetsSelector.findNewTargets(fromCards); + + // nothing to choose, e.g. no valid targets + if (!possibleTargetsSelector.hasAnyTargets()) { + return false; + } + + // can't choose + if (!possibleTargetsSelector.hasMinNumberOfTargets()) { + return false; + } + + while (!choices.isEmpty()) { // skip choices - if (tryToSkipSelection(choices, CHOICE_SKIP)) { - if (cards.isEmpty()) { - // cancel button forced in GUI on no possible choices - // TODO: need research - return false; - } else { - Assert.assertTrue("found skip choice, but it require more choices, needs " - + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", - target.getTargets().size() >= target.getMinNumberOfTargets()); - return false; // stop dialog - } + if (tryToSkipSelection(game, target, choices, CHOICE_SKIP)) { + Assert.assertTrue("found skip choice, but it require more choices, needs " + + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", + target.getTargets().size() >= target.getMinNumberOfTargets()); + return target.getSize() > 0 && target.isChosen(game); } - for (String choose2 : new ArrayList<>(choices)) { - // TODO: More targetting to fix - String[] targetList = choose2.split("\\^"); - boolean targetFound = false; - for (String targetName : targetList) { - for (Card card : cards.getCards(game)) { - if (target.contains(card.getId())) { - continue; - } - if (hasObjectTargetNameOrAlias(card, targetName)) { - if (target.isNotTarget() || target.canTarget(card.getId(), source, game)) { - target.add(card.getId(), game); - targetFound = true; + String choiceRecord = choices.get(0); + String[] targetList = choiceRecord.split("\\^"); + boolean isAddedSomething = false; + for (String targetName : targetList) { + boolean originOnly = false; + boolean copyOnly = false; + if (targetName.endsWith("]")) { + if (targetName.endsWith("[no copy]")) { + originOnly = true; + targetName = targetName.substring(0, targetName.length() - 9); + } + if (targetName.endsWith("[only copy]")) { + copyOnly = true; + targetName = targetName.substring(0, targetName.length() - 11); + } + } + + for (MageItem item : possibleTargetsSelector.getAny()) { + if (target.contains(item.getId())) { + continue; + } + if (item instanceof MageObject) { + MageObject object = (MageObject) item; + if (hasObjectTargetNameOrAlias(object, targetName)) { + if ((object.isCopy() && !originOnly) || (!object.isCopy() && !copyOnly)) { + target.add(object.getId(), game); + isAddedSomething = true; + break; + } + } else if ((object.getName() + '-' + object.getExpansionSetCode()).equals(targetName)) { // TODO: remove search by exp code? + if ((object.isCopy() && !originOnly) || (!object.isCopy() && !copyOnly)) { + target.add(object.getId(), game); + isAddedSomething = true; break; } } + } else if (item instanceof Player) { + Player player = (Player) item; + if (player.getName().equals(choiceRecord)) { + target.add(player.getId(), game); + isAddedSomething = true; + break; + } + } else { + throw new IllegalArgumentException("Unknown possible target type: " + getSimpleClassName(item.getClass())); } } - if (targetFound) { - choices.remove(choose2); - return true; - } } - assertWrongChoiceUsage(choices.size() > 0 ? choices.get(0) : "empty list"); + try { + if (isAddedSomething) { + if (target.isChoiceCompleted(abilityControllerId, source, game, fromCards)) { + return true; + } + // must refresh possible targets after each "add" cause there are dynamic targets like TargetCardAndOrCard from Sludge Titan + possibleTargetsSelector.findNewTargets(fromCards); + } else { + failOnLastBadChoice(game, source, target, choiceRecord, "invalid target or miss skip command"); + } + } finally { + choicesRemoveCurrent(game, source); + } } - this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, source, game, cards)); - return computerPlayer.choose(outcome, cards, target, source, game); + this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, source, game, fromCards)); + if (fromCards != null) { + return computerPlayer.choose(outcome, fromCards, (TargetCard) target, source, game); + } else { + return computerPlayer.choose(outcome, target, source, game); + } + } + + @Override + public boolean choose(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { + return makeChoose(outcome, target, source, game, cards); } @Override @@ -4347,12 +4249,11 @@ public class TestPlayer implements Player { while (!targets.isEmpty()) { // skip targets - if (targets.get(0).equals(TARGET_SKIP)) { + if (tryToSkipSelection(game, target, targets, TARGET_SKIP)) { Assert.assertTrue("found skip target, but it require more targets, needs " + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); - targets.remove(0); - return false; // false - stop to choose + return target.getSize() > 0 && target.isChosen(game); } // only target amount needs @@ -4376,7 +4277,7 @@ public class TestPlayer implements Player { Assert.assertTrue("target amount must be <= remaining = " + target.getAmountRemaining() + " " + targetInfo, targetAmount <= target.getAmountRemaining()); if (target.getAmountRemaining() > 0) { - for (UUID possibleTarget : target.possibleTargets(source.getControllerId(), source, game)) { + for (UUID possibleTarget : target.possibleTargets(abilityControllerId, source, game)) { boolean foundTarget = false; // permanent @@ -4392,10 +4293,10 @@ public class TestPlayer implements Player { } if (foundTarget) { - if (!target.contains(possibleTarget) && target.canTarget(possibleTarget, source, game)) { + if (!target.contains(possibleTarget) && target.canTarget(abilityControllerId, possibleTarget, source, game)) { // can select target.addTarget(possibleTarget, targetAmount, source, game); - targets.remove(0); + targetsRemoveCurrent(null, game, "on choose target amount"); // allow test player to choose as much as possible until skip command if (target.getAmountRemaining() <= 0) { return true; @@ -4437,7 +4338,7 @@ public class TestPlayer implements Player { // simulate cancel on mana payment (e.g. user press on cancel button) if (choices.get(0).equals(MANA_CANCEL)) { - choices.remove(0); + choicesRemoveCurrent(game, "on play mana"); // TODO: add info? return false; } @@ -4487,7 +4388,7 @@ public class TestPlayer implements Player { for (SpecialAction specialAction : specialActions.values()) { if (specialAction.getRule(true).startsWith(choice)) { if (specialAction.canActivate(this.getId(), game).canActivate()) { - choices.remove(0); + choicesRemoveCurrent(game, "on play mana"); // TODO: add info? choiceRemoved = true; if (activateAbility(specialAction, game)) { choiceUsed = true; @@ -4501,7 +4402,7 @@ public class TestPlayer implements Player { if (choiceUsed) { if (!choiceRemoved) { - choices.remove(0); + choicesRemoveCurrent(game, "on play mana"); // TODO: add info? } return true; } else { @@ -4682,7 +4583,7 @@ public class TestPlayer implements Player { String choice = choices.get(0); for (SpellAbility ability : useable.values()) { if (ability.toString().startsWith(choice)) { - choices.remove(0); + choicesRemoveCurrent(game, "on choose ability for cast - " + card.getName()); return ability; } } @@ -4714,7 +4615,7 @@ public class TestPlayer implements Player { if (!choices.isEmpty()) { for (ActivatedAbility ability : useable.values()) { if (ability.toString().startsWith(choices.get(0))) { - choices.remove(0); + choicesRemoveCurrent(game, "on choose land or spell ability - " + card.getName()); return ability; } } @@ -4762,13 +4663,52 @@ public class TestPlayer implements Player { } private void assertWrongChoiceUsage(String choice) { - // TODO: enable fail checks and fix tests, it's a part of setStrictChooseMode's implementation to all tests - //Assert.fail("Wrong choice command: " + choice); - LOGGER.warn("Wrong choice command: " + choice); + // TODO: enable and fix all failed tests + assertWrongChoiceUsage(choice, false); + } + + private void assertWrongChoiceUsage(String choice, boolean isRaiseError) { + if (isRaiseError) { + Assert.fail("Wrong choice command: " + choice); + } else { + LOGGER.warn("Wrong choice command: " + choice); + } } @Override public Player getRealPlayer() { return this.computerPlayer; } + + private void choicesRemoveCurrent(Game game, Ability source) { + choicesRemoveCurrent(game, CardUtil.getSourceLogName(game, "ability: ", source, "", "")); + } + + private void choicesRemoveCurrent(Game game, String reason) { + String realReason = reason.isEmpty() ? "SBA" : reason; + DataCollectorServices.getInstance().onTestsChoiceUse(game, this, choices.get(0), "by setChoice, " + realReason); + choices.remove(0); + } + + // TODO: implement after takeMaxTargetsPerChoose fix/remove (targets must use while logic all the time - same as choices) + // TODO: make same logs for AI choices + private void targetsRemoveCurrent(String targetToRemove, Game game, String reason) { + String realReason = reason.isEmpty() ? "SBA" : reason; + DataCollectorServices.getInstance().onTestsTargetUse(game, this, targets.get(0), "by addTarget, " + realReason); + if (targetToRemove == null) { + targets.remove(0); + } else { + targets.remove(targetToRemove); // TODO: must be targets.remove(0), see todos above + } + } + + private void modesRemoveCurrent(Game game, Mode mode) { + DataCollectorServices.getInstance().onTestsChoiceUse(game, this, modes.get(0), "by setMode, mode: " + mode.getEffects().getText(mode)); + modes.remove(0); + } + + private String getSimpleClassName(Class clazz) { + // anon classes don't have names, so return name instead simple + return clazz.getSimpleName().isEmpty() ? clazz.getName() : clazz.getSimpleName(); + } } 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 e8b225e759e..bb1b5f5da39 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 @@ -1706,6 +1706,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement private void assertAllCommandsUsed() throws AssertionError { for (Player player : currentGame.getPlayers().values()) { TestPlayer testPlayer = (TestPlayer) player; + + if (testPlayer.isSkipAllNextChooseCommands()) { + Assert.fail(testPlayer.getName() + " used skip next choose commands, but game do not call any choose dialog after it. Skip must be removed after debug."); + } + assertActionsMustBeEmpty(testPlayer); assertChoicesCount(testPlayer, 0); assertTargetsCount(testPlayer, 0); @@ -2008,7 +2013,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param step * @param player * @param cardName - * @param targetName for modes you can add "mode=3" before target name; + * @param targetName for non default mode you can add target by "mode=3target_name" style; * multiple targets can be separated by ^; * no target marks as TestPlayer.NO_TARGET; * warning, do not support cards with target adjusters - use addTarget instead @@ -2315,6 +2320,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * spell mode can be used only once like Demonic Pact, the * value has to be set to the number of the remaining modes * (e.g. if only 2 are left the number need to be 1 or 2). + * If you need to partly select then use TestPlayer.MODE_SKIP */ public void setModeChoice(TestPlayer player, String choice) { player.addModeChoice(choice); @@ -2439,6 +2445,26 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement gameOptions.skipInitShuffling = true; } + /** + * Debug only: skip all choose commands after that command. + *

+ * Alternative to comment/uncomment all test commands: + * - insert skip before first choice command; + * - run test and look at error message about miss choice; + * - make sure test use correct choice; + * - move skip command to next test's choice and repeat; + */ + protected void skipAllNextChooseCommands() { + playerA.skipAllNextChooseCommands(); + playerB.skipAllNextChooseCommands(); + if (playerC != null) { + playerC.skipAllNextChooseCommands(); + } + if (playerD != null) { + playerD.skipAllNextChooseCommands(); + } + } + public void assertDamageReceived(Player player, String cardName, int expected) { Permanent p = getPermanent(cardName, player); if (p != null) { diff --git a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java index 2adafa6e7d0..b257c3ef6b0 100644 --- a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java +++ b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java @@ -43,6 +43,8 @@ public final class MtgJsonCard { public String frameVersion; public List printings; // set codes with that card public boolean isFunny; + public List promoTypes; + public boolean isTextless; @Override public String toString() { diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 2bd11e04d93..18485b9f8c6 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -5,10 +5,7 @@ import com.google.gson.Gson; import mage.MageObject; import mage.Mana; import mage.ObjectColor; -import mage.abilities.Ability; -import mage.abilities.AbilityImpl; -import mage.abilities.Mode; -import mage.abilities.TriggeredAbility; +import mage.abilities.*; import mage.abilities.common.*; import mage.abilities.condition.Condition; import mage.abilities.costs.Cost; @@ -186,6 +183,7 @@ public class VerifyCardDataTest { // rarity // skipListAddName(SKIP_LIST_RARITY, set, cardName); skipListAddName(SKIP_LIST_RARITY, "CMR", "The Prismatic Piper"); // Collation is not yet set up for CMR https://www.lethe.xyz/mtg/collation/cmr.html + skipListAddName(SKIP_LIST_RARITY, "SPM", "Gwenom, Remorseless"); // temporary // missing abilities // skipListAddName(SKIP_LIST_MISSING_ABILITIES, set, cardName); @@ -700,32 +698,6 @@ public class VerifyCardDataTest { // String code = MtgJsonService.xMageToMtgJsonCodes.getOrDefault(set.getCode(), set.getCode()) + " - " + jsonCard.getNameAsFull() + " - " + jsonCard.number; // foundedJsonCards.add(code); // -// // CHECK: only lands can use full art in current version; -// // Another cards must be in text render mode as normal, example: https://scryfall.com/card/sld/76/athreos-god-of-passage -// // TODO: add support textless cards like https://scryfall.com/card/sch/12/thalia-and-the-gitrog-monster -// boolean isLand = card.getRarity().equals(Rarity.LAND); -// if (card.isFullArt() && !isLand) { -// errorsList.add("Error: only lands can use full art setting: " -// + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); -// } -// -// // CHECK: must use full art setting -// if (jsonCard.isFullArt && isLand && !card.isFullArt()) { -// errorsList.add("Error: card must use full art setting: " -// + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); -// } -// -// // CHECK: must not use full art setting -// if (!jsonCard.isFullArt && card.isFullArt()) { -// errorsList.add("Error: card must NOT use full art setting: " -// + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); -// } - - // CHECK: must use retro frame setting - if ((jsonCard.frameVersion.equals("1993") || jsonCard.frameVersion.equals("1997")) && !card.isRetroFrame()) { - errorsList.add("Error: card must use retro art setting: " - + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); - } } } @@ -769,6 +741,63 @@ public class VerifyCardDataTest { } } + @Test + @Ignore + public void test_checkWrongFullArtAndRetro() { + Collection errorsList = new ArrayList<>(); + Collection xmageSets = Sets.getInstance().values(); + + // CHECK: wrong card numbers + for (ExpansionSet set : xmageSets) { + if (skipListHaveName(SKIP_LIST_WRONG_CARD_NUMBERS, set.getCode())) { + continue; + } + + for (ExpansionSet.SetCardInfo card : set.getSetCardInfo()) { + MtgJsonCard jsonCard = MtgJsonService.cardFromSet(set.getCode(), card.getName(), card.getCardNumber()); + if (jsonCard == null) { + continue; + } + + // CHECK: poster promoType and/or textless must use full art setting + if (((jsonCard.promoTypes != null && jsonCard.promoTypes.contains("poster")) || jsonCard.isTextless) && !card.isFullArt()) { + errorsList.add("Error: card must use full art setting: " + + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + } + + // CHECK: full art lands must use full art setting + boolean isLand = card.getRarity().equals(Rarity.LAND); + if (isLand && jsonCard.isFullArt && !card.isFullArt()) { + errorsList.add("Error: card must use full art lands setting: " + + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + } + + // CHECK: non-full art lands must not use full art setting + if (isLand && !jsonCard.isFullArt && card.isFullArt()) { + errorsList.add("Error: card must NOT use full art lands setting: " + + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + } + + // CHECK: must use retro frame setting + if ((jsonCard.frameVersion.equals("1993") || jsonCard.frameVersion.equals("1997")) && !card.isRetroFrame()) { + errorsList.add("Error: card must use retro art setting: " + + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + } + + // CHECK: must not use retro frame setting + if ((!(jsonCard.frameVersion.equals("1993") || jsonCard.frameVersion.equals("1997"))) && card.isRetroFrame()) { + errorsList.add("Error: card must NOT use retro art setting: " + + set.getCode() + " - " + set.getName() + " - " + card.getName() + " - " + card.getCardNumber()); + } + } + } + + printMessages(errorsList); + if (errorsList.size() > 0) { + Assert.fail("Found wrong cards data in sets, errors: " + errorsList.size()); + } + } + @Test @Ignore // TODO: enable after all missing cards and settings fixes public void test_checkMissingScryfallSettingsAndCardNumbers() { @@ -1124,6 +1153,7 @@ public class VerifyCardDataTest { Set implementedSets = sets.stream().map(ExpansionSet::getCode).collect(Collectors.toSet()); MtgJsonService.sets().values().forEach(jsonSet -> { if (jsonSet.booster != null && !jsonSet.booster.isEmpty() && !implementedSets.contains(jsonSet.code)) { + // how-to fix: it's miss promo sets with boosters, so just add/generate it in most use cases errorsList.add(String.format("Error: missing set implementation (important for draft format) - %s - %s - boosters: %s", jsonSet.code, jsonSet.name, @@ -2152,6 +2182,73 @@ public class VerifyCardDataTest { } } + // There are many cards that use the word "target" or "targets" in reference to spells/abilities that target rather than actually themselves having a target. + // Examples include Wall of Shadows, Psychic Battle, Coalition Honor Guard, Aboleth Spawn, Akroan Crusader, Grip of Chaos, and many more + Pattern singularTargetRegexPattern = Pattern.compile("\\b(? recursiveTargetAbilityCheck(ability, depth - 1)); + } + if (obj instanceof Collection) { + return ((Collection) obj).stream().anyMatch(x -> recursiveTargetObjectCheck(x, depth - 1)); + } + return false; + } + + boolean recursiveTargetEffectCheck(Effect effect, int depth) { + if (depth < 0) { + return false; + } + return Arrays.stream(effect.getClass().getDeclaredFields()) + .anyMatch(f -> { + f.setAccessible(true); + try { + return recursiveTargetObjectCheck(f.get(effect), depth); // Intentionally not decreasing depth here + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); // Should never happen due to setAccessible + } + }); + } + + boolean recursiveTargetAbilityCheck(Ability ability, int depth) { + if (depth < 0) { + return false; + } + Collection modes = ability.getModes().values(); + return modes.stream().flatMap(mode -> mode.getTargets().stream()).anyMatch(target -> !target.isNotTarget()) + || ability.getTargetAdjuster() != null + || modes.stream().flatMap(mode -> mode.getEffects().stream()).anyMatch(effect -> recursiveTargetEffectCheck(effect, depth - 1)); + } + private void checkMissingAbilities(Card card, MtgJsonCard ref) { if (skipListHaveName(SKIP_LIST_MISSING_ABILITIES, card.getExpansionSetCode(), card.getName())) { return; @@ -2279,7 +2376,11 @@ public class VerifyCardDataTest { // search and check dies related abilities - String rules = triggeredAbility.getRule(); + // remove reminder text + String rules = triggeredAbility + .getRule() + .replaceAll("(?i) \\(.+\\)", "") + .replaceAll("(?i) \\(.+\\)", ""); if (ignoredAbilities.stream().anyMatch(rules::contains)) { continue; } @@ -2315,57 +2416,51 @@ public class VerifyCardDataTest { } } + // special check: wrong targeted ability - // possible fixes: - // * on "must set withNotTarget(true)": - // - check card's ability constructors and fix missing withNotTarget(true) param/field - // - it's can be a keyword action (only mtg rules contains a target word), so add it to the targetedKeywords - // * on "must be targeted": - // - TODO: enable and research checkMissTargeted - too much errors with it (is it possible to use that checks?) - boolean checkMissNonTargeted = true; // must set withNotTarget(true) - boolean checkMissTargeted = false; // must be targeted - List targetedKeywords = Arrays.asList( - "target", - "enchant", - "equip", - "backup", - "modular", - "partner" - ); - // xmage card can contain rules text from both sides, so must search ref card for all sides too - String additionalName; - if (card instanceof CardWithSpellOption) { - // adventure/omen cards - additionalName = ((CardWithSpellOption) card).getSpellCard().getName(); - } else if (card.isTransformable() && !card.isNightCard()) { - additionalName = card.getSecondCardFace().getName(); - } else { - additionalName = null; - } - if (additionalName != null) { - MtgJsonCard additionalRef = MtgJsonService.cardFromSet(card.getExpansionSetCode(), additionalName, card.getCardNumber()); - if (additionalRef == null) { - // how-to fix: add new card type processing for an additionalName searching above - fail(card, "abilities", "can't find second side info for target check"); - } else { - if (additionalRef.text != null && !additionalRef.text.isEmpty()) { - refLowerText += "\r\n" + additionalRef.text.toLowerCase(Locale.ENGLISH); + // Checks that no ability targets use withNotTarget (use OneShotNonTargetEffect if it's a choose effect) + // Checks that, if the text contains the word target, the ability does have a target. + // - In cases involving a target in a reflexive trigger or token or other complex situation, it assumes that it's fine + // - There are two versions of this complexity check, either can trigger: one on card text, one that uses Java reflection to inspect the ability's effects. + String[] excludedCards = {"Lodestone Bauble", // Needs to choose a player before targets are selected + "Blink", // Current XMage code does not correctly support non-consecutive chapter effects, duplicates effects as a workaround + "Artifact Ward"}; // This card is just implemented wrong, but would need significant work to fix + if (Arrays.stream(excludedCards).noneMatch(x -> x.equals(ref.name))) { + for (Ability ability : card.getAbilities()) { + boolean foundNotTarget = ability.getModes().values().stream() + .flatMap(mode -> mode.getTargets().stream()).anyMatch(Target::isNotTarget); + if (foundNotTarget) { + fail(card, "abilities", "notTarget should not be used as ability target, should be inside ability effect"); + } + String abilityText = ability.getRule().toLowerCase(Locale.ENGLISH); + boolean needTargetedAbility = singularTargetRegexPattern.matcher(abilityText).find() || pluralTargetsRegexPattern.matcher(abilityText).find() || targetKeywordRegexPattern.matcher(abilityText).find(); + boolean recursiveAbilityText = indirectTriggerTargetRegexPattern.matcher(abilityText).find() || quotedTargetRegexPattern.matcher(abilityText).find(); + + boolean foundTargetedAbility = recursiveTargetAbilityCheck(ability, 0); + boolean recursiveAbility = recursiveTargetAbilityCheck(ability, 4); + + if (needTargetedAbility && !(foundTargetedAbility || recursiveAbilityText || recursiveAbility) + && card.getAbilities().stream().noneMatch(x -> x instanceof LevelUpAbility)) { // Targeting Level Up abilities' text is put in the power-toughness setting effect + fail(card, "abilities", "wrong target settings (must be targeted, but is not):" + ability.getClass().getSimpleName()); + } + if (!needTargetedAbility && foundTargetedAbility + && !(ability instanceof SpellAbility && abilityText.equals("") && card.getSubtype().contains(SubType.AURA)) // Auras' SpellAbility targets, not the EnchantAbility + && !(ability instanceof SpellAbility && (recursiveTargetAbilityCheck(card.getSpellAbility(), 0))) // SurgeAbility is a modified copy of the main SpellAbility, so it targets + && !(ability instanceof SpellTransformedAbility)) { // DisturbAbility targets if the backside aura targets + fail(card, "abilities", "wrong target settings (targeted ability found but no target in text):" + ability.getClass().getSimpleName()); } } - } - - boolean needTargetedAbility = targetedKeywords.stream().anyMatch(refLowerText::contains); - boolean foundTargetedAbility = card.getAbilities() - .stream() - .map(Ability::getTargets) - .flatMap(Collection::stream) - .anyMatch(target -> !target.isNotTarget()); - boolean foundProblem = needTargetedAbility != foundTargetedAbility; - if (checkMissTargeted && needTargetedAbility && foundProblem) { - fail(card, "abilities", "wrong target settings (must be targeted, but it not)"); - } - if (checkMissNonTargeted && !needTargetedAbility && foundProblem) { - fail(card, "abilities", "wrong target settings (must set withNotTarget(true), but it not)"); + // Also check that the reference text and the final ability text have the same number of "target" + String preparedRefText = refLowerText.replaceAll("\\([^)]+\\)", ""); // Remove reminder text + int refTargetCount = (preparedRefText.length() - preparedRefText.replace("target", "").length()); + String preparedRuleText = cardLowerText.replaceAll("\\([^)]+\\)", ""); + if (!ref.subtypes.contains("Adventure") && !ref.subtypes.contains("Omen")) { + preparedRuleText = preparedRuleText.replaceAll("^(adventure|omen).*", ""); + } + int cardTargetCount = (preparedRuleText.length() - preparedRuleText.replace("target", "").length()); + if (refTargetCount != cardTargetCount) { + fail(card, "abilities", "target count text discrepancy: " + (refTargetCount / 6) + " in reference but " + (cardTargetCount / 6) + " in card."); + } } // special check: missing or wrong ability/effect rules hint @@ -3064,6 +3159,11 @@ public class VerifyCardDataTest { return; } + // Spacecraft are ignored as they shouldn't have a printed power/toughness but they do in the data + if (ref.subtypes.contains("Spacecraft")) { + return; + } + if (!eqPT(card.getPower().toString(), ref.power) || !eqPT(card.getToughness().toString(), ref.toughness)) { String pt = card.getPower() + "/" + card.getToughness(); String expected = ref.power + '/' + ref.toughness; diff --git a/Mage/src/main/java/mage/MageIdentifier.java b/Mage/src/main/java/mage/MageIdentifier.java index 3cca48c6e40..db65ff23de3 100644 --- a/Mage/src/main/java/mage/MageIdentifier.java +++ b/Mage/src/main/java/mage/MageIdentifier.java @@ -85,7 +85,10 @@ public enum MageIdentifier { WickerfolkIndomitableAlternateCast, UriangerAugureltAlternateCast, ValgavothTerrorEaterAlternateCast, - LightstallInquisitorAlternateCast; + LightstallInquisitorAlternateCast, + UndeadSprinterAlternateCast, + GwenomRemorselessAlternateCast, + AlienSymbiosisAlternateCast; /** * Additional text if there is need to differentiate two very similar effects diff --git a/Mage/src/main/java/mage/MageObjectReference.java b/Mage/src/main/java/mage/MageObjectReference.java index dd18a605c13..6fe3eff9337 100644 --- a/Mage/src/main/java/mage/MageObjectReference.java +++ b/Mage/src/main/java/mage/MageObjectReference.java @@ -65,7 +65,7 @@ public class MageObjectReference implements Comparable, Ser @Deprecated // cause of many bugs, see issue #10479 public MageObjectReference(Ability source, int modifier) { this.sourceId = source.getSourceId(); - this.zoneChangeCounter = source.getSourceObjectZoneChangeCounter() + modifier; + this.zoneChangeCounter = source.getStackMomentSourceZCC() + modifier; } /** @@ -162,8 +162,8 @@ public class MageObjectReference implements Comparable, Ser if (source == null || !source.getSourceId().equals(sourceId)) { return false; } - return zoneChangeCounter * source.getSourceObjectZoneChangeCounter() == 0 - || zoneChangeCounter == source.getSourceObjectZoneChangeCounter(); + return zoneChangeCounter * source.getStackMomentSourceZCC() == 0 + || zoneChangeCounter == source.getStackMomentSourceZCC(); } public Permanent getPermanent(Game game) { diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index a834cf8be68..c05feea8548 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -516,11 +516,12 @@ public interface Ability extends Controllable, Serializable { */ void initSourceObjectZoneChangeCounter(Game game, boolean force); - // TODO: it's activating time of ability, not current object's zcc, see #13737, - // in most use cases you must use game.getState().getZoneChangeCounter or input.getObject().getZoneChangeCounter(game) - // only ability related logic can use it (example: delayed triggers) - @Deprecated - int getSourceObjectZoneChangeCounter(); + /** + * Returns the internally stored Source Object ZCC value, which is set at the time this ability was put on the stack. + * For static abilities or trigger conditions, you probably want to use + * game.getState().getZoneChangeCounter or input.getObject().getZoneChangeCounter(game) instead + */ + int getStackMomentSourceZCC(); /** * Finds the source object (Permanent, StackObject, Card, etc.) as long as its zcc has not changed, otherwise null diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 50340177526..afe43785d56 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1227,7 +1227,7 @@ public abstract class AbilityImpl implements Ability { boolean validTargets = true; for (Target target : mode.getTargets()) { UUID abilityControllerId = target.getAffectedAbilityControllerId(controllerId); - if (!target.canChoose(abilityControllerId, ability, game)) { + if (!target.canChooseOrAlreadyChosen(abilityControllerId, ability, game)) { validTargets = false; break; } @@ -1658,8 +1658,8 @@ public abstract class AbilityImpl implements Ability { @Override public MageObject getSourceObjectIfItStillExists(Game game) { - if (getSourceObjectZoneChangeCounter() == 0 - || getSourceObjectZoneChangeCounter() == getCurrentSourceObjectZoneChangeCounter(game)) { + if (getStackMomentSourceZCC() == 0 + || getStackMomentSourceZCC() == getCurrentSourceObjectZoneChangeCounter(game)) { // exists or lki from battlefield return game.getObject(getSourceId()); } @@ -1688,7 +1688,7 @@ public abstract class AbilityImpl implements Ability { public Permanent getSourcePermanentOrLKI(Game game) { Permanent permanent = getSourcePermanentIfItStillExists(game); if (permanent == null) { - permanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, getSourceObjectZoneChangeCounter()); + permanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, getStackMomentSourceZCC()); } return permanent; } @@ -1720,7 +1720,7 @@ public abstract class AbilityImpl implements Ability { } @Override - public int getSourceObjectZoneChangeCounter() { + public int getStackMomentSourceZCC() { return sourceObjectZoneChangeCounter; } diff --git a/Mage/src/main/java/mage/abilities/Mode.java b/Mage/src/main/java/mage/abilities/Mode.java index 0cc7e2b1add..20b9bb1c2e5 100644 --- a/Mage/src/main/java/mage/abilities/Mode.java +++ b/Mage/src/main/java/mage/abilities/Mode.java @@ -130,4 +130,9 @@ public class Mode implements Serializable { public int getPawPrintValue() { return pawPrintValue; } + + @Override + public String toString() { + return String.format("%s", this.getEffects().getText(this)); + } } diff --git a/Mage/src/main/java/mage/abilities/SpecialAction.java b/Mage/src/main/java/mage/abilities/SpecialAction.java index ea17f671cc5..911e074c6e5 100644 --- a/Mage/src/main/java/mage/abilities/SpecialAction.java +++ b/Mage/src/main/java/mage/abilities/SpecialAction.java @@ -53,11 +53,9 @@ public abstract class SpecialAction extends ActivatedAbilityImpl { if (isManaAction()) { // limit play mana abilities by steps int currentStepOrder = 0; - if (!game.getStack().isEmpty()) { - StackObject stackObject = game.getStack().getFirst(); - if (stackObject instanceof Spell) { - currentStepOrder = ((Spell) stackObject).getCurrentActivatingManaAbilitiesStep().getStepOrder(); - } + StackObject stackObject = game.getStack().getFirstOrNull(); + if (stackObject instanceof Spell) { + currentStepOrder = ((Spell) stackObject).getCurrentActivatingManaAbilitiesStep().getStepOrder(); } if (currentStepOrder > manaAbility.useOnActivationManaAbilityStep().getStepOrder()) { return ActivationStatus.getFalse(); diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java index bb683bce3ea..1e745ea6294 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java @@ -241,7 +241,7 @@ public class TriggeredAbilities extends LinkedHashMap && game.getLKI().get(Zone.BATTLEFIELD) != null && game.getLKI().get(Zone.BATTLEFIELD).containsKey(ability.getSourceId())) { // need to check if object was face down for dies and destroy events because the ability triggers in the new zone, zone counter -1 is used - Permanent permanent = (Permanent) game.getLastKnownInformation(ability.getSourceId(), Zone.BATTLEFIELD, ability.getSourceObjectZoneChangeCounter() - 1); + Permanent permanent = (Permanent) game.getLastKnownInformation(ability.getSourceId(), Zone.BATTLEFIELD, ability.getStackMomentSourceZCC() - 1); if (permanent != null) { if (permanent.isFaceDown(game) && !isGainedAbility(ability, permanent) // the face down creature got the ability from an effect => so it should work diff --git a/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java b/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java index fc11c56c0e7..1ab07eda011 100644 --- a/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java @@ -1,33 +1,43 @@ package mage.abilities.common; -import mage.abilities.Ability; import mage.abilities.effects.common.InfoEffect; +import mage.constants.TargetController; import mage.constants.Zone; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; import mage.game.Game; import mage.target.Target; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; /** - * * @author LevelX2 */ public class AttachableToRestrictedAbility extends SimpleStaticAbility { + private final Target attachable; + public AttachableToRestrictedAbility(Target target) { super(Zone.BATTLEFIELD, new InfoEffect("{this} can be attached only to a " + target.getTargetName())); this.attachable = target.copy(); + + // verify check: make sure filter don't have controller predicate cause it used in code without controller info + List list = new ArrayList<>(); + Predicates.collectAllComponents(target.getFilter().getPredicates(), target.getFilter().getExtraPredicates(), list); + if (list.stream().anyMatch(TargetController.ControllerPredicate.class::isInstance)) { + throw new IllegalArgumentException("Wrong code usage: attachable restriction filter must not contain controller predicate"); + } } private AttachableToRestrictedAbility(AttachableToRestrictedAbility ability) { super(ability); - this.attachable = ability.attachable; // Since we never modify the target, we don't need to re-copy it + this.attachable = ability.attachable.copy(); } - public boolean canEquip(UUID toEquip, Ability source, Game game) { - if (source == null) { - return attachable.canTarget(toEquip, game); - } else return attachable.canTarget(toEquip, source, game); + public boolean canEquip(UUID toEquip, Game game) { + return attachable.canTarget(toEquip, null, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/common/CantPayLifeOrSacrificeAbility.java b/Mage/src/main/java/mage/abilities/common/CantPayLifeOrSacrificeAbility.java new file mode 100644 index 00000000000..23e4db0cb93 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/CantPayLifeOrSacrificeAbility.java @@ -0,0 +1,151 @@ +package mage.abilities.common; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.Optional; +import java.util.UUID; + +/** + * Effect used to prevent paying life and, optionally, sacrificing permanents as a cost for activated abilities and casting spells. + * @author Jmlundeen + */ +public class CantPayLifeOrSacrificeAbility extends SimpleStaticAbility { + + private final String rule; + + public CantPayLifeOrSacrificeAbility(FilterPermanent sacrificeFilter) { + this(false, sacrificeFilter); + } + + /** + * @param onlyNonManaAbilities boolean to set if the restriction should only apply to non-mana abilities + * @param sacrificeFilter filter for types of permanents that cannot be sacrificed, can be null if sacrifice not needed. + * e.g. Karn's Sylex + */ + public CantPayLifeOrSacrificeAbility(boolean onlyNonManaAbilities, FilterPermanent sacrificeFilter) { + super(new CantPayLifeEffect(onlyNonManaAbilities)); + if (sacrificeFilter != null) { + addEffect(new CantSacrificeEffect(onlyNonManaAbilities, sacrificeFilter)); + } + this.rule = makeRule(onlyNonManaAbilities, sacrificeFilter); + } + + private CantPayLifeOrSacrificeAbility(CantPayLifeOrSacrificeAbility effect) { + super(effect); + this.rule = effect.rule; + } + + public CantPayLifeOrSacrificeAbility copy() { + return new CantPayLifeOrSacrificeAbility(this); + } + + String makeRule(boolean nonManaAbilities, FilterPermanent sacrificeFilter) { + StringBuilder sb = new StringBuilder("Players can't pay life"); + if (sacrificeFilter != null) { + sb.append(" or sacrifice ").append(sacrificeFilter.getMessage()); + } + sb.append(" to cast spells or activate abilities"); + if (nonManaAbilities) { + sb.append(" that aren't mana abilities"); + } + sb.append("."); + return sb.toString(); + } + + @Override + public String getRule() { + return rule; + } +} + +class CantPayLifeEffect extends ContinuousEffectImpl { + + private final boolean onlyNonManaAbilities; + + /** + * @param onlyNonManaAbilities boolean to set if the restriction should only apply to non-mana abilities + */ + CantPayLifeEffect(boolean onlyNonManaAbilities) { + super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment); + this.onlyNonManaAbilities = onlyNonManaAbilities; + } + + private CantPayLifeEffect(CantPayLifeEffect effect) { + super(effect); + this.onlyNonManaAbilities = effect.onlyNonManaAbilities; + } + + public CantPayLifeEffect copy() { + return new CantPayLifeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + return false; + } + player.addPayLifeCostRestriction(Player.PayLifeCostRestriction.CAST_SPELLS); + if (this.onlyNonManaAbilities) { + player.addPayLifeCostRestriction(Player.PayLifeCostRestriction.ACTIVATE_NON_MANA_ABILITIES); + } else { + player.addPayLifeCostRestriction(Player.PayLifeCostRestriction.ACTIVATE_MANA_ABILITIES); + player.addPayLifeCostRestriction(Player.PayLifeCostRestriction.ACTIVATE_NON_MANA_ABILITIES); + } + } + return true; + } +} + +class CantSacrificeEffect extends ContinuousRuleModifyingEffectImpl { + + private final FilterPermanent sacrificeFilter; + private final boolean onlyNonManaAbilities; + + CantSacrificeEffect(boolean onlyNonManaAbilities, FilterPermanent sacrificeFilter) { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + this.sacrificeFilter = sacrificeFilter; + this.onlyNonManaAbilities = onlyNonManaAbilities; + } + + private CantSacrificeEffect(CantSacrificeEffect effect) { + super(effect); + this.sacrificeFilter = effect.sacrificeFilter.copy(); + this.onlyNonManaAbilities = effect.onlyNonManaAbilities; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.PAY_SACRIFICE_COST; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + Optional abilityOptional = game.getAbility(UUID.fromString(event.getData()), event.getSourceId()); + if (permanent == null || !abilityOptional.isPresent()) { + return false; + } + Ability abilityWithCost = abilityOptional.get(); + boolean isActivatedAbility = (onlyNonManaAbilities && abilityWithCost.isManaActivatedAbility()) || + (!onlyNonManaAbilities && abilityWithCost.isActivatedAbility()); + if (!isActivatedAbility && abilityWithCost.getAbilityType() != AbilityType.SPELL) { + return false; + } + return this.sacrificeFilter.match(permanent, event.getPlayerId(), source, game); + } + + @Override + public CantSacrificeEffect copy() { + return new CantSacrificeEffect(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/CycleAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/CycleAllTriggeredAbility.java index 7621d355e6d..51762197c1b 100644 --- a/Mage/src/main/java/mage/abilities/common/CycleAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CycleAllTriggeredAbility.java @@ -31,10 +31,7 @@ public class CycleAllTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (game.getState().getStack().isEmpty()) { - return false; - } - StackObject item = game.getState().getStack().getFirst(); + StackObject item = game.getState().getStack().getFirstOrNull(); return item instanceof StackAbility && item.getStackAbility() instanceof CyclingAbility; } diff --git a/Mage/src/main/java/mage/abilities/common/CycleControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/CycleControllerTriggeredAbility.java index ba2fe63e01e..3154476911a 100644 --- a/Mage/src/main/java/mage/abilities/common/CycleControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CycleControllerTriggeredAbility.java @@ -42,12 +42,11 @@ public class CycleControllerTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (game.getState().getStack().isEmpty() - || !event.getPlayerId().equals(this.getControllerId()) + if (!event.getPlayerId().equals(this.getControllerId()) || (event.getSourceId().equals(this.getSourceId()) && excludeSource)) { return false; } - StackObject item = game.getState().getStack().getFirst(); + StackObject item = game.getState().getStack().getFirstOrNull(); return item instanceof StackAbility && item.getStackAbility() instanceof CyclingAbility; } diff --git a/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java index 35acc7a0165..5f9d03e771a 100644 --- a/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java @@ -6,12 +6,14 @@ import mage.abilities.effects.Effect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.constants.TargetController; +import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.players.Player; import mage.util.CardUtil; -import mage.watchers.common.CardsDrawnThisTurnWatcher; +import mage.watchers.Watcher; + +import java.util.*; /** * @author TheElk801 @@ -48,6 +50,7 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl { this.addHint(hint); } setTriggerPhrase(generateTriggerPhrase()); + this.addWatcher(new DrawNthCardWatcher()); } protected DrawNthCardTriggeredAbility(final DrawNthCardTriggeredAbility ability) { @@ -75,8 +78,7 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl { } break; case OPPONENT: - Player controller = game.getPlayer(controllerId); - if (controller == null || !controller.hasOpponent(event.getPlayerId(), game)) { + if (!game.getOpponents(getControllerId()).contains(event.getPlayerId())) { return false; } break; @@ -86,8 +88,7 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl { default: throw new IllegalArgumentException("TargetController " + targetController + " not supported"); } - CardsDrawnThisTurnWatcher watcher = game.getState().getWatcher(CardsDrawnThisTurnWatcher.class); - return watcher != null && watcher.getCardsDrawnThisTurn(event.getPlayerId()) == cardNumber; + return DrawNthCardWatcher.checkEvent(event.getPlayerId(), event.getId(), game) + 1 == cardNumber; } public String generateTriggerPhrase() { @@ -110,3 +111,36 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl { return new DrawNthCardTriggeredAbility(this); } } + +class DrawNthCardWatcher extends Watcher { + + private final Map> playerDrawEventMap = new HashMap<>(); + + DrawNthCardWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.DREW_CARD) { + playerDrawEventMap + .computeIfAbsent(event.getPlayerId(), x -> new ArrayList<>()) + .add(event.getId()); + } + } + + @Override + public void reset() { + super.reset(); + playerDrawEventMap.clear(); + } + + static int checkEvent(UUID playerId, UUID eventId, Game game) { + return game + .getState() + .getWatcher(DrawNthCardWatcher.class) + .playerDrawEventMap + .getOrDefault(playerId, Collections.emptyList()) + .indexOf(eventId); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/DrawNthOrNthCardTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DrawNthOrNthCardTriggeredAbility.java new file mode 100644 index 00000000000..84f4f210934 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/DrawNthOrNthCardTriggeredAbility.java @@ -0,0 +1,151 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.common.CardsDrawnThisTurnDynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.constants.TargetController; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.*; + +/** + * @author TheElk801 + */ +public class DrawNthOrNthCardTriggeredAbility extends TriggeredAbilityImpl { + + private static final Hint hint = new ValueHint( + "Cards drawn this turn", CardsDrawnThisTurnDynamicValue.instance + ); + private final TargetController targetController; + private final int firstCardNumber; + private final int secondCardNumber; + + public DrawNthOrNthCardTriggeredAbility(Effect effect) { + this(effect, false); + } + + public DrawNthOrNthCardTriggeredAbility(Effect effect, boolean optional) { + this(effect, optional, 1); + } + + public DrawNthOrNthCardTriggeredAbility(Effect effect, boolean optional, int firstCardNumber) { + this(effect, optional, TargetController.YOU, firstCardNumber); + } + + public DrawNthOrNthCardTriggeredAbility(Effect effect, boolean optional, TargetController targetController, int firstCardNumber) { + this(Zone.BATTLEFIELD, effect, optional, targetController, firstCardNumber, 2); + } + + public DrawNthOrNthCardTriggeredAbility(Zone zone, Effect effect, boolean optional, TargetController targetController, int firstCardNumber, int secondCardNumber) { + super(zone, effect, optional); + this.targetController = targetController; + this.firstCardNumber = firstCardNumber; + this.secondCardNumber = secondCardNumber; + if (targetController == TargetController.YOU) { + this.addHint(hint); + } + setTriggerPhrase(generateTriggerPhrase()); + this.addWatcher(new DrawNthOrNthCardWatcher()); + } + + protected DrawNthOrNthCardTriggeredAbility(final DrawNthOrNthCardTriggeredAbility ability) { + super(ability); + this.targetController = ability.targetController; + this.firstCardNumber = ability.firstCardNumber; + this.secondCardNumber = ability.secondCardNumber; + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DREW_CARD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + switch (targetController) { + case YOU: + if (!isControlledBy(event.getPlayerId())) { + return false; + } + break; + case ACTIVE: + if (!game.isActivePlayer(event.getPlayerId())) { + return false; + } + break; + case OPPONENT: + if (!game.getOpponents(getControllerId()).contains(event.getPlayerId())) { + return false; + } + break; + case ANY: + // Doesn't matter who + break; + default: + throw new IllegalArgumentException("TargetController " + targetController + " not supported"); + } + int drawnCards = DrawNthOrNthCardWatcher.checkEvent(event.getPlayerId(), event.getId(), game) + 1; + return drawnCards == firstCardNumber || drawnCards == secondCardNumber; + } + + public String generateTriggerPhrase() { + String numberText = CardUtil.numberToOrdinalText(firstCardNumber) + " or " + CardUtil.numberToOrdinalText(secondCardNumber); + switch (targetController) { + case YOU: + return "Whenever you draw your " + numberText + " card each turn, "; + case ACTIVE: + return "Whenever a player draws their " + numberText + " card during their turn, "; + case OPPONENT: + return "Whenever an opponent draws their " + numberText + " card each turn, "; + case ANY: + return "Whenever a player draws their " + numberText + " card each turn, "; + default: + throw new IllegalArgumentException("TargetController " + targetController + " not supported"); + } + } + + @Override + public DrawNthOrNthCardTriggeredAbility copy() { + return new DrawNthOrNthCardTriggeredAbility(this); + } +} + +class DrawNthOrNthCardWatcher extends Watcher { + + private final Map> playerDrawEventMap = new HashMap<>(); + + DrawNthOrNthCardWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.DREW_CARD) { + playerDrawEventMap + .computeIfAbsent(event.getPlayerId(), x -> new ArrayList<>()) + .add(event.getId()); + } + } + + @Override + public void reset() { + super.reset(); + playerDrawEventMap.clear(); + } + + static int checkEvent(UUID playerId, UUID eventId, Game game) { + return game + .getState() + .getWatcher(DrawNthOrNthCardWatcher.class) + .playerDrawEventMap + .getOrDefault(playerId, Collections.emptyList()) + .indexOf(eventId); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/EscapesWithAbility.java b/Mage/src/main/java/mage/abilities/common/EscapesWithAbility.java index 095d277702a..6ac1bdcefb7 100644 --- a/Mage/src/main/java/mage/abilities/common/EscapesWithAbility.java +++ b/Mage/src/main/java/mage/abilities/common/EscapesWithAbility.java @@ -100,7 +100,7 @@ class EscapesWithEffect extends OneShotEffect { SpellAbility spellAbility = (SpellAbility) getValue(EntersBattlefieldEffect.SOURCE_CAST_SPELL_ABILITY); if (!(spellAbility instanceof EscapeAbility) || !spellAbility.getSourceId().equals(source.getSourceId()) - || permanent.getZoneChangeCounter(game) != spellAbility.getSourceObjectZoneChangeCounter()) { + || permanent.getZoneChangeCounter(game) != spellAbility.getStackMomentSourceZCC()) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/ForetellSourceControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ForetellSourceControllerTriggeredAbility.java index 975f5b725ec..81672a10fe9 100644 --- a/Mage/src/main/java/mage/abilities/common/ForetellSourceControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ForetellSourceControllerTriggeredAbility.java @@ -7,6 +7,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; +import mage.watchers.common.ForetoldWatcher; /** * @author jeffwadsworth @@ -16,6 +17,7 @@ public class ForetellSourceControllerTriggeredAbility extends TriggeredAbilityIm public ForetellSourceControllerTriggeredAbility(Effect effect) { super(Zone.BATTLEFIELD, effect, false); setTriggerPhrase("Whenever you foretell a card, "); + addWatcher(new ForetoldWatcher()); } protected ForetellSourceControllerTriggeredAbility(final ForetellSourceControllerTriggeredAbility ability) { @@ -24,16 +26,14 @@ public class ForetellSourceControllerTriggeredAbility extends TriggeredAbilityIm @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.FORETELL; + return event.getType() == GameEvent.EventType.CARD_FORETOLD; } @Override public boolean checkTrigger(GameEvent event, Game game) { Card card = game.getCard(event.getTargetId()); Player player = game.getPlayer(event.getPlayerId()); - return (card != null - && player != null - && isControlledBy(player.getId())); + return event.getFlag() && card != null && player != null && isControlledBy(player.getId()); } @Override diff --git a/Mage/src/main/java/mage/abilities/common/OneOrMoreLeaveWithoutDyingTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/OneOrMoreLeaveWithoutDyingTriggeredAbility.java new file mode 100644 index 00000000000..cad44fa1bf2 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/OneOrMoreLeaveWithoutDyingTriggeredAbility.java @@ -0,0 +1,52 @@ +package mage.abilities.common; + +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; + +/** + * @author TheElk801 + */ +public class OneOrMoreLeaveWithoutDyingTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + private final FilterPermanent filter; + + public OneOrMoreLeaveWithoutDyingTriggeredAbility(Effect effect, FilterPermanent filter) { + super(Zone.BATTLEFIELD, effect); + this.filter = filter; + setTriggerPhrase("Whenever one or more " + filter.getMessage() + " leave the battlefield without dying, "); + } + + private OneOrMoreLeaveWithoutDyingTriggeredAbility(final OneOrMoreLeaveWithoutDyingTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + } + + @Override + public OneOrMoreLeaveWithoutDyingTriggeredAbility copy() { + return new OneOrMoreLeaveWithoutDyingTriggeredAbility(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) { + return Zone.BATTLEFIELD.match(event.getFromZone()) + && !Zone.GRAVEYARD.match(event.getToZone()) + && filter.match(event.getTarget(), getControllerId(), this, game); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return !getFilteredEvents((ZoneChangeBatchEvent) event, game).isEmpty(); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/PutCounterOnCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutCounterOnCreatureTriggeredAbility.java deleted file mode 100644 index 6d953099ba8..00000000000 --- a/Mage/src/main/java/mage/abilities/common/PutCounterOnCreatureTriggeredAbility.java +++ /dev/null @@ -1,106 +0,0 @@ -package mage.abilities.common; - -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.constants.Zone; -import mage.counters.Counter; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; - -/** - * "Whenever you put one or more counters on a creature " triggered ability - * - * @author PurpleCrowbar - */ -public class PutCounterOnCreatureTriggeredAbility extends TriggeredAbilityImpl { - - private final Counter counterType; // when null, any counter type is accepted - private final FilterPermanent filter; - private final boolean setTargetPointer; - - public PutCounterOnCreatureTriggeredAbility(Effect effect) { - this(effect, (Counter) null); - } - - public PutCounterOnCreatureTriggeredAbility(Effect effect, Counter counter) { - this(effect, counter, new FilterCreaturePermanent()); - } - - public PutCounterOnCreatureTriggeredAbility(Effect effect, FilterPermanent filter) { - this(effect, null, filter); - } - - public PutCounterOnCreatureTriggeredAbility(Effect effect, Counter counter, FilterPermanent filter) { - this(effect, counter, filter, false); - } - - public PutCounterOnCreatureTriggeredAbility(Effect effect, Counter counter, FilterPermanent filter, boolean setTargetPointer) { - this(effect, counter, filter, setTargetPointer, false); - } - - - public PutCounterOnCreatureTriggeredAbility(Effect effect, Counter counter, FilterPermanent filter, boolean setTargetPointer, boolean optional) { - super(Zone.BATTLEFIELD, effect, optional); - this.counterType = counter; - this.filter = filter; - this.setTargetPointer = setTargetPointer; - - setFilterMessage(); - } - - protected PutCounterOnCreatureTriggeredAbility(final PutCounterOnCreatureTriggeredAbility ability) { - super(ability); - this.counterType = ability.counterType; - this.filter = ability.filter; - this.setTargetPointer = ability.setTargetPointer; - } - - @Override - public PutCounterOnCreatureTriggeredAbility copy() { - return new PutCounterOnCreatureTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.COUNTERS_ADDED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!isControlledBy(event.getPlayerId())) { - return false; - } - Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); - if (permanent == null) { - permanent = game.getPermanentEntering(event.getTargetId()); - } - if (permanent == null || !filter.match(permanent, controllerId, this, game)) { - return false; - } - if (counterType != null && !event.getData().equals(counterType.getName())) { - return false; - } - if (setTargetPointer) { - getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); - } - getEffects().setValue("countersAdded", event.getAmount()); - return true; - } - - private void setFilterMessage() { - String filterMessage = filter.getMessage(); - if (!filterMessage.startsWith("another")) { - filterMessage = "a " + filterMessage; - } - - if (this.counterType == null) { - setTriggerPhrase("Whenever you put one or more counters on " + filterMessage + ", "); - } else { - setTriggerPhrase("Whenever you put one or more " + this.counterType.getName() + " counters on " + filterMessage + ", "); - } - } -} diff --git a/Mage/src/main/java/mage/abilities/common/PutCounterOnPermanentTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutCounterOnPermanentTriggeredAbility.java new file mode 100644 index 00000000000..c0b32c1a0f9 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/PutCounterOnPermanentTriggeredAbility.java @@ -0,0 +1,79 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * "Whenever you put one or more [] counters on a []" triggered ability + * + * @author PurpleCrowbar + */ +public class PutCounterOnPermanentTriggeredAbility extends TriggeredAbilityImpl { + + private final CounterType counterType; // when null, any counter type is accepted + private final FilterPermanent filter; + private final boolean setTargetPointer; + + public PutCounterOnPermanentTriggeredAbility(Effect effect, CounterType counterType, FilterPermanent filter) { + this(effect, counterType, filter, false, false); + } + + public PutCounterOnPermanentTriggeredAbility(Effect effect, CounterType counterType, FilterPermanent filter, + boolean setTargetPointer, boolean optional) { + super(Zone.BATTLEFIELD, effect, optional); + this.counterType = counterType; + this.filter = filter; + this.setTargetPointer = setTargetPointer; + setTriggerPhrase("Whenever you put one or more " + + (this.counterType == null ? "" : this.counterType.getName() + " ") + + "counters on " + CardUtil.addArticle(filter.getMessage()) + ", "); + } + + protected PutCounterOnPermanentTriggeredAbility(final PutCounterOnPermanentTriggeredAbility ability) { + super(ability); + this.counterType = ability.counterType; + this.filter = ability.filter; + this.setTargetPointer = ability.setTargetPointer; + } + + @Override + public PutCounterOnPermanentTriggeredAbility copy() { + return new PutCounterOnPermanentTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTERS_ADDED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!isControlledBy(event.getPlayerId())) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null) { + permanent = game.getPermanentEntering(event.getTargetId()); + } + if (permanent == null || !filter.match(permanent, controllerId, this, game)) { + return false; + } + if (counterType != null && !event.getData().equals(counterType.getName())) { + return false; + } + if (setTargetPointer) { + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); + } + getEffects().setValue("countersAdded", event.getAmount()); + return true; + } + +} diff --git a/Mage/src/main/java/mage/abilities/common/TransformIntoSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/TransformIntoSourceTriggeredAbility.java index c1908819867..28005a50453 100644 --- a/Mage/src/main/java/mage/abilities/common/TransformIntoSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/TransformIntoSourceTriggeredAbility.java @@ -5,7 +5,6 @@ import mage.abilities.effects.Effect; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; /** * @author TheElk801 @@ -41,10 +40,6 @@ public class TransformIntoSourceTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getTargetId().equals(this.getSourceId())) { - return false; - } - Permanent permanent = getSourcePermanentIfItStillExists(game); - return permanent != null && permanent.isTransformed(); + return event.getTargetId().equals(this.getSourceId()); } } diff --git a/Mage/src/main/java/mage/abilities/common/TransformsOrEntersTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/TransformsOrEntersTriggeredAbility.java index 43635f5246c..41bb07e832b 100644 --- a/Mage/src/main/java/mage/abilities/common/TransformsOrEntersTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/TransformsOrEntersTriggeredAbility.java @@ -5,13 +5,11 @@ import mage.abilities.effects.Effect; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; /** * @author TheElk801 */ public class TransformsOrEntersTriggeredAbility extends TriggeredAbilityImpl { - public TransformsOrEntersTriggeredAbility(Effect effect, boolean optional) { super(Zone.BATTLEFIELD, effect, optional); setTriggerPhrase("Whenever this creature enters or transforms into {this}, "); @@ -34,16 +32,6 @@ public class TransformsOrEntersTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getTargetId().equals(this.getSourceId())) { - return false; - } - switch (event.getType()) { - case TRANSFORMED: - Permanent permanent = getSourcePermanentIfItStillExists(game); - return permanent != null && !permanent.isTransformed(); - case ENTERS_THE_BATTLEFIELD: - return true; - } - return false; + return event.getTargetId().equals(this.getSourceId()); } } diff --git a/Mage/src/main/java/mage/abilities/common/delayed/OnLeaveReturnExiledAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/OnLeaveReturnExiledAbility.java index 33afa3d86df..c1a47c44bc5 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/OnLeaveReturnExiledAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/OnLeaveReturnExiledAbility.java @@ -103,7 +103,7 @@ class ReturnExiledPermanentsEffect extends OneShotEffect { } private ExileZone getExileIfPossible(final Game game, final Ability source) { - UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); if (exileZone != null) { ExileZone exile = game.getExile().getExileZone(exileZone); diff --git a/Mage/src/main/java/mage/abilities/common/delayed/ReflexiveTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/ReflexiveTriggeredAbility.java index 621a1ab677e..2c4cc9afc07 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/ReflexiveTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/ReflexiveTriggeredAbility.java @@ -8,8 +8,6 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.util.CardUtil; -import java.util.Locale; - /** * @author TheElk801 */ diff --git a/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java index 86c873bdf00..cf51f1f5b3f 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java @@ -111,8 +111,8 @@ public class UntilYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAb } @Override - public int getSourceObjectZoneChangeCounter() { - return ability.getSourceObjectZoneChangeCounter(); + public int getStackMomentSourceZCC() { + return ability.getStackMomentSourceZCC(); } @Override diff --git a/Mage/src/main/java/mage/abilities/condition/common/CastAnotherSpellThisTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CastAnotherSpellThisTurnCondition.java index 10ee9bdd27f..d2368d42dba 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/CastAnotherSpellThisTurnCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/CastAnotherSpellThisTurnCondition.java @@ -30,7 +30,7 @@ public enum CastAnotherSpellThisTurnCondition implements Condition { return spells != null && spells .stream() .filter(Objects::nonNull) - .anyMatch(spell -> !spell.getSourceId().equals(source.getSourceId()) || spell.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter()); + .anyMatch(spell -> !spell.getSourceId().equals(source.getSourceId()) || spell.getZoneChangeCounter(game) != source.getStackMomentSourceZCC()); } public Hint getHint() { diff --git a/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java b/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java index c0e63fc8663..9e3159b298f 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java @@ -17,7 +17,7 @@ public enum ForetoldCondition implements Condition { public boolean apply(Game game, Ability source) { ForetoldWatcher watcher = game.getState().getWatcher(ForetoldWatcher.class); if (watcher != null) { - return watcher.cardWasForetold(source.getSourceId()); + return watcher.checkForetold(source.getSourceId(), game); } return false; } diff --git a/Mage/src/main/java/mage/abilities/condition/common/NotTransformedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/NotTransformedCondition.java new file mode 100644 index 00000000000..5df2942cec1 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/NotTransformedCondition.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.game.permanent.Permanent; + +/** + * @author noxx + */ +public enum NotTransformedCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getSourceId()); + return permanent != null && !permanent.isTransformed(); + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/SourceDealtDamageCondition.java b/Mage/src/main/java/mage/abilities/condition/common/SourceDealtDamageCondition.java index 5512e058588..694e7bb5d06 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/SourceDealtDamageCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/SourceDealtDamageCondition.java @@ -22,7 +22,7 @@ public class SourceDealtDamageCondition implements Condition { @Override public boolean apply(Game game, Ability source) { DamageDoneWatcher watcher = game.getState().getWatcher(DamageDoneWatcher.class); - return watcher != null && watcher.damageDoneBy(source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game) >= value; + return watcher != null && watcher.damageDoneBy(source.getSourceId(), source.getStackMomentSourceZCC(), game) >= value; } @Override diff --git a/Mage/src/main/java/mage/abilities/condition/common/SourceModifiedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/SourceModifiedCondition.java new file mode 100644 index 00000000000..e030d7052ea --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/SourceModifiedCondition.java @@ -0,0 +1,28 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.filter.predicate.permanent.ModifiedPredicate; +import mage.game.Game; + +import java.util.Optional; + +/** + * @author TheElk801 + */ +public enum SourceModifiedCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(source.getSourcePermanentIfItStillExists(game)) + .filter(permanent -> ModifiedPredicate.instance.apply(permanent, game)) + .isPresent(); + } + + @Override + public String toString() { + return "it's modified"; + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/TransformedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/TransformedCondition.java deleted file mode 100644 index b8057e8914a..00000000000 --- a/Mage/src/main/java/mage/abilities/condition/common/TransformedCondition.java +++ /dev/null @@ -1,43 +0,0 @@ - -package mage.abilities.condition.common; - -import mage.abilities.Ability; -import mage.abilities.condition.Condition; -import mage.game.Game; -import mage.game.permanent.Permanent; - -/** - * - * @author noxx - */ -public class TransformedCondition implements Condition { - - protected boolean notCondition; - - public TransformedCondition() { - this(false); - } - - /** - * The condition checks whether a permanent is transformed or not. - * - * @param notCondition if true the condition is true when the permanent is not transformed - * @return true if the condition is true, false if the condition is false - */ - public TransformedCondition(boolean notCondition) { - this.notCondition = notCondition; - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { - if (notCondition) { - return !permanent.isTransformed(); - } else { - return permanent.isTransformed(); - } - } - return false; - } -} diff --git a/Mage/src/main/java/mage/abilities/condition/common/WebSlingingCondition.java b/Mage/src/main/java/mage/abilities/condition/common/WebSlingingCondition.java new file mode 100644 index 00000000000..e47a5184363 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/WebSlingingCondition.java @@ -0,0 +1,30 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.keyword.WebSlingingAbility; +import mage.game.Game; +import mage.util.CardUtil; + +/** + * @author TheElk801 + */ +public enum WebSlingingCondition implements Condition { + THEY("they were"), + THIS("{this}"); + private final String message; + + WebSlingingCondition(String message) { + this.message = message; + } + + @Override + public boolean apply(Game game, Ability source) { + return CardUtil.checkSourceCostsTagExists(game, source, WebSlingingAbility.WEB_SLINGING_ACTIVATION_VALUE_KEY); + } + + @Override + public String toString() { + return message + " cast using web-slinging"; + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java b/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java index 62dd2fade0a..ad9f3fbae43 100644 --- a/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java +++ b/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java @@ -203,7 +203,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter } private String getActivatedKey(Ability source) { - return getActivatedKey(this.getOriginalId(), source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + return getActivatedKey(this.getOriginalId(), source.getSourceId(), source.getStackMomentSourceZCC()); } private static String getActivatedKey(UUID alternativeCostOriginalId, UUID sourceId, int sourceZCC) { @@ -217,7 +217,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter } private String getDynamicCostActivatedKey(Ability source) { - return getDynamicCostActivatedKey(this.getOriginalId(), source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + return getDynamicCostActivatedKey(this.getOriginalId(), source.getSourceId(), source.getStackMomentSourceZCC()); } private static String getDynamicCostActivatedKey(UUID alternativeCostOriginalId, UUID sourceId, int sourceZCC) { @@ -243,7 +243,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter String key = getActivatedKey( alternativeCostOriginalId, source.getSourceId(), - source.getSourceObjectZoneChangeCounter() + (searchPrevZCC ? -1 : 0) + source.getStackMomentSourceZCC() + (searchPrevZCC ? -1 : 0) ); Boolean status = (Boolean) game.getState().getValue(key); return status != null && status; diff --git a/Mage/src/main/java/mage/abilities/costs/Cost.java b/Mage/src/main/java/mage/abilities/costs/Cost.java index 9aecb8937f3..8a70a010b2c 100644 --- a/Mage/src/main/java/mage/abilities/costs/Cost.java +++ b/Mage/src/main/java/mage/abilities/costs/Cost.java @@ -19,9 +19,20 @@ public interface Cost extends Serializable, Copyable { /** * Check is it possible to pay * For mana it checks only single color and amount available, not total mana cost + *

+ * Warning, if you want to use canChoose, then don't forget about already selected targets (important for AI sims). */ boolean canPay(Ability ability, Ability source, UUID controllerId, Game game); + /** + * Simple canPay logic implementation with targets - cost has possible targets or already selected it, e.g. by AI sims + *

+ * Do not override + */ + default boolean canChooseOrAlreadyChosen(Ability ability, Ability source, UUID controllerId, Game game) { + return this.getTargets().stream().allMatch(target -> target.isChosen(game) || target.canChoose(controllerId, source, game)); + } + boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana); boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay); diff --git a/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java index 83b12475790..1316d2daf35 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java @@ -70,7 +70,7 @@ public class DiscardTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java index 552ab89af2d..6565f9e164d 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java @@ -120,7 +120,7 @@ public class ExileFromGraveCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileFromHandCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileFromHandCost.java index 9a9aee8454e..4b1466742d2 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileFromHandCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileFromHandCost.java @@ -82,7 +82,7 @@ public class ExileFromHandCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileTargetCost.java index c71f98d225b..07cf32a50c9 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileTargetCost.java @@ -68,7 +68,7 @@ public class ExileTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/PutCardFromHandOnTopOfLibraryCost.java b/Mage/src/main/java/mage/abilities/costs/common/PutCardFromHandOnTopOfLibraryCost.java index 565bb66c1b0..890ece07a43 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/PutCardFromHandOnTopOfLibraryCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/PutCardFromHandOnTopOfLibraryCost.java @@ -32,8 +32,7 @@ public class PutCardFromHandOnTopOfLibraryCost extends CostImpl { TargetCardInHand targetCardInHand = new TargetCardInHand(); targetCardInHand.setRequired(false); Card card; - if (targetCardInHand.canChoose(controllerId, source, game) - && controller.choose(Outcome.PreventDamage, targetCardInHand, source, game)) { + if (controller.choose(Outcome.PreventDamage, targetCardInHand, source, game)) { card = game.getCard(targetCardInHand.getFirstTarget()); paid = card != null && controller.moveCardToLibraryWithInfo(card, source, game, Zone.HAND, true, true); } diff --git a/Mage/src/main/java/mage/abilities/costs/common/PutCountersTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/PutCountersTargetCost.java index 1baac83c362..cb3cbdb36be 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/PutCountersTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/PutCountersTargetCost.java @@ -58,6 +58,6 @@ public class PutCountersTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } } diff --git a/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java b/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java index 03c1607a1fb..6264bd10aac 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java @@ -159,7 +159,7 @@ public class RemoveCounterCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } private String setText() { diff --git a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java index 640f9c7b0d3..48489cdbc0c 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java @@ -71,7 +71,7 @@ public class ReturnToHandChosenControlledPermanentCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandFromGraveyardCost.java b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandFromGraveyardCost.java index 3efc8aec28f..bc0df6c3a52 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandFromGraveyardCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandFromGraveyardCost.java @@ -52,7 +52,7 @@ public class ReturnToHandFromGraveyardCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java b/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java index 6ce286b3e5e..bb1dcffa446 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java @@ -89,7 +89,7 @@ public class RevealTargetFromHandCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return allowNoReveal || this.getTargets().canChoose(controllerId, source, game); + return allowNoReveal || canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/SacrificeAttachmentCost.java b/Mage/src/main/java/mage/abilities/costs/common/SacrificeAttachmentCost.java index 1764caf2bba..81ff57ddd63 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/SacrificeAttachmentCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/SacrificeAttachmentCost.java @@ -6,6 +6,7 @@ import mage.abilities.costs.SacrificeCost; import mage.abilities.costs.UseAttachedCost; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.players.Player; import java.util.UUID; @@ -52,7 +53,12 @@ public class SacrificeAttachmentCost extends UseAttachedCost implements Sacrific if (!super.canPay(ability, source, controllerId, game)) { return false; } - return game.getPermanent(source.getSourceId()).canBeSacrificed(); + Player controller = game.getPlayer(controllerId); + Permanent permanent = mageObjectReference.getPermanent(game); + if (controller == null || permanent == null) { + return false; + } + return controller.canPaySacrificeCost(permanent, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/SacrificeXTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/SacrificeXTargetCost.java index 4a3ae6f865f..804db8e45ac 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/SacrificeXTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/SacrificeXTargetCost.java @@ -7,8 +7,12 @@ import mage.abilities.costs.VariableCostImpl; import mage.abilities.costs.VariableCostType; import mage.filter.FilterPermanent; import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; import mage.target.common.TargetSacrifice; +import java.util.UUID; + /** * @author LevelX2 */ @@ -39,6 +43,26 @@ public class SacrificeXTargetCost extends VariableCostImpl implements SacrificeC this.minValue = cost.minValue; } + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + int canSacAmount = getValidSacAmount(source, controllerId, game); + return canSacAmount >= minValue; + } + + private int getValidSacAmount(Ability source, UUID controllerId, Game game) { + Player controller = game.getPlayer(controllerId); + if (controller == null) { + return -1; + } + int canSacAmount = 0; + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllerId, game)) { + if (controller.canPaySacrificeCost(permanent, source, controllerId, game)) { + canSacAmount++; + } + } + return canSacAmount; + } + @Override public SacrificeXTargetCost copy() { return new SacrificeXTargetCost(this); @@ -51,7 +75,7 @@ public class SacrificeXTargetCost extends VariableCostImpl implements SacrificeC @Override public int getMaxValue(Ability source, Game game) { - return game.getBattlefield().count(filter, source.getControllerId(), source, game); + return getValidSacAmount(source, source.getControllerId(), game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java index ea468535e43..761f22b5dc2 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java @@ -69,7 +69,7 @@ public class TapTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } public TargetControlledPermanent getTarget() { diff --git a/Mage/src/main/java/mage/abilities/costs/common/UntapTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/UntapTargetCost.java index 8e0506c2f5c..2ba5fa58762 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/UntapTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/UntapTargetCost.java @@ -63,7 +63,7 @@ public class UntapTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/WaterbendCost.java b/Mage/src/main/java/mage/abilities/costs/common/WaterbendCost.java new file mode 100644 index 00000000000..e1ea59d094a --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/common/WaterbendCost.java @@ -0,0 +1,44 @@ +package mage.abilities.costs.common; + +import mage.abilities.Ability; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.game.Game; + +import java.util.UUID; + +/** + * TODO: Implement properly + * + * @author TheElk801 + */ +public class WaterbendCost extends ManaCostsImpl { + + public WaterbendCost(int amount) { + this("{" + amount + '}'); + } + + public WaterbendCost(String mana) { + super(""); + this.text = "waterbend " + mana; + } + + private WaterbendCost(final WaterbendCost cost) { + super(cost); + } + + @Override + public WaterbendCost copy() { + return new WaterbendCost(this); + } + + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + return false; + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + return false; + } +} 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 70ba8044ff5..566934b34c7 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java @@ -10,7 +10,6 @@ import mage.constants.ColoredManaSymbol; import mage.constants.ManaType; import mage.constants.Outcome; import mage.filter.Filter; -import mage.filter.FilterMana; import mage.game.Game; import mage.players.ManaPool; import mage.players.Player; @@ -43,7 +42,7 @@ public class ManaCostsImpl extends ArrayList implements M load(mana); } - private ManaCostsImpl(final ManaCostsImpl costs) { + protected ManaCostsImpl(final ManaCostsImpl costs) { this.id = costs.id; this.text = costs.text; this.ensureCapacity(costs.size()); diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentPowerCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentPowerCount.java index 17a38a50c19..4550bc49500 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentPowerCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentPowerCount.java @@ -18,7 +18,7 @@ public enum AttachedPermanentPowerCount implements DynamicValue { public int calculate(Game game, Ability sourceAbility, Effect effect) { Permanent attachmentPermanent = game.getPermanent(sourceAbility.getSourceId()); if (attachmentPermanent == null) { - attachmentPermanent = (Permanent) game.getLastKnownInformation(sourceAbility.getSourceId(), Zone.BATTLEFIELD, sourceAbility.getSourceObjectZoneChangeCounter()); + attachmentPermanent = (Permanent) game.getLastKnownInformation(sourceAbility.getSourceId(), Zone.BATTLEFIELD, sourceAbility.getStackMomentSourceZCC()); } if (attachmentPermanent == null || attachmentPermanent.getAttachedTo() == null) { return 0; diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentToughnessCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentToughnessCount.java index 35543b26778..080de1d6c09 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentToughnessCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentToughnessCount.java @@ -17,7 +17,7 @@ public enum AttachedPermanentToughnessCount implements DynamicValue { public int calculate(Game game, Ability sourceAbility, Effect effect) { Permanent attachmentPermanent = game.getPermanent(sourceAbility.getSourceId()); if (attachmentPermanent == null) { - attachmentPermanent = (Permanent) game.getLastKnownInformation(sourceAbility.getSourceId(), Zone.BATTLEFIELD, sourceAbility.getSourceObjectZoneChangeCounter()); + attachmentPermanent = (Permanent) game.getLastKnownInformation(sourceAbility.getSourceId(), Zone.BATTLEFIELD, sourceAbility.getStackMomentSourceZCC()); } if (attachmentPermanent == null || attachmentPermanent.getAttachedTo() == null) { return 0; diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ConvokedSourceCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ConvokedSourceCount.java index 4af0a7d1837..bb091c2cf19 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ConvokedSourceCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ConvokedSourceCount.java @@ -20,7 +20,7 @@ public enum ConvokedSourceCount implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - return CardUtil.getSourceCostsTag(game, sourceAbility, ConvokeAbility.convokingCreaturesKey, new HashSet<>(0)).size(); + return CardUtil.getSourceCostsTag(game, sourceAbility, ConvokeAbility.convokingCreaturesKey, new HashSet<>()).size(); } @Override diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SunburstCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SunburstCount.java index db83fe6837e..0303cfe132b 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SunburstCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SunburstCount.java @@ -18,25 +18,24 @@ public enum SunburstCount implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { int count = 0; - if (!game.getStack().isEmpty()) { - StackObject spell = game.getStack().getFirst(); - if (spell instanceof Spell && ((Spell) spell).getSourceId().equals(sourceAbility.getSourceId())) { - Mana mana = ((Spell) spell).getSpellAbility().getManaCostsToPay().getUsedManaToPay(); - if (mana.getBlack() > 0) { - count++; - } - if (mana.getBlue() > 0) { - count++; - } - if (mana.getGreen() > 0) { - count++; - } - if (mana.getRed() > 0) { - count++; - } - if (mana.getWhite() > 0) { - count++; - } + + StackObject spell = game.getStack().getFirstOrNull(); + if (spell instanceof Spell && spell.getSourceId().equals(sourceAbility.getSourceId())) { + Mana mana = ((Spell) spell).getSpellAbility().getManaCostsToPay().getUsedManaToPay(); + if (mana.getBlack() > 0) { + count++; + } + if (mana.getBlue() > 0) { + count++; + } + if (mana.getGreen() > 0) { + count++; + } + if (mana.getRed() > 0) { + count++; + } + if (mana.getWhite() > 0) { + count++; } } return count; diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java index 7db5097dcc8..0f9f25acc3a 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java @@ -513,13 +513,10 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu // If the top card of your library changes while you’re casting a spell, playing a land, or activating an ability, // you can’t look at the new top card until you finish doing so. This means that if you cast the top card of // your library, you can’t look at the next one until you’re done paying for that spell. (2019-05-03) - if (!game.getStack().isEmpty()) { - StackObject stackObject = game.getStack().getFirst(); - return !(stackObject instanceof Spell) - || !Zone.LIBRARY.equals(((Spell) stackObject).getFromZone()) - || stackObject.getStackAbility().getManaCostsToPay().isPaid(); // mana payment finished - } - return true; + StackObject stackObject = game.getStack().getFirstOrNull(); + return !(stackObject instanceof Spell) + || !Zone.LIBRARY.equals(((Spell) stackObject).getFromZone()) + || stackObject.getStackAbility().getManaCostsToPay().isPaid(); // mana payment finished } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java b/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java index c9faecee202..d204f04b445 100644 --- a/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/PreventDamageAndRemoveCountersEffect.java @@ -71,7 +71,7 @@ public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl { if (permanent == null || watcher == null || damageAmount <= 0) { return false; } - MageObjectReference mor = new MageObjectReference(source.getId(), source.getSourceObjectZoneChangeCounter(), game); + MageObjectReference mor = new MageObjectReference(source.getId(), source.getStackMomentSourceZCC(), game); int beforeCount = permanent.getCounters(game).getCount(CounterType.P1P1); if (thatMany) { // Remove them. @@ -110,7 +110,7 @@ public class PreventDamageAndRemoveCountersEffect extends PreventionEffectImpl { if (whileHasCounter && !permanent.getCounters(game).containsKey(CounterType.P1P1)) { // If the last counter has already be removed for the same batch of prevention, we still want to prevent the damage. PreventDamageAndRemoveCountersWatcher watcher = game.getState().getWatcher(PreventDamageAndRemoveCountersWatcher.class); - MageObjectReference mor = new MageObjectReference(source.getId(), source.getSourceObjectZoneChangeCounter(), game); + MageObjectReference mor = new MageObjectReference(source.getId(), source.getStackMomentSourceZCC(), game); return watcher != null && watcher.hadMORCounterRemovedThisBatch(mor); } return true; @@ -149,4 +149,4 @@ class PreventDamageAndRemoveCountersWatcher extends Watcher { void addMOR(MageObjectReference mor) { morRemovedCounterThisDamageBatch.add(mor); } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/CantBeCounteredSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CantBeCounteredSourceEffect.java index 4d9f3514c6f..dde43e653e3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CantBeCounteredSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CantBeCounteredSourceEffect.java @@ -9,7 +9,6 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.stack.Spell; import mage.game.stack.StackObject; @@ -34,7 +33,7 @@ public class CantBeCounteredSourceEffect extends ContinuousRuleModifyingEffectIm @Override public String getInfoMessage(Ability source, GameEvent event, Game game) { - StackObject stackObject = game.getStack().getStackObject(event.getTargetId()); + StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); MageObject sourceObject = game.getObject(source); if (stackObject != null && sourceObject != null) { return sourceObject.getLogName() + " can't be countered by " + stackObject.getName(); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java index eaadf209ab4..f464355448e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java @@ -86,7 +86,7 @@ public class CopyEffect extends ContinuousEffectImpl { return false; } // As long as the permanent is still in the short living LKI continue to copy to get triggered abilities to TriggeredAbilities for dies events. - permanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, source.getSourceObjectZoneChangeCounter()); + permanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, source.getStackMomentSourceZCC()); if (permanent == null) { discard(); return false; diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java index 204befa2900..ca0a32efa39 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java @@ -176,8 +176,8 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { } // can target card or permanent - Card copyFrom; - CopyApplier applier = new EmptyCopyApplier(); + Card copyFrom = null; + CopyApplier applier = null; if (permanent != null) { // handle copies of copies Permanent copyFromPermanent = permanent; @@ -196,9 +196,17 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { } } } + // check if permanent was copying, but copy effect is no longer active + if (applier == null) { + if (permanent.isCopy() && permanent.getCopyFrom() instanceof Permanent) { + copyFromPermanent = (Permanent) permanent.getCopyFrom(); + } + applier = new EmptyCopyApplier(); + } copyFrom = copyFromPermanent; } else { copyFrom = game.getCard(getTargetPointer().getFirst(game, source)); + applier = new EmptyCopyApplier(); } if (copyFrom == null) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java index 84721c6224f..2df5d3a11cd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java @@ -6,9 +6,11 @@ import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; + +import java.util.Optional; /** * @author TheElk801 @@ -47,12 +49,12 @@ public class DamageWithExcessEffect extends OneShotEffect { return false; } int damage = amount.calculate(game, source, this); - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - lethal = Math.min(lethal, damage); - permanent.damage(lethal, source.getSourceId(), source, game); - Player player = game.getPlayer(permanent.getControllerId()); - if (player != null && lethal < damage) { - player.damage(damage - lethal, source.getSourceId(), source, game); + int excess = permanent.damageWithExcess(damage, source, game); + if (excess > 0) { + Optional.ofNullable(permanent) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.damage(excess, source, game)); } return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java index 1118d48eb50..9d6edca166b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java @@ -19,6 +19,7 @@ public class DoIfCostPaid extends OneShotEffect { protected final Effects executingEffects; protected final Effects otherwiseEffects; + protected String otherwiseText = "If you don't"; protected final Cost cost; private final String chooseUseText; private final boolean optional; @@ -79,6 +80,11 @@ public class DoIfCostPaid extends OneShotEffect { return this; } + public DoIfCostPaid setOtherwiseText(String otherwiseText) { + this.otherwiseText = otherwiseText; + return this; + } + /** * Allow to add additional info in pay dialog, so user can split it in diff use cases to remember by right click * Example: ignore untap payment for already untapped permanent like Mana Vault @@ -178,7 +184,7 @@ public class DoIfCostPaid extends OneShotEffect { return (optional ? "you may " : "") + CardUtil.addCostVerb(cost.getText()) + "." + (!executingEffects.isEmpty() ? " If you do, " + executingEffects.getText(mode) : "") - + (!otherwiseEffects.isEmpty() ? " If you don't, " + otherwiseEffects.getText(mode) : ""); + + (!otherwiseEffects.isEmpty() ? " " + otherwiseText + ", " + otherwiseEffects.getText(mode) : ""); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/DrawCardsEqualToDifferenceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DrawCardsEqualToDifferenceEffect.java new file mode 100644 index 00000000000..25c23069aef --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/DrawCardsEqualToDifferenceEffect.java @@ -0,0 +1,37 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +/** + * @author TheElk801 + */ +public class DrawCardsEqualToDifferenceEffect extends OneShotEffect { + + private final int amount; + + public DrawCardsEqualToDifferenceEffect(int amount) { + super(Outcome.Benefit); + this.amount = amount; + staticText = "draw cards equal to the difference"; + } + + private DrawCardsEqualToDifferenceEffect(final DrawCardsEqualToDifferenceEffect effect) { + super(effect); + this.amount = effect.amount; + } + + @Override + public DrawCardsEqualToDifferenceEffect copy() { + return new DrawCardsEqualToDifferenceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + return player != null && player.drawCards(Math.max(amount - player.getHand().size(), 0), source, game) > 0; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/EntersBattlefieldUnderControlOfOpponentOfChoiceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/EntersBattlefieldUnderControlOfOpponentOfChoiceEffect.java index 643864d3466..52462ed7b1d 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/EntersBattlefieldUnderControlOfOpponentOfChoiceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/EntersBattlefieldUnderControlOfOpponentOfChoiceEffect.java @@ -59,7 +59,7 @@ public class EntersBattlefieldUnderControlOfOpponentOfChoiceEffect extends OneSh Duration.Custom, true, opponent.getId() ); continuousEffect.setTargetPointer(new FixedTarget( - source.getSourceId(), source.getSourceObjectZoneChangeCounter() + source.getSourceId(), source.getStackMomentSourceZCC() )); game.addEffect(continuousEffect, source); return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect.java index 7763a00ee8c..176717e55eb 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect.java @@ -73,7 +73,7 @@ public class ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect extends OneShotEf return false; } // move card to exile - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? "" : sourceObject.getIdName(); for (Card card : cards.getCards(game)) { @@ -101,4 +101,4 @@ public class ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect extends OneShotEf } return true; } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileSourceEffect.java index de5e9aba55a..69a78aae6de 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileSourceEffect.java @@ -1,6 +1,5 @@ package mage.abilities.effects.common; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; @@ -59,7 +58,7 @@ public class ExileSourceEffect extends OneShotEffect { UUID exileZoneId = null; String exileZoneName = ""; if (toUniqueExileZone) { - exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); exileZoneName = card.getName(); } return controller.moveCardsToExile(card, source, game, true, exileZoneId, exileZoneName); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java index edc5433bba3..1d88dd2a854 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java @@ -111,7 +111,7 @@ public class ExileTargetEffect extends OneShotEffect { } if (toSourceExileZone) { MageObject sourceObject = source.getSourceObject(game); - exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); if (sourceObject != null) { exileZone = sourceObject.getIdName(); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java index 1b16ffdf372..11ece3e1bf3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java @@ -78,7 +78,7 @@ public class ExileTargetForSourceEffect extends OneShotEffect { } } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); // it can target permanents on battlefield, so use objects first Set cardsToMove = objectsToMove.stream() diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java index 0bcb084d1a0..267012e2c57 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java @@ -66,7 +66,7 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect { if (permanentLeftBattlefield != null) { exileId = CardUtil.getExileZoneId(game, source.getSourceId(), permanentLeftBattlefield.getZoneChangeCounter(game)); } else { - exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); } ExileZone exile = game.getExile().getExileZone(exileId); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderYourControlSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderYourControlSourceEffect.java index 7bc72ac3174..15dc5590c6f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderYourControlSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderYourControlSourceEffect.java @@ -38,7 +38,7 @@ public class ReturnToBattlefieldUnderYourControlSourceEffect extends OneShotEffe if (controller == null) { return false; } - UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); ExileZone exileZone = game.getExile().getExileZone(exileZoneId); if (exileZone != null && exileZone.contains(source.getSourceId())) { Card card = game.getCard(source.getSourceId()); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandSourceEffect.java index 29eaf85c47b..509ee503062 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandSourceEffect.java @@ -60,7 +60,7 @@ public class ReturnToHandSourceEffect extends OneShotEffect { } MageObject mageObject; if (returnFromNextZone - && game.getState().getZoneChangeCounter(source.getSourceId()) == source.getSourceObjectZoneChangeCounter() + 1) { + && game.getState().getZoneChangeCounter(source.getSourceId()) == source.getStackMomentSourceZCC() + 1) { mageObject = game.getObject(source); } else { mageObject = source.getSourceObjectIfItStillExists(game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java index 63bca73a3b5..5eecee67a66 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java @@ -74,8 +74,7 @@ public class SacrificeOpponentsUnlessPayEffect extends OneShotEffect { if (game.getBattlefield().count(TargetSacrifice.makeFilter(filter), player.getId(), source, game) > 0) { TargetSacrifice target = new TargetSacrifice(1, filter); - if (target.canChoose(player.getId(), source, game)) { - player.choose(Outcome.Sacrifice, target, source, game); + if (player.choose(Outcome.Sacrifice, target, source, game)) { permsToSacrifice.addAll(target.getTargets()); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java index c74c2318ea4..b6ab6a28099 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java @@ -41,7 +41,7 @@ public class SacrificeSourceEffect extends OneShotEffect { if (sourceObject == null) { // Check if the effect was installed by the spell the source was cast by (e.g. Necromancy), if not don't sacrifice the permanent if (game.getState().getZone(source.getSourceId()).equals(Zone.BATTLEFIELD) - && source.getSourceObjectZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) { + && source.getStackMomentSourceZCC() + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) { sourceObject = game.getPermanent(source.getSourceId()); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceUnlessPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceUnlessPaysEffect.java index cc0039f6a44..eb1cc799df9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceUnlessPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceUnlessPaysEffect.java @@ -76,7 +76,7 @@ public class SacrificeSourceUnlessPaysEffect extends OneShotEffect { } game.informPlayers(player.getLogName() + " chooses not to " + logMessage + " to prevent sacrifice effect"); - if (source.getSourceObjectZoneChangeCounter() == game.getState().getZoneChangeCounter(source.getSourceId()) + if (source.getStackMomentSourceZCC() == game.getState().getZoneChangeCounter(source.getSourceId()) && game.getState().getZone(source.getSourceId()) == Zone.BATTLEFIELD) { sourcePermanent.sacrifice(source, game); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/asthought/MayCastFromGraveyardAsAdventureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/asthought/MayCastFromGraveyardAsAdventureEffect.java index 78b3c80b95c..7ffec5a4206 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/asthought/MayCastFromGraveyardAsAdventureEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/asthought/MayCastFromGraveyardAsAdventureEffect.java @@ -49,6 +49,6 @@ public class MayCastFromGraveyardAsAdventureEffect extends AsThoughEffectImpl { return sourceCard != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD - && source.getSourceObjectZoneChangeCounter() == sourceCard.getZoneChangeCounter(game); + && source.getStackMomentSourceZCC() == sourceCard.getZoneChangeCounter(game); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddBasicLandTypeAllLandsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddBasicLandTypeAllLandsEffect.java index fb92836067d..c5fdd8277fb 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddBasicLandTypeAllLandsEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddBasicLandTypeAllLandsEffect.java @@ -20,6 +20,7 @@ public class AddBasicLandTypeAllLandsEffect extends ContinuousEffectImpl { this.subType = subType; this.staticText = "Each land is " + subType.getIndefiniteArticle() + " " + subType.getDescription() + " in addition to its other land types"; + this.dependendToTypes.add(DependencyType.BecomeNonbasicLand); switch (subType) { case PLAINS: this.dependencyTypes.add(DependencyType.BecomePlains); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesAllBasicsControlledEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesAllBasicsControlledEffect.java index b5991f27b3a..1e96acf7468 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesAllBasicsControlledEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesAllBasicsControlledEffect.java @@ -8,9 +8,6 @@ import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; -import java.util.ArrayList; -import java.util.List; - /** * @author TheElk801 */ @@ -27,6 +24,7 @@ public class BecomesAllBasicsControlledEffect extends ContinuousEffectImpl { public BecomesAllBasicsControlledEffect() { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment); this.staticText = "Lands you control are every basic land type in addition to their other types"; + dependendToTypes.add(DependencyType.BecomeNonbasicLand); dependencyTypes.add(DependencyType.BecomeMountain); dependencyTypes.add(DependencyType.BecomeForest); dependencyTypes.add(DependencyType.BecomeSwamp); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java index 4b4a8e3f362..2ada47074ad 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureTargetEffect.java @@ -7,6 +7,7 @@ import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.Token; +import mage.util.CardUtil; import java.util.UUID; @@ -194,8 +195,11 @@ public class BecomesCreatureTargetEffect extends ContinuousEffectImpl { " lose all their abilities and" : " loses all abilities and"); } - sb.append(getTargetPointer().isPlural(mode.getTargets()) ? " become " : " becomes a "); - sb.append(token.getDescription()); + if (getTargetPointer().isPlural(mode.getTargets())) { + sb.append(" become ").append(token.getDescription()); + } else { + sb.append(" becomes ").append(CardUtil.addArticle(token.getDescription())); + } if (!durationRuleAtStart && !duration.toString().isEmpty()) { sb.append(' ').append(duration.toString()); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java index 76afce20dce..2ef681f85e1 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java @@ -24,6 +24,7 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl { protected boolean independentEffect; protected String targetObjectName; protected boolean doesntRemoveItself = false; + protected boolean useQuotes = false; public GainAbilityAttachedEffect(Ability ability, AttachmentType attachmentType) { this(ability, attachmentType, Duration.WhileOnBattlefield); @@ -68,6 +69,7 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl { this.independentEffect = effect.independentEffect; this.targetObjectName = effect.targetObjectName; this.doesntRemoveItself = effect.doesntRemoveItself; + this.useQuotes = effect.useQuotes; } @Override @@ -130,20 +132,27 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl { return this; } + public GainAbilityAttachedEffect withQuotes(boolean useQuotes) { + this.useQuotes = useQuotes; + return this; + } + @Override public String getText(Mode mode) { if (staticText != null && !staticText.isEmpty()) { return staticText; } StringBuilder sb = new StringBuilder(); - sb.append(attachmentType.verb().toLowerCase()); - sb.append(" " + targetObjectName + " "); + if (attachmentType != null) { + sb.append(attachmentType.verb().toLowerCase()); + sb.append(" " + targetObjectName + " "); + } if (duration == Duration.WhileOnBattlefield) { sb.append("has "); } else { sb.append("gains "); } - boolean quotes = ability instanceof SimpleActivatedAbility + boolean quotes = useQuotes || ability instanceof SimpleActivatedAbility || ability instanceof TriggeredAbility || ability instanceof LoyaltyAbility || ability instanceof ManaAbility diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersSourceEffect.java index 358be527915..26fb56a60c8 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersSourceEffect.java @@ -110,8 +110,8 @@ public class AddCountersSourceEffect extends OneShotEffect { return false; } - if ((source.getSourceObjectZoneChangeCounter() == 0 // from static ability - || source.getSourceObjectZoneChangeCounter() == permanent.getZoneChangeCounter(game))) { // prevent to add counters to later source objects + if ((source.getStackMomentSourceZCC() == 0 // from static ability + || source.getStackMomentSourceZCC() == permanent.getZoneChangeCounter(game))) { // prevent to add counters to later source objects Counter newCounter = counter.copy(); int countersToAdd = amount.calculate(game, source, this); if (amount instanceof StaticValue || countersToAdd > 0) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/DoubleCounterOnEachPermanentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/DoubleCounterOnEachPermanentEffect.java index cf71cb93fdd..29abc2c89fc 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/DoubleCounterOnEachPermanentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/DoubleCounterOnEachPermanentEffect.java @@ -4,12 +4,14 @@ package mage.abilities.effects.common.counter; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; +import mage.counters.Counter; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.permanent.Permanent; import java.util.List; +import java.util.stream.Collectors; /** * @author Susucr @@ -23,7 +25,9 @@ public class DoubleCounterOnEachPermanentEffect extends OneShotEffect { super(Outcome.BoostCreature); this.counterType = counterType; this.filter = filter.copy(); - this.staticText = "double the number of " + counterType.getName() + " counters on each " + filter.getMessage(); + this.staticText = "double the number of " + + (counterType != null ? (counterType.getName() + " counters") : "each kind of counter") + + " on each " + filter.getMessage(); } private DoubleCounterOnEachPermanentEffect(final DoubleCounterOnEachPermanentEffect effect) { @@ -39,8 +43,19 @@ public class DoubleCounterOnEachPermanentEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - List permanents = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game); - for (Permanent permanent : permanents) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { + if (counterType == null) { + List counters = permanent + .getCounters(game) + .values() + .stream() + .map(Counter::copy) + .collect(Collectors.toList()); + for (Counter counter : counters) { + permanent.addCounters(counter, source, game); + } + continue; + } int existingCounters = permanent.getCounters(game).getCount(counterType); if (existingCounters > 0) { permanent.addCounters(counterType.createInstance(existingCounters), source.getControllerId(), source, game); 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 7cacad39c33..a6a958121c2 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 @@ -7,6 +7,7 @@ import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.cards.CardsImpl; import mage.constants.Outcome; +import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.game.Game; @@ -60,7 +61,7 @@ public class LookTargetHandChooseDiscardEffect extends OneShotEffect { } return true; } - TargetCard target = new TargetCardInHand(upTo ? 0 : num, num, filter); + TargetCard target = new TargetCard(upTo ? 0 : num, num, Zone.HAND, 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) diff --git a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CantCastDuringFirstThreeTurnsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CantCastDuringFirstThreeTurnsEffect.java new file mode 100644 index 00000000000..26e131a92a0 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CantCastDuringFirstThreeTurnsEffect.java @@ -0,0 +1,47 @@ +package mage.abilities.effects.common.ruleModifying; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * @author TheElk801 + */ +public class CantCastDuringFirstThreeTurnsEffect extends ContinuousRuleModifyingEffectImpl { + + public CantCastDuringFirstThreeTurnsEffect() { + this("this spell"); + } + + public CantCastDuringFirstThreeTurnsEffect(String selfName) { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + staticText = "you can't cast " + selfName + " during your first, second, or third turns of the game"; + } + + private CantCastDuringFirstThreeTurnsEffect(final CantCastDuringFirstThreeTurnsEffect effect) { + super(effect); + } + + @Override + public CantCastDuringFirstThreeTurnsEffect copy() { + return new CantCastDuringFirstThreeTurnsEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!event.getSourceId().equals(source.getSourceId())) { + return false; + } + Player controller = game.getPlayer(source.getControllerId()); + return controller != null && controller.getTurns() <= 3 && game.isActivePlayer(source.getControllerId()); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/AirbendTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/AirbendTargetEffect.java new file mode 100644 index 00000000000..b98f6b35221 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/keyword/AirbendTargetEffect.java @@ -0,0 +1,122 @@ +package mage.abilities.effects.keyword; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.AsThoughEffectType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public class AirbendTargetEffect extends OneShotEffect { + + public AirbendTargetEffect() { + super(Outcome.Benefit); + } + + private AirbendTargetEffect(final AirbendTargetEffect effect) { + super(effect); + } + + @Override + public AirbendTargetEffect copy() { + return new AirbendTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Set permanents = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (player == null || permanents.isEmpty()) { + return false; + } + player.moveCards(permanents, Zone.EXILED, source, game); + Cards cards = new CardsImpl(permanents); + cards.retainZone(Zone.EXILED, game); + for (Card card : cards.getCards(game)) { + game.addEffect(new AirbendingCastEffect(card, game), source); + } + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.AIRBENDED, source.getSourceId(), + source, source.getControllerId() + )); + return true; + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return "airbend " + getTargetPointer().describeTargets(mode.getTargets(), ""); + } +} + +class AirbendingCastEffect extends AsThoughEffectImpl { + + AirbendingCastEffect(Card card, Game game) { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.AIDontUseIt); + this.setTargetPointer(new FixedTarget(card, game)); + } + + private AirbendingCastEffect(final AirbendingCastEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public AirbendingCastEffect copy() { + return new AirbendingCastEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null) { + discard(); + return false; + } + if (!card.getId().equals(objectId) || !card.isOwnedBy(affectedControllerId)) { + return false; + } + Player player = game.getPlayer(affectedControllerId); + if (player == null) { + return false; + } + Costs newCosts = new CostsImpl<>(); + newCosts.addAll(card.getSpellAbility().getCosts()); + player.setCastSourceIdWithAlternateMana(card.getId(), new ManaCostsImpl<>("{2}"), newCosts); + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/ConniveTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/ConniveTargetEffect.java new file mode 100644 index 00000000000..cf2edfe58c1 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/keyword/ConniveTargetEffect.java @@ -0,0 +1,44 @@ +package mage.abilities.effects.keyword; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class ConniveTargetEffect extends OneShotEffect { + + public ConniveTargetEffect() { + super(Outcome.Benefit); + } + + private ConniveTargetEffect(final ConniveTargetEffect effect) { + super(effect); + } + + @Override + public ConniveTargetEffect copy() { + return new ConniveTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + ConniveSourceEffect.connive(game.getPermanent(targetId), 1, source, game); + } + return true; + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return this.getTargetPointer().describeTargets(mode.getTargets(), "it") + " connives"; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/EarthbendTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/EarthbendTargetEffect.java new file mode 100644 index 00000000000..9982a5dc7fc --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/keyword/EarthbendTargetEffect.java @@ -0,0 +1,114 @@ +package mage.abilities.effects.keyword; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.Mode; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * @author TheElk801 + */ +public class EarthbendTargetEffect extends OneShotEffect { + + private final int amount; + + public EarthbendTargetEffect(int amount) { + super(Outcome.Benefit); + this.amount = amount; + } + + private EarthbendTargetEffect(final EarthbendTargetEffect effect) { + super(effect); + this.amount = effect.amount; + } + + @Override + public EarthbendTargetEffect copy() { + return new EarthbendTargetEffect(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 BecomesCreatureTargetEffect( + new CreatureToken(0, 0) + .withAbility(HasteAbility.getInstance()), + false, true, Duration.Custom + ), source); + permanent.addCounters(CounterType.P1P1.createInstance(amount), source, game); + game.addDelayedTriggeredAbility(new EarthbendingDelayedTriggeredAbility(permanent, game), source); + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.EARTHBENDED, permanent.getId(), + source, source.getControllerId(), amount + )); + return true; + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return "earthbend " + amount + ". (Target land you control becomes a 0/0 creature " + + "with haste that's still a land. Put " + CardUtil.numberToText(amount, "a") + + " +1/+1 counter" + (amount > 1 ? "s" : "") + " on it. " + + "When it dies or is exiled, return it to the battlefield tapped.)"; + } +} + +class EarthbendingDelayedTriggeredAbility extends DelayedTriggeredAbility { + + private final MageObjectReference mor; + + EarthbendingDelayedTriggeredAbility(Permanent permanent, Game game) { + super(new ReturnToBattlefieldUnderOwnerControlTargetEffect(true, false), Duration.Custom, true, false); + this.mor = new MageObjectReference(permanent, game, 1); + this.getAllEffects().setTargetPointer(new FixedTarget(this.mor)); + } + + private EarthbendingDelayedTriggeredAbility(final EarthbendingDelayedTriggeredAbility ability) { + super(ability); + this.mor = ability.mor; + } + + @Override + public EarthbendingDelayedTriggeredAbility copy() { + return new EarthbendingDelayedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + return zEvent.getFromZone() == Zone.BATTLEFIELD + && (zEvent.getToZone() == Zone.GRAVEYARD || zEvent.getToZone() == Zone.EXILED) + && mor.refersTo(zEvent.getTarget(), game, -1); + } + + @Override + public String getRule() { + return "When it dies or is exiled, return it to the battlefield tapped."; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java b/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java index d6baf07740f..5ef72d017fc 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java @@ -160,7 +160,7 @@ class ChampionExileCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java b/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java index 377f7743323..70f36cad717 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java @@ -115,7 +115,7 @@ class CraftCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override @@ -169,7 +169,7 @@ class CraftEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); Card card = game.getCard(source.getSourceId()); - if (player == null || card == null || card.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter() + 1) { + if (player == null || card == null || card.getZoneChangeCounter(game) != source.getStackMomentSourceZCC() + 1) { return false; } game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE); diff --git a/Mage/src/main/java/mage/abilities/keyword/DisturbAbility.java b/Mage/src/main/java/mage/abilities/keyword/DisturbAbility.java index 0c5d97a8fab..1061efbd2ee 100644 --- a/Mage/src/main/java/mage/abilities/keyword/DisturbAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/DisturbAbility.java @@ -1,8 +1,12 @@ package mage.abilities.keyword; +import mage.abilities.Ability; +import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility; import mage.abilities.common.SpellTransformedAbility; +import mage.abilities.effects.common.ExileSourceEffect; import mage.cards.Card; -import mage.constants.*; +import mage.constants.SpellAbilityCastMode; +import mage.constants.Zone; import mage.game.Game; import java.util.UUID; @@ -21,6 +25,7 @@ import java.util.UUID; * @author notgreat, weirddan455, JayDi85 */ public class DisturbAbility extends SpellTransformedAbility { + public DisturbAbility(Card card, String manaCost) { super(card, manaCost); @@ -58,4 +63,8 @@ public class DisturbAbility extends SpellTransformedAbility { return "Disturb " + this.manaCost + " (You may cast this card transformed from your graveyard for its disturb cost.)"; } + + public static Ability makeBackAbility() { + return new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead")); + } } diff --git a/Mage/src/main/java/mage/abilities/keyword/EscapeAbility.java b/Mage/src/main/java/mage/abilities/keyword/EscapeAbility.java index aa370c2649d..d841f172422 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EscapeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EscapeAbility.java @@ -101,7 +101,7 @@ public class EscapeAbility extends SpellAbility { @Override public boolean activate(Game game, Set allowedIdentifiers, boolean noMana) { if (super.activate(game, allowedIdentifiers, noMana)) { - game.getState().setValue(CASTED_WITH_ESCAPE_KEY + getSourceId().toString() + (getSourceObjectZoneChangeCounter() + 1), Boolean.TRUE); + game.getState().setValue(CASTED_WITH_ESCAPE_KEY + getSourceId().toString() + (getStackMomentSourceZCC() + 1), Boolean.TRUE); return true; } return false; diff --git a/Mage/src/main/java/mage/abilities/keyword/FirebendingAbility.java b/Mage/src/main/java/mage/abilities/keyword/FirebendingAbility.java new file mode 100644 index 00000000000..6ac9f6c8b24 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/FirebendingAbility.java @@ -0,0 +1,91 @@ +package mage.abilities.keyword; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +import java.util.Collections; + +/** + * @author TheElk801 + */ +public class FirebendingAbility extends AttacksTriggeredAbility { + + private final DynamicValue amount; + + public FirebendingAbility(int amount) { + this(StaticValue.get(amount)); + } + + public FirebendingAbility(DynamicValue amount) { + super(new FirebendingAbilityEffect(amount)); + this.amount = amount; + } + + private FirebendingAbility(final FirebendingAbility ability) { + super(ability); + this.amount = ability.amount; + } + + @Override + public FirebendingAbility copy() { + return new FirebendingAbility(this); + } + + @Override + public String getRule() { + if (amount instanceof StaticValue) { + return "firebending " + amount + + " (Whenever this creature attacks, add " + + String.join("", Collections.nCopies(((StaticValue) amount).getValue(), "{R}")) + + ". This mana lasts until end of combat.)"; + } + return "firebending X, where X is " + amount.getMessage() + + ". (Whenever this creature attacks, add X {R}. This mana lasts until end of combat.)"; + } +} + +class FirebendingAbilityEffect extends OneShotEffect { + + private final DynamicValue amount; + + FirebendingAbilityEffect(DynamicValue amount) { + super(Outcome.Benefit); + this.amount = amount; + } + + private FirebendingAbilityEffect(final FirebendingAbilityEffect effect) { + super(effect); + this.amount = effect.amount; + } + + @Override + public FirebendingAbilityEffect copy() { + return new FirebendingAbilityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int amount = Math.max(this.amount.calculate(game, source, this), 0); + if (amount > 0) { + player.getManaPool().addMana(Mana.RedMana(amount), game, source, Duration.EndOfCombat); + } + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.FIREBENDED, source.getSourceId(), + source, source.getControllerId(), amount + )); + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java index d094a49f537..421a25858d3 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java @@ -11,11 +11,15 @@ import mage.abilities.costs.Costs; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.*; import mage.constants.*; +import mage.filter.common.FilterNonlandCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.ExileZone; import mage.game.Game; import mage.game.events.GameEvent; @@ -90,132 +94,236 @@ public class ForetellAbility extends SpecialAction { return " foretells a card from hand"; } - static class ForetellExileEffect extends OneShotEffect { - - private final Card card; - String foretellCost; - String foretellSplitCost; - - ForetellExileEffect(Card card, String foretellCost, String foretellSplitCost) { - super(Outcome.Neutral); - this.card = card; - this.foretellCost = foretellCost; - this.foretellSplitCost = foretellSplitCost; - } - - protected ForetellExileEffect(final ForetellExileEffect effect) { - super(effect); - this.card = effect.card; - this.foretellCost = effect.foretellCost; - this.foretellSplitCost = effect.foretellSplitCost; - } - - @Override - public ForetellExileEffect copy() { - return new ForetellExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null - && card != null) { - - // get main card id - UUID mainCardId = card.getMainCard().getId(); - - // retrieve the exileId of the foretold card - UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); - - // foretell turn number shows up on exile window - ExileTargetEffect effect = new ExileTargetEffect(exileId, " Foretell Turn Number: " + game.getTurnNum()); - - // remember turn number it was cast - game.getState().setValue(mainCardId.toString() + "Foretell Turn Number", game.getTurnNum()); - - // remember the foretell cost - game.getState().setValue(mainCardId.toString() + "Foretell Cost", foretellCost); - game.getState().setValue(mainCardId.toString() + "Foretell Split Cost", foretellSplitCost); - - // exile the card face-down - effect.setWithName(false); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - effect.apply(game, source); - card.setFaceDown(true, game); - game.addEffect(new ForetellAddCostEffect(new MageObjectReference(card, game)), source); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.FORETELL, card.getId(), null, source.getControllerId())); - return true; - } - return false; - } + public static boolean isCardInForetell(Card card, Game game) { + // searching ForetellCostAbility - it adds for foretelled cards only after exile + return card.getAbilities(game).containsClass(ForetellCostAbility.class); } - static class ForetellLookAtCardEffect extends AsThoughEffectImpl { + public static ContinuousEffect makeAddForetellEffect() { + return new ForetellAddAbilityEffect(); + } - ForetellLookAtCardEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.AIDontUseIt); + /** + * For use in apply() method of OneShotEffect + * Exile the target card. It becomes foretold. + * Its foretell cost is its mana cost reduced by [amountToReduceCost] + */ + public static boolean doExileBecomesForetold(Card card, Game game, Ability source, int amountToReduceCost) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; } - protected ForetellLookAtCardEffect(final ForetellLookAtCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public ForetellLookAtCardEffect copy() { - return new ForetellLookAtCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (affectedControllerId.equals(source.getControllerId())) { - Card card = game.getCard(objectId); - if (card != null) { - MageObject sourceObject = game.getObject(source); - if (sourceObject == null) { - return false; - } - UUID mainCardId = card.getMainCard().getId(); - UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); - ExileZone exile = game.getExile().getExileZone(exileId); - return exile != null - && exile.contains(mainCardId); + // process Split, MDFC, and Adventure cards first + // note that 'Foretell Cost' refers to the main card (left) and 'Foretell Split Cost' refers to the (right) card if it exists + ForetellAbility foretellAbility = null; + if (card instanceof SplitCard) { + String leftHalfCost = CardUtil.reduceCost(((SplitCard) card).getLeftHalfCard().getManaCost(), amountToReduceCost).getText(); + String rightHalfCost = CardUtil.reduceCost(((SplitCard) card).getRightHalfCard().getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Cost", leftHalfCost); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Split Cost", rightHalfCost); + foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); + } else if (card instanceof ModalDoubleFacedCard) { + ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + if (!leftHalfCard.isLand(game)) { // Only MDFC cards with a left side a land have a land on the right side too + String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Cost", leftHalfCost); + ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + if (rightHalfCard.isLand(game)) { + foretellAbility = new ForetellAbility(card, leftHalfCost); + } else { + String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Split Cost", rightHalfCost); + foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); } } - return false; + } else if (card instanceof CardWithSpellOption) { + String creatureCost = CardUtil.reduceCost(card.getMainCard().getManaCost(), amountToReduceCost).getText(); + String spellCost = CardUtil.reduceCost(((CardWithSpellOption) card).getSpellCard().getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Cost", creatureCost); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Split Cost", spellCost); + foretellAbility = new ForetellAbility(card, creatureCost, spellCost); + } else if (!card.isLand(game)) { + // normal card + String costText = CardUtil.reduceCost(card.getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getId().toString() + "Foretell Cost", costText); + foretellAbility = new ForetellAbility(card, costText); } + + // All card types (including lands) must be exiled + UUID exileId = CardUtil.getExileZoneId(card.getMainCard().getId().toString() + "foretellAbility", game); + controller.moveCardsToExile(card, source, game, false, exileId, " Foretell Turn Number: " + game.getTurnNum()); + card.setFaceDown(true, game); + + // all done pre-processing so stick the foretell cost effect onto the main card + // note that the card is not foretell'd into exile, it is put into exile and made foretold + // If the card is a non-land, it will not be exiled. + if (foretellAbility != null) { + // copy source and use it for the foretold effect on the exiled card + // bug #8673 + Ability copiedSource = source.copy(); + copiedSource.newId(); + copiedSource.setSourceId(card.getId()); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Turn Number", game.getTurnNum()); + foretellAbility.setSourceId(card.getId()); + foretellAbility.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, foretellAbility); + foretellAbility.activate(game, true); + game.addEffect(new ForetellAddCostEffect(new MageObjectReference(card, game)), copiedSource); + game.fireEvent(new GameEvent(GameEvent.EventType.CARD_FORETOLD, card.getId(), copiedSource, copiedSource.getControllerId(), 0, false)); + } + return true; } - public static class ForetellAddCostEffect extends ContinuousEffectImpl { +} - private final MageObjectReference mor; +class ForetellExileEffect extends OneShotEffect { - public ForetellAddCostEffect(MageObjectReference mor) { - super(Duration.EndOfGame, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.mor = mor; - staticText = "Foretold card"; + private final Card card; + String foretellCost; + String foretellSplitCost; + + ForetellExileEffect(Card card, String foretellCost, String foretellSplitCost) { + super(Outcome.Neutral); + this.card = card; + this.foretellCost = foretellCost; + this.foretellSplitCost = foretellSplitCost; + } + + private ForetellExileEffect(final ForetellExileEffect effect) { + super(effect); + this.card = effect.card; + this.foretellCost = effect.foretellCost; + this.foretellSplitCost = effect.foretellSplitCost; + } + + @Override + public ForetellExileEffect copy() { + return new ForetellExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null + && card != null) { + + // get main card id + UUID mainCardId = card.getMainCard().getId(); + + // retrieve the exileId of the foretold card + UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); + + // foretell turn number shows up on exile window + ExileTargetEffect effect = new ExileTargetEffect(exileId, " Foretell Turn Number: " + game.getTurnNum()); + + // remember turn number it was cast + game.getState().setValue(mainCardId.toString() + "Foretell Turn Number", game.getTurnNum()); + + // remember the foretell cost + game.getState().setValue(mainCardId.toString() + "Foretell Cost", foretellCost); + game.getState().setValue(mainCardId.toString() + "Foretell Split Cost", foretellSplitCost); + + // exile the card face-down + effect.setWithName(false); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + effect.apply(game, source); + card.setFaceDown(true, game); + game.addEffect(new ForetellAddCostEffect(new MageObjectReference(card, game)), source); + game.fireEvent(new GameEvent(GameEvent.EventType.CARD_FORETOLD, card.getId(), source, source.getControllerId(), 0, true)); + return true; } + return false; + } +} - protected ForetellAddCostEffect(final ForetellAddCostEffect effect) { - super(effect); - this.mor = effect.mor; - } +class ForetellLookAtCardEffect extends AsThoughEffectImpl { - @Override - public boolean apply(Game game, Ability source) { - Card card = mor.getCard(game); + ForetellLookAtCardEffect() { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.AIDontUseIt); + } + + private ForetellLookAtCardEffect(final ForetellLookAtCardEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public ForetellLookAtCardEffect copy() { + return new ForetellLookAtCardEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); if (card != null) { + MageObject sourceObject = game.getObject(source); + if (sourceObject == null) { + return false; + } UUID mainCardId = card.getMainCard().getId(); - if (game.getState().getZone(mainCardId) == Zone.EXILED) { - String foretellCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Cost"); - String foretellSplitCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Split Cost"); - if (card instanceof SplitCard) { - if (foretellCost != null) { - SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard(); + UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); + ExileZone exile = game.getExile().getExileZone(exileId); + return exile != null + && exile.contains(mainCardId); + } + } + return false; + } +} + +class ForetellAddCostEffect extends ContinuousEffectImpl { + + private final MageObjectReference mor; + + ForetellAddCostEffect(MageObjectReference mor) { + super(Duration.EndOfGame, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.mor = mor; + staticText = "Foretold card"; + } + + private ForetellAddCostEffect(final ForetellAddCostEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = mor.getCard(game); + if (card != null) { + UUID mainCardId = card.getMainCard().getId(); + if (game.getState().getZone(mainCardId) == Zone.EXILED) { + String foretellCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Cost"); + String foretellSplitCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Split Cost"); + if (card instanceof SplitCard) { + if (foretellCost != null) { + SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard(); + ForetellCostAbility ability = new ForetellCostAbility(foretellCost); + ability.setSourceId(leftHalfCard.getId()); + ability.setControllerId(source.getControllerId()); + ability.setSpellAbilityType(leftHalfCard.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(leftHalfCard.getName()); + game.getState().addOtherAbility(leftHalfCard, ability); + } + if (foretellSplitCost != null) { + SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard(); + ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); + ability.setSourceId(rightHalfCard.getId()); + ability.setControllerId(source.getControllerId()); + ability.setSpellAbilityType(rightHalfCard.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(rightHalfCard.getName()); + game.getState().addOtherAbility(rightHalfCard, ability); + } + } else if (card instanceof ModalDoubleFacedCard) { + if (foretellCost != null) { + ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + // some MDFC's are land IE: sea gate restoration + if (!leftHalfCard.isLand(game)) { ForetellCostAbility ability = new ForetellCostAbility(foretellCost); ability.setSourceId(leftHalfCard.getId()); ability.setControllerId(source.getControllerId()); @@ -223,8 +331,11 @@ public class ForetellAbility extends SpecialAction { ability.setAbilityName(leftHalfCard.getName()); game.getState().addOtherAbility(leftHalfCard, ability); } - if (foretellSplitCost != null) { - SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard(); + } + if (foretellSplitCost != null) { + ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + // some MDFC's are land IE: sea gate restoration + if (!rightHalfCard.isLand(game)) { ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); ability.setSourceId(rightHalfCard.getId()); ability.setControllerId(source.getControllerId()); @@ -232,240 +343,276 @@ public class ForetellAbility extends SpecialAction { ability.setAbilityName(rightHalfCard.getName()); game.getState().addOtherAbility(rightHalfCard, ability); } - } else if (card instanceof ModalDoubleFacedCard) { - if (foretellCost != null) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - // some MDFC's are land IE: sea gate restoration - if (!leftHalfCard.isLand(game)) { - ForetellCostAbility ability = new ForetellCostAbility(foretellCost); - ability.setSourceId(leftHalfCard.getId()); - ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(leftHalfCard.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(leftHalfCard.getName()); - game.getState().addOtherAbility(leftHalfCard, ability); - } - } - if (foretellSplitCost != null) { - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); - // some MDFC's are land IE: sea gate restoration - if (!rightHalfCard.isLand(game)) { - ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); - ability.setSourceId(rightHalfCard.getId()); - ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(rightHalfCard.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(rightHalfCard.getName()); - game.getState().addOtherAbility(rightHalfCard, ability); - } - } - } else if (card instanceof CardWithSpellOption) { - if (foretellCost != null) { - Card creatureCard = card.getMainCard(); - ForetellCostAbility ability = new ForetellCostAbility(foretellCost); - ability.setSourceId(creatureCard.getId()); - ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(creatureCard.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(creatureCard.getName()); - game.getState().addOtherAbility(creatureCard, ability); - } - if (foretellSplitCost != null) { - Card spellCard = ((CardWithSpellOption) card).getSpellCard(); - ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); - ability.setSourceId(spellCard.getId()); - ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(spellCard.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(spellCard.getName()); - game.getState().addOtherAbility(spellCard, ability); - } - } else if (foretellCost != null) { + } + } else if (card instanceof CardWithSpellOption) { + if (foretellCost != null) { + Card creatureCard = card.getMainCard(); ForetellCostAbility ability = new ForetellCostAbility(foretellCost); - ability.setSourceId(card.getId()); + ability.setSourceId(creatureCard.getId()); ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(card.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(card.getName()); - game.getState().addOtherAbility(card, ability); + ability.setSpellAbilityType(creatureCard.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(creatureCard.getName()); + game.getState().addOtherAbility(creatureCard, ability); } - return true; + if (foretellSplitCost != null) { + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); + ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); + ability.setSourceId(spellCard.getId()); + ability.setControllerId(source.getControllerId()); + ability.setSpellAbilityType(spellCard.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(spellCard.getName()); + game.getState().addOtherAbility(spellCard, ability); + } + } else if (foretellCost != null) { + ForetellCostAbility ability = new ForetellCostAbility(foretellCost); + ability.setSourceId(card.getId()); + ability.setControllerId(source.getControllerId()); + ability.setSpellAbilityType(card.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(card.getName()); + game.getState().addOtherAbility(card, ability); } + return true; } - discard(); - return true; - } - - @Override - public ForetellAddCostEffect copy() { - return new ForetellAddCostEffect(this); } + discard(); + return true; } - static class ForetellCostAbility extends SpellAbility { - - private String abilityName; - private SpellAbility spellAbilityToResolve; - - ForetellCostAbility(String foretellCost) { - super(null, "Testing", Zone.EXILED, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.NORMAL); - // Needed for Dream Devourer and Ethereal Valkyrie reducing the cost of a colorless CMC 2 or less spell to 0 - // CardUtil.reduceCost returns an empty string in that case so we add a cost of 0 here - // https://github.com/magefree/mage/issues/7607 - if (foretellCost != null && foretellCost.isEmpty()) { - foretellCost = "{0}"; - } - this.setAdditionalCostsRuleVisible(false); - this.name = "Foretell " + foretellCost; - this.addCost(new ManaCostsImpl<>(foretellCost)); - } - - protected ForetellCostAbility(final ForetellCostAbility ability) { - super(ability); - this.spellAbilityType = ability.spellAbilityType; - this.abilityName = ability.abilityName; - this.spellAbilityToResolve = ability.spellAbilityToResolve; - } - - @Override - public ActivationStatus canActivate(UUID playerId, Game game) { - if (super.canActivate(playerId, game).canActivate()) { - Card card = game.getCard(getSourceId()); - if (card != null) { - UUID mainCardId = card.getMainCard().getId(); - // Card must be in the exile zone - if (game.getState().getZone(mainCardId) != Zone.EXILED) { - return ActivationStatus.getFalse(); - } - Integer foretoldTurn = (Integer) game.getState().getValue(mainCardId.toString() + "Foretell Turn Number"); - UUID exileId = (UUID) game.getState().getValue(mainCardId.toString() + "foretellAbility"); - // Card must be Foretold - if (foretoldTurn == null || exileId == null) { - return ActivationStatus.getFalse(); - } - // Can't be cast if the turn it was Foretold is the same - if (foretoldTurn == game.getTurnNum()) { - return ActivationStatus.getFalse(); - } - // Check that the card is actually in the exile zone (ex: Oblivion Ring exiles it after it was Foretold, etc) - ExileZone exileZone = game.getState().getExile().getExileZone(exileId); - if (exileZone != null - && exileZone.isEmpty()) { - return ActivationStatus.getFalse(); - } - 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); - } - } else if (card instanceof CardWithSpellOption) { - if (card.getMainCard().getName().equals(abilityName)) { - return card.getMainCard().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); - } - } - 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 if (card instanceof CardWithSpellOption) { - if (card.getMainCard().getName().equals(abilityName)) { - spellAbilityCopy = card.getMainCard().getSpellAbility().copy(); - } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { - spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy(); - } - } else { - spellAbilityCopy = card.getSpellAbility().copy(); - } - if (spellAbilityCopy == null) { - return null; - } - spellAbilityCopy.setId(this.getId()); - spellAbilityCopy.clearManaCosts(); - spellAbilityCopy.clearManaCostsToPay(); - spellAbilityCopy.addCost(this.getCosts().copy()); - spellAbilityCopy.addCost(this.getManaCosts().copy()); - spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); - spellAbilityToResolve = spellAbilityCopy; - } - } - return spellAbilityToResolve; - } - - @Override - public Costs getCosts() { - if (spellAbilityToResolve == null) { - return super.getCosts(); - } - return spellAbilityToResolve.getCosts(); - } - - @Override - public ForetellCostAbility copy() { - return new ForetellCostAbility(this); - } - - @Override - public String getRule(boolean all) { - StringBuilder sbRule = new StringBuilder("Foretell"); - if (!getCosts().isEmpty()) { - sbRule.append("—"); - } else { - sbRule.append(' '); - } - if (!getManaCosts().isEmpty()) { - sbRule.append(getManaCosts().getText()); - } - if (!getCosts().isEmpty()) { - if (!getManaCosts().isEmpty()) { - sbRule.append(", "); - } - sbRule.append(getCosts().getText()); - sbRule.append('.'); - } - if (abilityName != null) { - sbRule.append(' '); - sbRule.append(abilityName); - } - sbRule.append(" (You may cast this card from exile for its foretell cost.)"); - return sbRule.toString(); - } - - /** - * Used for split card in PlayerImpl method: - * getOtherUseableActivatedAbilities - */ - public void setAbilityName(String abilityName) { - this.abilityName = abilityName; - } - - } - - public static boolean isCardInForetell(Card card, Game game) { - // searching ForetellCostAbility - it adds for foretelled cards only after exile - return card.getAbilities(game).containsClass(ForetellCostAbility.class); + @Override + public ForetellAddCostEffect copy() { + return new ForetellAddCostEffect(this); + } +} + +class ForetellCostAbility extends SpellAbility { + + private String abilityName; + private SpellAbility spellAbilityToResolve; + + ForetellCostAbility(String foretellCost) { + super(null, "Testing", Zone.EXILED, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.NORMAL); + // Needed for Dream Devourer and Ethereal Valkyrie reducing the cost of a colorless CMC 2 or less spell to 0 + // CardUtil.reduceCost returns an empty string in that case so we add a cost of 0 here + // https://github.com/magefree/mage/issues/7607 + if (foretellCost != null && foretellCost.isEmpty()) { + foretellCost = "{0}"; + } + this.setAdditionalCostsRuleVisible(false); + this.name = "Foretell " + foretellCost; + this.addCost(new ManaCostsImpl<>(foretellCost)); + } + + private ForetellCostAbility(final ForetellCostAbility ability) { + super(ability); + this.spellAbilityType = ability.spellAbilityType; + this.abilityName = ability.abilityName; + this.spellAbilityToResolve = ability.spellAbilityToResolve; + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + if (super.canActivate(playerId, game).canActivate()) { + Card card = game.getCard(getSourceId()); + if (card != null) { + UUID mainCardId = card.getMainCard().getId(); + // Card must be in the exile zone + if (game.getState().getZone(mainCardId) != Zone.EXILED) { + return ActivationStatus.getFalse(); + } + Integer foretoldTurn = (Integer) game.getState().getValue(mainCardId.toString() + "Foretell Turn Number"); + UUID exileId = (UUID) game.getState().getValue(mainCardId.toString() + "foretellAbility"); + // Card must be Foretold + if (foretoldTurn == null || exileId == null) { + return ActivationStatus.getFalse(); + } + // Can't be cast if the turn it was Foretold is the same + if (foretoldTurn == game.getTurnNum()) { + return ActivationStatus.getFalse(); + } + // Check that the card is actually in the exile zone (ex: Oblivion Ring exiles it after it was Foretold, etc) + ExileZone exileZone = game.getState().getExile().getExileZone(exileId); + if (exileZone != null + && exileZone.isEmpty()) { + return ActivationStatus.getFalse(); + } + 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); + } + } else if (card instanceof CardWithSpellOption) { + if (card.getMainCard().getName().equals(abilityName)) { + return card.getMainCard().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); + } + } + 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 if (card instanceof CardWithSpellOption) { + if (card.getMainCard().getName().equals(abilityName)) { + spellAbilityCopy = card.getMainCard().getSpellAbility().copy(); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { + spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy(); + } + } else { + spellAbilityCopy = card.getSpellAbility().copy(); + } + if (spellAbilityCopy == null) { + return null; + } + spellAbilityCopy.setId(this.getId()); + spellAbilityCopy.clearManaCosts(); + spellAbilityCopy.clearManaCostsToPay(); + spellAbilityCopy.addCost(this.getCosts().copy()); + spellAbilityCopy.addCost(this.getManaCosts().copy()); + spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); + spellAbilityToResolve = spellAbilityCopy; + } + } + return spellAbilityToResolve; + } + + @Override + public Costs getCosts() { + if (spellAbilityToResolve == null) { + return super.getCosts(); + } + return spellAbilityToResolve.getCosts(); + } + + @Override + public ForetellCostAbility copy() { + return new ForetellCostAbility(this); + } + + @Override + public String getRule(boolean all) { + StringBuilder sbRule = new StringBuilder("Foretell"); + if (!getCosts().isEmpty()) { + sbRule.append("—"); + } else { + sbRule.append(' '); + } + if (!getManaCosts().isEmpty()) { + sbRule.append(getManaCosts().getText()); + } + if (!getCosts().isEmpty()) { + if (!getManaCosts().isEmpty()) { + sbRule.append(", "); + } + sbRule.append(getCosts().getText()); + sbRule.append('.'); + } + if (abilityName != null) { + sbRule.append(' '); + sbRule.append(abilityName); + } + sbRule.append(" (You may cast this card from exile for its foretell cost.)"); + return sbRule.toString(); + } + + /** + * Used for split card in PlayerImpl method: + * getOtherUseableActivatedAbilities + */ + void setAbilityName(String abilityName) { + this.abilityName = abilityName; + } + +} + +class ForetellAddAbilityEffect extends ContinuousEffectImpl { + + private static final FilterNonlandCard filter = new FilterNonlandCard(); + + static { + filter.add(Predicates.not(new AbilityPredicate(ForetellAbility.class))); + } + + ForetellAddAbilityEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.staticText = "Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by {2}"; + } + + private ForetellAddAbilityEffect(final ForetellAddAbilityEffect effect) { + super(effect); + } + + @Override + public ForetellAddAbilityEffect copy() { + return new ForetellAddAbilityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + for (Card card : controller.getHand().getCards(filter, game)) { + ForetellAbility foretellAbility = null; + if (card instanceof SplitCard) { + String leftHalfCost = CardUtil.reduceCost(((SplitCard) card).getLeftHalfCard().getManaCost(), 2).getText(); + String rightHalfCost = CardUtil.reduceCost(((SplitCard) card).getRightHalfCard().getManaCost(), 2).getText(); + foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); + } else if (card instanceof ModalDoubleFacedCard) { + ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + // If front side of MDFC is land, do nothing as Dream Devourer does not apply to lands + // MDFC cards in hand are considered lands if front side is land + if (!leftHalfCard.isLand(game)) { + String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), 2).getText(); + ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + if (rightHalfCard.isLand(game)) { + foretellAbility = new ForetellAbility(card, leftHalfCost); + } else { + String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), 2).getText(); + foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); + } + } + } else if (card instanceof CardWithSpellOption) { + String creatureCost = CardUtil.reduceCost(card.getMainCard().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(); + foretellAbility = new ForetellAbility(card, costText); + } + if (foretellAbility != null) { + foretellAbility.setSourceId(card.getId()); + foretellAbility.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, foretellAbility); + } + } + return true; } } diff --git a/Mage/src/main/java/mage/abilities/keyword/JumpStartAbility.java b/Mage/src/main/java/mage/abilities/keyword/JumpStartAbility.java index 12270fcb6d5..d30d6a8a665 100644 --- a/Mage/src/main/java/mage/abilities/keyword/JumpStartAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/JumpStartAbility.java @@ -126,7 +126,7 @@ class JumpStartReplacementEffect extends ReplacementEffectImpl { if (event.getTargetId().equals(source.getSourceId()) && ((ZoneChangeEvent) event).getFromZone() == Zone.STACK && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { - return game.getState().getZoneChangeCounter(source.getSourceId()) == source.getSourceObjectZoneChangeCounter() + 1; + return game.getState().getZoneChangeCounter(source.getSourceId()) == source.getStackMomentSourceZCC() + 1; } return false; diff --git a/Mage/src/main/java/mage/abilities/keyword/MadnessAbility.java b/Mage/src/main/java/mage/abilities/keyword/MadnessAbility.java index 7297698bac1..d4e5dc7d20c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MadnessAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MadnessAbility.java @@ -280,7 +280,7 @@ enum MadnessCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - MageObject madnessSpell = game.getLastKnownInformation(source.getSourceId(), Zone.STACK, source.getSourceObjectZoneChangeCounter() - 1); + MageObject madnessSpell = game.getLastKnownInformation(source.getSourceId(), Zone.STACK, source.getStackMomentSourceZCC() - 1); if (!(madnessSpell instanceof Spell)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/keyword/MayhemAbility.java b/Mage/src/main/java/mage/abilities/keyword/MayhemAbility.java new file mode 100644 index 00000000000..5563f9940dc --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/MayhemAbility.java @@ -0,0 +1,112 @@ +package mage.abilities.keyword; + +import mage.MageIdentifier; +import mage.MageObjectReference; +import mage.abilities.SpellAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.cards.Card; +import mage.constants.SpellAbilityType; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.DiscardedCardsEvent; +import mage.game.events.GameEvent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class MayhemAbility extends SpellAbility { + + public static final String MAYHEM_ACTIVATION_VALUE_KEY = "mayhemActivation"; + + private final String rule; + + public MayhemAbility(Card card, String manaString) { + super(card.getSpellAbility()); + this.newId(); + this.setCardName(card.getName() + " with Mayhem"); + zone = Zone.GRAVEYARD; + spellAbilityType = SpellAbilityType.BASE_ALTERNATE; + + this.clearManaCosts(); + this.clearManaCostsToPay(); + this.addCost(new ManaCostsImpl<>(manaString)); + this.addWatcher(new MayhemWatcher()); + this.setRuleAtTheTop(true); + this.rule = "Mayhem " + manaString + + " (You may cast this card from your graveyard for " + manaString + + " if you discarded it this turn. Timing rules still apply.)"; + } + + protected MayhemAbility(final MayhemAbility ability) { + super(ability); + this.rule = ability.rule; + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + if (!Zone.GRAVEYARD.match(game.getState().getZone(getSourceId())) + || !MayhemWatcher.checkCard(getSourceId(), game)) { + return ActivationStatus.getFalse(); + } + return super.canActivate(playerId, game); + } + + @Override + public boolean activate(Game game, Set allowedIdentifiers, boolean noMana) { + if (!super.activate(game, allowedIdentifiers, noMana)) { + return false; + } + this.setCostsTag(MAYHEM_ACTIVATION_VALUE_KEY, null); + return true; + } + + @Override + public MayhemAbility copy() { + return new MayhemAbility(this); + } + + @Override + public String getRule() { + return rule; + } +} + +class MayhemWatcher extends Watcher { + + private final Set set = new HashSet<>(); + + MayhemWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.DISCARDED_CARDS) { + return; + } + for (Card card : ((DiscardedCardsEvent) event).getDiscardedCards().getCards(game)) { + set.add(new MageObjectReference(card, game)); + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkCard(UUID cardId, Game game) { + return game + .getState() + .getWatcher(MayhemWatcher.class) + .set + .stream() + .anyMatch(mor -> mor.refersTo(cardId, game)); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/MayhemLandAbility.java b/Mage/src/main/java/mage/abilities/keyword/MayhemLandAbility.java new file mode 100644 index 00000000000..14c5d962023 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/MayhemLandAbility.java @@ -0,0 +1,60 @@ +package mage.abilities.keyword; + +import mage.MageIdentifier; +import mage.abilities.PlayLandAbility; +import mage.cards.Card; +import mage.constants.Zone; +import mage.game.Game; + +import java.util.Set; +import java.util.UUID; + +public class MayhemLandAbility extends PlayLandAbility { + + private final String rule; + + public MayhemLandAbility(Card card) { + super(card.getName()); + this.zone = Zone.GRAVEYARD; + this.newId(); + this.name += " with Mayhem"; + this.addWatcher(new MayhemWatcher()); + this.setRuleAtTheTop(true); + this.rule = "Mayhem " + + " (You may play this card from your graveyard if you discarded it this turn. " + + "Timing rules still apply.)"; + } + + protected MayhemLandAbility(final MayhemLandAbility ability) { + super(ability); + this.rule = ability.rule; + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + if (!Zone.GRAVEYARD.match(game.getState().getZone(getSourceId())) + || !MayhemWatcher.checkCard(getSourceId(), game)) { + return ActivationStatus.getFalse(); + } + return super.canActivate(playerId, game); + } + + @Override + public boolean activate(Game game, Set allowedIdentifiers, boolean noMana) { + if (!super.activate(game, allowedIdentifiers, noMana)) { + return false; + } + this.setCostsTag(MayhemAbility.MAYHEM_ACTIVATION_VALUE_KEY, null); + return true; + } + + @Override + public MayhemLandAbility copy() { + return new MayhemLandAbility(this); + } + + @Override + public String getRule() { + return rule; + } + } \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java b/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java index 32422330d34..7410e49ad31 100644 --- a/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java @@ -171,7 +171,7 @@ class ReturnAttackerToHandTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java b/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java index 7333596bb50..c5a4e2ecbca 100644 --- a/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/UnearthAbility.java @@ -87,7 +87,7 @@ class UnearthDelayedTriggeredAbility extends DelayedTriggeredAbility { } // The delayed trigger source is the card in the graveyard. // So we need to exile the zcc + 1 permanent - MageObjectReference object = new MageObjectReference(getSourceId(), getSourceObjectZoneChangeCounter() + 1, game); + MageObjectReference object = new MageObjectReference(getSourceId(), getStackMomentSourceZCC() + 1, game); Permanent permanent = object.getPermanent(game); if (permanent == null || !permanent.isPhasedIn()) { // Triggers, but do nothing. @@ -131,7 +131,7 @@ class UnearthLeavesBattlefieldEffect extends ReplacementEffectImpl { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; if (zEvent.getFromZone() == Zone.BATTLEFIELD && zEvent.getToZone() != Zone.EXILED) { // Only move it to exile if it was this instance that was moved to battlefield with unearth - return source.getSourceObjectZoneChangeCounter() == game.getState().getZoneChangeCounter(source.getSourceId()); + return source.getStackMomentSourceZCC() == game.getState().getZoneChangeCounter(source.getSourceId()); } } return false; diff --git a/Mage/src/main/java/mage/abilities/keyword/WebSlingingAbility.java b/Mage/src/main/java/mage/abilities/keyword/WebSlingingAbility.java index 4faa0001a6e..be683fb32fb 100644 --- a/Mage/src/main/java/mage/abilities/keyword/WebSlingingAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/WebSlingingAbility.java @@ -16,7 +16,7 @@ import mage.target.common.TargetControlledPermanent; import java.util.Set; /** - * @author LevelX2 + * @author TheElk801 */ public class WebSlingingAbility extends SpellAbility { diff --git a/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java b/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java index 9e9b2f9ead3..ce1a111e405 100644 --- a/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java @@ -44,19 +44,16 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl public ActivationStatus canActivate(UUID playerId, Game game) { // check if player is in the process of playing spell costs and they are no longer allowed to use // activated mana abilities (e.g. because they started to use improvise or convoke) - if (!game.getStack().isEmpty()) { - StackObject stackObject = game.getStack().getFirst(); - if (stackObject instanceof Spell) { - switch (((Spell) stackObject).getCurrentActivatingManaAbilitiesStep()) { - case BEFORE: - case NORMAL: - break; - case AFTER: - return ActivationStatus.getFalse(); - } + StackObject stackObject = game.getStack().getFirstOrNull(); + if (stackObject instanceof Spell) { + switch (((Spell) stackObject).getCurrentActivatingManaAbilitiesStep()) { + case BEFORE: + case NORMAL: + break; + case AFTER: + return ActivationStatus.getFalse(); } } - return super.canActivate(playerId, game); } diff --git a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java index 55d98a00b13..7c5d37b484f 100644 --- a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java @@ -90,12 +90,14 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { boolean toRet = false; for (TriggeredAbility ability : triggeredAbilities) { - for (Effect e : getEffects()) { //Add effects to the sub-abilities so that they can set target pointers + for (Effect e : getEffects()) { // Add effects to the sub-abilities so that they can set target pointers ability.addEffect(e); } + ability.getTargets().addAll(this.getTargets()); // AtStepTriggeredAbility automatically sets target pointer if it can't find any targets if (ability.checkEventType(event, game) && ability.checkTrigger(event, game) && ability.checkTriggerCondition(game)) { toRet = true; } + ability.getTargets().clear(); ability.getEffects().clear(); //Remove afterwards, ensures that they remain synced even with copying } return toRet; diff --git a/Mage/src/main/java/mage/cards/ExpansionSet.java b/Mage/src/main/java/mage/cards/ExpansionSet.java index 90867e8ef2b..88d5a506841 100644 --- a/Mage/src/main/java/mage/cards/ExpansionSet.java +++ b/Mage/src/main/java/mage/cards/ExpansionSet.java @@ -145,7 +145,8 @@ public abstract class ExpansionSet implements Serializable { return this.graphicInfo != null && this.graphicInfo.getFrameStyle() != null && (this.graphicInfo.getFrameStyle() == FrameStyle.RETRO - || this.graphicInfo.getFrameStyle() == FrameStyle.LEA_ORIGINAL_DUAL_LAND_ART_BASIC); + || this.graphicInfo.getFrameStyle() == FrameStyle.LEA_ORIGINAL_DUAL_LAND_ART_BASIC + || this.graphicInfo.getFrameStyle() == FrameStyle.UGL_FULL_ART_BASIC); } } diff --git a/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java b/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java index f4ae4e9750a..06727a5e7a5 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java +++ b/Mage/src/main/java/mage/cards/decks/importer/CardLookup.java @@ -3,9 +3,10 @@ package mage.cards.decks.importer; import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; +import mage.util.CardUtil; import java.util.List; -import java.util.Optional; +import java.util.Objects; /** * Deck import: helper class to mock cards repository @@ -14,49 +15,40 @@ public class CardLookup { public static final CardLookup instance = new CardLookup(); - public Optional lookupCardInfo(String name) { - return Optional.ofNullable(CardRepository.instance.findPreferredCoreExpansionCard(name)); + public CardInfo lookupCardInfo(String name) { + return CardRepository.instance.findPreferredCoreExpansionCard(name); } public List lookupCardInfo(CardCriteria criteria) { + // can be override to make fake lookup, e.g. for tests return CardRepository.instance.findCards(criteria); } - public Optional lookupCardInfo(String name, String set) { - if (set == null) { - return lookupCardInfo(name); + public CardInfo lookupCardInfo(String name, String set, String cardNumber) { + CardCriteria cardCriteria = new CardCriteria(); + cardCriteria.name(name); + if (set != null) { + cardCriteria.setCodes(set); + } + if (cardNumber != null) { + int intCardNumber = CardUtil.parseCardNumberAsInt(cardNumber); + cardCriteria.minCardNumber(intCardNumber); + cardCriteria.maxCardNumber(intCardNumber); + } + List foundCards = lookupCardInfo(cardCriteria); + + CardInfo res = null; + + // if possible then use strict card number + if (cardNumber != null) { + res = foundCards.stream().filter(c -> Objects.equals(c.getCardNumber(), cardNumber)).findAny().orElse(null); } - Optional result = lookupCardInfo(new CardCriteria().name(name).setCodes(set)) - .stream() - .findAny(); - if (result.isPresent()) { - return result; + if (res == null) { + res = foundCards.stream().findAny().orElse(null); } - return lookupCardInfo(name); - } - - public Optional lookupCardInfo(String name, String set, String cardNumber) { - Optional result; - try { - int intCardNumber = Integer.parseInt(cardNumber); - result = lookupCardInfo( - new CardCriteria() - .name(name) - .setCodes(set) - .minCardNumber(intCardNumber) - .maxCardNumber(intCardNumber)) - .stream() - .findAny(); - if (result.isPresent()) { - return result; - } - } catch (NumberFormatException ignore) { - /* ignored */ - } - - return lookupCardInfo(name, set); + return res; } } diff --git a/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java index 7d75dc94f7a..19fd2086573 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/CodDeckImporter.java @@ -66,14 +66,13 @@ public class CodDeckImporter extends XmlDeckImporter { private static Function> toDeckCardInfo(CardLookup lookup, StringBuilder errors) { return node -> { String name = node.getAttributes().getNamedItem("name").getNodeValue().trim(); - Optional cardInfo = lookup.lookupCardInfo(name); - if (cardInfo.isPresent()) { - CardInfo info = cardInfo.get(); + CardInfo cardInfo = lookup.lookupCardInfo(name); + if (cardInfo != null) { int amount = getQuantityFromNode(node); - DeckCardInfo.makeSureCardAmountFine(amount, info.getName()); + DeckCardInfo.makeSureCardAmountFine(amount, cardInfo.getName()); return Collections.nCopies( amount, - new DeckCardInfo(info.getName(), info.getCardNumber(), info.getSetCode()) + new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode()) ).stream(); } else { errors.append("Could not find card: '").append(name).append("'\n"); diff --git a/Mage/src/main/java/mage/cards/decks/importer/DecDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DecDeckImporter.java index 0ec494d82b1..173a2ad88f4 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DecDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DecDeckImporter.java @@ -33,11 +33,10 @@ public class DecDeckImporter extends PlainTextDeckImporter { String lineName = line.substring(delim).trim(); try { int num = Integer.parseInt(lineNum); - Optional cardLookup = getCardLookup().lookupCardInfo(lineName); - if (!cardLookup.isPresent()) { + CardInfo cardInfo = getCardLookup().lookupCardInfo(lineName); + if (cardInfo == null) { sbMessage.append("Could not find card: '").append(lineName).append("' at line ").append(lineCount).append('\n'); } else { - CardInfo cardInfo = cardLookup.get(); DeckCardInfo.makeSureCardAmountFine(num, cardInfo.getName()); DeckCardInfo deckCardInfo = new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode()); for (int i = 0; i < num; i++) { diff --git a/Mage/src/main/java/mage/cards/decks/importer/DekDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DekDeckImporter.java index 92c7df9a6cf..1cc478d0913 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DekDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DekDeckImporter.java @@ -32,7 +32,7 @@ public class DekDeckImporter extends PlainTextDeckImporter { } boolean isSideboard = "true".equals(extractAttribute(line, "Sideboard")); - CardInfo cardInfo = getCardLookup().lookupCardInfo(cardName).orElse(null); + CardInfo cardInfo = getCardLookup().lookupCardInfo(cardName); if (cardInfo == null) { sbMessage.append("Could not find card: '").append(cardName).append("' at line ").append(lineCount).append('\n'); } else { diff --git a/Mage/src/main/java/mage/cards/decks/importer/DraftLogImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DraftLogImporter.java index aea081ed5c7..8b99bd864e4 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DraftLogImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DraftLogImporter.java @@ -29,9 +29,9 @@ public class DraftLogImporter extends PlainTextDeckImporter { Matcher pickMatcher = PICK_PATTERN.matcher(line); if (pickMatcher.matches()) { String name = pickMatcher.group(1); - CardInfo card = getCardLookup().lookupCardInfo(name, currentSet).orElse(null); - if (card != null) { - deckList.getCards().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getSetCode())); + CardInfo cardInfo = getCardLookup().lookupCardInfo(name, currentSet, null); + if (cardInfo != null) { + deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); } else { sbMessage.append("couldn't find: \"").append(name).append("\"\n"); } diff --git a/Mage/src/main/java/mage/cards/decks/importer/MWSDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/MWSDeckImporter.java index 94220902879..7458d3a54ce 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/MWSDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/MWSDeckImporter.java @@ -36,9 +36,9 @@ public class MWSDeckImporter extends PlainTextDeckImporter { int num = Integer.parseInt(lineNum); CardInfo cardInfo = null; if (setCode.isEmpty()) { - cardInfo = getCardLookup().lookupCardInfo(lineName, setCode).orElse(null); + cardInfo = getCardLookup().lookupCardInfo(lineName, setCode, null); } else { - cardInfo = getCardLookup().lookupCardInfo(lineName).orElse(null); + cardInfo = getCardLookup().lookupCardInfo(lineName); } if (cardInfo == null) { diff --git a/Mage/src/main/java/mage/cards/decks/importer/MtgaImporter.java b/Mage/src/main/java/mage/cards/decks/importer/MtgaImporter.java index 8e5047ddda0..471af47c595 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/MtgaImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/MtgaImporter.java @@ -8,6 +8,7 @@ import mage.cards.repository.CardInfo; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -18,7 +19,11 @@ import java.util.regex.Pattern; public class MtgaImporter extends PlainTextDeckImporter { private static final Map SET_REMAPPING = ImmutableMap.of("DAR", "DOM"); - private static final Pattern MTGA_PATTERN = Pattern.compile( + + // example: + // 4 Accumulated Knowledge (A25) 40 + // 4 Accumulated Knowledge + public static final Pattern MTGA_PATTERN = Pattern.compile( "(\\p{Digit}+)" + "\\p{javaWhitespace}+" + "(" + CardNameUtil.CARD_NAME_PATTERN.pattern() + ")" + @@ -33,13 +38,19 @@ public class MtgaImporter extends PlainTextDeckImporter { @Override protected void readLine(String line, DeckCardLists deckList, FixedInfo fixedInfo) { - line = line.trim(); + // moxfield support - remove foil status + line = line.replace(" *F*", ""); - if (line.equals("Deck")) { + line = line.trim(); + String lowerLine = line.toLowerCase(Locale.ENGLISH); + + // mainboard to support decks from archidekt.com + if (lowerLine.startsWith("deck") || lowerLine.startsWith("mainboard")) { + sideboard = false; return; } - if (line.equals("Sideboard") || line.equals("")) { + if (lowerLine.startsWith("sideboard") || lowerLine.startsWith("commander") || lowerLine.startsWith("maybeboard") || lowerLine.equals("")) { sideboard = true; return; } @@ -54,12 +65,12 @@ public class MtgaImporter extends PlainTextDeckImporter { CardInfo found; int count = Integer.parseInt(pattern.group(1)); String name = pattern.group(2); - if (pattern.group(3) != null && pattern.group(4) != null) { + if (pattern.group(3) != null) { String set = SET_REMAPPING.getOrDefault(pattern.group(3), pattern.group(3)); - String cardNumber = pattern.group(4); - found = lookup.lookupCardInfo(name, set, cardNumber).orElse(null); + String cardNumber = pattern.groupCount() >= 4 ? pattern.group(4) : null; + found = lookup.lookupCardInfo(name, set, cardNumber); } else { - found = lookup.lookupCardInfo(name).orElse(null); + found = lookup.lookupCardInfo(name, null, null); } if (found == null) { @@ -74,4 +85,33 @@ public class MtgaImporter extends PlainTextDeckImporter { zone.addAll(Collections.nCopies(count, deckCardInfo.copy())); } + public static boolean isMTGA(String data) { + // examples: + // 4 Accumulated Knowledge (A25) 40 + // 4 Accumulated Knowledge + // Deck + // Commander + // Mainboard - extra mark for archidekt.com + // Sideboard - extra mark for archidekt.com + // Maybeboard - extra mark for archidekt.com + + String firstLine = data.split("\\R", 2)[0]; + String firstLineLower = firstLine.toLowerCase(Locale.ENGLISH); + + // by deck marks + if (firstLineLower.startsWith("deck") + || firstLineLower.startsWith("mainboard") + || firstLineLower.startsWith("sideboard") + || firstLineLower.startsWith("commander") + || firstLineLower.startsWith("maybeboard")) { + return true; + } + + // by card marks + Matcher pattern = MtgaImporter.MTGA_PATTERN.matcher(CardNameUtil.normalizeCardName(firstLine)); + return pattern.matches() + && pattern.groupCount() >= 3 + && pattern.group(3) != null; + } + } diff --git a/Mage/src/main/java/mage/cards/decks/importer/MtgjsonDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/MtgjsonDeckImporter.java index fe3c6cacbba..ab89c48abe6 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/MtgjsonDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/MtgjsonDeckImporter.java @@ -63,11 +63,10 @@ public class MtgjsonDeckImporter extends JsonDeckImporter { } int num = JsonUtil.getAsInt(card, "count"); - Optional cardLookup = getCardLookup().lookupCardInfo(name, setCode); - if (!cardLookup.isPresent()) { + CardInfo cardInfo = getCardLookup().lookupCardInfo(name, setCode, null); + if (cardInfo == null) { sbMessage.append("Could not find card: '").append(name).append("'\n"); } else { - CardInfo cardInfo = cardLookup.get(); for (int i = 0; i < num; i++) { list.add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); } diff --git a/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java index 96fdb210c80..4fd27f3b662 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/O8dDeckImporter.java @@ -63,12 +63,11 @@ public class O8dDeckImporter extends XmlDeckImporter { private static Function> toDeckCardInfo(CardLookup lookup, StringBuilder errors) { return node -> { String name = node.getTextContent(); - Optional cardInfo = lookup.lookupCardInfo(name); - if (cardInfo.isPresent()) { - CardInfo info = cardInfo.get(); + CardInfo cardInfo = lookup.lookupCardInfo(name); + if (cardInfo != null) { return Collections.nCopies( getQuantityFromNode(node), - new DeckCardInfo(info.getName(), info.getCardNumber(), info.getSetCode())).stream(); + new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())).stream(); } else { errors.append("Could not find card: '").append(name).append("'\n"); return Stream.empty(); diff --git a/Mage/src/main/java/mage/collectors/DataCollector.java b/Mage/src/main/java/mage/collectors/DataCollector.java index db3471a4acd..399b304b815 100644 --- a/Mage/src/main/java/mage/collectors/DataCollector.java +++ b/Mage/src/main/java/mage/collectors/DataCollector.java @@ -2,6 +2,7 @@ package mage.collectors; import mage.game.Game; import mage.game.Table; +import mage.players.Player; import java.util.UUID; @@ -11,10 +12,12 @@ import java.util.UUID; * Supported features: * - [x] collect and print game logs in server output, including unit tests * - [x] collect and save full games history and decks - * - [ ] collect and print performance metrics like ApplyEffects calc time or inform players time (pings) - * - [ ] collect and send metrics to third party tools like prometheus + grafana - * - [ ] prepare "attachable" game data for bug reports - * - [ ] record game replays data (GameView history) + * - [ ] TODO: collect and print performance metrics like ApplyEffects calc time or inform players time (pings) + * - [ ] TODO: collect and send metrics to third party tools like prometheus + grafana + * - [x] tests: print used selections (choices, targets, modes, skips) TODO: add yes/no, replacement effect, coins, other choices + * - [ ] TODO: tests: print additional info like current resolve ability? + * - [ ] TODO: prepare "attachable" game data for bug reports + * - [ ] TODO: record game replays data (GameView history) *

* How-to enable or disable: * - use java params like -Dxmage.dataCollectors.saveGameHistory=true @@ -62,7 +65,27 @@ public interface DataCollector { void onChatTable(UUID tableId, String userName, String message); /** - * @param gameId chat sessings don't have full game access, so use onGameStart event to find game's ID before chat + * @param gameId chat session don't have full game access, so use onGameStart event to find game's ID before chat */ void onChatGame(UUID gameId, String userName, String message); + + /** + * Tests only: on any non-target choice like yes/no, mode, etc + */ + void onTestsChoiceUse(Game game, Player player, String usingChoice, String reason); + + /** + * Tests only: on any target choice + */ + void onTestsTargetUse(Game game, Player player, String usingTarget, String reason); + + /** + * Tests only: on push object to stack (calls before activate and make any choice/announce) + */ + void onTestsStackPush(Game game); + + /** + * Tests only: on stack object resolve (calls before starting resolve) + */ + void onTestsStackResolve(Game game); } diff --git a/Mage/src/main/java/mage/collectors/DataCollectorServices.java b/Mage/src/main/java/mage/collectors/DataCollectorServices.java index dc2740f078a..52a18713f37 100644 --- a/Mage/src/main/java/mage/collectors/DataCollectorServices.java +++ b/Mage/src/main/java/mage/collectors/DataCollectorServices.java @@ -4,6 +4,7 @@ import mage.collectors.services.PrintGameLogsDataCollector; import mage.collectors.services.SaveGameHistoryDataCollector; import mage.game.Game; import mage.game.Table; +import mage.players.Player; import org.apache.log4j.Logger; import java.util.LinkedHashSet; @@ -140,4 +141,28 @@ final public class DataCollectorServices implements DataCollector { public void onChatGame(UUID gameId, String userName, String message) { activeServices.forEach(c -> c.onChatGame(gameId, userName, message)); } + + @Override + public void onTestsChoiceUse(Game game, Player player, String usingChoice, String reason) { + if (game.isSimulation()) return; + activeServices.forEach(c -> c.onTestsChoiceUse(game, player, usingChoice, reason)); + } + + @Override + public void onTestsTargetUse(Game game, Player player, String usingTarget, String reason) { + if (game.isSimulation()) return; + activeServices.forEach(c -> c.onTestsTargetUse(game, player, usingTarget, reason)); + } + + @Override + public void onTestsStackPush(Game game) { + if (game.isSimulation()) return; + activeServices.forEach(c -> c.onTestsStackPush(game)); + } + + @Override + public void onTestsStackResolve(Game game) { + if (game.isSimulation()) return; + activeServices.forEach(c -> c.onTestsStackResolve(game)); + } } diff --git a/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java b/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java index 2e5f1793e81..c2e59fe7806 100644 --- a/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java +++ b/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java @@ -3,6 +3,7 @@ package mage.collectors.services; import mage.collectors.DataCollector; import mage.game.Game; import mage.game.Table; +import mage.players.Player; import java.util.UUID; @@ -67,4 +68,24 @@ public abstract class EmptyDataCollector implements DataCollector { public void onChatGame(UUID gameId, String userName, String message) { // nothing } + + @Override + public void onTestsChoiceUse(Game game, Player player, String usingChoice, String reason) { + // nothing + } + + @Override + public void onTestsTargetUse(Game game, Player player, String usingTarget, String reason) { + // nothing + } + + @Override + public void onTestsStackPush(Game game) { + // nothing + } + + @Override + public void onTestsStackResolve(Game game) { + // nothing + } } diff --git a/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java b/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java index f8fc9a8966a..c6c8741505c 100644 --- a/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java +++ b/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java @@ -1,7 +1,9 @@ package mage.collectors.services; import mage.game.Game; +import mage.players.Player; import mage.util.CardUtil; +import mage.util.ConsoleUtil; import org.apache.log4j.Logger; import org.jsoup.Jsoup; @@ -40,9 +42,47 @@ public class PrintGameLogsDataCollector extends EmptyDataCollector { @Override public void onGameLog(Game game, String message) { String needMessage = Jsoup.parse(message).text(); - writeLog("GAME", "LOG", String.format("%s: %s", + writeLog("LOG", "GAME", String.format("%s: %s", CardUtil.getTurnInfo(game), needMessage )); } + + @Override + public void onTestsChoiceUse(Game game, Player player, String choice, String reason) { + String needReason = Jsoup.parse(reason).text(); + writeLog("LOG", "GAME", ConsoleUtil.asYellow(String.format("%s: %s using choice: %s%s", + CardUtil.getTurnInfo(game), + player.getName(), + choice, + reason.isEmpty() ? "" : " (" + needReason + ")" + ))); + } + + @Override + public void onTestsTargetUse(Game game, Player player, String target, String reason) { + String needReason = Jsoup.parse(reason).text(); + writeLog("LOG", "GAME", ConsoleUtil.asYellow(String.format("%s: %s using target: %s%s", + CardUtil.getTurnInfo(game), + player.getName(), + target, + reason.isEmpty() ? "" : " (" + needReason + ")" + ))); + } + + @Override + public void onTestsStackPush(Game game) { + writeLog("LOG", "GAME", String.format("%s: Stack push: %s", + CardUtil.getTurnInfo(game), + game.getStack().toString() + )); + } + + @Override + public void onTestsStackResolve(Game game) { + writeLog("LOG", "GAME", String.format("%s: Stack resolve: %s", + CardUtil.getTurnInfo(game), + game.getStack().toString() + )); + } } diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index b354e9dca7f..50f81cc87e8 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -109,6 +109,7 @@ public enum SubType { BEHOLDER("Beholder", SubTypeSet.CreatureType), BERSERKER("Berserker", SubTypeSet.CreatureType), BIRD("Bird", SubTypeSet.CreatureType), + BISON("Bison", SubTypeSet.CreatureType), BITH("Bith", SubTypeSet.CreatureType, true), // Star Wars BLINKMOTH("Blinkmoth", SubTypeSet.CreatureType), BOAR("Boar", SubTypeSet.CreatureType), @@ -233,6 +234,7 @@ public enum SubType { ILLUSION("Illusion", SubTypeSet.CreatureType), IMP("Imp", SubTypeSet.CreatureType), INCARNATION("Incarnation", SubTypeSet.CreatureType), + INFINITY("Infinity", SubTypeSet.ArtifactType), INKLING("Inkling", SubTypeSet.CreatureType), INQUISITOR("Inquisitor", SubTypeSet.CreatureType), INSECT("Insect", SubTypeSet.CreatureType), @@ -245,6 +247,7 @@ public enum SubType { JUGGERNAUT("Juggernaut", SubTypeSet.CreatureType), // K KALEESH("Kaleesh", SubTypeSet.CreatureType, true), // Star Wars + KANGAROO("Kangaroo", SubTypeSet.CreatureType), KAVU("Kavu", SubTypeSet.CreatureType), KELDOR("KelDor", SubTypeSet.CreatureType, true), KILLBOT("Killbot", SubTypeSet.CreatureType, true), // Unstable @@ -260,6 +263,7 @@ public enum SubType { LAMIA("Lamia", SubTypeSet.CreatureType), LAMMASU("Lammasu", SubTypeSet.CreatureType), LEECH("Leech", SubTypeSet.CreatureType), + LEMUR("Lemur", SubTypeSet.CreatureType), LEVIATHAN("Leviathan", SubTypeSet.CreatureType), LHURGOYF("Lhurgoyf", SubTypeSet.CreatureType), LICID("Licid", SubTypeSet.CreatureType), @@ -399,6 +403,7 @@ public enum SubType { SQUIRREL("Squirrel", SubTypeSet.CreatureType), STARFISH("Starfish", SubTypeSet.CreatureType), STARSHIP("Starship", SubTypeSet.CreatureType, true), // Star Wars + STONE("Stone", SubTypeSet.ArtifactType), SULLUSTAN("Sullustan", SubTypeSet.CreatureType, true), // Star Wars SURRAKAR("Surrakar", SubTypeSet.CreatureType), SURVIVOR("Survivor", SubTypeSet.CreatureType), diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index 6cd4529ef60..c774348bd19 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -86,7 +86,9 @@ public enum CounterType { FELLOWSHIP("fellowship"), FETCH("fetch"), FILIBUSTER("filibuster"), + FILM("film"), FINALITY("finality"), + FIRE("fire"), FIRST_STRIKE("first strike"), FLAME("flame"), FLOOD("flood"), diff --git a/Mage/src/main/java/mage/filter/FilterCard.java b/Mage/src/main/java/mage/filter/FilterCard.java index c4eb8cf57ef..553fac22335 100644 --- a/Mage/src/main/java/mage/filter/FilterCard.java +++ b/Mage/src/main/java/mage/filter/FilterCard.java @@ -2,6 +2,7 @@ package mage.filter; import mage.abilities.Ability; import mage.cards.Card; +import mage.constants.SubType; import mage.constants.TargetController; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; @@ -29,7 +30,18 @@ public class FilterCard extends FilterObject { } public FilterCard(String name) { + this(null, name); + } + + public FilterCard(SubType subType) { + this(subType, subType + " card"); + } + + public FilterCard(SubType subType, String name) { super(name); + if (subType != null) { + this.add(subType.getPredicate()); + } } protected FilterCard(final FilterCard filter) { diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 05e18a8be30..748c56a2dec 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -185,24 +185,33 @@ public final class StaticFilters { FILTER_CARD_LAND_A.setLockedFilter(true); } - public static final FilterBasicLandCard FILTER_CARD_BASIC_LAND = new FilterBasicLandCard(); + public static final FilterLandCard FILTER_CARD_BASIC_LAND = new FilterLandCard("basic land card"); static { + FILTER_CARD_BASIC_LAND.add(SuperType.BASIC.getPredicate()); FILTER_CARD_BASIC_LAND.setLockedFilter(true); } - public static final FilterBasicLandCard FILTER_CARD_BASIC_LANDS = new FilterBasicLandCard("basic land cards"); + public static final FilterLandCard FILTER_CARD_BASIC_LANDS = new FilterLandCard("basic land cards"); static { + FILTER_CARD_BASIC_LANDS.add(SuperType.BASIC.getPredicate()); FILTER_CARD_BASIC_LANDS.setLockedFilter(true); } - public static final FilterBasicLandCard FILTER_CARD_BASIC_LAND_A = new FilterBasicLandCard("a basic land card"); + public static final FilterLandCard FILTER_CARD_BASIC_LAND_A = new FilterLandCard("a basic land card"); static { + FILTER_CARD_BASIC_LAND_A.add(SuperType.BASIC.getPredicate()); FILTER_CARD_BASIC_LAND_A.setLockedFilter(true); } + public static final FilterBasicCard FILTER_CARD_BASIC_PLAINS = new FilterBasicCard(SubType.PLAINS); + + static { + FILTER_CARD_BASIC_PLAINS.setLockedFilter(true); + } + public static final FilterNonlandCard FILTER_CARD_NON_LAND = new FilterNonlandCard(); static { @@ -1188,6 +1197,13 @@ public final class StaticFilters { FILTER_CONTROLLED_CREATURE_P1P1.setLockedFilter(true); } + public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_CREATURES_P1P1 = new FilterControlledCreaturePermanent("creatures you control with a +1/+1 counter on it"); + + static { + FILTER_CONTROLLED_CREATURES_P1P1.add(CounterType.P1P1.getPredicate()); + FILTER_CONTROLLED_CREATURES_P1P1.setLockedFilter(true); + } + public static final FilterControlledCreaturePermanent FILTER_EACH_CONTROLLED_CREATURE_P1P1 = new FilterControlledCreaturePermanent("each creature you control with a +1/+1 counter on it"); static { diff --git a/Mage/src/main/java/mage/filter/common/FilterBasicCard.java b/Mage/src/main/java/mage/filter/common/FilterBasicCard.java new file mode 100644 index 00000000000..5996f7d9042 --- /dev/null +++ b/Mage/src/main/java/mage/filter/common/FilterBasicCard.java @@ -0,0 +1,33 @@ +package mage.filter.common; + +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; + +/** + * @author BetaSteward_at_googlemail.com + */ +public class FilterBasicCard extends FilterCard { + + public FilterBasicCard(SubType subType) { + this(subType, "basic " + subType + " card"); + } + + public FilterBasicCard(String name) { + this(null, name); + } + + public FilterBasicCard(SubType subType, String name) { + super(subType, name); + this.add(SuperType.BASIC.getPredicate()); + } + + protected FilterBasicCard(final FilterBasicCard filter) { + super(filter); + } + + @Override + public FilterBasicCard copy() { + return new FilterBasicCard(this); + } +} diff --git a/Mage/src/main/java/mage/filter/common/FilterBasicLandCard.java b/Mage/src/main/java/mage/filter/common/FilterBasicLandCard.java deleted file mode 100644 index 521d283b722..00000000000 --- a/Mage/src/main/java/mage/filter/common/FilterBasicLandCard.java +++ /dev/null @@ -1,37 +0,0 @@ - -package mage.filter.common; - -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; - -/** - * @author BetaSteward_at_googlemail.com - */ -public class FilterBasicLandCard extends FilterCard { - - public FilterBasicLandCard() { - this("basic land card"); - } - - public FilterBasicLandCard(SubType subType) { - this("basic " + subType + " card"); - this.add(subType.getPredicate()); - } - - public FilterBasicLandCard(String name) { - super(name); - this.add(CardType.LAND.getPredicate()); - this.add(SuperType.BASIC.getPredicate()); - } - - protected FilterBasicLandCard(final FilterBasicLandCard filter) { - super(filter); - } - - @Override - public FilterBasicLandCard copy() { - return new FilterBasicLandCard(this); - } -} diff --git a/Mage/src/main/java/mage/filter/common/FilterBySubtypeCard.java b/Mage/src/main/java/mage/filter/common/FilterBySubtypeCard.java deleted file mode 100644 index b5bb2f023e4..00000000000 --- a/Mage/src/main/java/mage/filter/common/FilterBySubtypeCard.java +++ /dev/null @@ -1,31 +0,0 @@ -package mage.filter.common; - -import mage.constants.SubType; -import mage.filter.FilterCard; - -/** - * TODO: Collapse this into FilterCard - * - * @author LevelX2 - */ - -public class FilterBySubtypeCard extends FilterCard { - - public FilterBySubtypeCard(SubType subtype) { - this(subtype, subtype + " card"); - } - - public FilterBySubtypeCard(SubType subtype, String name) { - super(name); - this.add(subtype.getPredicate()); - } - - protected FilterBySubtypeCard(final FilterBySubtypeCard filter) { - super(filter); - } - - @Override - public FilterBySubtypeCard copy() { - return new FilterBySubtypeCard(this); - } -} diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenCardTypePredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenCardTypePredicate.java new file mode 100644 index 00000000000..ab1e4ab1ec1 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/ChosenCardTypePredicate.java @@ -0,0 +1,36 @@ +package mage.filter.predicate.mageobject; + +import mage.MageObject; +import mage.constants.CardType; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; + +/** + * + * @author jmlundeen + */ +public enum ChosenCardTypePredicate implements ObjectSourcePlayerPredicate { + TRUE(true), FALSE(false); + + private final boolean value; + + ChosenCardTypePredicate(boolean value) { + this.value = value; + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + Object savedType = game.getState().getValue(input.getSourceId() + "_type"); + if (!(savedType instanceof String)) { + return false; + } + CardType cardType = CardType.fromString((String) savedType); + return input.getObject().getCardType(game).contains(cardType) == value; + } + + @Override + public String toString() { + return "Chosen card type"; + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/ManaValueCompareToCountersSourceCountPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/ManaValueCompareToCountersSourceCountPredicate.java new file mode 100644 index 00000000000..721cb9bbf99 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/ManaValueCompareToCountersSourceCountPredicate.java @@ -0,0 +1,39 @@ +package mage.filter.predicate.mageobject; + +import mage.MageObject; +import mage.constants.ComparisonType; +import mage.counters.CounterType; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; + +import java.util.Optional; + +/** + * @author jmlundeen + */ +public class ManaValueCompareToCountersSourceCountPredicate implements ObjectSourcePlayerPredicate { + + private final CounterType counterType; + private final ComparisonType comparisonType; + + public ManaValueCompareToCountersSourceCountPredicate(CounterType counterType, ComparisonType comparisonType) { + this.counterType = counterType; + this.comparisonType = comparisonType; + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + int counterCount = Optional + .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) + .map(permanent -> permanent.getCounters(game)) + .map(counters -> counters.getCount(counterType)) + .orElse(-1); // always false + return ComparisonType.compare(input.getObject().getManaValue(), comparisonType, counterCount); + } + + @Override + public String toString() { + return "mana value " + comparisonType.toString() + " to the number of " + counterType.getName() + " counters on {this}"; + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToAttachedPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToAttachedPredicate.java new file mode 100644 index 00000000000..13ebc21e42d --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToAttachedPredicate.java @@ -0,0 +1,30 @@ +package mage.filter.predicate.permanent; + +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.Optional; + +/** + * @author TheElk801 + */ +public enum AttachedToAttachedPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return Optional + .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .filter(input.getObject()::isAttachedTo) + .isPresent(); + } + + @Override + public String toString() { + return "attached to attached permanent"; + } + +} diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java index cbb2b853a42..09ead62f82e 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java @@ -17,7 +17,7 @@ public enum ConvokedSourcePredicate implements ObjectSourcePlayerPredicate input, Game game) { - HashSet set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>(0)); + HashSet set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>()); return set.contains(new MageObjectReference(input.getObject(), game)); } } diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java index f150d8244ea..77ed2d89118 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java @@ -18,7 +18,7 @@ public enum SaddledSourceThisTurnPredicate implements ObjectSourcePlayerPredicat @Override public boolean apply(ObjectSourcePlayer input, Game game) { // for delayed triggers must use starting zcc (when delayed trigger created) - MageObjectReference startingMor = new MageObjectReference(input.getSourceId(), input.getSource().getSourceObjectZoneChangeCounter(), game); + MageObjectReference startingMor = new MageObjectReference(input.getSourceId(), input.getSource().getStackMomentSourceZCC(), game); return SaddledMountWatcher.checkIfSaddledThisTurn(input.getObject(), startingMor, game); } diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index ad0cd9232d4..ea21c5102b5 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -162,7 +162,7 @@ public interface Game extends MageItem, Serializable, Copyable { // Result must be checked for null. Possible errors search pattern: (\S*) = game.getPlayer.+\n(?!.+\1 != null) Player getPlayer(UUID playerId); - Player getPlayerOrPlaneswalkerController(UUID playerId); + Player getPlayerOrPlaneswalkerController(UUID targetId); /** * Static players list from start of the game. Use it to find player by ID or in game engine. diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index e3f59becee3..62a2b2f17cd 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -412,12 +412,12 @@ public abstract class GameImpl implements Game { } @Override - public Player getPlayerOrPlaneswalkerController(UUID playerId) { - Player player = getPlayer(playerId); + public Player getPlayerOrPlaneswalkerController(UUID targetId) { + Player player = getPlayer(targetId); if (player != null) { return player; } - Permanent permanent = getPermanent(playerId); + Permanent permanent = getPermanent(targetId); if (permanent == null) { return null; } @@ -1841,6 +1841,7 @@ public abstract class GameImpl implements Game { boolean wasError = false; try { top = state.getStack().peek(); + DataCollectorServices.getInstance().onTestsStackResolve(this); top.resolve(this); resetControlAfterSpellResolve(top.getId()); } catch (Throwable e) { @@ -2263,7 +2264,7 @@ public abstract class GameImpl implements Game { // // There are two possibility for the zcc: // 1/ the source is an Ability with a valid (not 0) zcc, and we must use the same. - int zcc = source.getSourceObjectZoneChangeCounter(); + int zcc = source.getStackMomentSourceZCC(); if (zcc == 0) { // 2/ the source has not a valid zcc (it is most likely a StaticAbility instantiated at beginning of game) // we use the source objects's zcc @@ -2819,7 +2820,7 @@ public abstract class GameImpl implements Game { if (attachedTo != null) { for (Ability ability : perm.getAbilities(this)) { if (ability instanceof AttachableToRestrictedAbility) { - if (!((AttachableToRestrictedAbility) ability).canEquip(attachedTo.getId(), null, this)) { + if (!((AttachableToRestrictedAbility) ability).canEquip(attachedTo.getId(), this)) { attachedTo = null; break; } diff --git a/Mage/src/main/java/mage/game/ZonesHandler.java b/Mage/src/main/java/mage/game/ZonesHandler.java index 809fc545a32..d6aa18cd37c 100644 --- a/Mage/src/main/java/mage/game/ZonesHandler.java +++ b/Mage/src/main/java/mage/game/ZonesHandler.java @@ -253,9 +253,9 @@ public final class ZonesHandler { spell = new Spell(card, card.getSpellAbility().copy(), card.getOwnerId(), event.getFromZone(), game); } spell.syncZoneChangeCounterOnStack(card, game); - game.getStack().push(spell); game.getState().setZone(spell.getId(), Zone.STACK); game.getState().setZone(card.getId(), Zone.STACK); + game.getStack().push(game, spell); } break; case BATTLEFIELD: diff --git a/Mage/src/main/java/mage/game/command/dungeons/TombOfAnnihilationDungeon.java b/Mage/src/main/java/mage/game/command/dungeons/TombOfAnnihilationDungeon.java index af928e4acfb..a9416f76d98 100644 --- a/Mage/src/main/java/mage/game/command/dungeons/TombOfAnnihilationDungeon.java +++ b/Mage/src/main/java/mage/game/command/dungeons/TombOfAnnihilationDungeon.java @@ -188,28 +188,22 @@ class OublietteTarget extends TargetSacrifice { return new OublietteTarget(this); } - @Override - public boolean canTarget(UUID playerId, UUID id, Ability ability, Game game) { - if (!super.canTarget(playerId, id, ability, game)) { - return false; - } - Permanent permanent = game.getPermanent(id); - if (permanent == null) { - return false; - } - if (this.getTargets().isEmpty()) { - return true; - } - Cards cards = new CardsImpl(this.getTargets()); - cards.add(permanent); - return cardTypeAssigner.getRoleCount(cards, game) >= cards.size(); - } - - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); + + // only valid roles + Cards existingTargets = new CardsImpl(this.getTargets()); + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + if (permanent == null) { + return true; + } + Cards newTargets = existingTargets.copy(); + newTargets.add(permanent); + return cardTypeAssigner.getRoleCount(newTargets, game) < newTargets.size(); + }); + return possibleTargets; } diff --git a/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java b/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java index ad9f4f4c560..853a832d931 100644 --- a/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java +++ b/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java @@ -29,11 +29,11 @@ public final class EmblemOfCard extends Emblem { String setCode, String infoTypeForError ) { - int cardNumberInt = CardUtil.parseCardNumberAsInt(cardNumber); + int intCardNumber = CardUtil.parseCardNumberAsInt(cardNumber); List found = CardRepository.instance.findCards(new CardCriteria() .name(cardName) - .minCardNumber(cardNumberInt) - .maxCardNumber(cardNumberInt) + .minCardNumber(intCardNumber) + .maxCardNumber(intCardNumber) .setCodes(setCode)); return found.stream() .filter(ci -> ci.getCardNumber().equals(cardNumber)) diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 632c2ede065..ed46fcc7717 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -615,8 +615,12 @@ public class GameEvent implements Serializable { DUNGEON_COMPLETED, TEMPTED_BY_RING, RING_BEARER_CHOSEN, REMOVED_FROM_COMBAT, // targetId id of permanent removed from combat - FORETOLD, // targetId id of card foretold - FORETELL, // targetId id of card foretell playerId id of the controller + /* card foretold + targetId id of card foretold + playerId id of player foretelling card + flag true if player did foretell, false if became foretold without foretell + */ + CARD_FORETOLD, /* villainous choice targetId player making the choice sourceId sourceId of the ability forcing the choice @@ -684,6 +688,17 @@ public class GameEvent implements Serializable { /* rad counter life loss/gain effect */ RADIATION_GAIN_LIFE, + /* for checking sacrifice as a cost + targetId the permanent to be sacrificed + sourceId of the ability + playerId controller of ability + data id of the ability being paid for + */ + PAY_SACRIFICE_COST, + EARTHBENDED, + AIRBENDED, + FIREBENDED, + WATERBENDED, // custom events - must store some unique data to track CUSTOM_EVENT; diff --git a/Mage/src/main/java/mage/game/events/TargetEvent.java b/Mage/src/main/java/mage/game/events/TargetEvent.java index f2c90ebc87b..f9b9f96484f 100644 --- a/Mage/src/main/java/mage/game/events/TargetEvent.java +++ b/Mage/src/main/java/mage/game/events/TargetEvent.java @@ -12,24 +12,21 @@ import java.util.UUID; public class TargetEvent extends GameEvent { /** - * @param target - * @param sourceId * @param sourceControllerId can be different from real controller (example: ability instructs another player to targeting) */ public TargetEvent(Card target, UUID sourceId, UUID sourceControllerId) { - super(GameEvent.EventType.TARGET, target.getId(), null, sourceControllerId); - this.setSourceId(sourceId); + this(target.getId(), sourceId, sourceControllerId); } public TargetEvent(Player target, UUID sourceId, UUID sourceControllerId) { - super(GameEvent.EventType.TARGET, target.getId(), null, sourceControllerId); + this(target.getId(), sourceId, sourceControllerId); + } + + public TargetEvent(UUID targetId, UUID sourceId, UUID sourceControllerId) { + super(GameEvent.EventType.TARGET, targetId, null, sourceControllerId); this.setSourceId(sourceId); } - /** - * @param targetId - * @param source - */ public TargetEvent(UUID targetId, Ability source) { super(GameEvent.EventType.TARGET, targetId, source, source.getControllerId()); } diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index 1447879b362..73075f8f663 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -8,6 +8,7 @@ import mage.constants.Zone; import mage.game.Controllable; import mage.game.Game; import mage.game.GameState; +import mage.util.CardUtil; import java.util.List; import java.util.Set; @@ -177,6 +178,23 @@ public interface Permanent extends Card, Controllable { int getLethalDamage(UUID attackerId, Game game); + /** + * Same arguments as regular damage method, but returns the amount of excess damage dealt instead + * + * @return + */ + default int damageWithExcess(int damage, Ability source, Game game) { + return this.damageWithExcess(damage, source.getSourceId(), source, game); + } + + default int damageWithExcess(int damage, UUID attackerId, Ability source, Game game) { + int lethal = getLethalDamage(attackerId, game); + int excess = Math.max(CardUtil.overflowDec(damage, lethal), 0); + int dealt = Math.min(lethal, damage); + this.damage(dealt, attackerId, source, game); + return excess; + } + void removeAllDamage(Game game); void reset(Game game); diff --git a/Mage/src/main/java/mage/game/permanent/token/AllyToken.java b/Mage/src/main/java/mage/game/permanent/token/AllyToken.java new file mode 100644 index 00000000000..d133366f7b0 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/AllyToken.java @@ -0,0 +1,29 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class AllyToken extends TokenImpl { + + public AllyToken() { + super("Ally Token", "1/1 white Ally creature token"); + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.ALLY); + power = new MageInt(1); + toughness = new MageInt(1); + } + + private AllyToken(final AllyToken token) { + super(token); + } + + public AllyToken copy() { + return new AllyToken(this); + } + +} diff --git a/Mage/src/main/java/mage/game/permanent/token/HumanCitizenToken.java b/Mage/src/main/java/mage/game/permanent/token/HumanCitizenToken.java new file mode 100644 index 00000000000..dd094f16380 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/HumanCitizenToken.java @@ -0,0 +1,31 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author LoneFox + */ +public final class HumanCitizenToken extends TokenImpl { + + public HumanCitizenToken() { + super("Human Token", "1/1 green and white Human Citizen creature token"); + cardType.add(CardType.CREATURE); + color.setGreen(true); + color.setWhite(true); + subtype.add(SubType.HUMAN); + subtype.add(SubType.CITIZEN); + power = new MageInt(1); + toughness = new MageInt(1); + } + + private HumanCitizenToken(final HumanCitizenToken token) { + super(token); + } + + @Override + public HumanCitizenToken copy() { + return new HumanCitizenToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/IllusionVillainToken.java b/Mage/src/main/java/mage/game/permanent/token/IllusionVillainToken.java new file mode 100644 index 00000000000..9862f81e717 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/IllusionVillainToken.java @@ -0,0 +1,30 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class IllusionVillainToken extends TokenImpl { + + public IllusionVillainToken() { + super("Illusion Token", "3/3 blue Illusion Villain creature token"); + cardType.add(CardType.CREATURE); + color.setBlue(true); + + subtype.add(SubType.ILLUSION); + subtype.add(SubType.VILLAIN); + power = new MageInt(3); + toughness = new MageInt(3); + } + + private IllusionVillainToken(final IllusionVillainToken token) { + super(token); + } + + public IllusionVillainToken copy() { + return new IllusionVillainToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/RobotFlyingToken.java b/Mage/src/main/java/mage/game/permanent/token/RobotFlyingToken.java new file mode 100644 index 00000000000..43350acd33a --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/RobotFlyingToken.java @@ -0,0 +1,31 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class RobotFlyingToken extends TokenImpl { + + public RobotFlyingToken() { + super("Robot Token", "1/1 colorless Robot artifact creature tokens with flying"); + cardType.add(CardType.ARTIFACT); + cardType.add(CardType.CREATURE); + subtype.add(SubType.ROBOT); + power = new MageInt(1); + toughness = new MageInt(1); + + addAbility(FlyingAbility.getInstance()); + } + + private RobotFlyingToken(final RobotFlyingToken token) { + super(token); + } + + public RobotFlyingToken copy() { + return new RobotFlyingToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/SoldierFirebendingToken.java b/Mage/src/main/java/mage/game/permanent/token/SoldierFirebendingToken.java new file mode 100644 index 00000000000..80dd650652a --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/SoldierFirebendingToken.java @@ -0,0 +1,30 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FirebendingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class SoldierFirebendingToken extends TokenImpl { + + public SoldierFirebendingToken() { + super("Soldier Token", "2/2 red Soldier creature token with firebending 1"); + cardType.add(CardType.CREATURE); + color.setRed(true); + subtype.add(SubType.SOLDIER); + power = new MageInt(2); + toughness = new MageInt(2); + this.addAbility(new FirebendingAbility(1)); + } + + private SoldierFirebendingToken(final SoldierFirebendingToken token) { + super(token); + } + + public SoldierFirebendingToken copy() { + return new SoldierFirebendingToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/SoldierRedToken.java b/Mage/src/main/java/mage/game/permanent/token/SoldierRedToken.java new file mode 100644 index 00000000000..3d6b5d3b400 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/SoldierRedToken.java @@ -0,0 +1,28 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class SoldierRedToken extends TokenImpl { + + public SoldierRedToken() { + super("Soldier Token", "2/2 red Soldier creature token"); + cardType.add(CardType.CREATURE); + color.setRed(true); + subtype.add(SubType.SOLDIER); + power = new MageInt(2); + toughness = new MageInt(2); + } + + private SoldierRedToken(final SoldierRedToken token) { + super(token); + } + + public SoldierRedToken copy() { + return new SoldierRedToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/SpiritWorldToken.java b/Mage/src/main/java/mage/game/permanent/token/SpiritWorldToken.java new file mode 100644 index 00000000000..b67361323db --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/SpiritWorldToken.java @@ -0,0 +1,44 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; +import mage.abilities.effects.common.combat.CantBlockCreaturesSourceEffect; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; + +/** + * @author TheElk801 + */ +public final class SpiritWorldToken extends TokenImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(Predicates.not(SubType.SPIRIT.getPredicate())); + } + + public SpiritWorldToken() { + super("Spirit Token", "1/1 colorless Spirit creature token with \"This token can't block or be blocked by non-Spirit creatures.\""); + cardType.add(CardType.CREATURE); + subtype.add(SubType.SPIRIT); + power = new MageInt(1); + toughness = new MageInt(1); + + Ability ability = new SimpleStaticAbility(new CantBlockCreaturesSourceEffect(filter).setText("this token can't block")); + ability.addEffect(new CantBeBlockedByCreaturesSourceEffect(filter, Duration.WhileOnBattlefield).setText("or be blocked by non-Spirit creatures")); + this.addAbility(ability); + } + + private SpiritWorldToken(final SpiritWorldToken token) { + super(token); + } + + public SpiritWorldToken copy() { + return new SpiritWorldToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 7cc905df6b6..2b1ed1937c6 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -42,7 +42,11 @@ public class Spell extends StackObjectImpl implements Card { private final List spellAbilities = new ArrayList<>(); + // this.card.getSpellAbility() - blueprint ability with zero selected targets + // this.ability - real casting ability with selected targets + // so if you need to check targets on cast then try to use "Ability source" param first (e.g. aura legality on etb) private final Card card; + private ManaCosts manaCost; private final ObjectColor color; private final ObjectColor frameColor; @@ -1157,7 +1161,7 @@ public class Spell extends StackObjectImpl implements Card { applier.modifySpell(spellCopy, game); } spellCopy.setZone(Zone.STACK, game); // required for targeting ex: Nivmagus Elemental - game.getStack().push(spellCopy); + game.getStack().push(game, spellCopy); // new targets if (newTargetFilterPredicate != null) { diff --git a/Mage/src/main/java/mage/game/stack/SpellStack.java b/Mage/src/main/java/mage/game/stack/SpellStack.java index 83458c5980b..149720cc740 100644 --- a/Mage/src/main/java/mage/game/stack/SpellStack.java +++ b/Mage/src/main/java/mage/game/stack/SpellStack.java @@ -3,6 +3,7 @@ package mage.game.stack; import mage.MageObject; import mage.abilities.Ability; +import mage.collectors.DataCollectorServices; import mage.constants.PutCards; import mage.constants.Zone; import mage.game.Game; @@ -50,6 +51,16 @@ public class SpellStack extends ArrayDeque { } } + @Override + @Deprecated // must use getFirstOrNull instead + public StackObject getFirst() { + return super.getFirst(); + } + + public StackObject getFirstOrNull() { + return this.isEmpty() ? null : this.getFirst(); + } + public boolean remove(StackObject object, Game game) { for (StackObject spell : this) { if (spell.getId().equals(object.getId())) { @@ -136,11 +147,18 @@ public class SpellStack extends ArrayDeque { } @Override + @Deprecated // must use only full version with game param public void push(StackObject e) { super.push(e); this.dateLastAdded = new Date(); } + public void push(Game game, StackObject e) { + super.push(e); + this.dateLastAdded = new Date(); + DataCollectorServices.getInstance().onTestsStackPush(game); + } + public Date getDateLastAdded() { return dateLastAdded; } diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index f83a90a5697..66211b60147 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -678,8 +678,8 @@ public class StackAbility extends StackObjectImpl implements Ability { ability.initSourceObjectZoneChangeCounter(game, force); } @Override - public int getSourceObjectZoneChangeCounter() { - return ability.getSourceObjectZoneChangeCounter(); + public int getStackMomentSourceZCC() { + return ability.getStackMomentSourceZCC(); } @Override @@ -744,7 +744,7 @@ public class StackAbility extends StackObjectImpl implements Ability { newAbility.setControllerId(newControllerId); StackAbility newStackAbility = new StackAbility(newAbility, newControllerId); - game.getStack().push(newStackAbility); + game.getStack().push(game, newStackAbility); // new targets if (newTargetFilterPredicate != null) { diff --git a/Mage/src/main/java/mage/players/ManaPool.java b/Mage/src/main/java/mage/players/ManaPool.java index 82bca7edcef..1311c7408fa 100644 --- a/Mage/src/main/java/mage/players/ManaPool.java +++ b/Mage/src/main/java/mage/players/ManaPool.java @@ -9,6 +9,7 @@ import mage.abilities.costs.Cost; import mage.abilities.effects.mana.ManaEffect; import mage.constants.Duration; import mage.constants.ManaType; +import mage.constants.PhaseStep; import mage.constants.TurnPhase; import mage.filter.Filter; import mage.filter.FilterMana; @@ -302,9 +303,17 @@ public class ManaPool implements Serializable { } private int emptyItem(ManaPoolItem item, Emptiable toEmpty, Game game, ManaType manaType) { - if (item.getDuration() == Duration.EndOfTurn - && game.getTurnPhaseType() != TurnPhase.END) { - return 0; + switch (item.getDuration()) { + case EndOfTurn: + if (game.getTurnPhaseType() != TurnPhase.END) { + return 0; + } + break; + case EndOfCombat: + if (game.getTurnPhaseType() != TurnPhase.COMBAT + || game.getTurnStepType() != PhaseStep.END_COMBAT) { + return 0; + } } if (manaBecomesBlack) { int amount = toEmpty.get(manaType); @@ -366,6 +375,10 @@ public class ManaPool implements Serializable { } public void addMana(Mana manaToAdd, Game game, Ability source, boolean dontLoseUntilEOT) { + addMana(manaToAdd, game, source, dontLoseUntilEOT ? Duration.EndOfTurn : null); + } + + public void addMana(Mana manaToAdd, Game game, Ability source, Duration duration) { if (manaToAdd != null) { Mana mana = manaToAdd.copy(); if (!game.replaceEvent(new ManaEvent(EventType.ADD_MANA, source.getId(), source, playerId, mana))) { @@ -376,8 +389,8 @@ public class ManaPool implements Serializable { source.getSourceObject(game), conditionalMana.getManaProducerOriginalId() != null ? conditionalMana.getManaProducerOriginalId() : source.getOriginalId() ); - if (dontLoseUntilEOT) { - item.setDuration(Duration.EndOfTurn); + if (duration != null) { + item.setDuration(duration); } this.manaItems.add(item); } else { @@ -392,8 +405,8 @@ public class ManaPool implements Serializable { source.getOriginalId(), mana.getFlag() ); - if (dontLoseUntilEOT) { - item.setDuration(Duration.EndOfTurn); + if (duration != null) { + item.setDuration(duration); } this.manaItems.add(item); } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 5e3e89f8a8d..084919b8f6d 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -20,7 +20,6 @@ import mage.designations.Designation; import mage.designations.DesignationType; import mage.filter.FilterCard; import mage.filter.FilterMana; -import mage.filter.FilterPermanent; import mage.game.*; import mage.game.draft.Draft; import mage.game.events.GameEvent; @@ -37,10 +36,7 @@ import mage.util.Copyable; import mage.util.MultiAmountMessage; import java.io.Serializable; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** @@ -57,16 +53,13 @@ import java.util.stream.Collectors; public interface Player extends MageItem, Copyable { /** - * Enum used to indicate what each player is allowed to spend life on. - * By default it is set to `allAbilities`, but can be changed by effects. - * E.g. Angel of Jubilation sets it to `nonSpellnonActivatedAbilities`, - * and Karn's Sylex sets it to `onlyManaAbilities`. - *

- *

- * Default is PayLifeCostLevel.allAbilities. + * Enum used to indicate what each player is not allowed to spend life on. + * By default a player has no restrictions, but can be changed by effects. + * E.g. Angel of Jubilation adds `CAST_SPELLS` and 'ACTIVATE_ABILITIES', + * and Karn's Sylex adds `CAST_SPELLS` and 'ACTIVATE_NON_MANA_ABILITIES'. */ - enum PayLifeCostLevel { - allAbilities, nonSpellnonActivatedAbilities, onlyManaAbilities, none + enum PayLifeCostRestriction { + CAST_SPELLS, ACTIVATE_NON_MANA_ABILITIES, ACTIVATE_MANA_ABILITIES } /** @@ -177,14 +170,14 @@ public interface Player extends MageItem, Copyable { boolean isCanGainLife(); /** - * Is the player allowed to pay life for casting spells or activate activated abilities + * Adds a {@link PayLifeCostRestriction} to the set of restrictions. * - * @param payLifeCostLevel + * @param payLifeCostRestriction */ - void setPayLifeCostLevel(PayLifeCostLevel payLifeCostLevel); + void addPayLifeCostRestriction(PayLifeCostRestriction payLifeCostRestriction); - PayLifeCostLevel getPayLifeCostLevel(); + EnumSet getPayLifeCostRestrictions(); /** * Can the player pay life to cast or activate the given ability @@ -194,10 +187,6 @@ public interface Player extends MageItem, Copyable { */ boolean canPayLifeCost(Ability Ability); - void setCanPaySacrificeCostFilter(FilterPermanent filter); - - FilterPermanent getSacrificeCostFilter(); - boolean canPaySacrificeCost(Permanent permanent, Ability source, UUID controllerId, Game game); void setLifeTotalCanChange(boolean lifeTotalCanChange); @@ -1231,10 +1220,6 @@ public interface Player extends MageItem, Copyable { /** * Only used for test player for pre-setting targets - * - * @param ability - * @param game - * @return */ boolean addTargets(Ability ability, Game game); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 94d126e11f5..ef0b43ea920 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -30,7 +30,6 @@ import mage.designations.DesignationType; import mage.designations.Speed; import mage.filter.FilterCard; import mage.filter.FilterMana; -import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreatureForCombat; @@ -101,7 +100,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected Cards sideboard; protected Cards hand; protected Graveyard graveyard; - protected Set commandersIds = new HashSet<>(0); + protected Set commandersIds = new HashSet<>(); protected Abilities abilities; protected Counters counters; @@ -150,14 +149,13 @@ public abstract class PlayerImpl implements Player, Serializable { protected boolean isFastFailInTestMode = true; protected boolean canGainLife = true; protected boolean canLoseLife = true; - protected PayLifeCostLevel payLifeCostLevel = PayLifeCostLevel.allAbilities; + protected EnumSet payLifeCostRestrictions = EnumSet.noneOf(PayLifeCostRestriction.class); protected boolean loseByZeroOrLessLife = true; protected boolean canPlotFromTopOfLibrary = false; protected boolean drawsFromBottom = false; protected boolean drawsOnOpponentsTurn = false; protected int speed = 0; - protected FilterPermanent sacrificeCostFilter; protected List alternativeSourceCosts = new ArrayList<>(); // TODO: rework turn controller to use single list (see other todos) @@ -264,8 +262,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.userData = player.userData; this.matchPlayer = player.matchPlayer; - this.payLifeCostLevel = player.payLifeCostLevel; - this.sacrificeCostFilter = player.sacrificeCostFilter; + this.payLifeCostRestrictions = player.payLifeCostRestrictions; this.alternativeSourceCosts = CardUtil.deepCopyObject(player.alternativeSourceCosts); this.storedBookmark = player.storedBookmark; @@ -364,9 +361,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.inRange.clear(); this.inRange.addAll(((PlayerImpl) player).inRange); - this.payLifeCostLevel = player.getPayLifeCostLevel(); - this.sacrificeCostFilter = player.getSacrificeCostFilter() != null - ? player.getSacrificeCostFilter().copy() : null; + this.payLifeCostRestrictions = player.getPayLifeCostRestrictions(); this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife(); this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary(); this.drawsFromBottom = player.isDrawsFromBottom(); @@ -481,14 +476,13 @@ public abstract class PlayerImpl implements Player, Serializable { //this.isTestMode // must keep this.canGainLife = true; this.canLoseLife = true; - this.payLifeCostLevel = PayLifeCostLevel.allAbilities; + this.payLifeCostRestrictions.clear(); this.loseByZeroOrLessLife = true; this.canPlotFromTopOfLibrary = false; this.drawsFromBottom = false; this.drawsOnOpponentsTurn = false; this.speed = 0; - this.sacrificeCostFilter = null; this.alternativeSourceCosts.clear(); this.isGameUnderControl = true; @@ -524,8 +518,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.maxAttackedBy = Integer.MAX_VALUE; this.canGainLife = true; this.canLoseLife = true; - this.payLifeCostLevel = PayLifeCostLevel.allAbilities; - this.sacrificeCostFilter = null; + this.payLifeCostRestrictions.clear(); this.loseByZeroOrLessLife = true; this.canPlotFromTopOfLibrary = false; this.drawsFromBottom = false; @@ -1546,7 +1539,7 @@ public abstract class PlayerImpl implements Player, Serializable { setStoredBookmark(bookmark); // move global bookmark to current state (if you activated mana before then you can't rollback it) ability.newId(); ability.setControllerId(playerId); - game.getStack().push(new StackAbility(ability, playerId)); + game.getStack().push(game, new StackAbility(ability, playerId)); if (ability.activate(game, false)) { game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATED_ABILITY, ability.getId(), ability, playerId)); @@ -1711,7 +1704,7 @@ public abstract class PlayerImpl implements Player, Serializable { ability.adjustTargets(game); if (ability.canChooseTarget(game, playerId)) { if (ability.isUsesStack()) { - game.getStack().push(new StackAbility(ability, playerId)); + game.getStack().push(game, new StackAbility(ability, playerId)); } if (ability.activate(game, false)) { if ((ability.isUsesStack() @@ -4524,7 +4517,6 @@ public abstract class PlayerImpl implements Player, Serializable { } else if (ability.getCosts().getTargets().getNextUnchosen(game) != null) { addCostTargetOptions(options, ability, 0, game); } - return options; } @@ -4561,29 +4553,45 @@ public abstract class PlayerImpl implements Player, Serializable { } /** - * AI related code + * AI related code, generate all possible usage use cases for activating ability (all possible targets combination) */ protected void addTargetOptions(List options, Ability option, int targetNum, Game game) { // TODO: target options calculated for triggered ability too, but do not used in real game - // TODO: there are rare errors with wrong targetNum - maybe multiple game sims can change same target object somehow? - // do not hide NullPointError here, research instead - for (Target target : option.getTargets().getNextUnchosen(game, targetNum).getTargetOptions(option, game)) { + if (targetNum >= option.getTargets().size()) { + return; + } + + // already selected for some reason (TODO: is it possible?) + Target currentTarget = option.getTargets().get(targetNum); + if (currentTarget.isChoiceSelected()) { + return; + } + + // analyse all possible use cases + for (Target targetOption : currentTarget.getTargetOptions(option, game)) { + // fill target Ability newOption = option.copy(); - if (target instanceof TargetAmount) { - for (UUID targetId : target.getTargets()) { - int amount = target.getTargetAmount(targetId); + if (targetOption instanceof TargetAmount) { + for (UUID targetId : targetOption.getTargets()) { + int amount = targetOption.getTargetAmount(targetId); newOption.getTargets().get(targetNum).addTarget(targetId, amount, newOption, game, true); } } else { - for (UUID targetId : target.getTargets()) { + for (UUID targetId : targetOption.getTargets()) { newOption.getTargets().get(targetNum).addTarget(targetId, newOption, game, true); } } - if (targetNum < option.getTargets().size() - 2) { // wtf + // don't forget about target's status (if it zero then must set skip choice too) + newOption.getTargets().get(targetNum).setSkipChoice(targetOption.isSkipChoice()); + + if (targetNum + 1 < option.getTargets().size()) { + // fill more targets addTargetOptions(options, newOption, targetNum + 1, game); } else if (!option.getCosts().getTargets().isEmpty()) { + // fill cost addCostTargetOptions(options, newOption, 0, game); } else { + // all filled, ability ready with all targets and costs options.add(newOption); } } @@ -4668,46 +4676,41 @@ public abstract class PlayerImpl implements Player, Serializable { return false; } - switch (payLifeCostLevel) { - case allAbilities: - return true; - case onlyManaAbilities: - return ability.isManaAbility(); - case nonSpellnonActivatedAbilities: - return !ability.getAbilityType().isActivatedAbility() - && ability.getAbilityType() != AbilityType.SPELL; - case none: - default: - return false; + boolean canPay = true; + for (PayLifeCostRestriction restriction : payLifeCostRestrictions) { + switch (restriction) { + case CAST_SPELLS: + canPay &= ability.getAbilityType() != AbilityType.SPELL; + break; + case ACTIVATE_NON_MANA_ABILITIES: + canPay &= !ability.isNonManaActivatedAbility(); + break; + case ACTIVATE_MANA_ABILITIES: + canPay &= !ability.isManaActivatedAbility(); + break; + } } + return canPay; } @Override - public PayLifeCostLevel getPayLifeCostLevel() { - return payLifeCostLevel; + public EnumSet getPayLifeCostRestrictions() { + return payLifeCostRestrictions; } @Override - public void setPayLifeCostLevel(PayLifeCostLevel payLifeCostLevel) { - this.payLifeCostLevel = payLifeCostLevel; + public void addPayLifeCostRestriction(PayLifeCostRestriction payLifeCostRestriction) { + this.payLifeCostRestrictions.add(payLifeCostRestriction); } @Override public boolean canPaySacrificeCost(Permanent permanent, Ability source, UUID controllerId, Game game) { - return permanent.canBeSacrificed() && - (sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, controllerId, source, game)); - } - - @Override - public void setCanPaySacrificeCostFilter(FilterPermanent filter - ) { - this.sacrificeCostFilter = filter; - } - - @Override - public FilterPermanent getSacrificeCostFilter() { - return sacrificeCostFilter; + if (!permanent.canBeSacrificed()) { + return false; + } + String sourceIdString = source.getId().toString(); + return !(game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PAY_SACRIFICE_COST, permanent.getId(), source, controllerId, sourceIdString, 1))); } @Override @@ -4938,6 +4941,7 @@ public abstract class PlayerImpl implements Player, Serializable { List infoList = new ArrayList<>(); for (Card card : cards) { fromZone = game.getState().getZone(card.getId()); + // 712.14a. If a spell or ability puts a transforming double-faced card onto the battlefield "transformed" // or "converted," it enters the battlefield with its back face up. If a player is instructed to put a card // that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in @@ -4946,15 +4950,29 @@ public abstract class PlayerImpl implements Player, Serializable { if (enterTransformed != null && enterTransformed && !card.isTransformable()) { continue; } + // 303.4g. If an Aura is entering the battlefield and there is no legal object or player for it to enchant, // the Aura remains in its current zone, unless that zone is the stack. In that case, the Aura is put into // its owner's graveyard instead of entering the battlefield. If the Aura is a token, it isn't created. - if (card.hasSubtype(SubType.AURA, game) - && card.getSpellAbility() != null - && !card.getSpellAbility().getTargets().isEmpty() - && !card.getSpellAbility().getTargets().get(0).copy().withNotTarget(true).canChoose(byOwner ? card.getOwnerId() : getId(), game)) { - continue; + if (card.hasSubtype(SubType.AURA, game) && !(source instanceof BestowAbility)) { + SpellAbility auraSpellAbility; + if (source instanceof SpellAbility && card.getAbilities(game).contains(source)) { + // cast aura - use source ability + auraSpellAbility = (SpellAbility) source; + } else { + // put to battlefield by another effect - use default spell + auraSpellAbility = card.getSpellAbility(); + } + if (auraSpellAbility != null) { + if (auraSpellAbility.getTargets().isEmpty()) { + throw new IllegalArgumentException("Something wrong, found etb aura with empty spell ability or without any targets: " + card + ", source: " + source); + } + if (!auraSpellAbility.getTargets().get(0).copy().withNotTarget(true).canChooseOrAlreadyChosen(byOwner ? card.getOwnerId() : getId(), source, game)) { + continue; + } + } } + ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), source, byOwner ? card.getOwnerId() : getId(), fromZone, Zone.BATTLEFIELD, appliedEffects); infoList.add(new ZoneChangeInfo.Battlefield(event, faceDown, tapped, source)); diff --git a/Mage/src/main/java/mage/players/StubPlayer.java b/Mage/src/main/java/mage/players/StubPlayer.java index d39f65a1444..b5efae370e3 100644 --- a/Mage/src/main/java/mage/players/StubPlayer.java +++ b/Mage/src/main/java/mage/players/StubPlayer.java @@ -43,7 +43,7 @@ public class StubPlayer extends PlayerImpl { if (target instanceof TargetPlayer) { for (Player player : game.getPlayers().values()) { if (player.getId().equals(getId()) - && target.canTarget(getId(), game) + && target.canTarget(getId(), source, game) && !target.contains(getId())) { target.add(player.getId(), game); return true; diff --git a/Mage/src/main/java/mage/target/Target.java b/Mage/src/main/java/mage/target/Target.java index c04395b2cc5..e8c17ff9c36 100644 --- a/Mage/src/main/java/mage/target/Target.java +++ b/Mage/src/main/java/mage/target/Target.java @@ -1,11 +1,13 @@ package mage.target; +import mage.MageObject; import mage.abilities.Ability; import mage.cards.Cards; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.Filter; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.Copyable; @@ -27,10 +29,24 @@ public interface Target extends Copyable, Serializable { * Warning, for "up to" targets it will return true all the time, so make sure your dialog * use do-while logic and call "choose" one time min or use isChoiceCompleted */ - @Deprecated // TODO: replace with UUID abilityControllerId, Ability source, Game game + @Deprecated + // TODO: replace with UUID abilityControllerId, Ability source, Game game boolean isChosen(Game game); - boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game); + /** + * Checking target complete and nothing to choose (X=0, all selected, all possible selected, etc) + * + * @param fromCards can be null for non cards selection + */ + boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game, Cards fromCards); + + /** + * Tests and AI related for "up to" targets (mark target that it was skipped on selection, so new choose dialog will be called) + * Example: AI sim possible target options + */ + boolean isSkipChoice(); + + void setSkipChoice(boolean isSkipChoice); void clearChosen(); @@ -51,16 +67,50 @@ public interface Target extends Copyable, Serializable { */ Target withNotTarget(boolean notTarget); - // methods for targets + /** + * Checks if there are enough targets the player can choose from among them + * or if they are autochosen since there are fewer than the minimum number. + *

+ * Implement as return canChooseFromPossibleTargets(sourceControllerId, source, game); + * TODO: remove after all canChoose replaced with default + * + * @param sourceControllerId - controller of the target event source + * @param source - can be null + * @param game + * @return - true if enough valid choices exist + */ boolean canChoose(UUID sourceControllerId, Ability source, Game game); + /** + * Make sure target can be fully selected or already selected, e.g. by AI sims + *

+ * Do not override + */ + default boolean canChooseOrAlreadyChosen(UUID sourceControllerId, Ability source, Game game) { + return this.isChosen(game) || this.canChoose(sourceControllerId, source, game); + } + + default boolean canChooseFromPossibleTargets(UUID sourceControllerId, Ability source, Game game) { + // TODO: replace all canChoose override methods by that code call + if (getMinNumberOfTargets() == 0) { + return true; + } + + int selectedCount = getSize(); + int moreSelectCount = possibleTargets(sourceControllerId, source, game).size(); + + if (selectedCount >= getMaxNumberOfTargets()) { + return false; + } + + return moreSelectCount > 0 && selectedCount + moreSelectCount >= getMinNumberOfTargets(); + } + /** * Returns a set of all possible targets that match the criteria of the implemented Target class. + * WARNING, it must filter already selected targets by keepValidPossibleTargets call at the end * - * @param sourceControllerId UUID of the ability's controller - * @param source Ability which requires the targets - * @param game Current game - * @return Set of the UUIDs of possible targets + * @param source - can be null */ Set possibleTargets(UUID sourceControllerId, Ability source, Game game); @@ -71,6 +121,42 @@ public interface Target extends Copyable, Serializable { .collect(Collectors.toSet()); } + /** + * Keep only valid and not selected targets - must be used inside any possibleTargets implementation + */ + default Set keepValidPossibleTargets(Set possibleTargets, UUID sourceControllerId, Ability source, Game game) { + // TODO: check target amount in human dialogs - is it allow to select it again + // do not override + // keep only valid and not selected targets list + return possibleTargets.stream() + .filter(this::notContains) + .filter(targetId -> { + // non-target allow any + if (source == null || source.getSourceId() == null || isNotTarget()) { + return true; + } + MageObject sourceObject = game.getObject(source); + if (sourceObject == null) { + return true; + } + + // target allow non-protected + Player targetPlayer = game.getPlayer(targetId); + if (targetPlayer != null) { + return !targetPlayer.hasLeft() + && canTarget(sourceControllerId, targetId, source, game) + && targetPlayer.canBeTargetedBy(sourceObject, sourceControllerId, source, game); + } + Permanent targetPermanent = game.getPermanent(targetId); + if (targetPermanent != null) { + return canTarget(sourceControllerId, targetId, source, game) + && targetPermanent.canBeTargetedBy(sourceObject, sourceControllerId, source, game); + } + return true; + }) + .collect(Collectors.toSet()); + } + /** * Priority method to make a choice from cards and other places, not a player.chooseXXX */ @@ -87,8 +173,6 @@ public interface Target extends Copyable, Serializable { void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent); - boolean canTarget(UUID id, Game game); - /** * @param id * @param source WARNING, it can be null for AI or other calls from events @@ -108,11 +192,12 @@ public interface Target extends Copyable, Serializable { */ List getTargetOptions(Ability source, Game game); - boolean canChoose(UUID sourceControllerId, Game game); + default boolean canChoose(UUID sourceControllerId, Game game) { + return canChoose(sourceControllerId, null, game); + } - Set possibleTargets(UUID sourceControllerId, Game game); - - @Deprecated // TODO: need replace to source only version? + @Deprecated + // TODO: need replace to source only version? boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game); /** @@ -236,6 +321,11 @@ public interface Target extends Copyable, Serializable { boolean contains(UUID targetId); + default boolean notContains(UUID targetId) { + // for better usage in streams + return !contains(targetId); + } + /** * This function tries to auto-choose the next target. *

diff --git a/Mage/src/main/java/mage/target/TargetAmount.java b/Mage/src/main/java/mage/target/TargetAmount.java index d28b1852459..9fcba18dee9 100644 --- a/Mage/src/main/java/mage/target/TargetAmount.java +++ b/Mage/src/main/java/mage/target/TargetAmount.java @@ -1,16 +1,13 @@ package mage.target; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; -import mage.cards.Card; +import mage.cards.Cards; import mage.constants.Outcome; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; -import mage.util.DebugUtil; -import mage.util.RandomUtil; +import mage.util.CardUtil; import java.util.*; import java.util.stream.Collectors; @@ -18,7 +15,7 @@ import java.util.stream.Collectors; /** * Distribute value between targets list (damage, counters, etc) * - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public abstract class TargetAmount extends TargetImpl { @@ -68,7 +65,7 @@ public abstract class TargetAmount extends TargetImpl { } @Override - public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) { + public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game, Cards fromCards) { // make sure target request called one time minimum (for "up to" targets) // choice is selected after any addTarget call (by test, AI or human players) if (!isChoiceSelected()) { @@ -80,6 +77,11 @@ public abstract class TargetAmount extends TargetImpl { return false; } + // already selected + if (this.getSize() >= getMaxNumberOfTargets()) { + return true; + } + // TODO: need auto-choose here? See super // all other use cases are fine @@ -178,7 +180,7 @@ public abstract class TargetAmount extends TargetImpl { chosen = isChosen(game); // stop by full complete - if (isChoiceCompleted(targetController.getId(), source, game)) { + if (isChoiceCompleted(targetController.getId(), source, game, null)) { break; } @@ -202,217 +204,21 @@ public abstract class TargetAmount extends TargetImpl { Set possibleTargets = possibleTargets(source.getControllerId(), source, game); // optimizations for less memory/cpu consumptions - printTargetsTableAndVariations("before optimize", game, possibleTargets, options, false); - optimizePossibleTargets(source, game, possibleTargets); - printTargetsTableAndVariations("after optimize", game, possibleTargets, options, false); - - // calc possible amount variations - addTargets(this, possibleTargets, options, source, game); - printTargetsTableAndVariations("after calc", game, possibleTargets, options, true); - - return options; - } - - /** - * AI related, trying to reduce targets for simulations - */ - private void optimizePossibleTargets(Ability source, Game game, Set possibleTargets) { - // remove duplicated/same creatures (example: distribute 3 damage between 10+ same tokens) - + TargetOptimization.printTargetsVariationsForTargetAmount("target amount - before optimize", game, possibleTargets, options, false); // it must have additional threshold to keep more variations for analyse - // // bad example: // - Blessings of Nature // - Distribute four +1/+1 counters among any number of target creatures. // on low targets threshold AI can put 1/1 to opponent's creature instead own, see TargetAmountAITest.test_AI_SimulateTargets + int maxPossibleTargetsToSimulate = CardUtil.overflowMultiply(this.remainingAmount, 2); + TargetOptimization.optimizePossibleTargets(source, game, possibleTargets, maxPossibleTargetsToSimulate); + TargetOptimization.printTargetsVariationsForTargetAmount("target amount - after optimize", game, possibleTargets, options, false); - int maxPossibleTargetsToSimulate = this.remainingAmount * 2; - if (possibleTargets.size() < maxPossibleTargetsToSimulate) { - return; - } + // calc possible amount variations + addTargets(this, possibleTargets, options, source, game); + TargetOptimization.printTargetsVariationsForTargetAmount("target amount - after calc", game, possibleTargets, options, true); - // split targets by groups - Map targetGroups = new HashMap<>(); - possibleTargets.forEach(id -> { - String groupKey = ""; - - // player - Player player = game.getPlayer(id); - if (player != null) { - groupKey = getTargetGroupKeyAsPlayer(player); - } - - // game object - MageObject object = game.getObject(id); - if (object != null) { - groupKey = object.getName(); - if (object instanceof Permanent) { - groupKey += getTargetGroupKeyAsPermanent(game, (Permanent) object); - } else if (object instanceof Card) { - groupKey += getTargetGroupKeyAsCard(game, (Card) object); - } else { - groupKey += getTargetGroupKeyAsOther(game, object); - } - } - - // unknown - use all - if (groupKey.isEmpty()) { - groupKey = id.toString(); - } - - targetGroups.put(id, groupKey); - }); - - Map> groups = new HashMap<>(); - targetGroups.forEach((id, groupKey) -> { - groups.computeIfAbsent(groupKey, k -> new ArrayList<>()); - groups.get(groupKey).add(id); - }); - - // optimize logic: - // - use one target from each target group all the time - // - add random target from random group until fill all remainingAmount condition - - // use one target per group - Set newPossibleTargets = new HashSet<>(); - groups.forEach((groupKey, groupTargets) -> { - UUID targetId = RandomUtil.randomFromCollection(groupTargets); - if (targetId != null) { - newPossibleTargets.add(targetId); - groupTargets.remove(targetId); - } - }); - - // use random target until fill condition - while (newPossibleTargets.size() < maxPossibleTargetsToSimulate) { - String groupKey = RandomUtil.randomFromCollection(groups.keySet()); - if (groupKey == null) { - break; - } - List groupTargets = groups.getOrDefault(groupKey, null); - if (groupTargets == null || groupTargets.isEmpty()) { - groups.remove(groupKey); - continue; - } - UUID targetId = RandomUtil.randomFromCollection(groupTargets); - if (targetId != null) { - newPossibleTargets.add(targetId); - groupTargets.remove(targetId); - } - } - - // keep final result - possibleTargets.clear(); - possibleTargets.addAll(newPossibleTargets); - } - - private String getTargetGroupKeyAsPlayer(Player player) { - // use all - return String.join(";", Arrays.asList( - player.getName(), - String.valueOf(player.getId().hashCode()) - )); - } - - private String getTargetGroupKeyAsPermanent(Game game, Permanent permanent) { - // split by name and stats - // TODO: rework and combine with PermanentEvaluator (to use battlefield score) - - // try to use short text/hash for lesser data on debug - return String.join(";", Arrays.asList( - permanent.getName(), - String.valueOf(permanent.getControllerId().hashCode()), - String.valueOf(permanent.getOwnerId().hashCode()), - String.valueOf(permanent.isTapped()), - String.valueOf(permanent.getPower().getValue()), - String.valueOf(permanent.getToughness().getValue()), - String.valueOf(permanent.getDamage()), - String.valueOf(permanent.getCardType(game).toString().hashCode()), - String.valueOf(permanent.getSubtype(game).toString().hashCode()), - String.valueOf(permanent.getCounters(game).getTotalCount()), - String.valueOf(permanent.getAbilities(game).size()), - String.valueOf(permanent.getRules(game).toString().hashCode()) - )); - } - - private String getTargetGroupKeyAsCard(Game game, Card card) { - // split by name and stats - return String.join(";", Arrays.asList( - card.getName(), - String.valueOf(card.getOwnerId().hashCode()), - String.valueOf(card.getCardType(game).toString().hashCode()), - String.valueOf(card.getSubtype(game).toString().hashCode()), - String.valueOf(card.getCounters(game).getTotalCount()), - String.valueOf(card.getAbilities(game).size()), - String.valueOf(card.getRules(game).toString().hashCode()) - )); - } - - private String getTargetGroupKeyAsOther(Game game, MageObject item) { - // use all - return String.join(";", Arrays.asList( - item.getName(), - String.valueOf(item.getId().hashCode()) - )); - } - - /** - * Debug only. Print targets table and variations. - */ - private void printTargetsTableAndVariations(String info, Game game, Set possibleTargets, List options, boolean isPrintOptions) { - if (!DebugUtil.AI_SHOW_TARGET_OPTIMIZATION_LOGS) return; - - // output example: - // - // Targets (after optimize): 5 - // 0. Balduvian Bears [ac8], C, BalduvianBears, DKM:22::0, 2/2 - // 1. PlayerA (SimulatedPlayer2) - // - // Target variations (info): 126 - // 0 -> 1; 1 -> 1; 2 -> 1; 3 -> 1; 4 -> 1 - // 0 -> 1; 1 -> 1; 2 -> 1; 3 -> 2 - // 0 -> 1; 1 -> 1; 2 -> 1; 4 -> 2 - - // print table - List list = new ArrayList<>(possibleTargets); - Collections.sort(list); - HashMap targetNumbers = new HashMap<>(); - System.out.println(); - System.out.println(String.format("Targets (%s): %d", info, list.size())); - for (int i = 0; i < list.size(); i++) { - targetNumbers.put(list.get(i), i); - String targetName; - Player player = game.getPlayer(list.get(i)); - if (player != null) { - targetName = player.toString(); - } else { - MageObject object = game.getObject(list.get(i)); - if (object != null) { - targetName = object.toString(); - } else { - targetName = "unknown"; - } - } - System.out.println(String.format("%d. %s", i, targetName)); - } - System.out.println(); - - if (!isPrintOptions) { - return; - } - - // print amount variations - List res = options - .stream() - .map(t -> t.getTargets() - .stream() - .map(id -> targetNumbers.get(id) + " -> " + t.getTargetAmount(id)) - .sorted() - .collect(Collectors.joining("; "))).sorted().collect(Collectors.toList()); - System.out.println(); - System.out.println(String.format("Target variations (info): %d", options.size())); - System.out.println(String.join("\n", res)); - System.out.println(); + return options; } final protected void addTargets(TargetAmount target, Set possibleTargets, List options, Ability source, Game game) { diff --git a/Mage/src/main/java/mage/target/TargetCard.java b/Mage/src/main/java/mage/target/TargetCard.java index 3abc11591dc..af7b05fc114 100644 --- a/Mage/src/main/java/mage/target/TargetCard.java +++ b/Mage/src/main/java/mage/target/TargetCard.java @@ -1,6 +1,5 @@ package mage.target; -import mage.MageItem; import mage.abilities.Ability; import mage.cards.Card; import mage.cards.Cards; @@ -56,196 +55,9 @@ public class TargetCard extends TargetObject { return this; } - /** - * Checks if there are enough {@link Card} that can be selected. - * - * @param sourceControllerId - controller of the select event - * @param game - * @return - true if enough valid {@link Card} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, null, game); - } - - /** - * Checks if there are enough {@link Card cards} in the appropriate zone that the player can choose from among them - * or if they are autochosen since there are fewer than the minimum number. - * - * @param sourceControllerId - controller of the target event source - * @param source - * @param game - * @return - true if enough valid {@link Card} exist - */ @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int possibleTargets = 0; - if (getMinNumberOfTargets() == 0) { // if 0 target is valid, the canChoose is always true - return true; - } - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null) { - if (this.minNumberOfTargets == 0) { - return true; - } - switch (zone) { - case HAND: - possibleTargets += countPossibleTargetInHand(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case GRAVEYARD: - possibleTargets += countPossibleTargetInGraveyard(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case LIBRARY: - possibleTargets += countPossibleTargetInLibrary(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case EXILED: - possibleTargets += countPossibleTargetInExile(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case COMMAND: - possibleTargets += countPossibleTargetInCommandZone(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case ALL: - possibleTargets += countPossibleTargetInAnyZone(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - default: - throw new IllegalArgumentException("Unsupported TargetCard zone: " + zone); - } - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - /** - * count up to N possible target cards in a player's hand matching the filter - */ - protected static int countPossibleTargetInHand(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : player.getHand().getCards(filter, sourceControllerId, source, game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in a player's graveyard matching the filter - */ - protected static int countPossibleTargetInGraveyard(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in a player's library matching the filter - */ - protected static int countPossibleTargetInLibrary(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : player.getLibrary().getUniqueCards(game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, game)) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in a player's exile matching the filter - */ - protected static int countPossibleTargetInExile(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : game.getExile().getPermanentExile().getCards(game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, player.getId(), source, game)) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in a player's command zone matching the filter - */ - protected static int countPossibleTargetInCommandZone(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - List possibleCards = game.getCommandersIds(player, CommanderCardType.ANY, false).stream() - .map(game::getCard) - .filter(Objects::nonNull) - .filter(card -> game.getState().getZone(card.getId()).equals(Zone.COMMAND)) - .filter(card -> filter.match(card, sourceControllerId, source, game)) - .collect(Collectors.toList()); - for (Card card : possibleCards) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in ANY zone - */ - protected static int countPossibleTargetInAnyZone(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : game.getCards()) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, game)) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return possibleTargets(sourceControllerId, null, game); - } - - public Set possibleTargets(UUID sourceControllerId, Cards cards, Ability source, Game game) { - return cards.getCards(filter, sourceControllerId, source, game).stream().map(MageItem::getId).collect(Collectors.toSet()); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override @@ -278,7 +90,7 @@ public class TargetCard extends TargetObject { } } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } /** @@ -286,12 +98,8 @@ public class TargetCard extends TargetObject { */ protected static Set getAllPossibleTargetInHand(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; for (Card card : player.getHand().getCards(filter, sourceControllerId, source, game)) { - // TODO: Why for sourceId == null? - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets.add(card.getId()); - } + possibleTargets.add(card.getId()); } return possibleTargets; } @@ -301,11 +109,8 @@ public class TargetCard extends TargetObject { */ protected static Set getAllPossibleTargetInGraveyard(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets.add(card.getId()); - } + possibleTargets.add(card.getId()); } return possibleTargets; } @@ -315,12 +120,9 @@ public class TargetCard extends TargetObject { */ protected static Set getAllPossibleTargetInLibrary(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; for (Card card : player.getLibrary().getUniqueCards(game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, sourceControllerId, source, game)) { - possibleTargets.add(card.getId()); - } + if (filter.match(card, sourceControllerId, source, game)) { + possibleTargets.add(card.getId()); } } return possibleTargets; @@ -332,11 +134,9 @@ public class TargetCard extends TargetObject { protected static Set getAllPossibleTargetInExile(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); UUID sourceId = source != null ? source.getSourceId() : null; - for (Card card : game.getExile().getPermanentExile().getCards(game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, sourceControllerId, source, game)) { - possibleTargets.add(card.getId()); - } + for (Card card : game.getExile().getAllCardsByRange(game, sourceControllerId)) { + if (filter.match(card, sourceControllerId, source, game)) { + possibleTargets.add(card.getId()); } } return possibleTargets; @@ -367,21 +167,16 @@ public class TargetCard extends TargetObject { */ protected static Set getAllPossibleTargetInAnyZone(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; for (Card card : game.getCards()) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, sourceControllerId, source, game)) { - possibleTargets.add(card.getId()); - } + if (filter.match(card, sourceControllerId, source, game)) { + possibleTargets.add(card.getId()); } } return possibleTargets; } - // TODO: check all class targets, if it override canTarget then make sure it override ALL 3 METHODS with canTarget and possibleTargets (method with cards doesn't need) - @Override - public boolean canTarget(UUID id, Game game) { + public boolean canTarget(UUID id, Ability source, Game game) { // copy-pasted from super but with card instead object Card card = game.getCard(id); return card != null @@ -389,11 +184,6 @@ public class TargetCard extends TargetObject { && getFilter() != null && getFilter().match(card, game); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - return canTarget(id, game); - } - @Override public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Card card = game.getCard(id); diff --git a/Mage/src/main/java/mage/target/TargetImpl.java b/Mage/src/main/java/mage/target/TargetImpl.java index 48f6f4bf73f..e0a00773cc2 100644 --- a/Mage/src/main/java/mage/target/TargetImpl.java +++ b/Mage/src/main/java/mage/target/TargetImpl.java @@ -3,6 +3,7 @@ package mage.target; import mage.MageObject; import mage.abilities.Ability; import mage.cards.Card; +import mage.cards.Cards; import mage.constants.AbilityType; import mage.constants.Outcome; import mage.constants.Zone; @@ -18,7 +19,7 @@ import mage.util.RandomUtil; import java.util.*; /** - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public abstract class TargetImpl implements Target { @@ -41,6 +42,8 @@ public abstract class TargetImpl implements Target { */ protected boolean chosen = false; + protected boolean isSkipChoice = false; + // is the target handled as targeted spell/ability (notTarget = true is used for not targeted effects like e.g. sacrifice) protected boolean notTarget = false; protected boolean atRandom = false; // for inner choose logic @@ -70,6 +73,7 @@ public abstract class TargetImpl implements Target { this.required = target.required; this.requiredExplicitlySet = target.requiredExplicitlySet; this.chosen = target.chosen; + this.isSkipChoice = target.isSkipChoice; this.targets.putAll(target.targets); this.zoneChangeCounters.putAll(target.zoneChangeCounters); this.atRandom = target.atRandom; @@ -163,6 +167,16 @@ public abstract class TargetImpl implements Target { return min == 0 && max == Integer.MAX_VALUE; } + @Override + public boolean isSkipChoice() { + return this.isSkipChoice; + } + + @Override + public void setSkipChoice(boolean isSkipChoice) { + this.isSkipChoice = isSkipChoice; + } + @Override public String getMessage(Game game) { // UI choose message @@ -261,7 +275,7 @@ public abstract class TargetImpl implements Target { } @Override - public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) { + public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game, Cards fromCards) { // make sure target request called one time minimum (for "up to" targets) // choice is selected after any addTarget call (by test, AI or human players) if (!isChoiceSelected()) { @@ -273,22 +287,23 @@ public abstract class TargetImpl implements Target { return false; } + // already selected + if (this.getSize() >= getMaxNumberOfTargets()) { + return true; + } + // make sure to auto-finish on all targets selection // - human player can select and deselect targets until fill all targets amount or press done button // - AI player can select all new targets as much as possible if (getMaxNumberOfTargets() > 0) { - if (getMaxNumberOfTargets() == Integer.MAX_VALUE) { - if (abilityControllerId != null && source != null) { - // any amount - nothing to choose - return this.getSize() >= this.possibleTargets(abilityControllerId, source, game).size(); - } else { - // any amount - any selected - return this.getSize() > 0; - } - } else { - // check selected limit - return this.getSize() >= getMaxNumberOfTargets(); + // full selection + if (this.getSize() >= getMaxNumberOfTargets()) { + return true; } + + // partly selection + int moreSelectCount = this.possibleTargets(abilityControllerId, source, game, fromCards).size(); + return moreSelectCount == 0 || isSkipChoice(); } // all other use cases are fine @@ -300,12 +315,13 @@ public abstract class TargetImpl implements Target { targets.clear(); zoneChangeCounters.clear(); chosen = false; + isSkipChoice = false; } @Override public boolean isChoiceSelected() { // min = max = 0 - for abilities with X=0, e.g. nothing to choose - return chosen || getMaxNumberOfTargets() == 0 && getMinNumberOfTargets() == 0; + return chosen || getMaxNumberOfTargets() == 0 && getMinNumberOfTargets() == 0 || isSkipChoice(); } @Override @@ -423,7 +439,7 @@ public abstract class TargetImpl implements Target { chosen = isChosen(game); // stop by full complete - if (isChoiceCompleted(abilityControllerId, source, game)) { + if (isChoiceCompleted(abilityControllerId, source, game, null)) { break; } @@ -503,7 +519,7 @@ public abstract class TargetImpl implements Target { chosen = isChosen(game); // stop by full complete - if (isChoiceCompleted(abilityControllerId, source, game)) { + if (isChoiceCompleted(abilityControllerId, source, game, null)) { break; } @@ -570,13 +586,24 @@ public abstract class TargetImpl implements Target { @Override public List getTargetOptions(Ability source, Game game) { List options = new ArrayList<>(); - List possibleTargets = new ArrayList<>(); - possibleTargets.addAll(possibleTargets(source.getControllerId(), source, game)); - possibleTargets.removeAll(getTargets()); + Set possibleTargets = possibleTargets(source.getControllerId(), source, game); + + // optimizations for less memory/cpu consumptions + int maxPossibleTargetsToSimulate = Math.min(TargetOptimization.AI_MAX_POSSIBLE_TARGETS_TO_CHOOSE, possibleTargets.size()); // see TargetAmount + if (getMinNumberOfTargets() > 0) { + maxPossibleTargetsToSimulate = Math.max(maxPossibleTargetsToSimulate, getMinNumberOfTargets()); + } + TargetOptimization.printTargetsVariationsForTarget("target - before optimize", game, possibleTargets, options, false); + TargetOptimization.optimizePossibleTargets(source, game, possibleTargets, maxPossibleTargetsToSimulate); + TargetOptimization.printTargetsVariationsForTarget("target - after optimize", game, possibleTargets, options, false); + + // calc all optimized combinations + // TODO: replace by google/apache lib to generate all combinations + List needPossibleTargets = new ArrayList<>(possibleTargets); // get the length of the array // e.g. for {'A','B','C','D'} => N = 4 - int N = possibleTargets.size(); + int N = needPossibleTargets.size(); // not enough targets, return no option if (N < getMinNumberOfTargets()) { return options; @@ -584,6 +611,7 @@ public abstract class TargetImpl implements Target { // not target but that's allowed, return one empty option if (N == 0) { TargetImpl target = this.copy(); + target.setSkipChoice(true); options.add(target); return options; } @@ -603,6 +631,7 @@ public abstract class TargetImpl implements Target { int minK = getMinNumberOfTargets(); if (getMinNumberOfTargets() == 0) { // add option without targets if possible TargetImpl target = this.copy(); + target.setSkipChoice(true); options.add(target); minK = 1; } @@ -631,7 +660,7 @@ public abstract class TargetImpl implements Target { //add the new target option TargetImpl target = this.copy(); for (int i = 0; i < combination.length; i++) { - target.addTarget(possibleTargets.get(combination[i]), source, game, true); + target.addTarget(needPossibleTargets.get(combination[i]), source, game, true); } options.add(target); index++; @@ -650,6 +679,9 @@ public abstract class TargetImpl implements Target { } } } + + TargetOptimization.printTargetsVariationsForTarget("target - after calc", game, possibleTargets, options, true); + return options; } diff --git a/Mage/src/main/java/mage/target/TargetObject.java b/Mage/src/main/java/mage/target/TargetObject.java index 1cce9447696..46918cfd479 100644 --- a/Mage/src/main/java/mage/target/TargetObject.java +++ b/Mage/src/main/java/mage/target/TargetObject.java @@ -50,27 +50,17 @@ public abstract class TargetObject extends TargetImpl { /** * Warning, don't use with non card objects here like commanders/emblems/etc. If you want it then * override canTarget in your own target. - * - * @param id - * @param game - * @return */ @Override - public boolean canTarget(UUID id, Game game) { + public boolean canTarget(UUID id, Ability source, Game game) { MageObject object = game.getObject(id); return object != null && zone != null && zone.match(game.getState().getZone(id)) && getFilter() != null && getFilter().match(object, game); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - return canTarget(id, game); - } - @Override public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { return canTarget(id, source, game); } - } diff --git a/Mage/src/main/java/mage/target/TargetOptimization.java b/Mage/src/main/java/mage/target/TargetOptimization.java new file mode 100644 index 00000000000..879b32e5f57 --- /dev/null +++ b/Mage/src/main/java/mage/target/TargetOptimization.java @@ -0,0 +1,237 @@ +package mage.target; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.cards.Card; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.util.DebugUtil; +import mage.util.RandomUtil; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Helper class to optimize possible targets list for AI and playable calcs + *

+ * Features: + * - less possible targets, less combinations, less CPU/memory usage for sims + * - group all possible targets by same characteristics; + * - fill target one by one from each group + * + * @author JayDi85 + */ +public class TargetOptimization { + + // for up to or any amount - limit max game sims to analyse + // (it's useless to calc all possible combinations on too much targets) + static public int AI_MAX_POSSIBLE_TARGETS_TO_CHOOSE = 7; + + public static void optimizePossibleTargets(Ability source, Game game, Set possibleTargets, int maxPossibleTargetsToSimulate) { + // remove duplicated/same creatures + // example: distribute 3 damage between 10+ same tokens + // example: target x1 from x10 forests - it's useless to recalc each forest + + if (possibleTargets.size() < maxPossibleTargetsToSimulate) { + return; + } + + // split targets by groups + Map targetGroups = new HashMap<>(); + possibleTargets.forEach(id -> { + String groupKey = ""; + + // player + Player player = game.getPlayer(id); + if (player != null) { + groupKey = getTargetGroupKeyAsPlayer(player); + } + + // game object + MageObject object = game.getObject(id); + if (object != null) { + groupKey = object.getName(); + if (object instanceof Permanent) { + groupKey += getTargetGroupKeyAsPermanent(game, (Permanent) object); + } else if (object instanceof Card) { + groupKey += getTargetGroupKeyAsCard(game, (Card) object); + } else { + groupKey += getTargetGroupKeyAsOther(game, object); + } + } + + // unknown - use all + if (groupKey.isEmpty()) { + groupKey = id.toString(); + } + + targetGroups.put(id, groupKey); + }); + + Map> groups = new HashMap<>(); + targetGroups.forEach((id, groupKey) -> { + groups.computeIfAbsent(groupKey, k -> new ArrayList<>()); + groups.get(groupKey).add(id); + }); + + // optimize logic: + // - use one target from each target group all the time + // - add random target from random group until fill all remainingAmount condition + + // use one target per group + Set newPossibleTargets = new HashSet<>(); + groups.forEach((groupKey, groupTargets) -> { + UUID targetId = RandomUtil.randomFromCollection(groupTargets); + if (targetId != null) { + newPossibleTargets.add(targetId); + groupTargets.remove(targetId); + } + }); + + // use random target until fill condition + while (newPossibleTargets.size() < maxPossibleTargetsToSimulate) { + String groupKey = RandomUtil.randomFromCollection(groups.keySet()); + if (groupKey == null) { + break; + } + List groupTargets = groups.getOrDefault(groupKey, null); + if (groupTargets == null || groupTargets.isEmpty()) { + groups.remove(groupKey); + continue; + } + UUID targetId = RandomUtil.randomFromCollection(groupTargets); + if (targetId != null) { + newPossibleTargets.add(targetId); + groupTargets.remove(targetId); + } + } + + // keep final result + possibleTargets.clear(); + possibleTargets.addAll(newPossibleTargets); + } + + private static String getTargetGroupKeyAsPlayer(Player player) { + // use all + return String.join(";", Arrays.asList( + player.getName(), + String.valueOf(player.getId().hashCode()) + )); + } + + private static String getTargetGroupKeyAsPermanent(Game game, Permanent permanent) { + // split by name and stats + // TODO: rework and combine with PermanentEvaluator (to use battlefield score) + + // try to use short text/hash for lesser data on debug + return String.join(";", Arrays.asList( + permanent.getName(), + String.valueOf(permanent.getControllerId().hashCode()), + String.valueOf(permanent.getOwnerId().hashCode()), + String.valueOf(permanent.isTapped()), + String.valueOf(permanent.getPower().getValue()), + String.valueOf(permanent.getToughness().getValue()), + String.valueOf(permanent.getDamage()), + String.valueOf(permanent.getCardType(game).toString().hashCode()), + String.valueOf(permanent.getSubtype(game).toString().hashCode()), + String.valueOf(permanent.getCounters(game).getTotalCount()), + String.valueOf(permanent.getAbilities(game).size()), + String.valueOf(permanent.getRules(game).toString().hashCode()) + )); + } + + private static String getTargetGroupKeyAsCard(Game game, Card card) { + // split by name and stats + return String.join(";", Arrays.asList( + card.getName(), + String.valueOf(card.getOwnerId().hashCode()), + String.valueOf(card.getCardType(game).toString().hashCode()), + String.valueOf(card.getSubtype(game).toString().hashCode()), + String.valueOf(card.getCounters(game).getTotalCount()), + String.valueOf(card.getAbilities(game).size()), + String.valueOf(card.getRules(game).toString().hashCode()) + )); + } + + private static String getTargetGroupKeyAsOther(Game game, MageObject item) { + // use all + return String.join(";", Arrays.asList( + item.getName(), + String.valueOf(item.getId().hashCode()) + )); + } + + public static void printTargetsVariationsForTarget(String info, Game game, Set possibleTargets, List options, boolean isPrintOptions) { + List usedOptions = options.stream() + .filter(Objects::nonNull) + .map(TargetImpl.class::cast) + .collect(Collectors.toList()); + printTargetsTableAndVariationsInner(info, game, possibleTargets, usedOptions, isPrintOptions); + } + + public static void printTargetsVariationsForTargetAmount(String info, Game game, Set possibleTargets, List options, boolean isPrintOptions) { + List usedOptions = options.stream() + .filter(Objects::nonNull) + .map(TargetImpl.class::cast) + .collect(Collectors.toList()); + printTargetsTableAndVariationsInner(info, game, possibleTargets, usedOptions, isPrintOptions); + } + + private static void printTargetsTableAndVariationsInner(String info, Game game, Set possibleTargets, List options, boolean isPrintOptions) { + if (!DebugUtil.AI_SHOW_TARGET_OPTIMIZATION_LOGS) return; + + // output example: + // + // Targets (after optimize): 5 + // 0. Balduvian Bears [ac8], C, BalduvianBears, DKM:22::0, 2/2 + // 1. PlayerA (SimulatedPlayer2) + // + // Target variations (info): 126 + // 0 -> 1; 1 -> 1; 2 -> 1; 3 -> 1; 4 -> 1 + // 0 -> 1; 1 -> 1; 2 -> 1; 3 -> 2 + // 0 -> 1; 1 -> 1; 2 -> 1; 4 -> 2 + + // print table + List list = new ArrayList<>(possibleTargets); + Collections.sort(list); + HashMap targetNumbers = new HashMap<>(); + System.out.println(); + System.out.println(String.format("Targets (%s): %d", info, list.size())); + for (int i = 0; i < list.size(); i++) { + targetNumbers.put(list.get(i), i); + String targetName; + Player player = game.getPlayer(list.get(i)); + if (player != null) { + targetName = player.toString(); + } else { + MageObject object = game.getObject(list.get(i)); + if (object != null) { + targetName = object.toString(); + } else { + targetName = "unknown"; + } + } + System.out.println(String.format("%d. %s", i, targetName)); + } + System.out.println(); + + if (!isPrintOptions) { + return; + } + + // print amount variations + List res = options + .stream() + .map(t -> t.getTargets() + .stream() + .map(id -> targetNumbers.get(id) + (t instanceof TargetAmount ? " -> " + t.getTargetAmount(id) : "")) + .sorted() + .collect(Collectors.joining("; "))).sorted().collect(Collectors.toList()); + System.out.println(); + System.out.println(String.format("Target variations (info): %d", options.size())); + System.out.println(String.join("\n", res)); + System.out.println(); + } + +} diff --git a/Mage/src/main/java/mage/target/TargetPermanent.java b/Mage/src/main/java/mage/target/TargetPermanent.java index c97ea339e01..16cf4a9c1fb 100644 --- a/Mage/src/main/java/mage/target/TargetPermanent.java +++ b/Mage/src/main/java/mage/target/TargetPermanent.java @@ -51,11 +51,11 @@ public class TargetPermanent extends TargetObject { @Override public boolean canTarget(UUID id, Ability source, Game game) { - return canTarget(source.getControllerId(), id, source, game); + return canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); if (permanent == null) { return false; @@ -67,19 +67,14 @@ public class TargetPermanent extends TargetObject { // first for protection from spells or abilities (e.g. protection from colored spells, r1753) // second for protection from sources (e.g. protection from artifacts + equip ability) if (!isNotTarget()) { - if (!permanent.canBeTargetedBy(game.getObject(source.getId()), controllerId, source, game) - || !permanent.canBeTargetedBy(game.getObject(source), controllerId, source, game)) { + if (!permanent.canBeTargetedBy(game.getObject(source.getId()), playerId, source, game) + || !permanent.canBeTargetedBy(game.getObject(source), playerId, source, game)) { return false; } } } - return filter.match(permanent, controllerId, source, game); - } - - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game, boolean flag) { - Permanent permanent = game.getPermanent(id); - return filter.match(permanent, controllerId, source, game); + return filter.match(permanent, playerId, source, game); } @Override @@ -87,91 +82,19 @@ public class TargetPermanent extends TargetObject { return this.filter; } - /** - * Checks if there are enough {@link Permanent} that can be chosen. - *

- * Takes into account notTarget parameter, in case it's true doesn't check - * for protection, shroud etc. - * - * @param sourceControllerId controller of the target event source - * @param source - * @param game - * @return true if enough valid {@link Permanent} exist - */ @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int remainingTargets = this.minNumberOfTargets - targets.size(); - if (remainingTargets <= 0) { - return true; - } - int count = 0; - MageObject targetSource = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId())) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - count++; - if (count >= remainingTargets) { - return true; - } - } - } - } - return false; - } - - /** - * Checks if there are enough {@link Permanent} that can be selected. Should - * not be used for Ability targets since this does not check for protection, - * shroud etc. - * - * @param sourceControllerId - controller of the select event - * @param game - * @return - true if enough valid {@link Permanent} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - int remainingTargets = this.minNumberOfTargets - targets.size(); - if (remainingTargets == 0) { - // if we return true, then AnowonTheRuinSage will hang for AI when no targets in play - // TODO: retest Anowon the Ruin Sage - return true; - } - int count = 0; - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, game)) { - if (!targets.containsKey(permanent.getId())) { - count++; - if (count >= remainingTargets) { - return true; - } - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { // TODO: check if possible targets works with setTargetController from some cards like Nicol Bolas, Dragon-God Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId())) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } + possibleTargets.add(permanent.getId()); } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, game)) { - if (!targets.containsKey(permanent.getId())) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/TargetPlayer.java b/Mage/src/main/java/mage/target/TargetPlayer.java index 9c1c13ad722..ef0696597b7 100644 --- a/Mage/src/main/java/mage/target/TargetPlayer.java +++ b/Mage/src/main/java/mage/target/TargetPlayer.java @@ -51,82 +51,21 @@ public class TargetPlayer extends TargetImpl { return filter; } - /** - * Checks if there are enough {@link Player} that can be chosen. Should only - * be used for Ability targets since this checks for protection, shroud etc. - * - * @param sourceControllerId - controller of the target event source - * @param source - * @param game - * @return - true if enough valid {@link Player} exist - */ @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - MageObject targetSource = game.getObject(source); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null && !player.hasLeft() && filter.match(player, sourceControllerId, source, game)) { - if (player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - } - return false; - } - - /** - * Checks if there are enough {@link Player} that can be selected. Should - * not be used for Ability targets since this does not check for protection, - * shroud etc. - * - * @param sourceControllerId - controller of the select event - * @param game - * @return - true if enough valid {@link Player} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - int count = 0; - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null && !player.hasLeft() && filter.match(player, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { Player player = game.getPlayer(playerId); - if (player != null && !player.hasLeft() && filter.match(player, sourceControllerId, source, game)) { - if (isNotTarget() || player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(playerId); - } - } - } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null && !player.hasLeft() && filter.match(player, game)) { + if (player != null && filter.match(player, sourceControllerId, source, game)) { possibleTargets.add(playerId); } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -138,12 +77,6 @@ public class TargetPlayer extends TargetImpl { return targets.keySet().stream().anyMatch(playerId -> canTarget(playerId, source, game)); } - @Override - public boolean canTarget(UUID id, Game game) { - Player player = game.getPlayer(id); - return filter.match(player, game); - } - @Override public boolean canTarget(UUID id, Ability source, Game game) { Player player = game.getPlayer(id); diff --git a/Mage/src/main/java/mage/target/TargetSource.java b/Mage/src/main/java/mage/target/TargetSource.java index 171e6b88236..5ffbc7639f2 100644 --- a/Mage/src/main/java/mage/target/TargetSource.java +++ b/Mage/src/main/java/mage/target/TargetSource.java @@ -89,63 +89,9 @@ public class TargetSource extends TargetObject { return true; } - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, (Ability) null, game); - } - @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - for (StackObject stackObject : game.getStack()) { - if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) - && filter.match(stackObject, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) { - if (filter.match(permanent, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Player player : game.getPlayers().values()) { - for (Card card : player.getGraveyard().getCards(game)) { - if (filter.match(card, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - } - for (Card card : game.getExile().getAllCards(game)) { - if (filter.match(card, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (CommandObject commandObject : game.getState().getCommand()) { - if (filter.match(commandObject, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return possibleTargets(sourceControllerId, (Ability) null, game); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override @@ -179,7 +125,8 @@ public class TargetSource extends TargetObject { possibleTargets.add(commandObject.getId()); } } - return possibleTargets; + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/TargetSpell.java b/Mage/src/main/java/mage/target/TargetSpell.java index 4ded8d97a8d..45d11a6a6c9 100644 --- a/Mage/src/main/java/mage/target/TargetSpell.java +++ b/Mage/src/main/java/mage/target/TargetSpell.java @@ -64,41 +64,16 @@ public class TargetSpell extends TargetObject { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (this.minNumberOfTargets == 0) { - return true; - } - int count = 0; - for (StackObject stackObject : game.getStack()) { - // rule 114.4. A spell or ability on the stack is an illegal target for itself. - if (source.getSourceId() != null && source.getSourceId().equals(stackObject.getSourceId())) { - continue; - } - if (canBeChosen(stackObject, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, null, game); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return game.getStack().stream() + Set possibleTargets = game.getStack().stream() .filter(stackObject -> canBeChosen(stackObject, sourceControllerId, source, game)) .map(StackObject::getId) .collect(Collectors.toSet()); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, null, game); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -107,7 +82,11 @@ public class TargetSpell extends TargetObject { } private boolean canBeChosen(StackObject stackObject, UUID sourceControllerId, Ability source, Game game) { + // rule 114.4. A spell or ability on the stack is an illegal target for itself. + boolean isSelfTarget = source.getSourceId() != null && source.getSourceId().equals(stackObject.getSourceId()); + return stackObject instanceof Spell + && !isSelfTarget && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) && canTarget(sourceControllerId, stackObject.getId(), source, game); } diff --git a/Mage/src/main/java/mage/target/TargetStackObject.java b/Mage/src/main/java/mage/target/TargetStackObject.java index 2b7840436c2..42df9dc7ec3 100644 --- a/Mage/src/main/java/mage/target/TargetStackObject.java +++ b/Mage/src/main/java/mage/target/TargetStackObject.java @@ -56,22 +56,7 @@ public class TargetStackObject extends TargetObject { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - for (StackObject stackObject : game.getStack()) { - if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) - && filter.match(stackObject, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, null, game); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override @@ -83,12 +68,7 @@ public class TargetStackObject extends TargetObject { possibleTargets.add(stackObject.getId()); } } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, null, game); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/Targets.java b/Mage/src/main/java/mage/target/Targets.java index cba1f867cef..10e6cba05ab 100644 --- a/Mage/src/main/java/mage/target/Targets.java +++ b/Mage/src/main/java/mage/target/Targets.java @@ -2,6 +2,7 @@ package mage.target; import mage.MageObject; import mage.abilities.Ability; +import mage.cards.Cards; import mage.constants.Outcome; import mage.game.Game; import mage.game.events.GameEvent; @@ -63,8 +64,8 @@ public class Targets extends ArrayList implements Copyable { return unchosenIndex < res.size() ? res.get(unchosenIndex) : null; } - public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) { - return stream().allMatch(t -> t.isChoiceCompleted(abilityControllerId, source, game)); + public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game, Cards fromCards) { + return stream().allMatch(t -> t.isChoiceCompleted(abilityControllerId, source, game, fromCards)); } public void clearChosen() { @@ -78,99 +79,64 @@ public class Targets extends ArrayList implements Copyable { } public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game) { - Player player = game.getPlayer(playerId); - if (player == null) { - return false; - } - - // in test mode some targets can be predefined already, e.g. by cast/activate command - // so do not clear chosen status here - - if (this.size() > 0) { - do { - // stop on disconnect or nothing to choose - if (!player.canRespond() || !canChoose(playerId, source, game)) { - break; - } - - // stop on complete - Target target = this.getNextUnchosen(game); - if (target == null) { - break; - } - - // stop on cancel/done - if (!target.choose(outcome, playerId, sourceId, source, game)) { - break; - } - - // target done, can take next one - } while (true); - } - - if (DebugUtil.GAME_SHOW_CHOOSE_TARGET_LOGS && !game.isSimulation()) { - printDebugTargets("choose finish", this, source, game); - } - - return isChosen(game); + return makeChoice(false, outcome, playerId, source, false, game, false); } public boolean chooseTargets(Outcome outcome, UUID playerId, Ability source, boolean noMana, Game game, boolean canCancel) { - Player player = game.getPlayer(playerId); - if (player == null) { - return false; - } + return makeChoice(true, outcome, playerId, source, noMana, game, canCancel); + } + private boolean makeChoice(boolean isTargetChoice, Outcome outcome, UUID playerId, Ability source, boolean noMana, Game game, boolean canCancel) { // in test mode some targets can be predefined already, e.g. by cast/activate command // so do not clear chosen status here - if (this.size() > 0) { - do { - // stop on disconnect or nothing to choose - if (!player.canRespond() || !canChoose(playerId, source, game)) { - break; - } + // there are possible multiple targets, so must check per target, not whole list + // good example: cast Scatter to the Winds with awaken + for (Target target : this) { + UUID abilityControllerId = target.getAffectedAbilityControllerId(playerId); - // stop on complete - Target target = this.getNextUnchosen(game); - if (target == null) { - break; - } + // stop on disconnect + Player player = game.getPlayer(abilityControllerId); + if (player == null || !player.canRespond()) { + return false; + } - // some targets can have controller different than ability controller - UUID targetController = playerId; - if (target.getTargetController() != null) { - targetController = target.getTargetController(); - } + // continue on nothing to choose or complete + if (target.isChoiceSelected() || !target.canChoose(abilityControllerId, source, game)) { + continue; + } - // disable cancel button - if cast without mana (e.g. by suspend you may not be able to cancel the casting if you are able to cast it - if (noMana) { - target.setRequired(true); - } - // enable cancel button - if (canCancel) { - target.setRequired(false); - } + // TODO: need research and remove or re-implement for other choices + // disable cancel button - if cast without mana (e.g. by Suspend) you may not be able to cancel the casting if you are able to cast it + if (noMana) { + target.setRequired(true); + } + // enable cancel button + if (canCancel) { + target.setRequired(false); + } - // stop on cancel/done - if (!target.chooseTarget(outcome, targetController, source, game)) { - if (!target.isChosen(game)) { - break; - } - } + // continue on cancel/skip one of the target + boolean choiceRes; + if (isTargetChoice) { + choiceRes = target.chooseTarget(outcome, abilityControllerId, source, game); + } else { + choiceRes = target.choose(outcome, abilityControllerId, source, game); + } + if (!choiceRes) { + //break; // do not stop targeting, example: two "or" targets from Finale of Promise + } + } - // reset on wrong restrictions and start from scratch - if (this.getNextUnchosen(game) == null - && game.replaceEvent(new GameEvent(GameEvent.EventType.TARGETS_VALID, source.getSourceId(), source, source.getControllerId()), source)) { - clearChosen(); - } - - // target done, can take next one - } while (true); + // TODO: need research or wait bug reports - old version was able to continue selection from scratch, + // current version just clear the chosen, but do not start selection again + // reset on wrong restrictions and start from scratch + if (isTargetChoice && isChosen(game) && game.replaceEvent(new GameEvent(GameEvent.EventType.TARGETS_VALID, source.getSourceId(), source, source.getControllerId()), source)) { + clearChosen(); } if (DebugUtil.GAME_SHOW_CHOOSE_TARGET_LOGS && !game.isSimulation()) { - printDebugTargets("chooseTargets finish", this, source, game); + printDebugTargets(isTargetChoice ? "target finish" : "choose finish", this, source, game); } return isChosen(game); diff --git a/Mage/src/main/java/mage/target/common/TargetActivatedAbility.java b/Mage/src/main/java/mage/target/common/TargetActivatedAbility.java index 17e7067d386..87ab3a0454d 100644 --- a/Mage/src/main/java/mage/target/common/TargetActivatedAbility.java +++ b/Mage/src/main/java/mage/target/common/TargetActivatedAbility.java @@ -54,38 +54,21 @@ public class TargetActivatedAbility extends TargetObject { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return canChoose(sourceControllerId, game); - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - for (StackObject stackObject : game.getStack()) { - if (stackObject.getStackAbility() != null - && stackObject.getStackAbility().isActivatedAbility() - && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getStackAbility().getControllerId()) - ) { - return true; - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return possibleTargets(sourceControllerId, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { Set possibleTargets = new HashSet<>(); for (StackObject stackObject : game.getStack()) { if (stackObject.getStackAbility().isActivatedAbility() && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getStackAbility().getControllerId()) - && filter.match(stackObject, game)) { + && filter.match(stackObject,sourceControllerId, source, game) + && this.notContains(stackObject.getId())) { possibleTargets.add(stackObject.getStackAbility().getId()); } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java b/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java index d5c01e5f4b7..4d542757de9 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java @@ -12,6 +12,7 @@ import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.target.TargetCard; import mage.util.CardUtil; @@ -70,48 +71,23 @@ public class TargetCardAndOrCard extends TargetCard { return new TargetCardAndOrCard(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 assignment.getRoleCount(cards, game) >= cards.size(); - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - // assuming max targets = 2, need to expand this code if not - Card card = game.getCard(this.getFirstTarget()); - if (card == null) { - return possibleTargets; // no further restriction if no target yet chosen - } - Cards cards = new CardsImpl(card); - if (assignment.getRoleCount(cards, game) == 2) { - // if the first chosen target is both types, no further restriction - return possibleTargets; - } - Set leftPossibleTargets = new HashSet<>(); - for (UUID possibleId : possibleTargets) { - Card possibleCard = game.getCard(possibleId); - Cards checkCards = cards.copy(); - checkCards.add(possibleCard); - if (assignment.getRoleCount(checkCards, game) == 2) { - // if the possible target and the existing target have both types, it's legal - // but this prevents the case of both targets with the same type - leftPossibleTargets.add(possibleId); + + // only valid roles + Cards existingTargets = new CardsImpl(this.getTargets()); + possibleTargets.removeIf(id -> { + Card card = game.getCard(id); + if (card == null) { + return true; } - } - return leftPossibleTargets; + Cards newTargets = existingTargets.copy(); + newTargets.add(card); + return assignment.getRoleCount(newTargets, game) < newTargets.size(); + }); + + return possibleTargets; } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java b/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java index ff4235ce33f..3def0cb8984 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java +++ b/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java @@ -14,7 +14,6 @@ import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; import mage.util.CardUtil; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -39,14 +38,6 @@ public class TargetCardAndOrCardInLibrary extends TargetCardInLibrary { private final PredicateCardAssignment assignment; - /** - * a [firstType] card and/or a [secondType] card - */ - protected TargetCardAndOrCardInLibrary(Predicate firstPredicate, Predicate secondPredicate, String filterText) { - super(0, 2, makeFilter(firstPredicate, secondPredicate, filterText)); - this.assignment = new PredicateCardAssignment(firstPredicate, secondPredicate); - } - public TargetCardAndOrCardInLibrary(CardType firstType, CardType secondType) { this(firstType.getPredicate(), secondType.getPredicate(), makeFilterText( CardUtil.getTextWithFirstCharLowerCase(firstType.toString()), @@ -61,6 +52,14 @@ public class TargetCardAndOrCardInLibrary extends TargetCardInLibrary { this(firstType.getPredicate(), secondType.getPredicate(), makeFilterText(firstType.getDescription(), secondType.getDescription())); } + /** + * a [firstType] card and/or a [secondType] card + */ + public TargetCardAndOrCardInLibrary(Predicate firstPredicate, Predicate secondPredicate, String filterText) { + super(0, 2, makeFilter(firstPredicate, secondPredicate, filterText)); + this.assignment = new PredicateCardAssignment(firstPredicate, secondPredicate); + } + protected TargetCardAndOrCardInLibrary(final TargetCardAndOrCardInLibrary target) { super(target); this.assignment = target.assignment; @@ -71,48 +70,23 @@ public class TargetCardAndOrCardInLibrary extends TargetCardInLibrary { return new TargetCardAndOrCardInLibrary(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 assignment.getRoleCount(cards, game) >= cards.size(); - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - // assuming max targets = 2, need to expand this code if not - Card card = game.getCard(this.getFirstTarget()); - if (card == null) { - return possibleTargets; // no further restriction if no target yet chosen - } - Cards cards = new CardsImpl(card); - if (assignment.getRoleCount(cards, game) == 2) { - // if the first chosen target is both types, no further restriction - return possibleTargets; - } - Set leftPossibleTargets = new HashSet<>(); - for (UUID possibleId : possibleTargets) { - Card possibleCard = game.getCard(possibleId); - Cards checkCards = cards.copy(); - checkCards.add(possibleCard); - if (assignment.getRoleCount(checkCards, game) == 2) { - // if the possible target and the existing target have both types, it's legal - // but this prevents the case of both targets with the same type - leftPossibleTargets.add(possibleId); + + // only valid roles + Cards existingTargets = new CardsImpl(this.getTargets()); + possibleTargets.removeIf(id -> { + Card card = game.getCard(id); + if (card == null) { + return true; } - } - return leftPossibleTargets; + Cards newTargets = existingTargets.copy(); + newTargets.add(card); + return assignment.getRoleCount(newTargets, game) < newTargets.size(); + }); + + return possibleTargets; } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInASingleGraveyard.java b/Mage/src/main/java/mage/target/common/TargetCardInASingleGraveyard.java index 38176140196..b0e98188d5a 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInASingleGraveyard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInASingleGraveyard.java @@ -5,7 +5,6 @@ import mage.cards.Card; import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; -import mage.game.events.TargetEvent; import mage.players.Player; import mage.target.TargetCard; @@ -28,71 +27,35 @@ public class TargetCardInASingleGraveyard extends TargetCard { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - UUID firstTarget = this.getFirstTarget(); - - // If a card is already targeted, ensure that this new target has the same owner as currently chosen target - if (firstTarget != null) { - Card card = game.getCard(firstTarget); - Card targetCard = game.getCard(id); - if (card == null || targetCard == null || !card.isOwnedBy(targetCard.getOwnerId())) { - return false; - } - } - - // If it has the same owner (or no target picked) check that it's a valid target with super - return super.canTarget(id, source, game); - } - - /** - * Set of UUIDs of all possible targets - * - * @param sourceControllerId UUID of the ability's controller - * @param source Ability which requires the targets - * @param game Current game - * @return Set of the UUIDs of possible targets - */ @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; - UUID controllerOfFirstTarget = null; - - // If any targets have been chosen, get the UUID of the owner in order to limit the targets to that owner's graveyard - if (!targets.isEmpty()) { - for (UUID cardInGraveyardId : targets.keySet()) { - Card targetCard = game.getCard(cardInGraveyardId); - if (targetCard == null) { - continue; - } - - controllerOfFirstTarget = targetCard.getOwnerId(); - break; // Only need the first UUID since they will all be the same - } - } + Card firstTarget = game.getCard(source.getFirstTarget()); for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - // If the playerId of this iteration is not the same as that of any existing target, then continue - // All cards must be from the same player's graveyard. - if (controllerOfFirstTarget != null && !playerId.equals(controllerOfFirstTarget)) { - continue; - } - Player player = game.getPlayer(playerId); if (player == null) { continue; } - for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - // TODO: Why for sourceId == null? - if (sourceId == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets.add(card.getId()); + if (firstTarget == null) { + // playable or not selected + // use any player + } else { + // already selected + // use from same player + if (!playerId.equals(firstTarget.getOwnerId())) { + continue; } } + + for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { + possibleTargets.add(card.getId()); + } } - return possibleTargets; + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInCommandZone.java b/Mage/src/main/java/mage/target/common/TargetCardInCommandZone.java index 46a8988d0e1..a06ea169ab5 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInCommandZone.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInCommandZone.java @@ -46,7 +46,7 @@ public class TargetCardInCommandZone extends TargetCard { @Override public boolean canTarget(UUID id, Ability source, Game game) { - return this.canTarget(source.getControllerId(), id, source, game); + return this.canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override @@ -59,30 +59,13 @@ public class TargetCardInCommandZone extends TargetCard { Cards cards = new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY)); for (Card card : cards.getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets.add(card.getId()); - } + possibleTargets.add(card.getId()); } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - Player player = game.getPlayer(sourceControllerId); - if (player == null) { - return false; - } - - int possibletargets = 0; - Cards cards = new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY)); - for (Card card : cards.getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibletargets++; - if (possibletargets >= this.minNumberOfTargets) { - return true; - } - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } } \ No newline at end of file diff --git a/Mage/src/main/java/mage/target/common/TargetCardInExile.java b/Mage/src/main/java/mage/target/common/TargetCardInExile.java index 2c8cba1d09a..292e958cb1c 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInExile.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInExile.java @@ -12,34 +12,22 @@ import java.util.HashSet; import java.util.Set; import java.util.UUID; - /** * @author BetaSteward_at_googlemail.com */ public class TargetCardInExile extends TargetCard { - // If null, can target any card in exile matching [filter] - // If non-null, can only target - private final UUID zoneId; + private final UUID zoneId; // use null to target any exile zone or only specific - /** - * @param filter filter for the card to be a target - */ public TargetCardInExile(FilterCard filter) { this(1, 1, filter); } - /** - * @param minNumTargets minimum number of targets - * @param maxNumTargets maximum number of targets - * @param filter filter for the card to be a target - */ public TargetCardInExile(int minNumTargets, int maxNumTargets, FilterCard filter) { this(minNumTargets, maxNumTargets, filter, null); } /** - * @param filter filter for the card to be a target * @param zoneId if non-null can only target cards in that exileZone. if null card can be in ever exile zone. */ public TargetCardInExile(FilterCard filter, UUID zoneId) { @@ -47,10 +35,7 @@ public class TargetCardInExile extends TargetCard { } /** - * @param minNumTargets minimum number of targets - * @param maxNumTargets maximum number of targets - * @param filter filter for the card to be a target - * @param zoneId if non-null can only target cards in that exileZone. if null card can be in ever exile zone. + * @param zoneId if non-null can only target cards in that exileZone. if null card can be in ever exile zone. */ public TargetCardInExile(int minNumTargets, int maxNumTargets, FilterCard filter, UUID zoneId) { super(minNumTargets, maxNumTargets, Zone.EXILED, filter); @@ -65,6 +50,7 @@ public class TargetCardInExile extends TargetCard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); + if (zoneId == null) { // no specific exile zone for (Card card : game.getExile().getAllCardsByRange(game, sourceControllerId)) { if (filter.match(card, sourceControllerId, source, game)) { @@ -81,40 +67,8 @@ public class TargetCardInExile extends TargetCard { } } } - return possibleTargets; - } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (zoneId == null) { // no specific exile zone - int numberTargets = 0; - for (ExileZone exileZone : game.getExile().getExileZones()) { - numberTargets += exileZone.count(filter, sourceControllerId, source, game); - if (numberTargets >= this.minNumberOfTargets) { - return true; - } - } - } else { - ExileZone exileZone = game.getExile().getExileZone(zoneId); - return exileZone != null && exileZone.count(filter, sourceControllerId, source, game) >= this.minNumberOfTargets; - - } - return false; - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Card card = game.getCard(id); - if (card != null && game.getState().getZone(card.getId()) == Zone.EXILED) { - if (zoneId == null) { // no specific exile zone - return filter.match(card, source.getControllerId(), source, game); - } - ExileZone exile = game.getExile().getExileZone(zoneId); - if (exile != null && exile.contains(id)) { - return filter.match(card, source.getControllerId(), source, game); - } - } - return false; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java b/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java index b86014f5cac..368978950c8 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java @@ -1,6 +1,5 @@ package mage.target.common; -import mage.MageObject; import mage.abilities.Ability; import mage.constants.ComparisonType; import mage.constants.Zone; @@ -18,7 +17,6 @@ import java.util.Set; import java.util.UUID; /** - * * @author LevelX2 */ public class TargetCardInGraveyardBattlefieldOrStack extends TargetCard { @@ -59,28 +57,12 @@ public class TargetCardInGraveyardBattlefieldOrStack extends TargetCard { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (super.canChoose(sourceControllerId, source, game)) { - return true; - } - MageObject targetSource = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, sourceControllerId, source, game)) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - return true; - } - } - for (StackObject stackObject : game.getStack()) { - if (stackObject instanceof Spell - && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) - && filterSpell.match(stackObject, sourceControllerId, source, game)) { - return true; - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public boolean canTarget(UUID id, Ability source, Game game) { - return this.canTarget(source.getControllerId(), id, source, game); + return this.canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override @@ -90,31 +72,22 @@ public class TargetCardInGraveyardBattlefieldOrStack extends TargetCard { } Permanent permanent = game.getPermanent(id); if (permanent != null) { - return filterPermanent.match(permanent, playerId, source, game); + return playerId == null ? filterPermanent.match(permanent, game) : filterPermanent.match(permanent, playerId, source, game); } Spell spell = game.getSpell(id); - return spell != null && filterSpell.match(spell, playerId, source, game); - } - - @Override - public boolean canTarget(UUID id, Game game) { - return this.canTarget(null, id, null, game); // wtf - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, (Ability) null, game); + return spell != null && (playerId == null ? filter.match(spell, game) : filterSpell.match(spell, playerId, source, game)); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); // in graveyard first - MageObject targetSource = game.getObject(source); + + // from battlefield for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, sourceControllerId, source, game)) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } + possibleTargets.add(permanent.getId()); } + + // from stack for (StackObject stackObject : game.getStack()) { if (stackObject instanceof Spell && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) @@ -122,7 +95,8 @@ public class TargetCardInGraveyardBattlefieldOrStack extends TargetCard { possibleTargets.add(stackObject.getId()); } } - return possibleTargets; + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInHand.java b/Mage/src/main/java/mage/target/common/TargetCardInHand.java index 25dd47d6bb1..2057ca9126e 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInHand.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInHand.java @@ -1,7 +1,9 @@ package mage.target.common; +import mage.MageObject; import mage.abilities.Ability; import mage.cards.Card; +import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; @@ -53,7 +55,7 @@ public class TargetCardInHand extends TargetCard { @Override public boolean canTarget(UUID id, Ability source, Game game) { - return this.canTarget(source.getControllerId(), id, source, game); + return this.canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override @@ -61,30 +63,16 @@ public class TargetCardInHand extends TargetCard { Set possibleTargets = new HashSet<>(); Player player = game.getPlayer(sourceControllerId); if (player != null) { - for (Card card : player.getHand().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets.add(card.getId()); - } - } - } - return possibleTargets; + player.getHand().getCards(filter, sourceControllerId, source, game).stream() + .map(MageObject::getId) + .forEach(possibleTargets::add); + }; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int possibleTargets = 0; - Player player = game.getPlayer(sourceControllerId); - if (player != null) { - for (Card card : player.getHand().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - } - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java index a6c111bca01..30475de61c2 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java @@ -97,7 +97,7 @@ public class TargetCardInLibrary extends TargetCard { chosen = isChosen(game); // stop by full complete - if (isChoiceCompleted(abilityControllerId, source, game)) { + if (isChoiceCompleted(abilityControllerId, source, game, null)) { break; } diff --git a/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java b/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java index e7f81c810ef..9968cd74126 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java @@ -70,50 +70,6 @@ public class TargetCardInOpponentsGraveyard extends TargetCard { return false; } - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, null, game); - } - - /** - * Checks if there are enough {@link Card} that can be chosen. - * - * @param sourceControllerId - controller of the target event source - * @param source - * @param game - * @return - true if enough valid {@link Card} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int possibleTargets = 0; - if (getMinNumberOfTargets() == 0) { // if 0 target is valid, the canChoose is always true - return true; - } - Player sourceController = game.getPlayer(sourceControllerId); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - if (!sourceController.hasOpponent(playerId, game)) { - continue; - } - if (this.allFromOneOpponent) { - possibleTargets = 0; - } - if (!playerId.equals(sourceControllerId)) { - Player player = game.getPlayer(playerId); - if (player != null) { - for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - } - } - } - } - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); @@ -126,9 +82,7 @@ public class TargetCardInOpponentsGraveyard extends TargetCard { if (player != null) { Set targetsInThisGraveyeard = new HashSet<>(); for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - targetsInThisGraveyeard.add(card.getId()); - } + targetsInThisGraveyeard.add(card.getId()); } // if there is not enough possible targets, the can't be any if (this.allFromOneOpponent && targetsInThisGraveyeard.size() < this.minNumberOfTargets) { @@ -137,7 +91,7 @@ public class TargetCardInOpponentsGraveyard extends TargetCard { possibleTargets.addAll(targetsInThisGraveyeard); } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java index 5920ba3717d..53264f40a7e 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java @@ -80,59 +80,9 @@ public class TargetCardInYourGraveyard extends TargetCard { Set possibleTargets = new HashSet<>(); Player player = game.getPlayer(sourceControllerId); for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets.add(card.getId()); - } + possibleTargets.add(card.getId()); } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Cards cards, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - Player player = game.getPlayer(sourceControllerId); - if (player == null) { - return possibleTargets; - } - - for (Card card : cards.getCards(filter, sourceControllerId, source, game)) { - if (player.getGraveyard().getCards(game).contains(card)) { - possibleTargets.add(card.getId()); - } - } - return possibleTargets; - } - - /** - * Checks if there are enough {@link Card} that can be selected. - * - * @param sourceControllerId - controller of the select event - * @param game - * @return - true if enough valid {@link Card} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return game.getPlayer(sourceControllerId).getGraveyard().count(filter, game) >= this.minNumberOfTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - Player player = game.getPlayer(sourceControllerId); - if (player != null) { - if (this.minNumberOfTargets == 0) { - return true; - } - int possibleTargets = 0; - for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - } - } - } - return false; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyardOrExile.java b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyardOrExile.java index fd5b6816627..c36eb9e0a13 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyardOrExile.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyardOrExile.java @@ -33,47 +33,26 @@ public class TargetCardInYourGraveyardOrExile extends TargetCard { this.filterExile = target.filterExile; } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - Player player = game.getPlayer(sourceControllerId); - if (player == null) { - return false; - } - int possibleTargets = 0; - // in your graveyard: - possibleTargets += countPossibleTargetInGraveyard(game, player, sourceControllerId, source, - filterGraveyard, isNotTarget(), this.minNumberOfTargets - possibleTargets); - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - // in exile: - possibleTargets += countPossibleTargetInExile(game, player, sourceControllerId, source, - filterExile, isNotTarget(), this.minNumberOfTargets - possibleTargets); - return possibleTargets >= this.minNumberOfTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, (Ability) null, game); - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); + Player player = game.getPlayer(sourceControllerId); if (player == null) { return possibleTargets; } - // in your graveyard: + + // in your graveyard possibleTargets.addAll(getAllPossibleTargetInGraveyard(game, player, sourceControllerId, source, filterGraveyard, isNotTarget())); - // in exile: + // in exile possibleTargets.addAll(getAllPossibleTargetInExile(game, player, sourceControllerId, source, filterExile, isNotTarget())); - return possibleTargets; + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean canTarget(UUID id, Ability source, Game game) { - return this.canTarget(source.getControllerId(), id, source, game); + return this.canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override @@ -84,19 +63,14 @@ public class TargetCardInYourGraveyardOrExile extends TargetCard { } switch (game.getState().getZone(id)) { case GRAVEYARD: - return filterGraveyard.match(card, playerId, source, game); + return playerId == null ? filterGraveyard.match(card, game) : filterGraveyard.match(card, playerId, source, game); case EXILED: - return filterExile.match(card, playerId, source, game); + return playerId == null ? filterExile.match(card, game) : filterExile.match(card, playerId, source, game); default: return false; } } - @Override - public boolean canTarget(UUID id, Game game) { - return this.canTarget(null, id, null, game); - } - @Override public TargetCardInYourGraveyardOrExile copy() { return new TargetCardInYourGraveyardOrExile(this); diff --git a/Mage/src/main/java/mage/target/common/TargetControlledLandPermanent.java b/Mage/src/main/java/mage/target/common/TargetControlledLandPermanent.java new file mode 100644 index 00000000000..6329c37bb9a --- /dev/null +++ b/Mage/src/main/java/mage/target/common/TargetControlledLandPermanent.java @@ -0,0 +1,30 @@ +package mage.target.common; + +import mage.filter.StaticFilters; + +/** + * @author TheElk801 + */ +public class TargetControlledLandPermanent extends TargetControlledPermanent { + + public TargetControlledLandPermanent() { + this(1); + } + + public TargetControlledLandPermanent(int numTargets) { + this(numTargets, numTargets); + } + + public TargetControlledLandPermanent(int minNumTargets, int maxNumTargets) { + super(minNumTargets, maxNumTargets, maxNumTargets > 1 ? StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS : StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, false); + } + + protected TargetControlledLandPermanent(final TargetControlledLandPermanent target) { + super(target); + } + + @Override + public TargetControlledLandPermanent copy() { + return new TargetControlledLandPermanent(this); + } +} diff --git a/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java b/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java index 121b2e8ca52..64689ae5288 100644 --- a/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java +++ b/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java @@ -34,8 +34,8 @@ public class TargetCreaturesWithDifferentPowers extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java b/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java index a3ddb8a237d..10a9db39355 100644 --- a/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java +++ b/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java @@ -12,6 +12,8 @@ import mage.target.TargetPermanent; import java.util.UUID; /** + * TODO: rework to support possible targets + * * @author Mael */ public class TargetOpponentsChoicePermanent extends TargetPermanent { @@ -28,20 +30,15 @@ public class TargetOpponentsChoicePermanent extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game, boolean flag) { - return opponentId != null && super.canTarget(opponentId, id, source, game, flag); - } - - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); if (opponentId != null) { if (permanent != null) { if (source != null) { boolean canSourceControllerTarget = true; if (!isNotTarget()) { - if (!permanent.canBeTargetedBy(game.getObject(source.getId()), controllerId, source, game) - || !permanent.canBeTargetedBy(game.getObject(source), controllerId, source, game)) { + if (!permanent.canBeTargetedBy(game.getObject(source.getId()), playerId, source, game) + || !permanent.canBeTargetedBy(game.getObject(source), playerId, source, game)) { canSourceControllerTarget = false; } } diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentAmount.java b/Mage/src/main/java/mage/target/common/TargetPermanentAmount.java index 9525f7b11cc..2b963c75377 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentAmount.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentAmount.java @@ -77,17 +77,8 @@ public class TargetPermanentAmount extends TargetAmount { return this.filter; } - @Override - public boolean canTarget(UUID objectId, Game game) { - Permanent permanent = game.getPermanent(objectId); - return filter.match(permanent, game); - } - @Override public boolean canTarget(UUID objectId, Ability source, Game game) { - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets().contains(objectId); - } Permanent permanent = game.getPermanent(objectId); if (permanent == null) { return false; @@ -95,8 +86,7 @@ public class TargetPermanentAmount extends TargetAmount { if (source == null) { return filter.match(permanent, game); } - MageObject targetSource = source.getSourceObject(game); - return (notTarget || permanent.canBeTargetedBy(targetSource, source.getControllerId(), source, game)) + return (isNotTarget() || permanent.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(permanent, source.getControllerId(), source, game); } @@ -107,47 +97,18 @@ public class TargetPermanentAmount extends TargetAmount { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game).size() - >= this.minNumberOfTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return game.getBattlefield().getActivePermanents(filter, sourceControllerId, game).size() - >= this.minNumberOfTargets; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets() - .stream() - .collect(Collectors.toSet()); - } - MageObject targetSource = game.getObject(source); - return game + Set possibleTargets = game .getBattlefield() .getActivePermanents(filter, sourceControllerId, source, game) .stream() - .filter(Objects::nonNull) - .filter(permanent -> notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) - .map(Permanent::getId) - .collect(Collectors.toSet()); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets() - .stream() - .collect(Collectors.toSet()); - } - return game - .getBattlefield() - .getActivePermanents(filter, sourceControllerId, game) - .stream() .map(Permanent::getId) .collect(Collectors.toSet()); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java index 6cc048b56ff..af75f3eda94 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java @@ -61,16 +61,6 @@ public class TargetPermanentOrPlayer extends TargetImpl { return filter; } - @Override - public boolean canTarget(UUID id, Game game) { - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filter.match(permanent, game); - } - Player player = game.getPlayer(id); - return filter.match(player, game); - } - @Override public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { return canTarget(id, source, game); @@ -183,33 +173,16 @@ public class TargetPermanentOrPlayer extends TargetImpl { MageObject targetSource = game.getObject(source); for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { Player player = game.getPlayer(playerId); - if (player != null && (notTarget || player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) && filter.match(player, sourceControllerId, source, game)) { + if (player != null && filter.match(player, sourceControllerId, source, game)) { possibleTargets.add(playerId); } } for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if ((notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) && filter.match(permanent, sourceControllerId, source, game)) { + if (filter.match(permanent, sourceControllerId, source, game)) { possibleTargets.add(permanent.getId()); } } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (filter.match(player, game)) { - possibleTargets.add(playerId); - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (filter.match(permanent, sourceControllerId, null, game)) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java index a1e91b817cc..eee75f2dd54 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java @@ -36,49 +36,30 @@ public abstract class TargetPermanentOrPlayerAmount extends TargetAmount { return this.filter; } - @Override - public boolean canTarget(UUID objectId, Game game) { - - // max targets limit reached (only selected can be chosen again) - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets().contains(objectId); - } - - Permanent permanent = game.getPermanent(objectId); - if (permanent != null) { - return filter.match(permanent, game); - } - Player player = game.getPlayer(objectId); - return filter.match(player, game); - } - @Override public boolean canTarget(UUID objectId, Ability source, Game game) { - - // max targets limit reached (only selected can be chosen again) - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets().contains(objectId); - } - Permanent permanent = game.getPermanent(objectId); Player player = game.getPlayer(objectId); if (source != null) { - MageObject targetSource = source.getSourceObject(game); if (permanent != null) { - return permanent.canBeTargetedBy(targetSource, source.getControllerId(), source, game) + return (isNotTarget() || permanent.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(permanent, source.getControllerId(), source, game); } if (player != null) { - return player.canBeTargetedBy(targetSource, source.getControllerId(), source, game) + return (isNotTarget() || player.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(player, game); } + } else { + if (permanent != null) { + return filter.match(permanent, game); + } + if (player != null) { + return filter.match(player, game); + } } - if (permanent != null) { - return filter.match(permanent, game); - } - return filter.match(player, game); + return false; } @Override @@ -88,100 +69,13 @@ public abstract class TargetPermanentOrPlayerAmount extends TargetAmount { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - // no max targets limit here - int count = 0; - MageObject targetSource = game.getObject(source); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player == null - || !player.canBeTargetedBy(targetSource, sourceControllerId, source, game) - || !filter.match(player, game)) { - continue; - } - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (!permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - continue; - } - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - // no max targets limit here - int count = 0; - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player == null || !filter.match(player, game)) { - continue; - } - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - // max targets limit reached (only selected can be chosen again) - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - possibleTargets.addAll(getTargets()); - return possibleTargets; - } - - MageObject targetSource = game.getObject(source); - - game.getState() - .getPlayersInRange(sourceControllerId, game) - .stream() - .map(game::getPlayer) - .filter(Objects::nonNull) - .filter(player -> player.canBeTargetedBy(targetSource, sourceControllerId, source, game) - && filter.match(player, game) - ) - .map(Player::getId) - .forEach(possibleTargets::add); - - game.getBattlefield() - .getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game) - .stream() - .filter(Objects::nonNull) - .filter(permanent -> permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) - .map(Permanent::getId) - .forEach(possibleTargets::add); - - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - - // max targets limit reached (only selected can be chosen again) - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - possibleTargets.addAll(getTargets()); - return possibleTargets; - } - game.getState() .getPlayersInRange(sourceControllerId, game) .stream() @@ -194,10 +88,11 @@ public abstract class TargetPermanentOrPlayerAmount extends TargetAmount { game.getBattlefield() .getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game) .stream() + .filter(Objects::nonNull) .map(Permanent::getId) .forEach(possibleTargets::add); - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrSuspendedCard.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrSuspendedCard.java index 0f00929e453..144b3aa29b3 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrSuspendedCard.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrSuspendedCard.java @@ -55,26 +55,14 @@ public class TargetPermanentOrSuspendedCard extends TargetImpl { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - MageObject sourceObject = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, sourceControllerId, source, game) && filter.match(permanent, sourceControllerId, source, game)) { - return true; - } - } - for (Card card : game.getExile().getAllCards(game)) { - if (filter.match(card, sourceControllerId, source, game)) { - return true; - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(20); - MageObject sourceObject = game.getObject(source); + Set possibleTargets = new HashSet<>(); for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, sourceControllerId, source, game) && filter.match(permanent, sourceControllerId, source, game)) { + if (filter.match(permanent, sourceControllerId, source, game)) { possibleTargets.add(permanent.getId()); } } @@ -83,17 +71,7 @@ public class TargetPermanentOrSuspendedCard extends TargetImpl { possibleTargets.add(card.getId()); } } - return possibleTargets; - } - - @Override - public boolean canTarget(UUID id, Game game) { - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filter.match(permanent, game); - } - Card card = game.getExile().getCard(id, game); - return filter.match(card, game); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -101,8 +79,7 @@ public class TargetPermanentOrSuspendedCard extends TargetImpl { Permanent permanent = game.getPermanent(id); if (permanent != null) { if (source != null) { - MageObject targetSource = game.getObject(source); - return permanent.canBeTargetedBy(targetSource, source.getControllerId(), source, game) + return (isNotTarget() || permanent.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(permanent, source.getControllerId(), source, game); } else { return filter.match(permanent, game); @@ -117,16 +94,6 @@ public class TargetPermanentOrSuspendedCard extends TargetImpl { return this.canTarget(id, source, game); } - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return this.canChoose(sourceControllerId, null, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, null, game); - } - @Override public String getTargetedName(Game game) { StringBuilder sb = new StringBuilder(); diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java b/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java index 9dd16fff7e8..b6c1898fc72 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java @@ -28,8 +28,8 @@ public class TargetPermanentSameController extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (this.getTargets().isEmpty()) { diff --git a/Mage/src/main/java/mage/target/common/TargetSpellOrPermanent.java b/Mage/src/main/java/mage/target/common/TargetSpellOrPermanent.java index 5122e3d5763..b8a432dab60 100644 --- a/Mage/src/main/java/mage/target/common/TargetSpellOrPermanent.java +++ b/Mage/src/main/java/mage/target/common/TargetSpellOrPermanent.java @@ -73,23 +73,12 @@ public class TargetSpellOrPermanent extends TargetImpl { this.filter = filter; } - @Override - public boolean canTarget(UUID id, Game game) { - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filter.match(permanent, game); - } - Spell spell = game.getStack().getSpell(id); - return filter.match(spell, game); - } - @Override public boolean canTarget(UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); if (permanent != null) { if (source != null) { - MageObject targetSource = game.getObject(source); - return permanent.canBeTargetedBy(targetSource, source.getControllerId(), source, game) + return (isNotTarget() || permanent.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(permanent, source.getControllerId(), source, game); } else { return filter.match(permanent, game); @@ -183,35 +172,18 @@ public class TargetSpellOrPermanent extends TargetImpl { for (StackObject stackObject : game.getStack()) { Spell spell = game.getStack().getSpell(stackObject.getId()); if (spell != null - && !source.getSourceId().equals(spell.getSourceId()) + && targetSource != null + && !targetSource.getId().equals(spell.getSourceId()) && filter.match(spell, sourceControllerId, source, game)) { possibleTargets.add(spell.getId()); } } for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game) && filter.match(permanent, sourceControllerId, source, game)) { + if (filter.match(permanent, sourceControllerId, source, game)) { possibleTargets.add(permanent.getId()); } } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (StackObject stackObject : game.getStack()) { - Spell spell = game.getStack().getSpell(stackObject.getId()); - if (spell != null - && filter.match(spell, sourceControllerId, null, game)) { - possibleTargets.add(spell.getId()); - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (filter.match(permanent, sourceControllerId, null, game)) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetTappedPermanentAsYouCast.java b/Mage/src/main/java/mage/target/common/TargetTappedPermanentAsYouCast.java index 4fa993cba4a..db7f03551a3 100644 --- a/Mage/src/main/java/mage/target/common/TargetTappedPermanentAsYouCast.java +++ b/Mage/src/main/java/mage/target/common/TargetTappedPermanentAsYouCast.java @@ -30,21 +30,21 @@ public class TargetTappedPermanentAsYouCast extends TargetPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return game.getBattlefield().getActivePermanents(getFilter(), source.getControllerId(), source, game).stream() + Set possibleTargets = game.getBattlefield().getActivePermanents(getFilter(), sourceControllerId, source, game).stream() .filter(Permanent::isTapped) .map(Permanent::getId) .collect(Collectors.toSet()); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return game.getBattlefield().getActivePermanents(getFilter(), source.getControllerId(), source, game).stream() - .anyMatch(Permanent::isTapped); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (super.canTarget(playerId, id, source, game)) { Permanent permanent = game.getPermanent(id); return permanent != null && permanent.isTapped(); } @@ -54,6 +54,14 @@ public class TargetTappedPermanentAsYouCast extends TargetPermanent { // See ruling: https://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/253345-dream-leash @Override public boolean stillLegalTarget(UUID controllerId, UUID id, Ability source, Game game) { + // on resolve must ignore tapped status + + // The middle ability of Enthralling Hold affects only the choice of target as the spell is cast. + // If the creature becomes untapped before the spell resolves, it still resolves. If a player is allowed + // to change the spell's target while it's on the stack, they may choose an untapped creature. If you put + // Enthralling Hold onto the battlefield without casting it, you may attach it to an untapped creature. + // (2020-06-23) + Permanent permanent = game.getPermanent(id); return permanent != null && getFilter().match(permanent, game) diff --git a/Mage/src/main/java/mage/target/common/TargetTriggeredAbility.java b/Mage/src/main/java/mage/target/common/TargetTriggeredAbility.java deleted file mode 100644 index 585af8abdd0..00000000000 --- a/Mage/src/main/java/mage/target/common/TargetTriggeredAbility.java +++ /dev/null @@ -1,100 +0,0 @@ -package mage.target.common; - -import mage.abilities.Ability; -import mage.constants.Zone; -import mage.filter.Filter; -import mage.filter.FilterStackObject; -import mage.game.Game; -import mage.game.stack.StackObject; -import mage.target.TargetObject; - -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * @author Styxo - */ -public class TargetTriggeredAbility extends TargetObject { - - protected final FilterStackObject filter; - - public TargetTriggeredAbility(FilterStackObject filter) { - this.minNumberOfTargets = 1; - this.maxNumberOfTargets = 1; - this.zone = Zone.STACK; - this.targetName = filter.getMessage(); - this.filter = filter; - } - - protected TargetTriggeredAbility(final TargetTriggeredAbility target) { - super(target); - this.filter = target.filter.copy(); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - // 114.4. A spell or ability on the stack is an illegal target for itself. - if (source != null && source.getSourceId().equals(id)) { - return false; - } - - StackObject stackObject = game.getStack().getStackObject(id); - return isTriggeredAbility(stackObject) - && source != null - && filter.match(stackObject, source.getControllerId(), source, game); - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - for (StackObject stackObject : game.getStack()) { - if (isTriggeredAbility(stackObject) - && filter.match(stackObject, sourceControllerId, source, game)) { - return true; - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return game.getStack() - .stream() - .anyMatch(TargetTriggeredAbility::isTriggeredAbility); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return possibleTargets(sourceControllerId, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return game.getStack().stream() - .filter(TargetTriggeredAbility::isTriggeredAbility) - .map(stackObject -> stackObject.getStackAbility().getId()) - .collect(Collectors.toSet()); - } - - @Override - public TargetTriggeredAbility copy() { - return new TargetTriggeredAbility(this); - } - - @Override - public Filter getFilter() { - return filter; - } - - static boolean isTriggeredAbility(StackObject stackObject) { - if (stackObject == null) { - return false; - } - if (stackObject instanceof Ability) { - Ability ability = (Ability) stackObject; - return ability.getAbilityType().isTriggeredAbility(); - } - return false; - } - -} diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index abc4f0bbe53..65a79575a4b 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -615,7 +615,7 @@ public final class CardUtil { } public static UUID getExileZoneId(Game game, Ability source, int offset) { - return getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter() + offset); + return getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC() + offset); } public static UUID getExileZoneId(Game game, UUID objectId, int zoneChangeCounter) { @@ -758,7 +758,7 @@ public final class CardUtil { MageObject sourceObject = game.getObject(source); if (sourceObject != null) { title = sourceObject.getIdName() - + " [" + source.getSourceObjectZoneChangeCounter() + "]" + + " [" + source.getStackMomentSourceZCC() + "]" + (textSuffix == null ? "" : " " + textSuffix); } else { title = textSuffix == null ? "" : textSuffix; @@ -1812,7 +1812,7 @@ public final class CardUtil { */ public static int getActualSourceObjectZoneChangeCounter(Game game, Ability source) { // current object zcc, find from source object (it can be permanent or spell on stack) - int zcc = source.getSourceObjectZoneChangeCounter(); + int zcc = source.getStackMomentSourceZCC(); if (zcc == 0) { // if ability is not activated yet then use current object's zcc (example: triggered etb ability checking the kicker conditional) zcc = game.getState().getZoneChangeCounter(source.getSourceId()); diff --git a/Mage/src/main/java/mage/util/ConsoleUtil.java b/Mage/src/main/java/mage/util/ConsoleUtil.java new file mode 100644 index 00000000000..cb143692785 --- /dev/null +++ b/Mage/src/main/java/mage/util/ConsoleUtil.java @@ -0,0 +1,19 @@ +package mage.util; + +/** + * Helper class to work with console logs + */ +public class ConsoleUtil { + + public static String asRed(String text) { + return "\u001B[31m" + text + "\u001B[0m"; + } + + public static String asGreen(String text) { + return "\u001B[32m" + text + "\u001B[0m"; + } + + public static String asYellow(String text) { + return "\u001B[33m" + text + "\u001B[0m"; + } +} diff --git a/Mage/src/main/java/mage/util/DebugUtil.java b/Mage/src/main/java/mage/util/DebugUtil.java index fcad86ea034..8706f5f2ffb 100644 --- a/Mage/src/main/java/mage/util/DebugUtil.java +++ b/Mage/src/main/java/mage/util/DebugUtil.java @@ -15,7 +15,7 @@ public class DebugUtil { // game simulations runs in multiple threads, if you stop code to debug then it will be terminated by timeout // so AI debug mode will make single simulation thread without any timeouts public static boolean AI_ENABLE_DEBUG_MODE = false; - public static boolean AI_SHOW_TARGET_OPTIMIZATION_LOGS = false; // works with target amount + public static boolean AI_SHOW_TARGET_OPTIMIZATION_LOGS = false; // works with target and target amount calculations // SERVER // data collectors - enable additional logs and data collection for better AI and human games debugging diff --git a/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java b/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java index 3213e8b76de..b37b0edf9f3 100644 --- a/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java @@ -1,12 +1,13 @@ package mage.watchers.common; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; +import java.util.*; + +import mage.MageObjectReference; import mage.cards.Card; import mage.constants.WatcherScope; import mage.game.Game; import mage.game.events.GameEvent; +import mage.players.Player; import mage.util.CardUtil; import mage.watchers.Watcher; @@ -16,9 +17,12 @@ import mage.watchers.Watcher; */ public class ForetoldWatcher extends Watcher { - // If foretell was activated or a card was Foretold by the controller this turn, this list stores it. Cleared at the end of the turn. - private final Set foretellCardsThisTurn = new HashSet<>(); - private final Set foretoldCards = new HashSet<>(); + private final Set foretoldCards = new HashSet<>(); + // cards foretold - ZCC stored to reference from stack (exile zone plus 1) + + private final Map playerForetellCount = new HashMap<>(); + // map of player id to number of times they foretell a card, cleared each turn + public ForetoldWatcher() { super(WatcherScope.GAME); @@ -26,35 +30,32 @@ public class ForetoldWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.FORETELL) { - Card card = game.getCard(event.getTargetId()); - if (card != null - && controllerId == event.getPlayerId()) { - foretellCardsThisTurn.add(card.getId()); - foretoldCards.add(card.getId()); - } + if (event.getType() != GameEvent.EventType.CARD_FORETOLD) { + return; } - // Ethereal Valkyrie - if (event.getType() == GameEvent.EventType.FORETOLD) { - Card card = game.getCard(event.getTargetId()); - if (card != null) { - // Ethereal Valkyrie does not Foretell the card, it becomes Foretold, so don't add it to the Foretell list - foretoldCards.add(card.getId()); + Card card = game.getCard(event.getTargetId()); + if (card != null) { + foretoldCards.add(new MageObjectReference(card, game, 1)); + } + if (event.getFlag()) { + Player player = game.getPlayer(event.getPlayerId()); + if (player != null) { + playerForetellCount.compute(player.getId(), CardUtil::setOrIncrementValue); } } } - public boolean cardWasForetold(UUID sourceId) { - return foretoldCards.contains(sourceId); + public boolean checkForetold(UUID sourceId, Game game) { + return foretoldCards.contains(new MageObjectReference(sourceId, game)); } - public int countNumberForetellThisTurn() { - return foretellCardsThisTurn.size(); + public int getPlayerForetellCountThisTurn(UUID playerId) { + return playerForetellCount.getOrDefault(playerId, 0); } @Override public void reset() { super.reset(); - foretellCardsThisTurn.clear(); + playerForetellCount.clear(); } } diff --git a/Mage/src/main/java/mage/watchers/common/RevoltWatcher.java b/Mage/src/main/java/mage/watchers/common/RevoltWatcher.java index b6f0c6583e1..e5d49b85a47 100644 --- a/Mage/src/main/java/mage/watchers/common/RevoltWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/RevoltWatcher.java @@ -17,7 +17,7 @@ import java.util.UUID; */ public class RevoltWatcher extends Watcher { - private final Set revoltActivePlayerIds = new HashSet<>(0); + private final Set revoltActivePlayerIds = new HashSet<>(); public RevoltWatcher() { super(WatcherScope.GAME); diff --git a/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java b/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java index d97285e86dc..259a0b6588d 100644 --- a/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java @@ -86,7 +86,7 @@ public class SpellsCastWatcher extends Watcher { .map(Spell::getCard) .map(Card::getMainCard) .anyMatch(card -> card.getId().equals(source.getSourceId()) - && card.getZoneChangeCounter(game) == source.getSourceObjectZoneChangeCounter())) { + && card.getZoneChangeCounter(game) == source.getStackMomentSourceZCC())) { return entry.getKey(); } } diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index 8d0dacf120a..d3d050e1e5d 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -1535,7 +1535,8 @@ |Generate|TOK:SLD|Hydra|||ZaxaraTheExemplaryHydraToken| |Generate|TOK:SLD|Icingdeath, Frost Tongue|||IcingdeathFrostTongueToken| |Generate|TOK:SLD|Marit Lage|||MaritLageToken| -|Generate|TOK:SLD|Mechtitan|||MechtitanToken| +|Generate|TOK:SLD|Mechtitan|1||MechtitanToken| +|Generate|TOK:SLD|Mechtitan|2||MechtitanToken| |Generate|TOK:SLD|Myr|||MyrToken| |Generate|TOK:SLD|Saproling|||SaprolingToken| |Generate|TOK:SLD|Shapeshifter|1||ShapeshifterBlueToken| @@ -2942,3 +2943,9 @@ # PL23 |Generate|TOK:PL23|Food|||FoodToken| + +# PL24 +|Generate|TOK:PL24|Dragon|||DragonToken| + +# PL25 +|Generate|TOK:PL25|Snake|||SnakeToken| \ No newline at end of file diff --git a/Mage/src/test/data/importer/testdeck.mtga b/Mage/src/test/data/importer/testdeck.mtga index fe31c404b5b..887a568c686 100644 --- a/Mage/src/test/data/importer/testdeck.mtga +++ b/Mage/src/test/data/importer/testdeck.mtga @@ -5,6 +5,9 @@ 1 Expansion // Explosion (GRN) 224 1 Forest (XLN) 277 1 Teferi, Hero of Dominaria (DAR) 207 +1 Benalish Marshal (PDOM) 6p +1 Benalish Marshal (PDOM) 6s +1 Benalish Marshal 3 Unmoored Ego (GRN) 212 1 Beacon Bolt (GRN) 154 diff --git a/Mage/src/test/java/mage/cards/decks/importer/FakeCardLookup.java b/Mage/src/test/java/mage/cards/decks/importer/FakeCardLookup.java index c655b8fff16..beab8edea02 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/FakeCardLookup.java +++ b/Mage/src/test/java/mage/cards/decks/importer/FakeCardLookup.java @@ -29,26 +29,29 @@ public class FakeCardLookup extends CardLookup { return this; } - public Optional lookupCardInfo(String cardName) { + public CardInfo lookupCardInfo(String cardName) { CardInfo card = lookup.get(cardName); if (card != null) { - return Optional.of(card); + return card; } if (alwaysMatches) { - return Optional.of(new CardInfo() {{ + return new CardInfo() {{ name = cardName; - }}); + }}; } - return Optional.empty(); + return null; } @Override public List lookupCardInfo(CardCriteria criteria) { - return lookupCardInfo(criteria.getName()) - .map(Collections::singletonList) - .orElse(Collections.emptyList()); + CardInfo found = lookupCardInfo(criteria.getName()); + if (found != null) { + return new ArrayList<>(Collections.singletonList(found)); + } else { + return Collections.emptyList(); + } } } diff --git a/Mage/src/test/java/mage/cards/decks/importer/MtgaImporterTest.java b/Mage/src/test/java/mage/cards/decks/importer/MtgaImporterTest.java index 865dae482f5..46c82f86a4d 100644 --- a/Mage/src/test/java/mage/cards/decks/importer/MtgaImporterTest.java +++ b/Mage/src/test/java/mage/cards/decks/importer/MtgaImporterTest.java @@ -36,11 +36,12 @@ public class MtgaImporterTest { .addMain("Expansion // Explosion", 1) .addMain("Forest", 1) .addMain("Teferi, Hero of Dominaria", 1) + .addMain("Benalish Marshal", 3) .addSide("Unmoored Ego", 3) .addSide("Beacon Bolt", 1) - .verify(deck, 8, 4); + .verify(deck, 11, 4); } } \ No newline at end of file diff --git a/Utils/cardInfo.tmpl b/Utils/cardInfo.tmpl new file mode 100644 index 00000000000..5b04c6a01b1 --- /dev/null +++ b/Utils/cardInfo.tmpl @@ -0,0 +1,7 @@ + /* + [=$cardName=] + [=$manaCost=] + [=$typeLine=] + [=$abilities=][= $powerToughness ? "\n $powerToughness" : "" =] + */ + private static final String [=$classNameLower=] = "[=$cardName=]"; \ No newline at end of file diff --git a/Utils/cardTest.tmpl b/Utils/cardTest.tmpl new file mode 100644 index 00000000000..e6cab7e6c31 --- /dev/null +++ b/Utils/cardTest.tmpl @@ -0,0 +1,18 @@ +package org.mage.test.cards.single.[=$setCode=]; + +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author [=$author=] + */ +public class [=$className=]Test extends CardTestPlayerBase { + +[=$cardInfo=] + + @Test + public void test[=$className=]() { + setStrictChooseMode(true); + } +} \ No newline at end of file diff --git a/Utils/gen-card-test.pl b/Utils/gen-card-test.pl new file mode 100644 index 00000000000..1817ed3a63f --- /dev/null +++ b/Utils/gen-card-test.pl @@ -0,0 +1,198 @@ +#!/usr/bin/perl -w + +#author: North/Jmlundeen +=begin comment + +To use this script you can call it with perl ./gen-card-test.pl "Storm Crow" "Lightning Bolt" +The first argument (Storm Crow) is the main card to generate a test class for. +The cards after (Lighting Bolt) will place card info and create a variable inside the test class. +You can add as many additional cards as you like. + +You can also call the script without arguments and it will prompt you for card names +=cut + + +use Text::Template; +use strict; +use File::Path qw(make_path); + +my $authorFile = 'author.txt'; +my $dataFile = 'mtg-cards-data.txt'; +my $setsFile = 'mtg-sets-data.txt'; +my $knownSetsFile = 'known-sets.txt'; +my $keywordsFile = 'keywords.txt'; + +my %cards; +my %sets; +my %knownSets; +my %keywords; + +sub toCamelCase { + my $string = $_[0]; + $string =~ s/\b([\w']+)\b/ucfirst($1)/ge; + $string =~ s/[-,\s\':.!\/]//g; + $string; +} + +sub fixCost { + my $string = $_[0]; + $string =~ s/{([2BUGRW])([2BUGRW])}/{$1\/$2}/g; + $string; +} + +sub generateCardInfo { + my ($cardName, $infoTemplate) = @_; + + if (!exists $cards{$cardName}) { + warn "Card name doesn't exist: $cardName (skipping)\n"; + return ""; + } + + my %vars; + $vars{'classNameLower'} = lcfirst(toCamelCase($cardName)); + my @card; + + foreach my $setName (keys %{$cards{$cardName}}) { + @card = @{(values(%{$cards{$cardName}{$setName}}))[0]}; + last; # Just get the first one for additional cards + } + + $vars{'cardName'} = $card[0]; + $vars{'manaCost'} = $card[4]; + $vars{'typeLine'} = $card[5]; + + my $cardAbilities = $card[8]; + my @abilities = split(/\$/, $cardAbilities); + my $abilitiesFormatted = join("\n ", @abilities); + $vars{'abilities'} = $abilitiesFormatted; + if ($card[6]) { + $vars{'powerToughness'} = "$card[6]/$card[7]" + } + + return $infoTemplate->fill_in(HASH => \%vars); +} + +my $author; +if (-e $authorFile) { + open(DATA, $authorFile) || die "can't open $authorFile : $!"; + $author = ; + chomp $author; + close(DATA); +} else { + $author = 'anonymous'; +} + +open(DATA, $dataFile) || die "can't open $dataFile : $!"; +while (my $line = ) { + my @data = split('\\|', $line); + $cards{$data[0]}{$data[1]}{$data[2]} = \@data; +} +close(DATA); + +open(DATA, $setsFile) || die "can't open $setsFile : $!"; +while (my $line = ) { + my @data = split('\\|', $line); + $sets{$data[0]} = $data[1]; +} +close(DATA); + +open(DATA, $knownSetsFile) || die "can't open $knownSetsFile : $!"; +while (my $line = ) { + my @data = split('\\|', $line); + $knownSets{$data[0]} = $data[1]; +} +close(DATA); + +open(DATA, $keywordsFile) || die "can't open $keywordsFile : $!"; +while (my $line = ) { + my @data = split('\\|', $line); + $keywords{toCamelCase($data[0])} = $data[1]; +} +close(DATA); + +# Get card names from arguments +my @cardNames = @ARGV; +if (@cardNames == 0) { + print 'Enter a card name: '; + my $input = ; + chomp $input; + push @cardNames, $input; + + # Prompt for additional cards + print 'Enter additional card names (one per line, empty line to finish): '; + while (my $additionalCard = ) { + chomp $additionalCard; + last if $additionalCard eq ''; # Empty line ends input + push @cardNames, $additionalCard; + } +} + +# Main card is the first one +my $mainCardName = $cardNames[0]; +my @additionalCards = @cardNames[1..$#cardNames]; + +if (!exists $cards{$mainCardName}) { + my $possible; + foreach $possible (sort keys(%cards)) { + if ($possible =~ m/$mainCardName/img && $mainCardName =~ m/..../) { + print("Did you mean $possible?\n"); + } + } + die "Card name doesn't exist: $mainCardName\n"; +} + +my $cardTemplate = 'cardTest.tmpl'; +my $cardInfoTemplate = 'cardInfo.tmpl'; +my $originalName = $mainCardName; +my $setCode; + +# Generate lines to corresponding sets +my %vars; +$vars{'className'} = toCamelCase($mainCardName); +$vars{'classNameLower'} = lcfirst(toCamelCase($mainCardName)); +$vars{'cardNameFirstLetter'} = lc substr($mainCardName, 0, 1); + +foreach my $setName (keys %{$cards{$originalName}}) { + $setCode = lc($sets{$setName}); +} + +# Check if card is already implemented +my $fileName = "../Mage.Tests/src/test/java/org/mage/test/cards/single/" . $setCode . "/" . toCamelCase($mainCardName) . "Test.java"; +if (-e $fileName) { + die "$mainCardName is already implemented.\n$fileName\n"; +} + +# Create directory if it doesn't exist +my $dir = "../Mage.Tests/src/test/java/org/mage/test/cards/single/" . $setCode; +make_path($dir) unless -d $dir; + +# Generate the card templates +my $result; +my $template = Text::Template->new(TYPE => 'FILE', SOURCE => $cardTemplate, DELIMITERS => [ '[=', '=]' ]); +my $infoTemplate = Text::Template->new(TYPE => 'FILE', SOURCE => $cardInfoTemplate, DELIMITERS => [ '[=', '=]' ]); + +$vars{'author'} = $author; +$vars{'setCode'} = $setCode; + +# Generate main card info +my $allCardInfo = generateCardInfo($mainCardName, $infoTemplate); + +# Generate additional card info templates +foreach my $additionalCard (@additionalCards) { + my $additionalInfo = generateCardInfo($additionalCard, $infoTemplate); + if (defined $additionalInfo) { + $allCardInfo .= "\n\n" . $additionalInfo; + } +} + +$vars{'cardInfo'} = $allCardInfo; +$result = $template->fill_in(HASH => \%vars); + +open CARD, "> $fileName"; +print CARD $result; +close CARD; + +print "$fileName\n"; +if (@additionalCards > 0) { + print "Additional cards included: " . join(", ", @additionalCards) . "\n"; +} \ No newline at end of file diff --git a/Utils/gen-list-unimplemented-cards-for-set.pl b/Utils/gen-list-unimplemented-cards-for-set.pl index 3d8d2849a3c..560f76812f1 100755 --- a/Utils/gen-list-unimplemented-cards-for-set.pl +++ b/Utils/gen-list-unimplemented-cards-for-set.pl @@ -1,13 +1,14 @@ #!/usr/bin/perl -w #author: North - +use Text::Template; use strict; use Scalar::Util qw(looks_like_number); my $dataFile = "mtg-cards-data.txt"; my $setsFile = "mtg-sets-data.txt"; my $knownSetsFile = "known-sets.txt"; +my $templateFile = "issue_tracker.tmpl"; my %sets; my %knownSets; @@ -16,12 +17,13 @@ my @setCards; open (DATA, $knownSetsFile) || die "can't open $knownSetsFile"; while(my $line = ) { + chomp $line; my @data = split('\\|', $line); $knownSets{$data[0]} = $data[1]; - #print ("$data[0] ===> $data[1]\n"); } close(DATA); +my @basicLands = ("Plains", "Island", "Swamp", "Mountain", "Forest"); # gets the set name my $setName = $ARGV[0]; @@ -29,7 +31,6 @@ if(!$setName) { print 'Enter a set name: '; $setName = ; chomp $setName; - $setName = $setName; } while (!defined ($knownSets{$setName})) @@ -49,13 +50,12 @@ while (!defined ($knownSets{$setName})) print 'Enter a set name: '; $setName = ; - $setName = $setName; chomp $setName; } - open (DATA, $dataFile) || die "can't open $dataFile"; while(my $line = ) { + chomp $line; my @data = split('\\|', $line); if ($data[1] eq $setName) { push(@setCards, \@data); @@ -65,70 +65,97 @@ close(DATA); open (DATA, $setsFile) || die "can't open $setsFile"; while(my $line = ) { + chomp $line; my @data = split('\\|', $line); $sets{$data[0]}= $data[1]; } close(DATA); - sub cardSort { - if (!looks_like_number(@{$a}[2])) { return -1; } - if (!looks_like_number(@{$b}[2])) { return 1; } - if (@{$a}[2] < @{$b}[2]) { return -1; } - elsif (@{$a}[2] == @{$b}[2]) { return 0;} - elsif (@{$a}[2] > @{$b}[2]) { return 1; } + if (!looks_like_number(@{$a}[2])) { return -1; } + if (!looks_like_number(@{$b}[2])) { return 1; } + if (@{$a}[2] < @{$b}[2]) { return -1; } + elsif (@{$a}[2] == @{$b}[2]) { return 0;} + elsif (@{$a}[2] > @{$b}[2]) { return 1; } } sub toCamelCase { my $string = $_[0]; $string =~ s/\b([\w']+)\b/ucfirst($1)/ge; - $string =~ s/[-,\s\']//g; + $string =~ s/[-,\s\'\.!@#*\(\)]//g; $string; } -# TODO: check for basic lands with ending 1,2,3,4,5 ... +# Check which cards are implemented my %cardNames; -my $toPrint = ''; -my $setAbbr = $sets{$setName}; -foreach my $card (sort cardSort @setCards) { - my $className = toCamelCase(@{$card}[0]); +my %seenCards; # Track which card names we've already processed +my @implementedCards; +my @unimplementedCards; +my $previousCollectorNumber = -1; +my %vars; - $cardNames {@{$card}[0]} = 1; +my $setAbbr = $sets{$setName}; + +foreach my $card (sort cardSort @setCards) { + my $className = toCamelCase(@{$card}[0]); + if ($className ~~ @basicLands) { + next; + } + + my $cardName = @{$card}[0]; + my $collectorNumber = @{$card}[2]; + + # Skip if we've already processed this card name or is the back face of a card + if (exists $seenCards{$cardName} or $previousCollectorNumber == $collectorNumber) { + $seenCards{$cardName} = 1; + next; + } + $seenCards{$cardName} = 1; + $previousCollectorNumber = $collectorNumber; my $currentFileName = "../Mage.Sets/src/mage/cards/" . lc(substr($className, 0, 1)) . "/" . $className . ".java"; - if(! -e $currentFileName) { - $cardNames {@{$card}[0]} = 0; - if ($toPrint) { - $toPrint .= "\n"; - } - my $cardName = @{$card}[0]; - $cardName =~ s/ /+/g; - $toPrint .= "@{$card}[2]|[@{$card}[0]](https://scryfall.com/search?q=!\"$cardName\" e:$setAbbr)"; - } -} + my $cardNameForUrl = $cardName; + $cardNameForUrl =~ s/ //g; + my $cardEntry = "- [ ] In progress -- [$cardName](https://scryfall.com/search?q=!\"$cardNameForUrl\" e:$setAbbr)"; -open CARD, "> " . lc($sets{$setName}) ."_unimplemented.txt"; -print CARD $toPrint; -close CARD; - - -print ("Unimplemented cards are here: " . lc($sets{$setName}) ."_unimplemented.txt\n"); - -open ISSUE_TRACKER, "> " . lc($sets{$setName}) ."_issue_tracker.txt"; -print ISSUE_TRACKER "# Cards in set:\n"; - - -my $cn; -foreach $cn (sort keys (%cardNames)) -{ - my $x_or_not = "[ ]"; - if ($cardNames {$cn} == 1) - { - $x_or_not = "[x]"; + if(-e $currentFileName) { + # Card is implemented + $cardNames{$cardName} = 1; + my $implementedEntry = "- [x] Done -- [$cardName](https://scryfall.com/search?q=!\"$cardNameForUrl\" e:$setAbbr)"; + push(@implementedCards, $implementedEntry); + } else { + # Card is not implemented + $cardNames{$cardName} = 0; + push(@unimplementedCards, $cardEntry); } - my $cn2 = $cn; - $cn2 =~ s/ /+/g; - print ISSUE_TRACKER "- $x_or_not [$cn](https://scryfall.com/search?q=!\"$cn2\" e:$setAbbr)\n"; } -close ISSUE_TRACKER; -print ("Tracking Issue text for a new Github issue (similar to https://github.com/magefree/mage/issues/2215): " . lc($setAbbr) ."_issue_tracker.txt\n"); + +# Build the unimplemented URL for Scryfall +my $unimplementedUrl = "https://scryfall.com/search?q="; +my @unimplementedNames; +foreach my $cardName (sort keys %cardNames) { + if ($cardNames{$cardName} == 0) { + my $urlCardName = $cardName; + $urlCardName =~ s/ //g; + $urlCardName =~ s/"/\\"/g; # Escape quotes + push(@unimplementedNames, "!\"$urlCardName\""); + } +} +$unimplementedUrl .= join("or", @unimplementedNames) . "&unique=cards"; + +# Read template file +my $template = Text::Template->new(TYPE => 'FILE', SOURCE => $templateFile, DELIMITERS => [ '[=', '=]' ]); +$vars{'unimplemented'} = join("\n", @unimplementedCards); +$vars{'implemented'} = join("\n", @implementedCards); +$vars{'setName'} = $setName; +$vars{'unimplementedUrl'} = $unimplementedUrl; +my $result = $template->fill_in(HASH => \%vars); +# Write the final issue tracker file +my $outputFile = lc($sets{$setName}) . "_issue_tracker.txt"; +open(OUTPUT, "> $outputFile") || die "can't open $outputFile for writing"; +print OUTPUT $result; +close(OUTPUT); + +print "Issue tracker generated: $outputFile\n"; +print "Implemented cards: " . scalar(@implementedCards) . "\n"; +print "Unimplemented cards: " . scalar(@unimplementedCards) . "\n"; \ No newline at end of file diff --git a/Utils/issue_tracker.tmpl b/Utils/issue_tracker.tmpl new file mode 100644 index 00000000000..17b7b196da2 --- /dev/null +++ b/Utils/issue_tracker.tmpl @@ -0,0 +1,18 @@ +This checklist is here to help manage the implementation of [=$setName=]. If a card is marked as being in progress then someone is working on it. + +If you're new to implementing cards then you likely don't have permission to check things off. This is totally fine! We still appreciate your contributions so just leave a comment to let us know that you're working on it. + +Don't worry about moving things from in progress to completed either, there's a script that handles that, and don't worry about fixing text issues as those are usually handled when the set is done. + +[All Sets](https://github.com/magefree/mage/wiki/Set-implementation-list) + +# Unimplemented Cards + +[=$unimplemented=] + +[Scryfall gallery of everything currently unimplemented]([=$unimplementedUrl=]) +# Implemented Cards +

Click to expand + +[=$implemented=] +
diff --git a/Utils/keywords.txt b/Utils/keywords.txt index f23982f5082..a2e1ccb4ebe 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -52,6 +52,7 @@ Exploit|new| Extort|new| Fabricate|number| Fear|instance| +Firebending|number| First strike|instance| Flanking|new| Flash|instance| @@ -84,6 +85,7 @@ Lifelink|instance| Living metal|new| Living weapon|new| Madness|cost| +Mayhem|card, manaString| Melee|new| Menace|new| Mentor|new| diff --git a/Utils/known-sets.txt b/Utils/known-sets.txt index a3483768fea..9ea77b07980 100644 --- a/Utils/known-sets.txt +++ b/Utils/known-sets.txt @@ -19,6 +19,7 @@ Asia Pacific Land Program|AsiaPacificLandProgram| Assassin's Creed|AssassinsCreed| Avacyn Restored|AvacynRestored| Avatar: The Last Airbender|AvatarTheLastAirbender| +Avatar: The Last Airbender Eternal|AvatarTheLastAirbenderEternal| Battlebond|Battlebond| Battle for Zendikar|BattleForZendikar| Betrayers of Kamigawa|BetrayersOfKamigawa| diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 473eac70a0e..96a670e932f 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -59718,64 +59718,518 @@ Temple of Triumph|Edge of Eternities Commander|188|R||Land|||This land enters ta Twilight Mire|Edge of Eternities Commander|189|R||Land|||{T}: Add {C}.${B/G}, {T}: Add {B}{B}, {B}{G}, or {G}{G}.| Viridescent Bog|Edge of Eternities Commander|190|R||Land|||{1}, {T}: Add {B}{G}.| Wastes|Edge of Eternities Commander|191|C||Basic Land|||{T}: Add {C}.| +Aang's Journey|Avatar: The Last Airbender|1|C|{2}|Sorcery - Lesson|||Kicker {2}$Search your library for a basic land card. If this spell was kicked, instead search your library for a basic land card and a Shrine card. Reveal those cards, put them into your hand, then shuffle.$You gain 2 life.| +Aang, the Last Airbender|Avatar: The Last Airbender|4|U|{3}{W}|Legendary Creature - Human Avatar Ally|3|2|Flying$When Aang enters, airbend up to one other target nonland permanent.$Whenever you cast a Lesson spell, Aang gains lifelink until end of turn.| +Aang's Iceberg|Avatar: The Last Airbender|5|R|{2}{W}|Enchantment|||Flash$When this enchantment enters, exile up to one other target nonland permanent until this enchantment leaves the battlefield.$Waterbend {3}: Sacrifice this enchantment. If you do, scry 2.| +Airbending Lesson|Avatar: The Last Airbender|8|C|{2}{W}|Instant - Lesson|||Airbend target nonland permanent.$Draw a card.| +Appa, Steadfast Guardian|Avatar: The Last Airbender|10|M|{2}{W}{W}|Legendary Creature - Bison Ally|3|4|Flash$Flying$When Appa enters, airbend any number of other target nonland permanents you control.$Whenever you cast a spell from exile, create a 1/1 white Ally creature token.| +Avatar Enthusiasts|Avatar: The Last Airbender|11|C|{2}{W}|Creature - Human Peasant Ally|2|2|Whenever another Ally you control enters, put a +1/+1 counter on this creature.| +Glider Kids|Avatar: The Last Airbender|21|C|{2}{W}|Creature - Human Pilot Ally|2|3|Flying$When this creature enters, scry 1.| +Jeong Jeong's Deserters|Avatar: The Last Airbender|25|C|{1}{W}|Creature - Human Rebel Ally|1|2|When this creature enters, put a +1/+1 counter on target creature.| +Master Piandao|Avatar: The Last Airbender|28|U|{4}{W}|Legendary Creature - Human Warrior Ally|4|4|First strike$Whenever Master Piandao attacks, look at the top four cards of your library. You may reveal an Ally, Equipment, or Lesson card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.| +Momo, Friendly Flier|Avatar: The Last Airbender|29|R|{W}|Legendary Creature - Lemur Bat Ally|1|1|Flying$The first non-Lemur creature spell with flying you cast during each of your turns costs {1} less to cast.$Whenever another creature you control with flying enters, Momo gets +1/+1 until end of turn.| +Path to Redemption|Avatar: The Last Airbender|31|C|{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature can't attack or block.${5}, Sacrifice this Aura: Exile enchanted creature. Create a 1/1 white Ally creature token. Activate only during your turn.| +Rabaroo Troop|Avatar: The Last Airbender|32|C|{3}{W}{W}|Creature - Rabbit Kangaroo|3|5|Landfall -- Whenever a land you control enters, this creature gains flying until end of turn and you gain 1 life.$Plainscycling {2}| +Razor Rings|Avatar: The Last Airbender|33|C|{1}{W}|Instant|||Razor Rings deals 4 damage to target attacking or blocking creature. You gain life equal to the excess damage dealt this way.| +Southern Air Temple|Avatar: The Last Airbender|36|U|{3}{W}|Legendary Enchantment - Shrine|||When Southern Air Temple enters, put X +1/+1 counters on each creature you control, where X is the number of Shrines you control.$Whenever another Shrine you control enters, put a +1/+1 counter on each creature you control.| +First-Time Flyer|Avatar: The Last Airbender|49|C|{1}{U}|Creature - Human Pilot Ally|1|2|Flying$This creature gets +1/+1 as long as there's a Lesson card in your graveyard.| +Flexible Waterbender|Avatar: The Last Airbender|50|C|{3}{U}|Creature - Human Warrior Ally|2|5|Vigilance$Waterbend {3}: This creature has base power and toughness 5/2 until end of turn.| +Geyser Leaper|Avatar: The Last Airbender|52|C|{4}{U}|Creature - Human Warrior Ally|4|3|Flying$Waterbend {4}: Draw a card, then discard a card.| +Giant Koi|Avatar: The Last Airbender|53|C|{4}{U}{U}|Creature - Fish|5|7|Waterbend {3}: This creature can't be blocked this turn.$Islandcycling {2}| +Iguana Parrot|Avatar: The Last Airbender|56|C|{2}{U}|Creature - Lizard Bird Pirate|2|2|Flying, vigilance$Prowess| +It'll Quench Ya!|Avatar: The Last Airbender|58|C|{1}{U}|Instant - Lesson|||Counter target spell unless its controller pays {2}.| +Katara, Bending Prodigy|Avatar: The Last Airbender|59|U|{2}{U}|Legendary Creature - Human Warrior Ally|2|3|At the beginning of your end step, if Katara is tapped, put a +1/+1 counter on her.$Waterbend {6}: Draw a card.| +Master Pakku|Avatar: The Last Airbender|63|U|{1}{U}|Legendary Creature - Human Advisor Ally|1|3|Prowess$Whenever Master Pakku becomes tapped, target player mills X cards, where X is the number of Lesson cards in your graveyard.| +Otter-Penguin|Avatar: The Last Airbender|67|C|{1}{U}|Creature - Otter Bird|2|1|Whenever you draw your second card each turn, this creature gets +1/+2 until end of turn and can't be blocked this turn.| +Rowdy Snowballers|Avatar: The Last Airbender|68|C|{2}{U}|Creature - Human Peasant Ally|2|2|When this creature enters, tap target creature an opponent controls and put a stun counter on it.| +Serpent of the Pass|Avatar: The Last Airbender|70|U|{5}{U}{U}|Creature - Serpent|6|5|If there are three or more Lesson cards in your graveyard, you may cast this spell as though it had flash.$This spell costs {1} less to cast for each noncreature, nonland card in your graveyard.| +Sokka's Haiku|Avatar: The Last Airbender|71|U|{3}{U}{U}|Instant - Lesson|||Counter target spell.$Draw a card, then mill three cards.$Untap target land.| +Waterbending Lesson|Avatar: The Last Airbender|80|C|{3}{U}|Sorcery - Lesson|||Draw three cards. Then discard a card unless you waterbend {2}.| +Watery Grasp|Avatar: The Last Airbender|82|C|{U}|Enchantment - Aura|||Enchant creature$Enchanted creature doesn't untap during its controller's untap step.$Waterbend {5}: Enchanted creature's owner shuffles it into their library.| +Yue, the Moon Spirit|Avatar: The Last Airbender|83|R|{3}{U}|Legendary Creature - Spirit Ally|3|3|Flying, vigilance$Waterbend {5}, {T}: You may cast a noncreature spell from your hand without paying its mana cost.| +Azula Always Lies|Avatar: The Last Airbender|84|C|{1}{B}|Instant - Lesson|||Choose one or both --$* Target creature gets -1/-1 until end of turn.$* Put a +1/+1 counter on target creature.| +Beetle-Headed Merchants|Avatar: The Last Airbender|86|C|{4}{B}|Creature - Human Citizen|5|4|Whenever this creature attacks, you may sacrifice another creature or artifact. If you do, draw a card and put a +1/+1 counter on this creature.| +Buzzard-Wasp Colony|Avatar: The Last Airbender|88|U|{3}{B}|Creature - Bird Insect|2|2|Flying$When this creature enters, you may sacrifice an artifact or creature. If you do, draw a card.$Whenever another creature you control dies, if it had counters on it, put its counters on this creature.| +Cat-Gator|Avatar: The Last Airbender|91|U|{6}{B}|Creature - Fish Crocodile|3|2|Lifelink$When this creature enters, it deals damage equal to the number of Swamps you control to any target.| +Corrupt Court Official|Avatar: The Last Airbender|92|C|{1}{B}|Creature - Human Advisor|1|1|When this creature enters, target opponent discards a card.| +Dai Li Indoctrination|Avatar: The Last Airbender|93|C|{1}{B}|Sorcery - Lesson|||Choose one --$* Target opponent reveals their hand. You choose a nonland permanent card from it. That player discards that card.$* Earthbend 2.| +Epic Downfall|Avatar: The Last Airbender|96|U|{1}{B}|Sorcery|||Exile target creature with mana value 3 or greater.| +Fire Nation Engineer|Avatar: The Last Airbender|99|U|{2}{B}|Creature - Human Artificer|2|3|Raid -- At the beginning of your end step, if you attacked this turn, put a +1/+1 counter on another target creature or Vehicle you control.| +Heartless Act|Avatar: The Last Airbender|103|U|{1}{B}|Instant|||Choose one --$* Destroy target creature with no counters on it.$* Remove up to three counters from target creature.| +Hog-Monkey|Avatar: The Last Airbender|104|C|{2}{B}|Creature - Boar Monkey|3|2|At the beginning of combat on your turn, target creature you control with a +1/+1 counter on it gains menace until end of turn.$Exhaust -- {5}: Put two +1/+1 counters on this creature.| +Merchant of Many Hats|Avatar: The Last Airbender|110|C|{1}{B}|Creature - Human Peasant Ally|2|2|{2}{B}: Return this card from your graveyard to your hand.| +Ozai's Cruelty|Avatar: The Last Airbender|113|U|{2}{B}|Sorcery - Lesson|||Ozai's Cruelty deals 2 damage to target player. That player discards two cards.| +The Rise of Sozin|Avatar: The Last Airbender|117|M|{4}{B}{B}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter.)$I -- Destroy all creatures.$II -- Choose a card name. Search target opponent's graveyard, hand, and library for up to four cards with that name and exile them. Then that player shuffles.$III -- Exile this Saga, then return it to the battlefield transformed under your control.| +Fire Lord Sozin|Avatar: The Last Airbender|117|M||Legendary Creature - Human Noble|5|5|Menace, firebending 3$Whenever Fire Lord Sozin deals combat damage to a player, you may pay {X}. When you do, put any number of target creature cards with total mana value X or less from that player's graveyard onto the battlefield under your control.| +Deserter's Disciple|Avatar: The Last Airbender|131|C|{1}{R}|Creature - Human Rebel Ally|2|2|{T}: Another target creature you control with power 2 or less can't be blocked this turn.| +Fated Firepower|Avatar: The Last Airbender|132|M|{X}{R}{R}{R}|Enchantment|||Flash$This enchantment enters with X fire counters on it.$If a source you control would deal damage to an opponent or a permanent an opponent controls, it deals that much damage plus an amount of damage equal to the number of fire counters on this enchantment instead.| +Fire Nation Attacks|Avatar: The Last Airbender|133|U|{4}{R}|Instant|||Create two 2/2 red Soldier creature tokens with firebending 1.$Flashback {8}{R}| +Fire Sages|Avatar: The Last Airbender|136|U|{1}{R}|Creature - Human Cleric|2|2|Firebending 1${1}{R}{R}: Put a +1/+1 counter on this creature.| +How to Start a Riot|Avatar: The Last Airbender|140|C|{2}{R}|Instant - Lesson|||Target creature gains menace until end of turn.$Creatures target player controls get +2/+0 until end of turn.| +Lightning Strike|Avatar: The Last Airbender|146|C|{1}{R}|Instant|||Lightning Strike deals 3 damage to any target.| +Mongoose Lizard|Avatar: The Last Airbender|148|C|{4}{R}{R}|Creature - Mongoose Lizard|5|6|Menace$When this creature enters, it deals 1 damage to any target.$Mountaincycling {2}| +Redirect Lightning|Avatar: The Last Airbender|151|R|{R}|Instant - Lesson|||As an additional cost to cast this spell, pay 5 life or pay {2}.$Change the target of target spell or ability with a single target.| +Rough Rhino Cavalry|Avatar: The Last Airbender|152|C|{4}{R}|Creature - Human Mercenary|5|5|Firebending 2$Exhaust -- {8}: Put two +1/+1 counters on this creature. It gains trample until end of turn.| +Yuyan Archers|Avatar: The Last Airbender|161|C|{1}{R}|Creature - Human Archer|3|1|Reach$When this creature enters, you may discard a card. If you do, draw a card.| +Zuko, Exiled Prince|Avatar: The Last Airbender|163|U|{3}{R}|Legendary Creature - Human Noble|4|3|Firebending 3${3}: Exile the top card of your library. You may play that card this turn.| +Badgermole|Avatar: The Last Airbender|166|C|{4}{G}|Creature - Badger Mole|4|4|When this creature enters, earthbend 2.$Creatures you control with +1/+1 counters on them have trample.| +Earth Rumble|Avatar: The Last Airbender|174|U|{3}{G}|Sorcery|||Earthbend 2. When you do, up to one target creature you control fights target creature an opponent controls.| +Earthbending Lesson|Avatar: The Last Airbender|176|C|{3}{G}|Sorcery - Lesson|||Earthbend 4.| +Flopsie, Bumi's Buddy|Avatar: The Last Airbender|179|U|{4}{G}{G}|Legendary Creature - Ape Goat|4|4|When Flopsie enters, put a +1/+1 counter on each creature you control.$Each creature you control with power 4 or greater can't be blocked by more than one creature.| +Haru, Hidden Talent|Avatar: The Last Airbender|182|U|{1}{G}|Legendary Creature - Human Peasant Ally|1|1|Whenever another Ally you control enters, earthbend 1.| +Ostrich-Horse|Avatar: The Last Airbender|188|C|{2}{G}|Creature - Bird Horse|3|1|When this creature enters, mill three cards. You may put a land card from among them into your hand. If you don't, put a +1/+1 counter on this creature.| +Pillar Launch|Avatar: The Last Airbender|189|C|{G}|Instant|||Target creature gets +2/+2 and gains reach until end of turn. Untap it.| +Raucous Audience|Avatar: The Last Airbender|190|C|{1}{G}|Creature - Human Citizen|2|1|{T}: Add {G}. If you control a creature with power 4 or greater, add {G}{G} instead.| +Rebellious Captives|Avatar: The Last Airbender|191|C|{1}{G}|Creature - Human Peasant Ally|2|2|Exhaust -- {6}: Put two +1/+1 counters on this creature, then earthbend 2.| +Rocky Rebuke|Avatar: The Last Airbender|193|C|{1}{G}|Instant|||Target creature you control deals damage equal to its power to target creature an opponent controls.| +Saber-Tooth Moose-Lion|Avatar: The Last Airbender|194|C|{4}{G}{G}|Creature - Elk Cat|7|7|Reach$Forestcycling {2}| +Toph, the Blind Bandit|Avatar: The Last Airbender|198|U|{2}{G}|Legendary Creature - Human Warrior Ally|*|3|When Toph enters, earthbend 2.$Toph's power is equal to the number of +1/+1 counters on lands you control.| +Turtle-Duck|Avatar: The Last Airbender|200|C|{G}|Creature - Turtle Bird|0|4|{3}: Until end of turn, this creature has base power 4 and gains trample.| +Abandon Attachments|Avatar: The Last Airbender|205|C|{1}{U/R}|Instant - Lesson|||You may discard a card. If you do, draw two cards.| +Avatar Aang|Avatar: The Last Airbender|207|M|{R}{G}{W}{U}|Legendary Creature - Human Avatar Ally|4|4|Flying, firebending 2$Whenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform Avatar Aang.| +Aang, Master of Elements|Avatar: The Last Airbender|207|M||Legendary Creature - Avatar Ally|6|6|Flying$Spells you cast cost {W}{U}{B}{R}{G} less to cast.$At the beginning of each upkeep, you may transform Aang, Master of Elements. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent.| +Cat-Owl|Avatar: The Last Airbender|212|C|{3}{W/U}|Creature - Cat Bird|3|3|Flying$Whenever this creature attacks, untap target artifact or creature.| +Earth Kingdom Soldier|Avatar: The Last Airbender|216|C|{4}{G/W}|Creature - Human Soldier|3|4|Vigilance$When this creature enters, put a +1/+1 counter on each of up to two target creatures you control.| +Earth Village Ruffians|Avatar: The Last Airbender|219|C|{2}{B/G}|Creature - Human Soldier Rogue|3|1|When this creature dies, earthbend 2.| +Fire Lord Zuko|Avatar: The Last Airbender|221|R|{R}{W}{B}|Legendary Creature - Human Noble Ally|2|4|Firebending X, where X is Fire Lord Zuko's power.$Whenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control.| +Hei Bai, Spirit of Balance|Avatar: The Last Airbender|225|U|{2}{W/B}{W/B}|Legendary Creature - Bear Spirit|3|3|Whenever Hei Bai enters or attacks, you may sacrifice another creature or artifact. If you do, put two +1/+1 counters on Hei Bai.$When Hei Bai leaves the battlefield, put its counters on target creature you control.| +Katara, the Fearless|Avatar: The Last Airbender|230|R|{G}{W}{U}|Legendary Creature - Human Warrior Ally|3|3|If a triggered ability of an Ally you control triggers, that ability triggers an additional time.| +Katara, Water Tribe's Hope|Avatar: The Last Airbender|231|R|{2}{W}{U}{U}|Legendary Creature - Human Warrior Ally|3|3|Vigilance$When Katara enters, create a 1/1 white Ally creature token.$Waterbend {X}: Creatures you control have base power and toughness X/X until end of turn. X can't be 0. Activate only during your turn.| +Long Feng, Grand Secretariat|Avatar: The Last Airbender|233|U|{1}{B/G}{B/G}|Legendary Creature - Human Advisor|2|3|Whenever another creature you control or a land you control is put into a graveyard from the battlefield, put a +1/+1 counter on target creature you control.| +Pretending Poxbearers|Avatar: The Last Airbender|237|C|{1}{W/B}|Creature - Human Citizen Ally|2|1|When this creature dies, create a 1/1 white Ally creature token.| +Sokka, Bold Boomeranger|Avatar: The Last Airbender|240|R|{U}{R}|Legendary Creature - Human Warrior Ally|1|1|When Sokka enters, discard up to two cards, then draw that many cards.$Whenever you cast an artifact or Lesson spell, put a +1/+1 counter on Sokka.| +Sokka, Lateral Strategist|Avatar: The Last Airbender|241|U|{1}{W/U}{W/U}|Legendary Creature - Human Warrior Ally|2|4|Vigilance$Whenever Sokka and at least one other creature attack, draw a card.| +Suki, Kyoshi Warrior|Avatar: The Last Airbender|243|U|{2}{G/W}{G/W}|Legendary Creature - Human Warrior Ally|*|4|Suki's power is equal to the number of creatures you control.$Whenever Suki attacks, create a 1/1 white Ally creature token that's tapped and attacking.| +Toph, the First Metalbender|Avatar: The Last Airbender|247|R|{1}{R}{G}{W}|Legendary Creature - Human Warrior Ally|3|3|Nontoken artifacts you control are lands in addition to their other types.$At the beginning of your end step, earthbend 2.| +Vindictive Warden|Avatar: The Last Airbender|249|C|{2}{B/R}|Creature - Human Soldier|2|3|Menace$Firebending 1${3}: This creature deals 1 damage to each opponent.| +Barrels of Blasting Jelly|Avatar: The Last Airbender|254|C|{1}|Artifact|||{1}: Add one mana of any color. Activate only once each turn.${5}, {T}, Sacrifice this artifact: It deals 5 damage to target creature.| +Bender's Waterskin|Avatar: The Last Airbender|255|C|{3}|Artifact|||Untap this artifact during each other player's untap step.${T}: Add one mana of any color.| +Plains|Avatar: The Last Airbender|282|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Avatar: The Last Airbender|283|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Avatar: The Last Airbender|284|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Avatar: The Last Airbender|285|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Avatar: The Last Airbender|286|C||Basic Land - Forest|||({T}: Add {G}.)| +Plains|Avatar: The Last Airbender|287|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Avatar: The Last Airbender|288|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Avatar: The Last Airbender|289|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Avatar: The Last Airbender|290|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Avatar: The Last Airbender|291|C||Basic Land - Forest|||({T}: Add {G}.)| +Appa, Steadfast Guardian|Avatar: The Last Airbender|316|M|{2}{W}{W}|Legendary Creature - Bison Ally|3|4|Flash$Flying$When Appa enters, airbend any number of other target nonland permanents you control.$Whenever you cast a spell from exile, create a 1/1 white Ally creature token.| +Momo, Friendly Flier|Avatar: The Last Airbender|317|R|{W}|Legendary Creature - Lemur Bat Ally|1|1|Flying$The first non-Lemur creature spell with flying you cast during each of your turns costs {1} less to cast.$Whenever another creature you control with flying enters, Momo gets +1/+1 until end of turn.| +Aang's Iceberg|Avatar: The Last Airbender|336|R|{2}{W}|Enchantment|||Flash$When this enchantment enters, exile up to one other target nonland permanent until this enchantment leaves the battlefield.$Waterbend {3}: Sacrifice this enchantment. If you do, scry 2.| +Yue, the Moon Spirit|Avatar: The Last Airbender|338|R|{3}{U}|Legendary Creature - Spirit Ally|3|3|Flying, vigilance$Waterbend {5}, {T}: You may cast a noncreature spell from your hand without paying its mana cost.| +Fated Firepower|Avatar: The Last Airbender|341|M|{X}{R}{R}{R}|Enchantment|||Flash$This enchantment enters with X fire counters on it.$If a source you control would deal damage to an opponent or a permanent an opponent controls, it deals that much damage plus an amount of damage equal to the number of fire counters on this enchantment instead.| +Redirect Lightning|Avatar: The Last Airbender|343|R|{R}|Instant - Lesson|||As an additional cost to cast this spell, pay 5 life or pay {2}.$Change the target of target spell or ability with a single target.| +Katara, the Fearless|Avatar: The Last Airbender|350|R|{G}{W}{U}|Legendary Creature - Human Warrior Ally|3|3|If a triggered ability of an Ally you control triggers, that ability triggers an additional time.| +Katara, Water Tribe's Hope|Avatar: The Last Airbender|351|R|{2}{W}{U}{U}|Legendary Creature - Human Warrior Ally|3|3|Vigilance$When Katara enters, create a 1/1 white Ally creature token.$Waterbend {X}: Creatures you control have base power and toughness X/X until end of turn. X can't be 0. Activate only during your turn.| +Toph, the First Metalbender|Avatar: The Last Airbender|353|R|{1}{R}{G}{W}|Legendary Creature - Human Warrior Ally|3|3|Nontoken artifacts you control are lands in addition to their other types.$At the beginning of your end step, earthbend 2.| +The Rise of Sozin|Avatar: The Last Airbender|356|M|{4}{B}{B}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter.)$I -- Destroy all creatures.$II -- Choose a card name. Search target opponent's graveyard, hand, and library for up to four cards with that name and exile them. Then that player shuffles.$III -- Exile this Saga, then return it to the battlefield transformed under your control.| +Fire Lord Sozin|Avatar: The Last Airbender|356|M||Legendary Creature - Human Noble|5|5|Menace, firebending 3$Whenever Fire Lord Sozin deals combat damage to a player, you may pay {X}. When you do, put any number of target creature cards with total mana value X or less from that player's graveyard onto the battlefield under your control.| +Fire Lord Zuko|Avatar: The Last Airbender|360|R|{R}{W}{B}|Legendary Creature - Human Noble Ally|2|4|Firebending X, where X is Fire Lord Zuko's power.$Whenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control.| +Katara, the Fearless|Avatar: The Last Airbender|361|R|{G}{W}{U}|Legendary Creature - Human Warrior Ally|3|3|If a triggered ability of an Ally you control triggers, that ability triggers an additional time.| +Toph, the First Metalbender|Avatar: The Last Airbender|362|R|{1}{R}{G}{W}|Legendary Creature - Human Warrior Ally|3|3|Nontoken artifacts you control are lands in addition to their other types.$At the beginning of your end step, earthbend 2.| Avatar Aang|Avatar: The Last Airbender|363|M|{R}{G}{W}{U}|Legendary Creature - Human Avatar Ally|4|4|Flying, firebending 2$Whenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform Avatar Aang.| -Aang, Master of Elements|Avatar: The Last Airbender|363|M||Legendary Creature - Avatar Ally|6|6|Flying$Spelsl you cast cost {W}{U}{B}{R}{G} less to cast.$At the beginning of each upkeep, you may transform Aang, Master of Elements. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent.| +Aang, Master of Elements|Avatar: The Last Airbender|363|M||Legendary Creature - Avatar Ally|6|6|Flying$Spells you cast cost {W}{U}{B}{R}{G} less to cast.$At the beginning of each upkeep, you may transform Aang, Master of Elements. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent.| +Hakoda, Selfless Commander|Avatar: The Last Airbender|366|R|{3}{W}|Legendary Creature - Human Warrior Ally|3|5|Vigilance$You may look at the top card of your library any time.$You may cast Ally spells from the top of your library.$Sacrifice Hakoda: Creatures you control get +0/+5 and gain indestructible until end of turn.| +Sokka, Bold Boomeranger|Avatar: The Last Airbender|383|R|{U}{R}|Legendary Creature - Human Warrior Ally|1|1|When Sokka enters, discard up to two cards, then draw that many cards.$Whenever you cast an artifact or Lesson spell, put a +1/+1 counter on Sokka.| +Anti-Venom, Horrifying Healer|Marvel's Spider-Man|1|M|{W}{W}{W}{W}{W}|Legendary Creature - Symbiote Hero|5|5|When Anti-Venom enters, if he was cast, return target creature card from your graveyard to the battlefield.$If damage would be dealt to Anti-Venom, prevent that damage and put that many +1/+1 counters on him.| +Arachne, Psionic Weaver|Marvel's Spider-Man|2|R|{2}{W}|Legendary Creature - Spider Human Hero|3|3|Web-slinging {W}$As Arachne enters, look at target opponent's hand, then choose a noncreature card type.$Spells of the chosen type cost {1} more to cast.| Aunt May|Marvel's Spider-Man|3|U|{W}|Legendary Creature - Human Citizen|0|2|Whenever another creature you control enters, you gain 1 life. If it's a Spider, put a +1/+1 counter on it.| +City Pigeon|Marvel's Spider-Man|4|C|{W}|Creature - Bird|1|1|Flying$When this creature leaves the battlefield, create a Food token.| +Costume Closet|Marvel's Spider-Man|5|U|{1}{W}|Artifact|||This artifact enters with two +1/+1 counters on it.${T}: Move a +1/+1 counter from this artifact onto target creature you control. Activate only as a sorcery.$Whenever a modified creature you control leaves the battlefield, put a +1/+1 counter on this artifact.| Daily Bugle Reporters|Marvel's Spider-Man|6|C|{3}{W}|Creature - Human Citizen|2|3|When this creature enters, choose one --$* Puff Piece -- Put a +1/+1 counter on each of up to two target creatures.$* Investigative Journalism -- Return target creature card with mana value 2 or less from your graveyard to your hand.| +Flash Thompson, Spider-Fan|Marvel's Spider-Man|7|U|{1}{W}|Legendary Creature - Human Citizen|2|2|Flash$When Flash Thompson enters, choose one or both--$* Heckle -- Tap target creature.$* Hero Worship -- Untap target creature.| +Friendly Neighborhood|Marvel's Spider-Man|8|R|{3}{W}|Enchantment - Aura|||Enchant land$When this Aura enters, create three 1/1 green and white Human Citizen creature tokens.$Enchanted land has "{1}, {T}: Target creature gets +1/+1 until end of turn for each creature you control. Activate only as a sorcery."| Origin of Spider-Man|Marvel's Spider-Man|9|R|{1}{W}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Create a 2/1 green Spider creature token with reach.$II -- Put a +1/+1 counter on target creature you control. It becomes a legendary Spider Hero in addition to its other types.$III -- Target creature you control gains double strike until end of turn.| Peter Parker|Marvel's Spider-Man|10|M|{1}{W}|Legendary Creature - Human Scientist Hero|0|1|When Peter Parker enters, create a 2/1 green Spider creature token with reach.${1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery.| Amazing Spider-Man|Marvel's Spider-Man|10|M|{1}{G}{W}{U}|Legendary Creature - Spider Human Hero|4|4|Vigilance, reach$Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}.| +Rent Is Due|Marvel's Spider-Man|11|R|{W}|Enchantment|||At the beginning of your end step, you may tap two untapped creatures and/or Treasures you control. If you do, draw a card. Otherwise, sacrifice this enchantment.| Selfless Police Captain|Marvel's Spider-Man|12|C|{1}{W}|Creature - Human Detective|1|1|This creature enters with a +1/+1 counter on it.$When this creature leaves the battlefield, put its +1/+1 counters on target creature you control.| +Silver Sable, Mercenary Leader|Marvel's Spider-Man|13|U|{2}{W}|Legendary Creature - Human Mercenary Hero|2|3|When Silver Sable enters, put a +1/+1 counter on another target creature.$Whenever Silver Sable attacks, target modified creature you control gains lifelink until end of turn.| +Spectacular Spider-Man|Marvel's Spider-Man|14|R|{1}{W}|Legendary Creature - Spider Human Hero|3|2|Flash${1}: Spectacular Spider-Man gains flying until end of turn.${1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn.| Spectacular Tactics|Marvel's Spider-Man|15|C|{1}{W}|Instant|||Choose one --$* Put a +1/+1 counter on target creature you control. It gains hexproof until end of turn.$* Destroy target creature with power 4 or greater.| Spider-Man, Web-Slinger|Marvel's Spider-Man|16|C|{2}{W}|Legendary Creature - Spider Human Hero|3|3|Web-slinging {W}| +Spider-UK|Marvel's Spider-Man|17|U|{3}{W}|Legendary Creature - Spider Human Hero|3|4|Web-slinging {2}{W}$At the beginning of your end step, if two or more creatures entered the battlefield under your control this turn, you draw a card and gain 2 life.| Starling, Aerial Ally|Marvel's Spider-Man|18|C|{4}{W}|Legendary Creature - Human Hero|3|4|Flying$When Starling enters, another target creature you control gains flying until end of turn.| +Sudden Strike|Marvel's Spider-Man|19|U|{1}{W}|Instant|||Destroy target attacking or blocking creature.| Thwip!|Marvel's Spider-Man|20|C|{W}|Instant|||Target creature gets +2/+2 and gains flying until end of turn. If it's a Spider, you gain 2 life.| Web Up|Marvel's Spider-Man|21|C|{2}{W}|Enchantment|||When this enchantment enters, exile target nonland permanent an opponent controls until this enchantment leaves the battlefield.| +Web-Shooters|Marvel's Spider-Man|22|U|{1}{W}|Artifact - Equipment|||Equipped creature gets +1/+1 and has reach and "Whenever this creature attacks, tap target creature an opponent controls."$Equip {2}| Wild Pack Squad|Marvel's Spider-Man|23|C|{2}{W}|Creature - Human Mercenary|2|3|At the beginning of combat on your turn, up to one target creature gains first strike and vigilance until end of turn.| +With Great Power...|Marvel's Spider-Man|24|R|{3}{W}|Enchantment - Aura|||Enchant creature you control$Enchanted creature gets +2/+2 for each Aura and Equipment attached to it.$All damage that would be dealt to you is dealt to enchanted creature instead.| +Amazing Acrobatics|Marvel's Spider-Man|25|C|{1}{U}{U}|Instant|||Choose one or both --$* Counter target spell.$* Tap one or two target creatures.| Beetle, Legacy Criminal|Marvel's Spider-Man|26|C|{3}{U}|Legendary Creature - Human Rogue Villain|3|3|Flying${1}{U}, Exile this card from your graveyard: Put a +1/+1 counter on target creature. It gains flying until end of turn. Activate only as a sorcery.| +Chameleon, Master of Disguise|Marvel's Spider-Man|27|U|{3}{U}|Legendary Creature - Human Shapeshifter Villain|2|3|You may have Chameleon enter as a copy of a creature you control, except his name is Chameleon, Master of Disguise.$Mayhem {2}{U}| +The Clone Saga|Marvel's Spider-Man|28|R|{3}{U}|Enchantment - Saga|||(As this Saga enters step, add a lore counter. Sacrifice after III.)$I -- Surveil 3.$II -- When you next cast a creature spell this turn, copy it, except the copy isn't legendary.$III -- Choose a card name. Whenever a creature with the chosen name deals combat damage to a player this turn, draw a card.| Doc Ock, Sinister Scientist|Marvel's Spider-Man|29|C|{4}{U}|Legendary Creature - Human Scientist Villain|4|5|As long as there are eight or more cards in your graveyard, Doc Ock has base power and toughness 8/8.$As long as you control another Villain, Doc Ock has hexproof.| Doc Ock's Henchmen|Marvel's Spider-Man|30|C|{2}{U}|Creature - Human Villain|2|1|Flash$Whenever this creature attacks, it connives.| Flying Octobot|Marvel's Spider-Man|31|U|{1}{U}|Artifact Creature - Robot Villain|1|1|Flying$Whenever another Villain you control enters, put a +1/+1 counter on this creature. This ability triggers only once each turn.| +Hide on the Ceiling|Marvel's Spider-Man|32|R|{X}{U}|Instant|||Exile X target artifacts and/or creatures. Return the exiled cards to the battlefield under their owners' control at the beginning of the next end step.| +Hydro-Man, Fluid Felon|Marvel's Spider-Man|33|R|{U}{U}|Legendary Creature - Elemental Villain|2|2|Whenever you cast a blue spell, if Hydro-Man is a creature, he gets +1/+1 until end of turn.$At the beginning of your end step, untap Hydro-Man. Until your next turn, he becomes a land and gains "{T}: Add {U}."| +Impostor Syndrome|Marvel's Spider-Man|34|M|{4}{U}{U}|Enchantment|||Whenever a nontoken creature you control deals combat damage to a player, create a token that's a copy of it, except it isn't legendary.| +Lady Octopus, Inspired Inventor|Marvel's Spider-Man|35|R|{U}|Legendary Creature - Human Scientist Villain|0|2|Whenever you draw your first or second card each turn, put an ingenuity counter on Lady Octopus.${T}: You may cast an artifact spell from your hand with mana value less than or equal to the number of ingenuity counters on Lady Octopus without paying its mana cost.| +Madame Web, Clairvoyant|Marvel's Spider-Man|36|U|{4}{U}{U}|Legendary Creature - Mutant Advisor|4|4|You may look at the top card of your library any time.$You may cast Spider spells and noncreature spells from the top of your library.$Whenever you attack, you may mill a card.| +Mysterio, Master of Illusion|Marvel's Spider-Man|37|R|{3}{U}|Legendary Creature - Human Villain|3|3|When Mysterio enters, create a 3/3 blue Illusion Villain creature token for each nontoken Villain you control. Exile those tokens when Mysterio leaves the battlefield.| +Mysterio's Phantasm|Marvel's Spider-Man|38|C|{1}{U}|Creature - Illusion Villain|1|3|Flying, vigilance$Whenever this creature attacks, mill a card.| +Norman Osborn|Marvel's Spider-Man|39|M|{1}{U}|Legendary Creature - Human Scientist Villain|1|1|Norman Osborn can't be blocked.$Whenever Norman Osborn deals combat damage to a player, he connives.${1}{U}{B}{R}: Transform Norman Osborn. Activate only as a sorcery.| +Green Goblin|Marvel's Spider-Man|39|M|{1}{U}{B}{R}|Legendary Creature - Goblin Human Villain|3|3|Flying, menace$Spells you cast from your graveyard cost {2} less to cast.$Goblin Formula -- Each nonland card in your graveyard has mayhem. The mayhem cost is equal to its mana cost.| Oscorp Research Team|Marvel's Spider-Man|40|C|{3}{U}|Creature - Human Scientist|1|5|{6}{U}: Draw two cards.| +Robotics Mastery|Marvel's Spider-Man|41|U|{4}{U}|Enchantment - Aura|||Flash$Enchant creature$When this Aura enters, create two 1/1 colorless Robot artifact creature tokens with flying.$Enchanted creature gets +2/+2.| +School Daze|Marvel's Spider-Man|42|U|{3}{U}{U}|Instant|||Choose one --$* Do Homework -- Draw three cards.$* Fight Crime -- Counter target spell. Draw a card.| +Secret Identity|Marvel's Spider-Man|43|U|{U}|Instant|||Choose one --$* Conceal -- Until end of turn, target creature you control becomes a Citizen with base power and toughness 1/1 and gains hexproof.$* Reveal -- Until end of turn, target creature you control becomes a Hero with base power and toughness 3/4 and gains flying and vigilance.| Spider-Byte, Web Warden|Marvel's Spider-Man|44|U|{2}{U}|Legendary Creature - Spider Avatar Hero|2|2|When Spider-Byte enters, return up to one target nonland permanent to its owner's hand.| +Spider-Man No More|Marvel's Spider-Man|45|C|{1}{U}|Enchantment - Aura|||Enchant creature$Enchanted creature is a Citizen with base power and toughness 1/1. It has defender and loses all other abilities.| +Spider-Sense|Marvel's Spider-Man|46|R|{1}{U}|Instant|||Web-slinging {U}$Counter target instant spell, sorcery spell, or triggered ability.| Unstable Experiment|Marvel's Spider-Man|47|C|{1}{U}|Instant|||Target player draws a card, then up to one target creature you control connives.| Whoosh!|Marvel's Spider-Man|48|C|{1}{U}|Instant|||Kicker {1}{U}$Return target nonland permanent to its owner's hand. If this spell was kicked, draw a card.| +Agent Venom|Marvel's Spider-Man|49|R|{2}{B}|Legendary Creature - Symbiote Soldier Hero|2|3|Flash$Menace$Whenever another nontoken creature you control dies, you draw a card and lose 1 life.| +Alien Symbiosis|Marvel's Spider-Man|50|U|{1}{B}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +1/+1, has menace, and is a Symbiote in addition to its other types.$You may cast this card from your graveyard by discarding a card in addition to paying its other costs.| +Behold the Sinister Six!|Marvel's Spider-Man|51|M|{6}{B}|Sorcery|||Return up to six target creature cards with different names from your graveyard to the battlefield.| +Black Cat, Cunning Thief|Marvel's Spider-Man|52|R|{3}{B}{B}|Legendary Creature - Human Rogue Villain|2|3|When Black Cat enters, look at the top nine cards of target opponent's library, exile two of them face down, then put the rest on the bottom of their library in a random order. You may play the exiled cards for as long as they remain exiled. Mana of any type can be spent to cast spells this way.| +Common Crook|Marvel's Spider-Man|53|C|{1}{B}|Creature - Human Rogue Villain|2|2|When this creature dies, create a Treasure token.| +The Death of Gwen Stacy|Marvel's Spider-Man|54|R|{2}{B}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Destroy target creature.$II -- Each player may discard a card. Each player who doesn't loses 3 life.$III -- Exile any number of target players' graveyards.| +Eddie Brock|Marvel's Spider-Man|55|M|{2}{B}|Legendary Creature - Human Hero Villain|5|5|When Eddie Brock enters, return target creature card with mana value 1 or less from your graveyard to the battlefield.${3}{B}{R}{G}: Transform Eddie Brock. Activate only as a sorcery.| +Venom, Lethal Protector|Marvel's Spider-Man|55|M|{3}{B}{R}{G}|Legendary Creature - Symbiote Hero Villain|5|5|Menace, trample, haste$Whenever Venom attacks, you may sacrifice another creature. If you do, draw X cards, then you may put a permanent card with mana value X or less from your hand onto the battlefield, where X is the sacrificed creature's mana value.| +Gwenom, Remorseless|Marvel's Spider-Man|56|M|{3}{B}{B}|Legendary Creature - Symbiote Spider Hero|4|4|Deathtouch, lifelink$Whenever Gwenom attacks, until end of turn you may look at the top card of your library any time and you may play cards from the top of your library. If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost.| +Inner Demons Gangsters|Marvel's Spider-Man|57|C|{3}{B}|Creature - Human Rogue Villain|3|4|Discard a card: This creature gets +1/+0 and gains menace until end of turn. Activate only as a sorcery.| Merciless Enforcers|Marvel's Spider-Man|58|C|{1}{B}|Creature - Human Mercenary Villain|2|1|Lifelink${3}{B}: This creature deals 1 damage to each opponent.| +Morlun, Devourer of Spiders|Marvel's Spider-Man|59|R|{X}{B}{B}|Legendary Creature - Vampire Villain|2|1|Lifelink$Morlun enters with X+1/+1 counters on him.$When Morlun enters, he deals X damage to target opponent.| +Parker Luck|Marvel's Spider-Man|60|R|{2}{B}|Enchantment|||At the beginning of your end step, two target players each reveal the top card of their library. They each lose life equal to the mana value of the card revealed by the other player. Then they each put the card they revealed into their hand.| +Prison Break|Marvel's Spider-Man|61|U|{4}{B}|Sorcery|||Return target creature card from your graveyard to the battlefield with a +1/+1 counter on it.$Mayhem {3}{B}| Risky Research|Marvel's Spider-Man|62|C|{2}{B}|Sorcery|||Surveil 2, then draw two cards. You lose 2 life.| +Sandman's Quicksand|Marvel's Spider-Man|63|U|{1}{B}{B}|Sorcery|||Mayhem {3}{B}$All creatures get -2/-2 until end of turn. If this spell's mayhem cost was paid, creatures your opponents control get -2/-2 until end of turn instead.| Scorpion, Seething Striker|Marvel's Spider-Man|64|U|{3}{B}|Legendary Creature - Scorpion Human Villain|3|3|Deathtouch$At the beginning of your end step, if a creature died this turn, target creature you control connives.| Scorpion's Sting|Marvel's Spider-Man|65|C|{1}{B}|Instant|||Target creature gets -3/-3 until end of turn.| +The Soul Stone|Marvel's Spider-Man|66|M|{1}{B}|Legendary Artifact - Infinity Stone|||Indestructible${T}: Add {B}.${6}{B}, {T}, Exile a creature you control: Harness The Soul Stone.$ -- At the beginning of your upkeep, return target creature card from your graveyard to the battlefield.| Spider-Man Noir|Marvel's Spider-Man|67|U|{4}{B}|Legendary Creature - Spider Human Hero|4|4|Menace$Whenever a creature you control attacks alone, put a +1/+1 counter on it. Then surveil X, where X is the number of counters on it.| +The Spot's Portal|Marvel's Spider-Man|68|U|{2}{B}|Instant|||Put target creature on the bottom of its owner's library. You lose 2 life unless you control a Villain.| +Swarm, Being of Bees|Marvel's Spider-Man|69|C|{2}{B}|Legendary Creature - Insect Villain|2|2|Flash$Flying$Mayhem {B}| Tombstone, Career Criminal|Marvel's Spider-Man|70|U|{2}{B}|Legendary Creature - Human Villain|2|2|When Tombstone enters, return target Villain card from your graveyard to your hand.$Villain spells you cast cost {1} less to cast.| Venom, Evil Unleashed|Marvel's Spider-Man|71|C|{4}{B}|Legendary Creature - Symbiote Villain|4|5|Deathtouch${2}{B}, Exile this card from your graveyard: Put two +1/+1 counters on target creature. It gains deathtouch until end of turn. Activate only as a sorcery.| +Venomized Cat|Marvel's Spider-Man|72|C|{2}{B}|Creature - Symbiote Cat Villain|2|3|Deathtouch$When this creature enters, mill two cards.| Venom's Hunger|Marvel's Spider-Man|73|C|{4}{B}|Sorcery|||This spell costs {2} less to cast if you control a Villain.$Destroy target creature. You gain 2 life.| +Villainous Wrath|Marvel's Spider-Man|74|R|{3}{B}{B}|Sorcery|||Target opponent loses life equal to the number of creatures they control. Then destroy all creatures.| Angry Rabble|Marvel's Spider-Man|75|C|{1}{R}|Creature - Human Citizen|2|2|Trample$Whenever you cast a spell with mana value 4 or greater, this creature deals 1 damage to each opponent.${5}{R}: Put two +1/+1 counters on this creature. Activate only as a sorcery.| +Electro, Assaulting Battery|Marvel's Spider-Man|76|R|{1}{R}{R}|Legendary Creature - Human Villain|2|3|Flying$You don't lose unspent red mana as steps and phases end.$Whenever you cast an instant or sorcery spell, add {R}.$When Electro leaves the battlefield, you may pay {X}. When you do, he deals X damage to target player.| Electro's Bolt|Marvel's Spider-Man|77|C|{2}{R}|Sorcery|||Electro's Bolt deals 4 damage to target creature.$Mayhem {1}{R}| +Gwen Stacy|Marvel's Spider-Man|78|M|{1}{R}|Legendary Creature - Human Performer Hero|2|1|When Gwen Stacy enters, exile the top card of your library. You may play that card for as long as you control this creature.${2}{U}{R}{W}: Transform Gwen Stacy. Activate only as a sorcery.| +Ghost-Spider|Marvel's Spider-Man|78|M|{2}{U}{R}{W}|Legendary Creature - Spider Human Hero|4|4|Flying, vigilance, haste$Whenever you play a land from exile or cast a spell from exile, put a +1/+1 counter on Ghost-Spider.$Remove two counters from Ghost-Spider: Exile the top card of your library. You may play that card this turn.| +Heroes' Hangout|Marvel's Spider-Man|79|U|{R}|Sorcery|||Choose one --$* Date Night -- Exile the top two cards of your library. Choose one of them. Until the end of your next turn, you may play that card.$* Patrol Night -- One or two target creatures each get +1/+0 and gain first strike until end of turn.| +Hobgoblin, Mantled Marauder|Marvel's Spider-Man|80|U|{1}{R}|Legendary Creature - Goblin Human Villain|1|2|Flying, haste$Whenever you discard a card, Hobgoblin gets +2/+0 until end of turn.| +J. Jonah Jameson|Marvel's Spider-Man|81|R|{2}{R}|Legendary Creature - Human Citizen|2|2|When J. Jonah Jameson enters, suspect up to one target creature.$Whenever a creature you control with menace attacks, create a Treasure token.| Masked Meower|Marvel's Spider-Man|82|C|{R}|Creature - Spider Cat Hero|1|1|Haste$Discard a card, Sacrifice this creature: Draw a card.| +Maximum Carnage|Marvel's Spider-Man|83|R|{4}{R}|Enchantment - Saga|||(As this Saga enters step and after your draw step, add a lore counter. Sacrifice after III.)$I -- Until your next turn, each creature attacks each combat if able and attacks a player other than you if able.$II -- Add {R}{R}{R}.$III -- This Saga deals 5 damage to each opponent.| +Molten Man, Inferno Incarnate|Marvel's Spider-Man|84|U|{2}{R}|Legendary Creature - Elemental Villain|0|0|When Molten Man enters, search your library$for a basic Mountain card, put it onto the$battlefield tapped, then shuffle.$Molten Man gets +1/+1 for each Mountain$you control.$When Molten Man leaves the battlefield,$sacrifice a land.| +Raging Goblinoids|Marvel's Spider-Man|85|U|{4}{R}|Creature - Goblin Berserker Villain|5|4|Haste$Mayhem {2}{R}| Romantic Rendezvous|Marvel's Spider-Man|86|C|{1}{R}|Sorcery|||Discard a card, then draw two cards.| +Shadow of the Goblin|Marvel's Spider-Man|87|R|{1}{R}|Enchantment|||Unreliable Visions -- At the beginning of your first main phase, discard a card. If you do, draw a card.$Undying Vengeance -- Whenever you play a land or cast a spell from anywhere other than your hand, this enchantment deals 1 damage to each opponent.| Shock|Marvel's Spider-Man|88|C|{R}|Instant|||Shock deals 2 damage to any target.| Shocker, Unshakable|Marvel's Spider-Man|89|U|{4}{R}{R}|Legendary Creature - Human Rogue Villain|5|5|During your turn, Shocker has first strike.$Vibro-Shock Gauntlets -- When Shocker enters, he deals 2 damage to target creature and 2 damage to that creature's controller.| Spider-Gwen, Free Spirit|Marvel's Spider-Man|90|C|{2}{R}|Legendary Creature - Spider Human Hero|2|3|Reach$Whenever Spider-Gwen becomes tapped, you may discard a card. If you do, draw a card.| Spider-Islanders|Marvel's Spider-Man|91|C|{3}{R}|Creature - Spider Horror Citizen|4|3|Mayhem {1}{R}| +Spider-Punk|Marvel's Spider-Man|92|R|{1}{R}|Legendary Creature - Spider Human Hero|2|1|Riot$Other Spiders you control have riot.$Spells and abilities can't be countered.$Damage can't be prevented.| +Spider-Verse|Marvel's Spider-Man|93|M|{3}{R}{R}|Enchantment|||The "legend rule" doesn't apply to Spiders you control.$Whenever you cast a spell from anywhere other than your hand, you may copy it. If you do, you may choose new targets for the copy. If the copy is a permanent spell, it gains haste. Do this only once each turn.| +Spinneret and Spiderling|Marvel's Spider-Man|94|R|{R}|Legendary Creature - Spider Human Hero|1|2|Whenever you attack with two or more Spiders, put a +1/+1 counter on Spinneret and Spiderling.$Whenever Spinneret and Spiderling deals 4 or more damage, exile the top card of your library. Until the end of your next turn, you may play that card.| Stegron the Dinosaur Man|Marvel's Spider-Man|95|C|{4}{R}|Legendary Creature - Dinosaur Villain|5|4|Menace$Dinosaur Formula -- {1}{R}, Discard this card: Until end of turn, target creature you control gets +3/+1 and becomes a Dinosaur in addition to its other types.| +Superior Foes of Spider-Man|Marvel's Spider-Man|96|U|{2}{R}|Creature Human Rogue Villain|3|3|Trample$Whenever you cast a spell with mana value 4 or greater, you may exile the top card of your library. If you do, you may play that card until you exile another card with this creature.| Taxi Driver|Marvel's Spider-Man|97|C|{1}{R}|Creature - Human Pilot|3|1|{1}, {T}: Target creature gains haste until end of turn.| +Wisecrack|Marvel's Spider-Man|98|U|{2}{R}|Instant|||Target creature deals damage equal to its power to itself. If that creature is attacking, Wisecrack deals 2 damage to that creature's controller.| +Damage Control Crew|Marvel's Spider-Man|99|U|{3}{G}|Creature - Human Citizen|3|3|When this creature enters, choose one --$* Repair -- Return target card with mana value 4 or greater from your graveyard to your hand.$* Impound -- Exile target artifact or enchantment.| +Ezekiel Sims, Spider-Totem|Marvel's Spider-Man|100|U|{4}{G}|Legendary Creature - Spider Human Advisor|3|5|Reach$At the beginning of combat on your turn, target Spider you control gets +2/+2 until end of turn.| Grow Extra Arms|Marvel's Spider-Man|101|C|{1}{G}|Instant|||This spell costs {1} less to cast if it targets a Spider.$Target creature gets +4/+4 until end of turn.| Guy in the Chair|Marvel's Spider-Man|102|C|{2}{G}|Creature - Human Advisor|2|3|{T}: Add one mana of any color.$Web Support -- {2}{G}, {T}: Put a +1/+1 counter on target Spider. Activate only as a sorcery.| Kapow!|Marvel's Spider-Man|103|C|{2}{G}|Sorcery|||Put a +1/+1 counter on target creature you control. It fights target creature an opponent controls.| Kraven's Cats|Marvel's Spider-Man|104|C|{1}{G}|Creature - Cat Villain|2|2|{2}{G}: This creature gets +2/+2 until end of turn. Activate only once each turn.| +Kraven's Last Hunt|Marvel's Spider-Man|105|R|{3}{G}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Mill five cards. When you do, this Saga deals damage equal to the greatest power among creature cards in your graveyard to target creature.$II -- Target creature you control gets +2/+2 until end of turn.$III -- Return target creature card from your graveyard to your hand.| +Lizard, Connors's Curse|Marvel's Spider-Man|106|R|{2}{G}{G}|Legendary Creature - Lizard Villain|5|5|Trample$Lizard Formula -- When Lizard, Connors's Curse enters, up to one other target creature loses all abilities and becomes a green Lizard creature with base power and toughness 4/4.| Lurking Lizards|Marvel's Spider-Man|107|C|{1}{G}|Creature - Lizard Villain|1|3|Trample$Whenever you cast a spell with mana value 4 or greater, put a +1/+1 counter on this creature.| +Miles Morales|Marvel's Spider-Man|108|M|{1}{G}|Legendary Creature - Human Citizen Hero|1|2|When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures.${3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery.| +Ultimate Spider-Man|Marvel's Spider-Man|108|M|{3}{R}{G}{W}|Legendary Creature - Spider Human Hero|4|3|First strike, haste$Camouflage -- {2}: Put a +1/+1 counter on Ultimate Spider-Man. He gains hexproof and becomes colorless until end of turn.$Whenever you attack, double the number of each kind of counter on each Spider and legendary creature you control.| +Pictures of Spider-Man|Marvel's Spider-Man|109|U|{2}{G}|Artifact|||When this artifact enters, look at the top five cards of your library. You may reveal up to two creature cards from among them and put them into your hand. Put the rest on the bottom of your library in a random order.${1}, {T}, Sacrifice this artifact: Create a Treasure token.| +Professional Wrestler|Marvel's Spider-Man|110|C|{3}{G}|Creature - Human Warrior Performer|4|4|When this creature enters, create a Treasure token.$This creature can't be blocked by more than one creature.| +Radioactive Spider|Marvel's Spider-Man|111|R|{G}|Creature - Spider|1|1|Reach, deathtouch$Fateful Bite -- {2}, Sacrifice this creature: Search your library for a Spider Hero card, reveal it, put it into your hand, then shuffle. Activate only as a sorcery.| +Sandman, Shifting Scoundrel|Marvel's Spider-Man|112|R|{1}{G}{G}|Legendary Creature - Sand Elemental Villain|*|*|Sandman's power and toughness are each equal to the number of lands you control.$Sandman can't be blocked by creatures with power 2 or less.${3}{G}{G}: Return this card and target land card from your graveyard to the battlefield tapped.| Scout the City|Marvel's Spider-Man|113|C|{1}{G}|Sorcery|||Choose one --$* Look Around -- Mill three cards. You may put a permanent card from among them into your hand. You gain 3 life.$* Bring Down -- Destroy target creature with flying.| Spider-Ham, Peter Porker|Marvel's Spider-Man|114|R|{1}{G}|Legendary Creature - Spider Boar Hero|2|2|When Spider-Ham enters, create a Food token.$Animal May-Ham -- Other Spiders, Boars, Bats, Bears, Birds, Cats, Dogs, Frogs, Jackals, Lizards, Mice, Otters, Rabbits, Raccoons, Rats, Squirrels, Turtles, and Wolves you control get +1/+1.| Spider-Man, Brooklyn Visionary|Marvel's Spider-Man|115|C|{4}{G}|Legendary Creature - Spider Human Hero|4|3|Web-slinging {2}{G}$When Spider-Man enters, search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| Spider-Rex, Daring Dino|Marvel's Spider-Man|116|C|{4}{G}{G}|Legendary Creature - Spider Dinosaur Hero|6|6|Reach, trample$Ward {2}| +Spiders-Man, Heroic Horde|Marvel's Spider-Man|117|U|{1}{G}|Legendary Creature - Spider Hero|2|3|Web-slinging {4}{G}{G}$When Spiders-Man enters, if they were cast using web-slinging, you gain 3 life and create two 2/1 green Spider creature tokens with reach.| +Strength of Will|Marvel's Spider-Man|118|R|{1}{G}|Instant|||Until end of turn, target creature you control gains indestructible and "Whenever this creature is dealt damage, put that many +1/+1 counters on it."| +Supportive Parents|Marvel's Spider-Man|119|U|{2}{G}|Creature - Human Citizen|3|3|Tap two untapped creatures you control: Add one mana of any color.| +Terrific Team-Up|Marvel's Spider-Man|120|U|{3}{G}|Instant|||This spell costs {2} less to cast if you control a permanent with mana value 4 or greater.$One or two target creatures you control each get +1/+0 until end of turn. They each deal damage equal to their power to target creature an opponent controls.| +Wall Crawl|Marvel's Spider-Man|121|U|{3}{G}|Enchantment|||When this enchantment enters, create a 2/1 green Spider creature token with reach, then you gain 1 life for each Spider you control.$Spiders you control get +1/+1 and can't be blocked by creatures with defender.| +Web of Life and Destiny|Marvel's Spider-Man|122|M|{6}{G}{G}|Enchantment|||Convoke$At the beginning of combat on your turn, look at the top five cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order.| +Arana, Heart of the Spider|Marvel's Spider-Man|123|R|{1}{R}{W}|Legendary Creature - Spider Human Hero|3|3|Whenever you attack, put a +1/+1 counter on target attacking creature.$Whenever a modified creature you control deals combat damage to a player, exile the top card of your library. You may play that card this turn.| +Biorganic Carapace|Marvel's Spider-Man|124|R|{2}{W}{U}|Artifact - Equipment|||When this Equipment enters, attach it to target creature you control.$Equipped creature gets +2/+2 and has "Whenever this creature deals combat damage to a player, draw a card for each modified creature you control."$Equip {2}| +Carnage, Crimson Chaos|Marvel's Spider-Man|125|R|{2}{B}{R}|Legendary Creature - Symbiote Villain|4|3|Trample$When Carnage enters, return target creature card with mana value 3 or less from your graveyard to the battlefield. It gains "This creature attacks each combat if able" and "When this creature deals combat damage to a player, sacrifice it."$Mayhem {B}{R}| +Cheering Crowd|Marvel's Spider-Man|126|R|{1}{R/G}|Creature - Human Citizen|2|2|At the beginning of each player's first main phase, that player may put a +1/+1 counter on this creature. If they do, they add {C} for each counter on it.| +Cosmic Spider-Man|Marvel's Spider-Man|127|M|{W}{U}{B}{R}{G}|Legendary Creature - Spider Human Hero|5|5|Flying, first strike, trample, lifelink, haste$At the beginning of combat on your turn, other Spiders you control gain flying, first strike, trample, lifelink, and haste until end of turn.| +Doctor Octopus, Master Planner|Marvel's Spider-Man|128|M|{5}{U}{B}|Legendary Creature - Human Scientist Villain|4|8|Other Villains you control get +2/+2.$Your maximum hand size is eight.$At the beginning of your end step, if you have fewer than eight cards in hand, draw cards equal to the difference.| +Gallant Citizen|Marvel's Spider-Man|129|C|{G/W}{G/W}|Creature - Human Citizen|1|1|When this creature enters, draw a card.| +Green Goblin, Revenant|Marvel's Spider-Man|130|U|{3}{B}{R}|Legendary Creature - Goblin Human Villain|3|3|Flying, deathtouch$Whenever Green Goblin attacks, discard a card. Then draw a card for each card you've discarded this turn.| +Jackal, Genius Geneticist|Marvel's Spider-Man|131|R|{G}{U}|Legendary Creature - Human Scientist Villain|1|1|Trample$Whenever you cast a creature spell with mana value equal to Jackal's power, copy that spell, except the copy isn't legendary. Then put a +1/+1 counter on Jackal.| +Kraven, Proud Predator|Marvel's Spider-Man|132|U|{1}{R}{G}|Legendary Creature - Human Warrior Villain|*|4|Vigilance$Top of the Food Chain -- Kraven's power is equal to the greatest mana value among permanents you control.| +Kraven the Hunter|Marvel's Spider-Man|133|R|{1}{B}{G}|Legendary Creature - Human Warrior Villain|4|3|Trample$Whenever a creature an opponent controls with the greatest power among creatures that player controls dies, draw a card and put a +1/+1 counter on Kraven the Hunter.| +Mary Jane Watson|Marvel's Spider-Man|134|R|{1}{G/W}|Legendary Creature - Human Performer|2|2|Whenever a Spider you control enters, draw a card. This ability triggers only once each turn.| +Mister Negative|Marvel's Spider-Man|135|M|{5}{W}{B}|Legendary Creature - Human Villain|5|5|Vigilance, lifelink$Darkforce Inversion -- When Mister Negative enters, you may exchange your life total with target opponent. If you lose life this way, draw that many cards.| +Mob Lookout|Marvel's Spider-Man|136|C|{1}{U/B}|Creature - Human Rogue Villain|0|3|When this creature enters, target creature you control connives.| +Morbius the Living Vampire|Marvel's Spider-Man|137|U|{2}{U}{B}|Legendary Creature - Vampire Scientist Villain|3|1|Flying, vigilance, lifelink${U}{B}, Exile this card from your graveyard: Look at the top three cards of your library. Put one of them into your hand and the rest on the bottom of your library in any order.| +Prowler, Clawed Thief|Marvel's Spider-Man|138|U|{1}{U}{B}|Legendary Creature - Human Rogue Villain|2|3|Menace$Whenever another Villain you control enters, Prowler connives.| +Pumpkin Bombardment|Marvel's Spider-Man|139|C|{B/R}|Sorcery|||As an additional cost to cast this spell, discard a card or pay {2}.$Pumpkin Bombardment deals 3 damage to target creature.| +Rhino, Barreling Brute|Marvel's Spider-Man|140|U|{3}{R}{R}{G}{G}|Legendary Creature - Human Villain|6|7|Vigilance, trample, haste$Whenever Rhino attacks, if you've cast a spell with mana value 4 or greater this turn, draw a card.| +Rhino's Rampage|Marvel's Spider-Man|141|U|{R/G}|Sorcery|||Target creature you control gets +1/+0 until end of turn. It fights target creature an opponent controls. When excess damage is dealt to the creature an opponent controls this way, destroy up to one target noncreature artifact with mana value 3 or less.| +Scarlet Spider, Ben Reilly|Marvel's Spider-Man|142|R|{1}{R}{G}|Legendary Creature - Spider Human Hero|4|3|Web-slinging {R}{G}$Trample$Sensational Save -- If Scarlet Spider was cast using web-slinging, he enters with X +1/+1 counters on him, where X is the mana value of the returned creature.| +Scarlet Spider, Kaine|Marvel's Spider-Man|143|U|{B}{R}|Legendary Creature - Spider Human Hero|2|1|Menace$When Scarlet Spider enters, you may discard a card. If you do, put a +1/+1 counter on him.$Mayhem {B/R}| +Shriek, Treblemaker|Marvel's Spider-Man|144|U|{2}{B/R}|Legendary Creature - Mutant Villain|2|3|At the beginning of your first main phase, you may discard a card. When you do, target creature can't block this turn.$Sonic Blast -- Whenever a creature an opponent controls dies, Shriek deals 1 damage to that player.| +Silk, Web Weaver|Marvel's Spider-Man|145|R|{2}{G}{W}|Legendary Creature - Spider Human Hero|3|5|Web-slinging {1}{G}{W}$Whenever you cast a creature spell, create a 1/1 green and white Human Citizen creature token.${3}{G}{W}: Creatures you control get +2/+2 and gain vigilance until end of turn.| +Skyward Spider|Marvel's Spider-Man|146|C|{W/U}{W/U}|Creature - Spider Human Hero|2|2|Ward {2}$This creature has flying as long as it's modified.| SP//dr, Piloted by Peni|Marvel's Spider-Man|147|U|{3}{W}{U}|Legendary Artifact Creature - Spider Hero|4|4|Vigilance$When SP//dr enters, put a +1/+1 counter on target creature.$Whenever a modified creature you control deals combat damage to a player, draw a card.| +Spider Manifestation|Marvel's Spider-Man|148|C|{1}{R/G}|Creature - Spider Avatar|2|2|Reach${T}: Add {R} or {G}.$Whenever you cast a spell with mana value 4 or greater, untap this creature.| +Spider-Girl, Legacy Hero|Marvel's Spider-Man|149|U|{G}{W}|Legendary Creature - Spider Human Hero|2|2|During your turn, Spider-Girl has flying.$When Spider-Girl leaves the battlefield, create a 1/1 green and white Human Citizen creature token.| Spider-Man 2099|Marvel's Spider-Man|150|R|{U}{R}|Legendary Creature - Spider Human Hero|2|3|From the Future -- You can't cast Spider-Man 2099 during your first, second, or third turns of the game.$Double strike, vigilance$At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target.| +Spider-Man India|Marvel's Spider-Man|151|U|{3}{G}{W}|Legendary Creature - Spider Human Hero|4|4|Web-slinging {1}{G}{W}$Pavitr's Seva -- Whenever you cast a creature spell, put a +1/+1 counter on target creature you control. It gains flying until end of turn.| +Spider-Woman, Stunning Savior|Marvel's Spider-Man|152|R|{1}{W/U}|Legendary Creature - Spider Human Hero|2|2|Flying$Venom Blast -- Artifacts and creatures your opponents control enter tapped.| +The Spot, Living Portal|Marvel's Spider-Man|153|R|{3}{W}{B}|Legendary Creature - Human Scientist Villain|4|4|When The Spot enters, exile up to one target nonland permanent and up to one target nonland permanent card from a graveyard.$When The Spot dies, put him on the bottom of his owner's library. If you do, return the exiled cards to their owners' hands.| +Sun-Spider, Nimble Webber|Marvel's Spider-Man|154|U|{3}{W/U}|Legendary Creature - Spider Human Hero|3|2|During your turn, Sun-Spider has flying.$When Sun-Spider enters, search your library for an Aura or Equipment card, reveal it, put it into your hand, then shuffle.| +Superior Spider-Man|Marvel's Spider-Man|155|R|{2}{U}{B}|Legendary Creature - Spider Human Hero|4|4|Mind Swap -- You may have Superior Spider- Man enter as a copy of any creature card in a graveyard, except his name is Superior Spider-Man and he's a 4/4 Spider Human Hero in addition to his other types. When you do, exile that card.| +Symbiote Spider-Man|Marvel's Spider-Man|156|R|{2}{U/B}|Legendary Creature - Symbiote Spider Hero|2|4|Whenever this creature deals combat damage to a player, look at that many cards from the top of your library. Put one of them into your hand and the rest into your graveyard.$Find New Host -- {2}{U/B}, Exile this card from your graveyard: Put a +1/+1 counter on target creature you control. It gains this card's other abilities. Activate only as a sorcery.| +Ultimate Green Goblin|Marvel's Spider-Man|157|R|{1}{B/R}{B/R}|Legendary Creature - Goblin Villain|5|4|At the beginning of your upkeep, discard a card, then create a Treasure token.$Mayhem {2}{B/R}| +Vulture, Scheming Scavenger|Marvel's Spider-Man|158|U|{5}{U/B}|Legendary Creature - Human Artificer Villain|4|6|Flying$Whenever Vulture attacks, other Villains you control gain flying until end of turn.| +Web-Warriors|Marvel's Spider-Man|159|U|{4}{G/W}|Creature - Spider Hero|4|3|When this creature enters, put a +1/+1 counter on each other creature you control.| +Wraith, Vicious Vigilante|Marvel's Spider-Man|160|U|{1}{W}{U}|Legendary Creature - Human Detective Hero|1|1|Double strike$Fear Gas -- Wraith can't be blocked.| +Bagel and Schmear|Marvel's Spider-Man|161|C|{1}|Artifact - Food|||Share -- {W}, {T}, Sacrifice this artifact: Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery.$Nosh -- {2}, {T}, Sacrifice this artifact: You gain 3 life and draw a card.| +Doc Ock's Tentacles|Marvel's Spider-Man|162|R|{1}|Artifact - Equipment|||Whenever a creature you control with mana value 5 or greater enters, you may attach this Equipment to it.$Equipped creature gets +4/+4.$Equip {5}| Eerie Gravestone|Marvel's Spider-Man|163|C|{2}|Artifact|||When this artifact enters, draw a card.${1}{B}, Sacrifice this artifact: Mill four cards. You may put a creature card from among them into your hand.| +Hot Dog Cart|Marvel's Spider-Man|164|C|{3}|Artifact|||When this artifact enters, create a Food token.${T}: Add one mana of any color.| +Interdimensional Web Watch|Marvel's Spider-Man|165|R|{4}|Artifact|||When this artifact enters, exile the top two cards of your library. Until the end of your next turn, you may play those cards.${T}: Add two mana in any combination of colors. Spend this mana only to cast spells from exile.| +Iron Spider, Stark Upgrade|Marvel's Spider-Man|166|R|{3}|Legendary Artifact Creature - Spider Hero|2|3|Vigilance${T}: Put a +1/+1 counter on each artifact creature and/or Vehicle you control.${2}, Remove two +1/+1 counters from among artifacts you control: Draw a card.| +Living Brain, Mechanical Marvel|Marvel's Spider-Man|167|U|{4}|Legendary Artifact Creature - Robot Villain|3|3|At the beginning of combat on your turn, target non-Equipment artifact you control becomes an artifact creature with base power and toughness 3/3 until end of turn. Untap it.| Mechanical Mobster|Marvel's Spider-Man|168|C|{3}|Artifact Creature - Human Robot Villain|2|1|When this creature enters, exile up to one target card from a graveyard. Target creature you control connives.| +News Helicopter|Marvel's Spider-Man|169|C|{3}|Artifact Creature - Construct|1|1|Flying$When this creature enters, create a 1/1 green and white Human Citizen creature token.| +Passenger Ferry|Marvel's Spider-Man|170|C|{3}|Artifact - Vehicle|4|3|Whenever this Vehicle attacks, you may pay {U}. When you do, another target attacking creature can't be blocked this turn.$Crew 2| +Peter Parker's Camera|Marvel's Spider-Man|171|R|{1}|Artifact|||This artifact enters with three film counters on it.${2}, {T}, Remove a film counter from this artifact: Copy target activated or triggered ability you control. You may choose new targets for the copy.| +Rocket-Powered Goblin Glider|Marvel's Spider-Man|172|R|{3}|Artifact - Equipment|||When this Equipment enters, if it was cast from your graveyard, attach it to target creature you control.$Equipped creature gets +2/+0 and has flying and haste.$Equip {2}$Mayhem {2}| Spider-Bot|Marvel's Spider-Man|173|C|{2}|Artifact Creature - Spider Robot Scout|2|1|Reach$When this creature enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top.| +Spider-Mobile|Marvel's Spider-Man|174|U|{3}|Artifact - Vehicle|3|3|Trample$Whenever this Vehicle attacks or blocks, it gets +1/+1 until end of turn for each Spider you control.$Crew 2| +Spider-Slayer, Hatred Honed|Marvel's Spider-Man|175|U|{2}|Legendary Artifact Creature - Human Villain|2|1|Whenever Spider-Slayer deals damage to a Spider, destroy that creature.${6}, Exile this card from your graveyard: Create two tapped 1/1 colorless Robot artifact creature tokens with flying.| +Spider-Suit|Marvel's Spider-Man|176|U|{1}|Artifact - Equipment|||Equipped creature gets +2/+2 and is a Spider Hero in addition to its other types.$Equip {3}| +Steel Wrecking Ball|Marvel's Spider-Man|177|C|{5}|Artifact|||When this artifact enters, it deals 5 damage to target creature.${1}{R}, Discard this card: Destroy target artifact.| +Subway Train|Marvel's Spider-Man|178|C|{2}|Artifact - Vehicle|3|1|When this Vehicle enters, you may pay {G}. If you do, search your library for a basic land card, reveal it, put it into your hand, then shuffle.$Crew 2| +Daily Bugle Building|Marvel's Spider-Man|179|U||Land|||{T}: Add {C}.${1}, {T}: Add one mana of any color.$Smear Campaign -- {1}, {T}: Target legendary creature gains menace until end of turn. Activate only as a sorcery.| +Multiversal Passage|Marvel's Spider-Man|180|R||Land|||As this land enters, choose a basic land type. Then you may pay 2 life. If you don't, it enters tapped.$This land is the chosen type.| +Ominous Asylum|Marvel's Spider-Man|181|C||Land|||This land enters tapped.${T}: Add {B} or {R}.${4}, {T}: Surveil 1.| +Oscorp Industries|Marvel's Spider-Man|182|R||Land|||This land enters tapped.$When this land enters from a graveyard, you lose 2 life.${T}: Add {U}, {B}, or {R}.$Mayhem| +Savage Mansion|Marvel's Spider-Man|183|C||Land|||This land enters tapped.${T}: Add {R} or {G}.${4}, {T}: Surveil 1.| +Sinister Hideout|Marvel's Spider-Man|184|C||Land|||This land enters tapped.${T}: Add {U} or {B}.${4}, {T}: Surveil 1.| +Suburban Sanctuary|Marvel's Spider-Man|185|C||Land|||This land enters tapped.${T}: Add {G} or {W}.${4}, {T}: Surveil 1.| +University Campus|Marvel's Spider-Man|186|C||Land|||This land enters tapped.${T}: Add {W} or {U}.${4}, {T}: Surveil 1.| +Urban Retreat|Marvel's Spider-Man|187|R||Land|||This land enters tapped.${T}: Add {G}, {W} , or {U}.${2}, Return a tapped creature you control to its owner's hand: Put this card from your hand onto the battlefield. Activate only as a sorcery.| +Vibrant Cityscape|Marvel's Spider-Man|188|C||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| +Plains|Marvel's Spider-Man|189|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Marvel's Spider-Man|190|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Marvel's Spider-Man|191|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Marvel's Spider-Man|192|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Marvel's Spider-Man|193|C||Basic Land - Forest|||({T}: Add {G}.)| Plains|Marvel's Spider-Man|194|C||Basic Land - Plains|||({T}: Add {W}.)| Island|Marvel's Spider-Man|195|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Marvel's Spider-Man|196|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|Marvel's Spider-Man|197|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|Marvel's Spider-Man|198|C||Basic Land - Forest|||({T}: Add {G}.)| +Miles Morales|Marvel's Spider-Man|200|M|{1}{G}|Legendary Creature - Human Citizen Hero|1|2|When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures.${3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery.| +Ultimate Spider-Man|Marvel's Spider-Man|200|M|{3}{R}{G}{W}|Legendary Creature - Spider Human Hero|4|3|First strike, haste$Camouflage -- {2}: Put a +1/+1 counter on Ultimate Spider-Man. He gains hexproof and becomes colorless until end of turn.$Whenever you attack, double the number of each kind of counter on each Spider and legendary creature you control.| +Spider-Ham, Peter Porker|Marvel's Spider-Man|201|R|{1}{G}|Legendary Creature - Spider Boar Hero|2|2|When Spider-Ham enters, create a Food token.$Animal May-Ham -- Other Spiders, Boars, Bats, Bears, Birds, Cats, Dogs, Frogs, Jackals, Lizards, Mice, Otters, Rabbits, Raccoons, Rats, Squirrels, Turtles, and Wolves you control get +1/+1.| +Gwen Stacy|Marvel's Spider-Man|202|M|{1}{R}|Legendary Creature - Human Performer Hero|2|1|When Gwen Stacy enters, exile the top card of your library. You may play that card for as long as you control this creature.${2}{U}{R}{W}: Transform Gwen Stacy. Activate only as a sorcery.| +Ghost-Spider|Marvel's Spider-Man|202|M|{2}{U}{R}{W}|Legendary Creature - Spider Human Hero|4|4|Flying, vigilance, haste$Whenever you play a land from exile or cast a spell from exile, put a +1/+1 counter on Ghost-Spider.$Remove two counters from Ghost-Spider: Exile the top card of your library. You may play that card this turn.| +Web-Warriors|Marvel's Spider-Man|203|U|{4}{G/W}|Creature - Spider Hero|4|3|When this creature enters, put a +1/+1 counter on each other creature you control.| +Spider-Man Noir|Marvel's Spider-Man|204|U|{4}{B}|Legendary Creature - Spider Human Hero|4|4|Menace$Whenever a creature you control attacks alone, put a +1/+1 counter on it. Then surveil X, where X is the number of counters on it.| +Spider-Man 2099|Marvel's Spider-Man|205|R|{U}{R}|Legendary Creature - Spider Human Hero|2|3|From the Future -- You can't cast Spider-Man 2099 during your first, second, or third turns of the game.$Double strike, vigilance$At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target.| +Multiversal Passage|Marvel's Spider-Man|206|R||Land|||As this land enters, choose a basic land type. Then you may pay 2 life. If you don't, it enters tapped.$This land is the chosen type.| +Spider-Punk|Marvel's Spider-Man|207|R|{1}{R}|Legendary Creature - Spider Human Hero|2|1|Riot$Other Spiders you control have riot.$Spells and abilities can't be countered.$Damage can't be prevented.| +Peter Parker|Marvel's Spider-Man|208|M|{1}{W}|Legendary Creature - Human Scientist Hero|0|1|When Peter Parker enters, create a 2/1 green Spider creature token with reach.${1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery.| +Amazing Spider-Man|Marvel's Spider-Man|208|M|{1}{G}{W}{U}|Legendary Creature - Spider Human Hero|4|4|Vigilance, reach$Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}.| +Gwen Stacy|Marvel's Spider-Man|209|M|{1}{R}|Legendary Creature - Human Performer Hero|2|1|When Gwen Stacy enters, exile the top card of your library. You may play that card for as long as you control this creature.${2}{U}{R}{W}: Transform Gwen Stacy. Activate only as a sorcery.| +Ghost-Spider|Marvel's Spider-Man|209|M|{2}{U}{R}{W}|Legendary Creature - Spider Human Hero|4|4|Flying, vigilance, haste$Whenever you play a land from exile or cast a spell from exile, put a +1/+1 counter on Ghost-Spider.$Remove two counters from Ghost-Spider: Exile the top card of your library. You may play that card this turn.| +Spider-Punk|Marvel's Spider-Man|210|R|{1}{R}|Legendary Creature - Spider Human Hero|2|1|Riot$Other Spiders you control have riot.$Spells and abilities can't be countered.$Damage can't be prevented.| +Miles Morales|Marvel's Spider-Man|211|M|{1}{G}|Legendary Creature - Human Citizen Hero|1|2|When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures.${3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery.| +Ultimate Spider-Man|Marvel's Spider-Man|211|M|{3}{R}{G}{W}|Legendary Creature - Spider Human Hero|4|3|First strike, haste$Camouflage -- {2}: Put a +1/+1 counter on Ultimate Spider-Man. He gains hexproof and becomes colorless until end of turn.$Whenever you attack, double the number of each kind of counter on each Spider and legendary creature you control.| +Radioactive Spider|Marvel's Spider-Man|212|R|{G}|Creature - Spider|1|1|Reach, deathtouch$Fateful Bite -- {2}, Sacrifice this creature: Search your library for a Spider Hero card, reveal it, put it into your hand, then shuffle. Activate only as a sorcery.| +Arana, Heart of the Spider|Marvel's Spider-Man|213|R|{1}{R}{W}|Legendary Creature - Spider Human Hero|3|3|Whenever you attack, put a +1/+1 counter on target attacking creature.$Whenever a modified creature you control deals combat damage to a player, exile the top card of your library. You may play that card this turn.| +Scarlet Spider, Ben Reilly|Marvel's Spider-Man|214|R|{1}{R}{G}|Legendary Creature - Spider Human Hero|4|3|Web-slinging {R}{G}$Trample$Sensational Save -- If Scarlet Spider was cast using web-slinging, he enters with X +1/+1 counters on him, where X is the mana value of the returned creature.| +Silk, Web Weaver|Marvel's Spider-Man|215|R|{2}{G}{W}|Legendary Creature - Spider Human Hero|3|5|Web-slinging {1}{G}{W}$Whenever you cast a creature spell, create a 1/1 green and white Human Citizen creature token.${3}{G}{W}: Creatures you control get +2/+2 and gain vigilance until end of turn.| +Spider-Man 2099|Marvel's Spider-Man|216|R|{U}{R}|Legendary Creature - Spider Human Hero|2|3|From the Future -- You can't cast Spider-Man 2099 during your first, second, or third turns of the game.$Double strike, vigilance$At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target.| +Symbiote Spider-Man|Marvel's Spider-Man|217|R|{2}{U/B}|Legendary Creature - Symbiote Spider Hero|2|4|Whenever this creature deals combat damage to a player, look at that many cards from the top of your library. Put one of them into your hand and the rest into your graveyard.$Find New Host -- {2}{U/B}, Exile this card from your graveyard: Put a +1/+1 counter on target creature you control. It gains this card's other abilities. Activate only as a sorcery.| +Origin of Spider-Man|Marvel's Spider-Man|218|R|{1}{W}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Create a 2/1 green Spider creature token with reach.$II -- Put a +1/+1 counter on target creature you control. It becomes a legendary Spider Hero in addition to its other types.$III -- Target creature you control gains double strike until end of turn.| +The Clone Saga|Marvel's Spider-Man|219|R|{3}{U}|Enchantment - Saga|||(As this Saga enters step, add a lore counter. Sacrifice after III.)$I -- Surveil 3.$II -- When you next cast a creature spell this turn, copy it, except the copy isn't legendary.$III -- Choose a card name. Whenever a creature with the chosen name deals combat damage to a player this turn, draw a card.| +Norman Osborn|Marvel's Spider-Man|220|M|{1}{U}|Legendary Creature - Human Scientist Villain|1|1|Norman Osborn can't be blocked.$Whenever Norman Osborn deals combat damage to a player, he connives.${1}{U}{B}{R}: Transform Norman Osborn. Activate only as a sorcery.| +Green Goblin|Marvel's Spider-Man|220|M|{1}{U}{B}{R}|Legendary Creature - Goblin Human Villain|3|3|Flying, menace$Spells you cast from your graveyard cost {2} less to cast.$Goblin Formula -- Each nonland card in your graveyard has mayhem. The mayhem cost is equal to its mana cost.| +Behold the Sinister Six!|Marvel's Spider-Man|221|M|{6}{B}|Sorcery|||Return up to six target creature cards with different names from your graveyard to the battlefield.| +Black Cat, Cunning Thief|Marvel's Spider-Man|222|R|{3}{B}{B}|Legendary Creature - Human Rogue Villain|2|3|When Black Cat enters, look at the top nine cards of target opponent's library, exile two of them face down, then put the rest on the bottom of their library in a random order. You may play the exiled cards for as long as they remain exiled. Mana of any type can be spent to cast spells this way.| +The Death of Gwen Stacy|Marvel's Spider-Man|223|R|{2}{B}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Destroy target creature.$II -- Each player may discard a card. Each player who doesn't loses 3 life.$III -- Exile any number of target players' graveyards.| +Eddie Brock|Marvel's Spider-Man|224|M|{2}{B}|Legendary Creature - Human Hero Villain|5|5|When Eddie Brock enters, return target creature card with mana value 1 or less from your graveyard to the battlefield.${3}{B}{R}{G}: Transform Eddie Brock. Activate only as a sorcery.| +Venom, Lethal Protector|Marvel's Spider-Man|224|M|{3}{B}{R}{G}|Legendary Creature - Symbiote Hero Villain|5|5|Menace, trample, haste$Whenever Venom attacks, you may sacrifice another creature. If you do, draw X cards, then you may put a permanent card with mana value X or less from your hand onto the battlefield, where X is the sacrificed creature's mana value.| +Maximum Carnage|Marvel's Spider-Man|225|R|{4}{R}|Enchantment - Saga|||(As this Saga enters step and after your draw step, add a lore counter. Sacrifice after III.)$I -- Until your next turn, each creature attacks each combat if able and attacks a player other than you if able.$II -- Add {R}{R}{R}.$III -- This Saga deals 5 damage to each opponent.| +Kraven's Last Hunt|Marvel's Spider-Man|226|R|{3}{G}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Mill five cards. When you do, this Saga deals damage equal to the greatest power among creature cards in your graveyard to target creature.$II -- Target creature you control gets +2/+2 until end of turn.$III -- Return target creature card from your graveyard to your hand.| +Carnage, Crimson Chaos|Marvel's Spider-Man|227|R|{2}{B}{R}|Legendary Creature - Symbiote Villain|4|3|Trample$When Carnage enters, return target creature card with mana value 3 or less from your graveyard to the battlefield. It gains "This creature attacks each combat if able" and "When this creature deals combat damage to a player, sacrifice it."$Mayhem {B}{R}| +Doctor Octopus, Master Planner|Marvel's Spider-Man|228|M|{5}{U}{B}|Legendary Creature - Human Scientist Villain|4|8|Other Villains you control get +2/+2.$Your maximum hand size is eight.$At the beginning of your end step, if you have fewer than eight cards in hand, draw cards equal to the difference.| +Mary Jane Watson|Marvel's Spider-Man|229|R|{1}{G/W}|Legendary Creature - Human Performer|2|2|Whenever a Spider you control enters, draw a card. This ability triggers only once each turn.| +Spider-Woman, Stunning Savior|Marvel's Spider-Man|230|R|{1}{W/U}|Legendary Creature - Spider Human Hero|2|2|Flying$Venom Blast -- Artifacts and creatures your opponents control enter tapped.| +The Spot, Living Portal|Marvel's Spider-Man|231|R|{3}{W}{B}|Legendary Creature - Human Scientist Villain|4|4|When The Spot enters, exile up to one target nonland permanent and up to one target nonland permanent card from a graveyard.$When The Spot dies, put him on the bottom of his owner's library. If you do, return the exiled cards to their owners' hands.| Peter Parker|Marvel's Spider-Man|232|M|{1}{W}|Legendary Creature - Human Scientist Hero|0|1|When Peter Parker enters, create a 2/1 green Spider creature token with reach.${1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery.| Amazing Spider-Man|Marvel's Spider-Man|232|M|{1}{G}{W}{U}|Legendary Creature - Spider Human Hero|4|4|Vigilance, reach$Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}.| +Eddie Brock|Marvel's Spider-Man|233|M|{2}{B}|Legendary Creature - Human Hero Villain|5|5|When Eddie Brock enters, return target creature card with mana value 1 or less from your graveyard to the battlefield.${3}{B}{R}{G}: Transform Eddie Brock. Activate only as a sorcery.| +Venom, Lethal Protector|Marvel's Spider-Man|233|M|{3}{B}{R}{G}|Legendary Creature - Symbiote Hero Villain|5|5|Menace, trample, haste$Whenever Venom attacks, you may sacrifice another creature. If you do, draw X cards, then you may put a permanent card with mana value X or less from your hand onto the battlefield, where X is the sacrificed creature's mana value.| +Miles Morales|Marvel's Spider-Man|234|M|{1}{G}|Legendary Creature - Human Citizen Hero|1|2|When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures.${3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery.| +Ultimate Spider-Man|Marvel's Spider-Man|234|M|{3}{R}{G}{W}|Legendary Creature - Spider Human Hero|4|3|First strike, haste$Camouflage -- {2}: Put a +1/+1 counter on Ultimate Spider-Man. He gains hexproof and becomes colorless until end of turn.$Whenever you attack, double the number of each kind of counter on each Spider and legendary creature you control.| +Spectacular Spider-Man|Marvel's Spider-Man|235|R|{1}{W}|Legendary Creature - Spider Human Hero|3|2|Flash${1}: Spectacular Spider-Man gains flying until end of turn.${1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn.| +Spectacular Spider-Man|Marvel's Spider-Man|236|R|{1}{W}|Legendary Creature - Spider Human Hero|3|2|Flash${1}: Spectacular Spider-Man gains flying until end of turn.${1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn.| +Spectacular Spider-Man|Marvel's Spider-Man|237|R|{1}{W}|Legendary Creature - Spider Human Hero|3|2|Flash${1}: Spectacular Spider-Man gains flying until end of turn.${1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn.| +Spectacular Spider-Man|Marvel's Spider-Man|238|R|{1}{W}|Legendary Creature - Spider Human Hero|3|2|Flash${1}: Spectacular Spider-Man gains flying until end of turn.${1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn.| +Spectacular Spider-Man|Marvel's Spider-Man|239|R|{1}{W}|Legendary Creature - Spider Human Hero|3|2|Flash${1}: Spectacular Spider-Man gains flying until end of turn.${1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn.| +Spectacular Spider-Man|Marvel's Spider-Man|240|R|{1}{W}|Legendary Creature - Spider Human Hero|3|2|Flash${1}: Spectacular Spider-Man gains flying until end of turn.${1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn.| +Spectacular Spider-Man|Marvel's Spider-Man|241|R|{1}{W}|Legendary Creature - Spider Human Hero|3|2|Flash${1}: Spectacular Spider-Man gains flying until end of turn.${1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn.| +The Soul Stone|Marvel's Spider-Man|242|M|{1}{B}|Legendary Artifact - Infinity Stone|||Indestructible${T}: Add {B}.${6}{B}, {T}, Exile a creature you control: Harness The Soul Stone.$ -- At the beginning of your upkeep, return target creature card from your graveyard to the battlefield.| +The Soul Stone|Marvel's Spider-Man|243|M|{1}{B}|Legendary Artifact - Infinity Stone|||Indestructible${T}: Add {B}.${6}{B}, {T}, Exile a creature you control: Harness The Soul Stone.$ -- At the beginning of your upkeep, return target creature card from your graveyard to the battlefield.| +Anti-Venom, Horrifying Healer|Marvel's Spider-Man|244|M|{W}{W}{W}{W}{W}|Legendary Creature - Symbiote Hero|5|5|When Anti-Venom enters, if he was cast, return target creature card from your graveyard to the battlefield.$If damage would be dealt to Anti-Venom, prevent that damage and put that many +1/+1 counters on him.| +Arachne, Psionic Weaver|Marvel's Spider-Man|245|R|{2}{W}|Legendary Creature - Spider Human Hero|3|3|Web-slinging {W}$As Arachne enters, look at target opponent's hand, then choose a noncreature card type.$Spells of the chosen type cost {1} more to cast.| +Friendly Neighborhood|Marvel's Spider-Man|246|R|{3}{W}|Enchantment - Aura|||Enchant land$When this Aura enters, create three 1/1 green and white Human Citizen creature tokens.$Enchanted land has "{1}, {T}: Target creature gets +1/+1 until end of turn for each creature you control. Activate only as a sorcery."| +Rent Is Due|Marvel's Spider-Man|247|R|{W}|Enchantment|||At the beginning of your end step, you may tap two untapped creatures and/or Treasures you control. If you do, draw a card. Otherwise, sacrifice this enchantment.| +With Great Power...|Marvel's Spider-Man|248|R|{3}{W}|Enchantment - Aura|||Enchant creature you control$Enchanted creature gets +2/+2 for each Aura and Equipment attached to it.$All damage that would be dealt to you is dealt to enchanted creature instead.| +Hide on the Ceiling|Marvel's Spider-Man|249|R|{X}{U}|Instant|||Exile X target artifacts and/or creatures. Return the exiled cards to the battlefield under their owners' control at the beginning of the next end step.| +Hydro-Man, Fluid Felon|Marvel's Spider-Man|250|R|{U}{U}|Legendary Creature - Elemental Villain|2|2|Whenever you cast a blue spell, if Hydro-Man is a creature, he gets +1/+1 until end of turn.$At the beginning of your end step, untap Hydro-Man. Until your next turn, he becomes a land and gains "{T}: Add {U}."| +Impostor Syndrome|Marvel's Spider-Man|251|M|{4}{U}{U}|Enchantment|||Whenever a nontoken creature you control deals combat damage to a player, create a token that's a copy of it, except it isn't legendary.| +Lady Octopus, Inspired Inventor|Marvel's Spider-Man|252|R|{U}|Legendary Creature - Human Scientist Villain|0|2|Whenever you draw your first or second card each turn, put an ingenuity counter on Lady Octopus.${T}: You may cast an artifact spell from your hand with mana value less than or equal to the number of ingenuity counters on Lady Octopus without paying its mana cost.| +Mysterio, Master of Illusion|Marvel's Spider-Man|253|R|{3}{U}|Legendary Creature - Human Villain|3|3|When Mysterio enters, create a 3/3 blue Illusion Villain creature token for each nontoken Villain you control. Exile those tokens when Mysterio leaves the battlefield.| +Spider-Sense|Marvel's Spider-Man|254|R|{1}{U}|Instant|||Web-slinging {U}$Counter target instant spell, sorcery spell, or triggered ability.| +Agent Venom|Marvel's Spider-Man|255|R|{2}{B}|Legendary Creature - Symbiote Soldier Hero|2|3|Flash$Menace$Whenever another nontoken creature you control dies, you draw a card and lose 1 life.| +Gwenom, Remorseless|Marvel's Spider-Man|256|M|{3}{B}{B}|Legendary Creature - Symbiote Spider Hero|4|4|Deathtouch, lifelink$Whenever Gwenom attacks, until end of turn you may look at the top card of your library any time and you may play cards from the top of your library. If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost.| +Morlun, Devourer of Spiders|Marvel's Spider-Man|257|R|{X}{B}{B}|Legendary Creature - Vampire Villain|2|1|Lifelink$Morlun enters with X+1/+1 counters on him.$When Morlun enters, he deals X damage to target opponent.| +Parker Luck|Marvel's Spider-Man|258|R|{2}{B}|Enchantment|||At the beginning of your end step, two target players each reveal the top card of their library. They each lose life equal to the mana value of the card revealed by the other player. Then they each put the card they revealed into their hand.| +Villainous Wrath|Marvel's Spider-Man|259|R|{3}{B}{B}|Sorcery|||Target opponent loses life equal to the number of creatures they control. Then destroy all creatures.| +Electro, Assaulting Battery|Marvel's Spider-Man|260|R|{1}{R}{R}|Legendary Creature - Human Villain|2|3|Flying$You don't lose unspent red mana as steps and phases end.$Whenever you cast an instant or sorcery spell, add {R}.$When Electro leaves the battlefield, you may pay {X}. When you do, he deals X damage to target player.| +J. Jonah Jameson|Marvel's Spider-Man|261|R|{2}{R}|Legendary Creature - Human Citizen|2|2|When J. Jonah Jameson enters, suspect up to one target creature.$Whenever a creature you control with menace attacks, create a Treasure token.| +Shadow of the Goblin|Marvel's Spider-Man|262|R|{1}{R}|Enchantment|||Unreliable Visions -- At the beginning of your first main phase, discard a card. If you do, draw a card.$Undying Vengeance -- Whenever you play a land or cast a spell from anywhere other than your hand, this enchantment deals 1 damage to each opponent.| +Spider-Verse|Marvel's Spider-Man|263|M|{3}{R}{R}|Enchantment|||The "legend rule" doesn't apply to Spiders you control.$Whenever you cast a spell from anywhere other than your hand, you may copy it. If you do, you may choose new targets for the copy. If the copy is a permanent spell, it gains haste. Do this only once each turn.| +Spinneret and Spiderling|Marvel's Spider-Man|264|R|{R}|Legendary Creature - Spider Human Hero|1|2|Whenever you attack with two or more Spiders, put a +1/+1 counter on Spinneret and Spiderling.$Whenever Spinneret and Spiderling deals 4 or more damage, exile the top card of your library. Until the end of your next turn, you may play that card.| +Lizard, Connors's Curse|Marvel's Spider-Man|265|R|{2}{G}{G}|Legendary Creature - Lizard Villain|5|5|Trample$Lizard Formula -- When Lizard, Connors's Curse enters, up to one other target creature loses all abilities and becomes a green Lizard creature with base power and toughness 4/4.| +Sandman, Shifting Scoundrel|Marvel's Spider-Man|266|R|{1}{G}{G}|Legendary Creature - Sand Elemental Villain|*|*|Sandman's power and toughness are each equal to the number of lands you control.$Sandman can't be blocked by creatures with power 2 or less.${3}{G}{G}: Return this card and target land card from your graveyard to the battlefield tapped.| +Strength of Will|Marvel's Spider-Man|267|R|{1}{G}|Instant|||Until end of turn, target creature you control gains indestructible and "Whenever this creature is dealt damage, put that many +1/+1 counters on it."| +Web of Life and Destiny|Marvel's Spider-Man|268|M|{6}{G}{G}|Enchantment|||Convoke$At the beginning of combat on your turn, look at the top five cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order.| +Biorganic Carapace|Marvel's Spider-Man|269|R|{2}{W}{U}|Artifact - Equipment|||When this Equipment enters, attach it to target creature you control.$Equipped creature gets +2/+2 and has "Whenever this creature deals combat damage to a player, draw a card for each modified creature you control."$Equip {2}| +Cheering Crowd|Marvel's Spider-Man|270|R|{1}{R/G}|Creature - Human Citizen|2|2|At the beginning of each player's first main phase, that player may put a +1/+1 counter on this creature. If they do, they add {C} for each counter on it.| +Cosmic Spider-Man|Marvel's Spider-Man|271|M|{W}{U}{B}{R}{G}|Legendary Creature - Spider Human Hero|5|5|Flying, first strike, trample, lifelink, haste$At the beginning of combat on your turn, other Spiders you control gain flying, first strike, trample, lifelink, and haste until end of turn.| +Jackal, Genius Geneticist|Marvel's Spider-Man|272|R|{G}{U}|Legendary Creature - Human Scientist Villain|1|1|Trample$Whenever you cast a creature spell with mana value equal to Jackal's power, copy that spell, except the copy isn't legendary. Then put a +1/+1 counter on Jackal.| +Kraven the Hunter|Marvel's Spider-Man|273|R|{1}{B}{G}|Legendary Creature - Human Warrior Villain|4|3|Trample$Whenever a creature an opponent controls with the greatest power among creatures that player controls dies, draw a card and put a +1/+1 counter on Kraven the Hunter.| +Mister Negative|Marvel's Spider-Man|274|M|{5}{W}{B}|Legendary Creature - Human Villain|5|5|Vigilance, lifelink$Darkforce Inversion -- When Mister Negative enters, you may exchange your life total with target opponent. If you lose life this way, draw that many cards.| +Superior Spider-Man|Marvel's Spider-Man|275|R|{2}{U}{B}|Legendary Creature - Spider Human Hero|4|4|Mind Swap -- You may have Superior Spider- Man enter as a copy of any creature card in a graveyard, except his name is Superior Spider-Man and he's a 4/4 Spider Human Hero in addition to his other types. When you do, exile that card.| +Ultimate Green Goblin|Marvel's Spider-Man|276|R|{1}{B/R}{B/R}|Legendary Creature - Goblin Villain|5|4|At the beginning of your upkeep, discard a card, then create a Treasure token.$Mayhem {2}{B/R}| +Doc Ock's Tentacles|Marvel's Spider-Man|277|R|{1}|Artifact - Equipment|||Whenever a creature you control with mana value 5 or greater enters, you may attach this Equipment to it.$Equipped creature gets +4/+4.$Equip {5}| +Interdimensional Web Watch|Marvel's Spider-Man|278|R|{4}|Artifact|||When this artifact enters, exile the top two cards of your library. Until the end of your next turn, you may play those cards.${T}: Add two mana in any combination of colors. Spend this mana only to cast spells from exile.| +Iron Spider, Stark Upgrade|Marvel's Spider-Man|279|R|{3}|Legendary Artifact Creature - Spider Hero|2|3|Vigilance${T}: Put a +1/+1 counter on each artifact creature and/or Vehicle you control.${2}, Remove two +1/+1 counters from among artifacts you control: Draw a card.| +Peter Parker's Camera|Marvel's Spider-Man|280|R|{1}|Artifact|||This artifact enters with three film counters on it.${2}, {T}, Remove a film counter from this artifact: Copy target activated or triggered ability you control. You may choose new targets for the copy.| +Rocket-Powered Goblin Glider|Marvel's Spider-Man|281|R|{3}|Artifact - Equipment|||When this Equipment enters, if it was cast from your graveyard, attach it to target creature you control.$Equipped creature gets +2/+0 and has flying and haste.$Equip {2}$Mayhem {2}| +Oscorp Industries|Marvel's Spider-Man|282|R||Land|||This land enters tapped.$When this land enters from a graveyard, you lose 2 life.${T}: Add {U}, {B}, or {R}.$Mayhem| +Urban Retreat|Marvel's Spider-Man|283|R||Land|||This land enters tapped.${T}: Add {G}, {W} , or {U}.${2}, Return a tapped creature you control to its owner's hand: Put this card from your hand onto the battlefield. Activate only as a sorcery.| +Spider-Sense|Marvel's Spider-Man|284|R|{1}{U}|Instant|||Web-slinging {U}$Counter target instant spell, sorcery spell, or triggered ability.| +Radioactive Spider|Marvel's Spider-Man|285|R|{G}|Creature - Spider|1|1|Reach, deathtouch$Fateful Bite -- {2}, Sacrifice this creature: Search your library for a Spider Hero card, reveal it, put it into your hand, then shuffle. Activate only as a sorcery.| +Gwenom, Remorseless|Marvel's Spider-Man|286|M|{3}{B}{B}|Legendary Creature - Symbiote Spider Hero|4|4|Deathtouch, lifelink$Whenever Gwenom attacks, until end of turn you may look at the top card of your library any time and you may play cards from the top of your library. If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost.| +Force of Negation|Avatar: The Last Airbender Eternal|13|M|{1}{U}{U}|Instant|||If it's not your turn, you may exile a blue card from your hand rather than pay this spell's mana cost.$Counter target noncreature spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard.| +The Great Henge|Avatar: The Last Airbender Eternal|41|M|{7}{G}{G}|Legendary Artifact|||This spell costs {X} less to cast, where X is the greatest power among creatures you control.${T}: Add {G}{G}. You gain 2 life.$Whenever a nontoken creature you control enters, put a +1/+1 counter on it and draw a card.| +Aang, Airbending Master|Avatar: The Last Airbender Eternal|74|M|{4}{W}|Legendary Creature - Human Avatar Ally|4|4|When Aang enters, airbend another target creature.$Whenever one or more creatures you control leave the battlefield without dying, you get an experience counter.$At the beginning of your upkeep, create a 1/1 white Ally creature token for each experience counter you have.| +Katara, Waterbending Master|Avatar: The Last Airbender Eternal|93|M|{1}{U}|Legendary Creature - Human Warrior Ally|1|3|Whenever you cast a spell during an opponent's turn, you get an experience counter.$Whenever Katara attacks, you may draw a card for each experience counter you have. If you do, discard a card.| +Fire Lord Ozai|Avatar: The Last Airbender Eternal|104|M|{3}{B}|Legendary Creature - Human Noble|4|4|Whenever Fire Lord Ozai attacks, you may sacrifice another creature. If you do, add an amount of {R} equal to the sacrificed creature's power. Until end of combat, you don't lose this mana as steps end.${6}: Exile the top card of each opponent's library. Until end of turn, you may play one of those cards without paying its mana cost.| +The Cabbage Merchant|Avatar: The Last Airbender Eternal|134|R|{2}{G}|Legendary Creature - Human Citizen|2|2|Whenever an opponent casts a noncreature spell, create a Food token.$Whenever a creature deals combat damage to you, sacrifice a Food token.$Tap two untapped Foods you control: Add one mana of any color.| +Aang, Air Nomad|Avatar: The Last Airbender Eternal|210|R|{3}{W}{W}|Legendary Creature - Human Avatar Ally|5|4|Flying$Vigilance$Other creatures you control have vigilance.| +Aang's Defense|Avatar: The Last Airbender Eternal|211|C|{W}|Instant|||Target blocking creature you control gets +2/+2 until end of turn.$Draw a card.| +Aardvark Sloth|Avatar: The Last Airbender Eternal|212|C|{3}{W}|Creature - Sloth Beast|3|3|Lifelink| +Allied Teamwork|Avatar: The Last Airbender Eternal|213|R|{2}{W}|Enchantment|||When this enchantment enters, create a 1/1 white Ally creature token.$Allies you control get +1/+1.| +Appa, Aang's Companion|Avatar: The Last Airbender Eternal|214|U|{3}{W}|Legendary Creature - Bison Ally|2|4|Flying$Whenever Appa attacks, another target attacking creature without flying gains flying until end of turn.| +Katara, Heroic Healer|Avatar: The Last Airbender Eternal|215|U|{4}{W}|Legendary Creature - Human Warrior Ally|2|3|Lifelink$When Katara enters, put a +1/+1 counter on each other creature you control.| +Kyoshi Warrior Guard|Avatar: The Last Airbender Eternal|216|C|{1}{W}|Creature - Human Warrior Ally|2|3|| +Momo, Rambunctious Rascal|Avatar: The Last Airbender Eternal|217|U|{2}{W}|Legendary Creature - Lemur Bat Ally|1|1|Flying$When Momo enters, he deals 4 damage to target tapped creature an opponent controls.| +Sledding Otter-Penguin|Avatar: The Last Airbender Eternal|218|C|{2}{W}|Creature - Otter Bird|2|3|{3}: Put a +1/+1 counter on this creature.| +Sokka, Wolf Cove's Protector|Avatar: The Last Airbender Eternal|219|U|{2}{W}|Legendary Creature - Human Warrior Ally|3|3|Vigilance| +Tundra Wall|Avatar: The Last Airbender Eternal|220|C|{1}{W}|Creature - Wall|0|4|Defender| +Wolf Cove Villager|Avatar: The Last Airbender Eternal|221|C|{W}|Creature - Human Peasant|2|2|This creature enters tapped.| +Deny Entry|Avatar: The Last Airbender Eternal|222|C|{2}{U}|Instant|||Counter target creature spell. Draw a card, then discard a card.| +Flying Dolphin-Fish|Avatar: The Last Airbender Eternal|223|C|{1}{U}|Creature - Whale Fish|1|3|Flying| +Lost in the Spirit World|Avatar: The Last Airbender Eternal|224|U|{2}{U}|Sorcery|||Return up to one target creature to its owner's hand. Create a 1/1 colorless Spirit creature token with "This token can't block or be blocked by non-Spirit creatures."| +The Terror of Serpent's Pass|Avatar: The Last Airbender Eternal|225|R|{5}{U}{U}|Legendary Creature - Serpent|8|8|Hexproof| +Turtle-Seals|Avatar: The Last Airbender Eternal|226|C|{3}{U}|Creature - Turtle Seal|2|4|Vigilance| +Water Whip|Avatar: The Last Airbender Eternal|227|R|{U}{U}|Sorcery - Lesson|||As an additional cost to cast this spell, waterbend {5}.$Return up to two target creatures to their owners' hands. Draw two cards.| +Elephant-Rat|Avatar: The Last Airbender Eternal|228|C|{1}{B}|Creature - Elephant Rat|1|3|Menace| +Fire Nation Ambushers|Avatar: The Last Airbender Eternal|229|C|{2}{B}|Creature - Human Soldier|3|2|Flash| +Fire Nation Sentinels|Avatar: The Last Airbender Eternal|230|R|{3}{B}{B}|Creature - Human Soldier|4|4|Whenever a nontoken creature an opponent controls dies, put a +1/+1 counter on each creature you control.| +Gilacorn|Avatar: The Last Airbender Eternal|231|C|{B}|Creature - Lizard|1|1|Deathtouch| +Lion Vulture|Avatar: The Last Airbender Eternal|232|R|{3}{B}|Creature - Cat Bird|2|2|Flying$At the beginning of your end step, if an opponent lost life this turn, put a +1/+1 counter on this creature and draw a card.| +Purple Pentapus|Avatar: The Last Airbender Eternal|233|C|{B}|Creature - Octopus Starfish|1|1|When this creature enters, surveil 1.${2}{B}, Tap an untapped creature you control: Return this card from your graveyard to the battlefield tapped.| +Capital Guard|Avatar: The Last Airbender Eternal|234|C|{1}{R}|Creature - Human Soldier|2|2|| +Dragon Moose|Avatar: The Last Airbender Eternal|235|C|{3}{R}|Creature - Dragon Elk|3|3|Haste| +Explosive Shot|Avatar: The Last Airbender Eternal|236|C|{1}{R}|Sorcery|||Explosive Shot deals 4 damage to target creature.| +Fire Nation Archers|Avatar: The Last Airbender Eternal|237|R|{3}{R}|Creature - Human Archer|3|4|Reach${5}: This creature deals 2 damage to each opponent. Create a 2/2 red Soldier creature token.| +Fire Nation Soldier|Avatar: The Last Airbender Eternal|238|C|{2}{R}|Creature - Human Soldier|3|2|Haste| +Fire Nation's Conquest|Avatar: The Last Airbender Eternal|239|U|{2}{R}|Enchantment|||Creatures you control get +1/+0.| +Iroh, Firebending Instructor|Avatar: The Last Airbender Eternal|240|U|{2}{R}|Legendary Creature - Human Noble Ally|2|2|Whenever Iroh attacks, attacking creatures get +1/+1 until end of turn.| +Komodo Rhino|Avatar: The Last Airbender Eternal|241|C|{3}{R}|Creature - Lizard Rhino|5|2|Trample| +Loyal Fire Sage|Avatar: The Last Airbender Eternal|242|U|{2}{R}|Creature - Human Cleric Ally|3|3|Firebending 1${5}: Create a 1/1 white Ally creature token.| +Roku's Mastery|Avatar: The Last Airbender Eternal|243|U|{X}{R}{R}|Instant|||Roku's Mastery deals X damage to target creature. If X is 4 or greater, scry 2.| +Warship Scout|Avatar: The Last Airbender Eternal|244|C|{R}|Creature - Human Scout|2|1|| +Zhao, the Seething Flame|Avatar: The Last Airbender Eternal|245|U|{4}{R}|Legendary Creature - Human Soldier|5|5|Menace| +Zuko, Avatar Hunter|Avatar: The Last Airbender Eternal|246|R|{3}{R}{R}|Legendary Creature - Human Noble|4|5|Reach$Whenever you cast a red spell, create a 2/2 red Soldier creature token.| +Zuko's Offense|Avatar: The Last Airbender Eternal|247|C|{R}|Sorcery|||Zuko's Offense deals 2 damage to any target.| +Bumi, Eclectic Earthbender|Avatar: The Last Airbender Eternal|248|R|{3}{G}{G}|Legendary Creature - Human Noble Ally|4|4|When Bumi enters, earthbend 1.$Whenever Bumi attacks, put two +1/+1 counters on each land creature you control.| +Earthbending Student|Avatar: The Last Airbender Eternal|249|U|{2}{G}|Creature - Human Warrior Ally|1|3|When this creature enters, earthbend 2.$Land creatures you control have vigilance.| +Eel-Hounds|Avatar: The Last Airbender Eternal|250|U|{3}{G}|Creature - Fish Dog|4|2|Trample$Whenever this creature attacks, another target creature you control gets +2/+2 and gains trample until end of turn.| +Frog-Squirrels|Avatar: The Last Airbender Eternal|251|C|{1}{G}|Creature - Frog Squirrel|2|2|Reach| +Hippo-Cows|Avatar: The Last Airbender Eternal|252|C|{4}{G}|Creature - Hippo Ox|5|4|Trample| +Match the Odds|Avatar: The Last Airbender Eternal|253|U|{2}{G}|Sorcery - Lesson|||Create a 1/1 white Ally creature token. Put a +1/+1 counter on it for each creature your opponents control.| +Seismic Tutelage|Avatar: The Last Airbender Eternal|254|R|{3}{G}|Enchantment - Aura|||Enchant creature$When this Aura enters, put a +1/+1 counter on enchanted creature.$Whenever enchanted creature attacks, double the number of +1/+1 counters on it.| +Hog-Monkey Rampage|Avatar: The Last Airbender Eternal|255|U|{1}{R/G}|Instant|||Choose target creature you control and target creature an opponent controls. Put a +1/+1 counter on the creature you control if it has power 4 or greater. Then those creatures fight each other.| +Mechanical Glider|Avatar: The Last Airbender Eternal|256|C|{1}|Artifact - Equipment|||When this Equipment enters, attach it to target creature you control.$Equipped creature has flying.$Equip {2}| +Feed the Swarm|Avatar: The Last Airbender Eternal|257|C|{1}{B}|Sorcery|||Destroy target creature or enchantment an opponent controls. You lose life equal to that permanent's mana value.| +Run Amok|Avatar: The Last Airbender Eternal|258|C|{1}{R}|Instant|||Target attacking creature gets +3/+3 and gains trample until end of turn.| +Explore|Avatar: The Last Airbender Eternal|259|C|{1}{G}|Sorcery|||You may play an additional land this turn.$Draw a card.| +Thriving Bluff|Avatar: The Last Airbender Eternal|260|C||Land|||This land enters tapped. As it enters, choose a color other than red.${T}: Add {R} or one mana of the chosen color.| +Thriving Grove|Avatar: The Last Airbender Eternal|261|C||Land|||This land enters tapped. As it enters, choose a color other than green.${T}: Add {G} or one mana of the chosen color.| +Thriving Heath|Avatar: The Last Airbender Eternal|262|C||Land|||This land enters tapped. As it enters, choose a color other than white.${T}: Add {W} or one mana of the chosen color.| +Thriving Isle|Avatar: The Last Airbender Eternal|263|C||Land|||This land enters tapped. As it enters, choose a color other than blue.${T}: Add {U} or one mana of the chosen color.| +Thriving Moor|Avatar: The Last Airbender Eternal|264|C||Land|||This land enters tapped. As it enters, choose a color other than black.${T}: Add {B} or one mana of the chosen color.| +Aang, Air Nomad|Avatar: The Last Airbender Eternal|265|R|{3}{W}{W}|Legendary Creature - Human Avatar Ally|5|4|Flying$Vigilance$Other creatures you control have vigilance.| +Aang's Defense|Avatar: The Last Airbender Eternal|266|C|{W}|Instant|||Target blocking creature you control gets +2/+2 until end of turn.$Draw a card.| +Aardvark Sloth|Avatar: The Last Airbender Eternal|267|C|{3}{W}|Creature - Sloth Beast|3|3|Lifelink| +Appa, Aang's Companion|Avatar: The Last Airbender Eternal|268|U|{3}{W}|Legendary Creature - Bison Ally|2|4|Flying$Whenever Appa attacks, another target attacking creature without flying gains flying until end of turn.| +Katara, Heroic Healer|Avatar: The Last Airbender Eternal|269|U|{4}{W}|Legendary Creature - Human Warrior Ally|2|3|Lifelink$When Katara enters, put a +1/+1 counter on each other creature you control.| +Momo, Rambunctious Rascal|Avatar: The Last Airbender Eternal|270|U|{2}{W}|Legendary Creature - Lemur Bat Ally|1|1|Flying$When Momo enters, he deals 4 damage to target tapped creature an opponent controls.| +Path to Redemption|Avatar: The Last Airbender Eternal|271|C|{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature can't attack or block.${5}, Sacrifice this Aura: Exile enchanted creature. Create a 1/1 white Ally creature token. Activate only during your turn.| +Razor Rings|Avatar: The Last Airbender Eternal|272|C|{1}{W}|Instant|||Razor Rings deals 4 damage to target attacking or blocking creature. You gain life equal to the excess damage dealt this way.| +Sledding Otter-Penguin|Avatar: The Last Airbender Eternal|273|C|{2}{W}|Creature - Otter Bird|2|3|{3}: Put a +1/+1 counter on this creature.| +Sokka, Wolf Cove's Protector|Avatar: The Last Airbender Eternal|274|U|{2}{W}|Legendary Creature - Human Warrior Ally|3|3|Vigilance| +Tundra Wall|Avatar: The Last Airbender Eternal|275|C|{1}{W}|Creature - Wall|0|4|Defender| +Wolf Cove Villager|Avatar: The Last Airbender Eternal|276|C|{W}|Creature - Human Peasant|2|2|This creature enters tapped.| +Capital Guard|Avatar: The Last Airbender Eternal|277|C|{1}{R}|Creature - Human Soldier|2|2|| +Dragon Moose|Avatar: The Last Airbender Eternal|278|C|{3}{R}|Creature - Dragon Elk|3|3|Haste| +Explosive Shot|Avatar: The Last Airbender Eternal|279|C|{1}{R}|Sorcery|||Explosive Shot deals 4 damage to target creature.| +Fire Nation Soldier|Avatar: The Last Airbender Eternal|280|C|{2}{R}|Creature - Human Soldier|3|2|Haste| +Fire Nation's Conquest|Avatar: The Last Airbender Eternal|281|U|{2}{R}|Enchantment|||Creatures you control get +1/+0.| +Iroh, Firebending Instructor|Avatar: The Last Airbender Eternal|282|U|{2}{R}|Legendary Creature - Human Noble Ally|2|2|Whenever Iroh attacks, attacking creatures get +1/+1 until end of turn.| +Komodo Rhino|Avatar: The Last Airbender Eternal|283|C|{3}{R}|Creature - Lizard Rhino|5|2|Trample| +Run Amok|Avatar: The Last Airbender Eternal|284|C|{1}{R}|Instant|||Target attacking creature gets +3/+3 and gains trample until end of turn.| +Warship Scout|Avatar: The Last Airbender Eternal|285|C|{R}|Creature - Human Scout|2|1|| +Zhao, the Seething Flame|Avatar: The Last Airbender Eternal|286|U|{4}{R}|Legendary Creature - Human Soldier|5|5|Menace| +Zuko, Avatar Hunter|Avatar: The Last Airbender Eternal|287|R|{3}{R}{R}|Legendary Creature - Human Noble|4|5|Reach$Whenever you cast a red spell, create a 2/2 red Soldier creature token.| +Zuko's Offense|Avatar: The Last Airbender Eternal|288|C|{R}|Sorcery|||Zuko's Offense deals 2 damage to any target.| +Mountain|Avatar: The Last Airbender Eternal|289|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|290|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|291|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|292|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|293|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|294|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|295|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|296|C||Basic Land - Mountain|||({T}: Add {R}.)| +Plains|Avatar: The Last Airbender Eternal|297|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|298|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|299|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|300|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|301|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|302|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|303|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|304|C||Basic Land - Plains|||({T}: Add {W}.)| diff --git a/Utils/mtg-sets-data.txt b/Utils/mtg-sets-data.txt index 49164ecb554..df42f6ac90c 100644 --- a/Utils/mtg-sets-data.txt +++ b/Utils/mtg-sets-data.txt @@ -30,6 +30,7 @@ Antiquities|ATQ| Assassin's Creed|ACR| Avacyn Restored|AVR| Avatar: The Last Airbender|TLA| +Avatar: The Last Airbender Eternal|TLE| Battle for Zendikar|BFZ| Battlebond|BBD| Born of the Gods|BNG| diff --git a/Utils/print-card-info.pl b/Utils/print-card-info.pl new file mode 100644 index 00000000000..1c79be89324 --- /dev/null +++ b/Utils/print-card-info.pl @@ -0,0 +1,97 @@ +#!/usr/bin/perl -w + +#author: Jmlundeen + +use Text::Template; +use strict; +use utf8; +use open ':std', ':encoding(UTF-8)'; + +my $dataFile = 'mtg-cards-data.txt'; +my $setsFile = 'mtg-sets-data.txt'; +my $cardInfoTemplate = 'cardInfo.tmpl'; + +my %cards; +my %sets; + +sub toCamelCase { + my $string = $_[0]; + $string =~ s/\b([\w']+)\b/ucfirst($1)/ge; + $string =~ s/[-,\s\':.!\/]//g; + $string; +} + +sub printCardInfo { + my ($cardName, $infoTemplate) = @_; + + if (!exists $cards{$cardName}) { + print "Card name doesn't exist: $cardName\n\n"; + return; + } + + my %vars; + $vars{'classNameLower'} = lcfirst(toCamelCase($cardName)); + my @card; + + foreach my $setName (keys %{$cards{$cardName}}) { + @card = @{(values(%{$cards{$cardName}{$setName}}))[0]}; + last; # Just get the first one + } + + $vars{'cardName'} = $card[0]; + $vars{'manaCost'} = $card[4]; + $vars{'typeLine'} = $card[5]; + + # Combine power and toughness if they exist + my $powerToughness = ""; + if ($card[6] && $card[7] && $card[6] ne "" && $card[7] ne "") { + $powerToughness = "$card[6]/$card[7]"; + } + $vars{'powerToughness'} = $powerToughness; + + my $cardAbilities = $card[8]; + my @abilities = split(/\$/, $cardAbilities); + my $abilitiesFormatted = join("\n ", @abilities); + $vars{'abilities'} = $abilitiesFormatted; + + my $result = $infoTemplate->fill_in(HASH => \%vars); + print "$result\n\n"; +} + +# Load data files +open(DATA, $dataFile) || die "can't open $dataFile : $!"; +while (my $line = ) { + my @data = split('\\|', $line); + $cards{$data[0]}{$data[1]}{$data[2]} = \@data; +} +close(DATA); + +open(DATA, $setsFile) || die "can't open $setsFile : $!"; +while (my $line = ) { + my @data = split('\\|', $line); + $sets{$data[0]} = $data[1]; +} +close(DATA); + +# Get card names from arguments +my @cardNames = @ARGV; +if (@cardNames == 0) { + print 'Enter card names (one per line, empty line to finish): '; + while (my $input = ) { + chomp $input; + last if $input eq ''; + push @cardNames, $input; + } +} + +if (@cardNames == 0) { + die "No card names provided.\n"; +} + +# Load template +my $infoTemplate = Text::Template->new(TYPE => 'FILE', SOURCE => $cardInfoTemplate, DELIMITERS => [ '[=', '=]' ]); + +# Print card info for each card +foreach my $cardName (@cardNames) { + printCardInfo($cardName, $infoTemplate); +} \ No newline at end of file