diff --git a/.gitignore b/.gitignore index 7ecdf2fd4b8..70260d0197e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ syntax: glob *.log.* */gamelogs */gamelogsJson +*/gamesHistory # Mage.Client Mage.Client/plugins/images diff --git a/.travis.yml b/.travis.yml index f665f8924cd..91b52ed33ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ git: before_install: - echo "MAVEN_OPTS='-Xmx2g'" > ~/.mavenrc script: - - mvn test -B -Dlog4j.configuration=file:${TRAVIS_BUILD_DIR}/.travis/log4j.properties + - mvn test -B -Dxmage.dataCollectors.printGameLogs=false -Dlog4j.configuration=file:${TRAVIS_BUILD_DIR}/.travis/log4j.properties cache: directories: - $HOME/.m2 \ No newline at end of file diff --git a/Mage.Client/pom.xml b/Mage.Client/pom.xml index d27d1ff605e..6f7b308bc65 100644 --- a/Mage.Client/pom.xml +++ b/Mage.Client/pom.xml @@ -87,14 +87,6 @@ jetlang 0.2.23 - - - - com.amazonaws - aws-java-sdk-s3 - 1.12.78 - - com.jgoodies diff --git a/Mage.Client/src/main/java/mage/client/constants/Constants.java b/Mage.Client/src/main/java/mage/client/constants/Constants.java index e45fd4a68cd..5f30e4e6d8f 100644 --- a/Mage.Client/src/main/java/mage/client/constants/Constants.java +++ b/Mage.Client/src/main/java/mage/client/constants/Constants.java @@ -122,6 +122,9 @@ public final class Constants { public static final String RESOURCE_SYMBOL_FOLDER_SVG = "svg"; public static final String RESOURCE_SYMBOL_FOLDER_PNG = "png"; + // download rarity icons to large folder by default + public static final String RESOURCE_PATH_SYMBOLS_RARITY_DEFAULT_PATH = RESOURCE_PATH_SYMBOLS + File.separator + RESOURCE_SYMBOL_FOLDER_LARGE; + public enum ResourceSymbolSize { SMALL, // TODO: delete SMALL, MEDIUM and LARGE as outdated (svg or generated png works fine) MEDIUM, @@ -133,12 +136,12 @@ public final class Constants { // resources - sets public static final String RESOURCE_PATH_SETS = File.separator + "sets"; public static final String RESOURCE_SET_FOLDER_SMALL = "small"; - public static final String RESOURCE_SET_FOLDER_MEDIUM = ""; // empty, medium images laydown in "sets" folder, TODO: delete that and auto gen, use png for html, not gif + public static final String RESOURCE_SET_FOLDER_LARGE = "large"; public static final String RESOURCE_SET_FOLDER_SVG = "svg"; public enum ResourceSetSize { SMALL, - MEDIUM, + LARGE, SVG } diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java index 3f611168e97..904eea401a4 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java @@ -44,7 +44,7 @@ public class DeckGeneratorPool { // List of cards so far in the deck private final List deckCards = new ArrayList<>(); // List of reserve cards found to fix up undersized decks - private final List reserveSpells = new ArrayList<>(); + private final Map reserveSpells = new HashMap<>(); private final Deck deck; /** @@ -170,12 +170,13 @@ public class DeckGeneratorPool { * @param card the card to add. */ public void addCard(Card card) { - Object cnt = cardCounts.get((card.getName())); - if (cnt == null) - cardCounts.put(card.getName(), 0); - int existingCount = cardCounts.get((card.getName())); - cardCounts.put(card.getName(), existingCount + 1); + int count = cardCounts.getOrDefault(card.getName(), 0); + cardCounts.put(card.getName(), count + 1); deckCards.add(card); + + if (deckCards.stream().distinct().collect(Collectors.toList()).size() != deckCards.size()) { + System.out.println("wtf " + card.getName()); + } } public void clearCards(boolean isClearReserve) { @@ -198,8 +199,8 @@ public class DeckGeneratorPool { // Only cards with CMC < 7 and don't already exist in the deck // can be added to our reserve pool as not to overwhelm the curve // with high CMC cards and duplicates. - if (cardCMC < 7 && getCardCount(card.getName()) == 0) { - this.reserveSpells.add(card); + if (cardCMC < 7 && getCardCount(card.getName()) == 0 && !this.reserveSpells.containsKey(card.getName())) { + this.reserveSpells.put(card.getName(), card); return true; } return false; @@ -416,48 +417,38 @@ public class DeckGeneratorPool { * @return a fixed list of cards for this deck. */ private List getFixedSpells() { - int spellSize = deckCards.size(); + int spellsSize = deckCards.size(); int nonLandSize = (deckSize - landCount); - // Less spells than needed - if (spellSize < nonLandSize) { - - int spellsNeeded = nonLandSize - spellSize; - - // If we haven't got enough spells in reserve to fulfil the amount we need, skip adding any. - if (reserveSpells.size() >= spellsNeeded) { - - List spellsToAdd = new ArrayList<>(spellsNeeded); - - // Initial reservoir - for (int i = 0; i < spellsNeeded; i++) - spellsToAdd.add(reserveSpells.get(i)); - - for (int i = spellsNeeded + 1; i < reserveSpells.size() - 1; i++) { - int j = RandomUtil.nextInt(i); - Card randomCard = reserveSpells.get(j); - if (isValidSpellCard(randomCard) && j < spellsToAdd.size()) { - spellsToAdd.set(j, randomCard); - } + // fewer spells than needed - add + if (spellsSize < nonLandSize) { + int needExtraSpells = nonLandSize - spellsSize; + List possibleSpells = new ArrayList<>(reserveSpells.values()); + while (needExtraSpells > 0) { + Card card = RandomUtil.randomFromCollection(possibleSpells); + if (card == null) { + break; } - // Add randomly selected spells needed - deckCards.addAll(spellsToAdd); + if (isValidSpellCard(card)) { + needExtraSpells--; + deckCards.add(card); + } + possibleSpells.remove(card); } } - // More spells than needed - else if (spellSize > (deckSize - landCount)) { - int spellsRemoved = (spellSize) - (deckSize - landCount); - for (int i = 0; i < spellsRemoved; ++i) { - deckCards.remove(RandomUtil.nextInt(deckCards.size())); + // more spells than needed - remove + if (spellsSize > nonLandSize) { + int removeCount = spellsSize - nonLandSize; + for (int i = 0; i < removeCount; ++i) { + deckCards.remove(RandomUtil.randomFromCollection(deckCards)); } } - // Check we have exactly the right amount of cards for a deck. if (deckCards.size() != nonLandSize) { - logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors"); + logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors (wrong non land cards amount)"); } - // Return the fixed amount + return deckCards; } @@ -619,48 +610,75 @@ public class DeckGeneratorPool { if (needCommandersCount > 0 && !genPool.cardCounts.isEmpty()) { throw new IllegalArgumentException("Wrong code usage: generateSpells with creatures and commanders must be called as first"); } - List cardPool = CardRepository.instance.findCards(criteria); + List cardsPool = CardRepository.instance.findCards(criteria).stream() + .map(CardInfo::createMockCard) + .filter(genPool::isValidSpellCard) + .collect(Collectors.toList()); + List commandersPool = cardsPool.stream() + .filter(genPool::isValidCommander) + .collect(Collectors.toList()); + List deckCMCs = genPool.getCMCsForSpellCount(needCardsCount); - int count = 0; + int usedCardsCount = 0; int validCommanders = 0; int reservesAdded = 0; - if (cardPool.size() > 0 && cardPool.size() >= needCardsCount) { + if (cardsPool.size() > 0 && cardsPool.size() >= needCardsCount) { int tries = 0; + List possibleCards = new ArrayList<>(cardsPool); + List possibleCommanders = new ArrayList<>(commandersPool); while (true) { tries++; // can't finish deck, stop and use reserved cards later if (tries > DeckGenerator.MAX_TRIES) { - logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors"); + logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors (max tries exceeded)"); break; } // can finish deck - but make sure it has commander - if (count >= needCardsCount) { + if (usedCardsCount >= needCardsCount) { if (validCommanders < needCommandersCount) { // reset deck search from scratch (except reserved cards) - count = 0; + usedCardsCount = 0; validCommanders = 0; deckCMCs = genPool.getCMCsForSpellCount(needCardsCount); - genPool.clearCards(false); + genPool.clearCards(true); + possibleCards = new ArrayList<>(cardsPool); + possibleCommanders = new ArrayList<>(commandersPool); continue; } break; } - Card card = cardPool.get(RandomUtil.nextInt(cardPool.size())).createMockCard(); + if (possibleCards.isEmpty()) { + throw new IllegalStateException("Not enough cards to generate deck (possible cards is empty)"); + } + + // choose commander first + Card card = null; + if (validCommanders < needCommandersCount && !possibleCommanders.isEmpty()) { + card = RandomUtil.randomFromCollection(possibleCommanders); + } + + // choose other cards after commander + if (card == null) { + card = RandomUtil.randomFromCollection(possibleCards); + } + if (!genPool.isValidSpellCard(card)) { + possibleCards.remove(card); + possibleCommanders.remove(card); continue; } int cardCMC = card.getManaValue(); for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) { if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) { - int currentAmount = deckCMC.getAmount(); - if (currentAmount > 0) { - deckCMC.setAmount(currentAmount - 1); + int needAmount = deckCMC.getAmount(); + if (needAmount > 0) { + deckCMC.setAmount(needAmount - 1); genPool.addCard(card.copy()); - count++; + usedCardsCount++; // make sure it has compatible commanders if (genPool.isValidCommander(card)) { validCommanders++; @@ -674,7 +692,7 @@ public class DeckGeneratorPool { } } } else { - throw new IllegalStateException("Not enough cards to generate deck."); + throw new IllegalStateException("Not enough cards to generate deck (cards pool too small)"); } } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index 326b38bbea9..af9528f5e77 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -19,7 +19,6 @@ import mage.client.dialog.AddLandDialog; import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.Event; -import mage.client.util.GUISizeHelper; import mage.client.util.Listener; import mage.client.util.audio.AudioManager; import mage.components.CardInfoPane; @@ -31,7 +30,6 @@ import mage.util.XmageThreadFactory; import mage.view.CardView; import mage.view.SimpleCardView; import org.apache.log4j.Logger; -import org.mage.card.arcane.ManaSymbols; import javax.swing.*; import javax.swing.border.Border; @@ -534,7 +532,8 @@ public class DeckEditorPanel extends javax.swing.JPanel { } } }); - refreshDeck(true); + + refreshDeck(true, false); // auto-import dropped files from OS if (mode == DeckEditorMode.FREE_BUILDING) { @@ -673,20 +672,28 @@ public class DeckEditorPanel extends javax.swing.JPanel { private void refreshDeck() { if (this.isVisible()) { // TODO: test auto-close deck with active lands dialog, e.g. on timeout - refreshDeck(false); + refreshDeck(false, false); } } - private void refreshDeck(boolean useLayout) { + private void refreshDeck(boolean useLayout, boolean useDeckValidation) { try { - setCursor(new Cursor(Cursor.WAIT_CURSOR)); + MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR)); this.txtDeckName.setText(deck.getName()); deckArea.loadDeck(deck, useLayout, bigCard); + if (useDeckValidation) { + validateDeck(); + } } finally { - setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } } + private void validateDeck() { + this.deckLegalityDisplay.setVisible(true); + this.deckLegalityDisplay.validateDeck(deck); + } + private void setTimeout(int s) { int minute = s / 60; int second = s - (minute * 60); @@ -762,7 +769,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { if (newDeck != null) { deck = newDeck; - refreshDeck(); + refreshDeck(false, true); } // save last deck import folder @@ -818,7 +825,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { Deck deckToAppend = Deck.load(DeckImporter.importDeckFromFile(tempDeckPath, errorMessages, false), true, true); processAndShowImportErrors(errorMessages); this.deck = Deck.append(deckToAppend, this.deck); - refreshDeck(); + refreshDeck(false, true); } catch (GameException e1) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE); } finally { @@ -922,7 +929,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { if (newDeck != null) { deck = newDeck; - refreshDeck(); + refreshDeck(false, true); return true; } @@ -1441,7 +1448,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { if (newDeck != null) { deck = newDeck; - refreshDeck(true); + refreshDeck(true, true); } // save last deck history @@ -1474,7 +1481,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { // in deck editor mode - clear all cards deck = new Deck(); } - refreshDeck(); + refreshDeck(false, true); }//GEN-LAST:event_btnNewActionPerformed private void btnExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExitActionPerformed @@ -1483,7 +1490,9 @@ public class DeckEditorPanel extends javax.swing.JPanel { private void btnAddLandActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAddLandActionPerformed AddLandDialog dialog = new AddLandDialog(); - dialog.showDialog(deck, mode, this::refreshDeck); + dialog.showDialog(deck, mode, () -> { + this.refreshDeck(false, true); + }); }//GEN-LAST:event_btnAddLandActionPerformed private void btnGenDeckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnGenDeckActionPerformed @@ -1501,7 +1510,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { } finally { MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } - refreshDeck(); + refreshDeck(false, true); }//GEN-LAST:event_btnGenDeckActionPerformed private void btnSubmitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSubmitActionPerformed @@ -1548,8 +1557,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { }//GEN-LAST:event_btnExportActionPerformed private void btnLegalityActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLegalityActionPerformed - this.deckLegalityDisplay.setVisible(true); - this.deckLegalityDisplay.validateDeck(deck); + validateDeck(); }//GEN-LAST:event_btnLegalityActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/Mage.Client/src/main/java/mage/client/draft/DraftPickLogger.java b/Mage.Client/src/main/java/mage/client/draft/DraftPickLogger.java index db585e1a227..90c094fd055 100644 --- a/Mage.Client/src/main/java/mage/client/draft/DraftPickLogger.java +++ b/Mage.Client/src/main/java/mage/client/draft/DraftPickLogger.java @@ -75,7 +75,7 @@ public class DraftPickLogger { private void appendToDraftLog(String data) { if (logging) { try { - Files.write(logPath, data.getBytes(), StandardOpenOption.APPEND); + Files.write(logPath, data.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (IOException ex) { LOGGER.error(null, ex); } diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index 3fb8dcea16f..cc1be98996a 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -257,10 +257,7 @@ public class CallbackClientImpl implements CallbackClient { if (panel != null) { Session session = SessionHandler.getSession(); if (session.isJsonLogActive()) { - UUID gameId = callback.getObjectId(); appendJsonEvent("GAME_OVER", callback.getObjectId(), message); - String logFileName = "game-" + gameId + ".json"; - S3Uploader.upload(logFileName, gameId.toString()); } panel.endMessage(callback.getMessageId(), message.getGameView(), message.getOptions(), message.getMessage()); } diff --git a/Mage.Client/src/main/java/mage/client/remote/S3Uploader.java b/Mage.Client/src/main/java/mage/client/remote/S3Uploader.java deleted file mode 100644 index 85968f6ea03..00000000000 --- a/Mage.Client/src/main/java/mage/client/remote/S3Uploader.java +++ /dev/null @@ -1,46 +0,0 @@ -package mage.client.remote; - -import com.amazonaws.AmazonClientException; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.services.s3.transfer.TransferManager; -import com.amazonaws.services.s3.transfer.Upload; -import org.apache.log4j.Logger; - -import java.io.File; - -public class S3Uploader { - - private static final Logger logger = Logger.getLogger(S3Uploader.class); - - public static Boolean upload(String filePath, String keyName) throws Exception { - String existingBucketName = System.getenv("S3_BUCKET") != null ? System.getenv("S3_BUCKET") - : "xmage-game-logs-dev"; - - String accessKeyId = System.getenv("AWS_ACCESS_ID"); - String secretKeyId = System.getenv("AWS_SECRET_KEY"); - - if (accessKeyId == null || accessKeyId.isEmpty() - || secretKeyId == null || secretKeyId.isEmpty() - || existingBucketName.isEmpty()) { - logger.info("Aborting json log sync."); - return false; - } - - String path = new File("./" + filePath).getCanonicalPath(); - logger.info("Syncing " + path + " to bucket: " + existingBucketName + " with AWS Access Id: " + accessKeyId); - - BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKeyId, secretKeyId); - TransferManager tm = new TransferManager(awsCreds); - Upload upload = tm.upload(existingBucketName, "/game/" + keyName + ".json", new File(path)); - - try { - upload.waitForUploadResult(); - logger.info("Sync Complete For " + path + " to bucket: " + existingBucketName + " with AWS Access Id: " + accessKeyId); - new File(path); - return true; - } catch (AmazonClientException amazonClientException) { - logger.fatal("Unable to upload file, upload was aborted.", amazonClientException); - return false; - } - } -} diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 302fe95ffbb..0cb2e6e4242 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -896,7 +896,7 @@ public class TablesPanel extends javax.swing.JPanel { formatFilterList.add(RowFilter.regexFilter("^Limited", TablesTableModel.COLUMN_DECK_TYPE)); } if (btnFormatOther.isSelected()) { - formatFilterList.add(RowFilter.regexFilter("^Momir Basic|^Constructed - Pauper|^Constructed - Frontier|^Constructed - Extended|^Constructed - Eternal|^Constructed - Historical|^Constructed - Super|^Constructed - Freeform|^Australian Highlander|^European Highlander|^Canadian Highlander|^Constructed - Old|^Constructed - Historic", TablesTableModel.COLUMN_DECK_TYPE)); + formatFilterList.add(RowFilter.regexFilter("^Momir Basic|^Constructed - Pauper|^Constructed - Frontier|^Constructed - Extended|^Constructed - Eternal|^Constructed - Historical|^Constructed - Super|^Constructed - Freeform|^Constructed - Freeform Unlimited|^Australian Highlander|^European Highlander|^Canadian Highlander|^Constructed - Old|^Constructed - Historic", TablesTableModel.COLUMN_DECK_TYPE)); } // skill diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index dd77bf28980..e9d05e19d23 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -89,11 +89,6 @@ public final class ManaSymbols { public static void loadImages() { logger.info("Symbols: loading..."); - // TODO: delete files rename jpg->gif (it was for backward compatibility for one of the old version?) - renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.SMALL)); - renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.MEDIUM)); - renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.LARGE)); - //renameSymbols(getSymbolsPath(ResourceSymbolSize.SVG)); // not need // TODO: remove medium sets files to "medium" folder like symbols above? // prepare svg's css settings @@ -145,9 +140,9 @@ public final class ManaSymbols { Map rarityImages = new EnumMap<>(Rarity.class); setImages.put(set, rarityImages); - // load medium size + // load large size for (Rarity rarityCode : codes) { - File file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + rarityCode.getCode() + ".jpg"); + File file = new File(getResourceSetsPath(ResourceSetSize.LARGE) + set + '-' + rarityCode.getCode() + ".png"); try { Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); int width = image.getWidth(null); @@ -167,7 +162,7 @@ public final class ManaSymbols { // generate small size try { - File file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM)); + File file = new File(getResourceSetsPath(ResourceSetSize.LARGE)); if (!file.exists()) { file.mkdirs(); } @@ -175,11 +170,11 @@ public final class ManaSymbols { for (Rarity code : codes) { File newFile = new File(pathRoot + '-' + code + ".png"); if (!(MageFrame.isSkipSmallSymbolGenerationForExisting() && newFile.exists())) {// skip if option enabled and file already exists - file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".png"); + file = new File(getResourceSetsPath(ResourceSetSize.LARGE) + set + '-' + code + ".png"); if (file.exists()) { continue; } - file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".jpg"); + file = new File(getResourceSetsPath(ResourceSetSize.LARGE) + set + '-' + code + ".png"); Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); try { int width = image.getWidth(null); @@ -239,7 +234,7 @@ public final class ManaSymbols { } } - private static File getSymbolFileNameAsGIF(String symbol, int size) { + private static File getSymbolFileNameAsPNG(String symbol, int size) { ResourceSymbolSize needSize = null; if (size <= 15) { @@ -250,15 +245,15 @@ public final class ManaSymbols { needSize = ResourceSymbolSize.LARGE; } - return new File(getResourceSymbolsPath(needSize) + symbol + ".gif"); + return new File(getResourceSymbolsPath(needSize) + symbol + ".png"); } - private static BufferedImage loadSymbolAsGIF(String symbol, int resizeToWidth, int resizeToHeight) { - File file = getSymbolFileNameAsGIF(symbol, resizeToWidth); - return loadSymbolAsGIF(file, resizeToWidth, resizeToHeight); + private static BufferedImage loadSymbolAsPNG(String symbol, int resizeToWidth, int resizeToHeight) { + File file = getSymbolFileNameAsPNG(symbol, resizeToWidth); + return loadSymbolAsPNG(file, resizeToWidth, resizeToHeight); } - private static BufferedImage loadSymbolAsGIF(File sourceFile, int resizeToWidth, int resizeToHeight) { + private static BufferedImage loadSymbolAsPNG(File sourceFile, int resizeToWidth, int resizeToHeight) { BufferedImage image = null; @@ -315,9 +310,9 @@ public final class ManaSymbols { // gif (if svg fails) if (image == null) { - file = getSymbolFileNameAsGIF(symbol, size); + file = getSymbolFileNameAsPNG(symbol, size); if (file.exists()) { - image = loadSymbolAsGIF(file, size, size); + image = loadSymbolAsPNG(file, size, size); } } if (image == null) { @@ -352,29 +347,6 @@ public final class ManaSymbols { return errorInfo.isEmpty(); } - private static void renameSymbols(String path) { - File file = new File(path); - if (!file.exists()) { - return; - } - - final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg"); - try { - Files.walkFileTree(Paths.get(path), new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (matcher.matches(file)) { - Path gifPath = file.resolveSibling(file.getFileName().toString().replaceAll("\\.jpg$", ".gif")); - Files.move(file, gifPath, StandardCopyOption.REPLACE_EXISTING); - } - return FileVisitResult.CONTINUE; - } - }); - } catch (IOException e) { - logger.error("Couldn't rename mana symbols on " + path, e); - } - } - private static String getResourceSymbolsPath(ResourceSymbolSize needSize) { // return real path to symbols (default or user defined) @@ -420,8 +392,8 @@ public final class ManaSymbols { case SMALL: path = path + Constants.RESOURCE_SET_FOLDER_SMALL; break; - case MEDIUM: - path = path + Constants.RESOURCE_SET_FOLDER_MEDIUM; + case LARGE: + path = path + Constants.RESOURCE_SET_FOLDER_LARGE; break; case SVG: path = path + Constants.RESOURCE_SET_FOLDER_SVG; diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java b/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java index e3288ff43c7..acdd0aec2d5 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java @@ -663,7 +663,9 @@ public class CardPluginImpl implements CardPlugin { // mana symbols (low quality) jobs = new GathererSymbols(); for (DownloadJob job : jobs) { - downloader.add(job); + // TODO: gatherer removed mana symbols icons after 2025, see https://github.com/magefree/mage/issues/13797 + // remove GathererSymbols code after few releases as unused (2025.06.28) + // downloader.add(job); } // set code symbols (low quality) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java index ad44256a076..57ca99791f9 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java @@ -46,8 +46,10 @@ public class Downloader extends AbstractLaternaBean { public Downloader() { // prepare 10 threads and start to waiting new download jobs from queue + // TODO: gatherer website has download rate limits, so limit max threads as temporary solution + int maxThreads = 3; PoolFiberFactory f = new PoolFiberFactory(pool); - for (int i = 0, numThreads = 10; i < numThreads; i++) { + for (int i = 0, numThreads = maxThreads; i < numThreads; i++) { Fiber fiber = f.create(); fiber.start(); fibers.add(fiber); 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 f12baabcae2..18de4e36e2d 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 @@ -16,7 +16,9 @@ import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; /** * Download: set code symbols download from wizards web size *

- * Warning, it's outdated source with low quality images. TODO: must migrate to scryfall like mana icons + * Warning, it's outdated source with low quality images. + * TODO: must migrate to scryfall like mana icons, + * see https://github.com/magefree/mage/issues/13261 */ public class GathererSets implements Iterable { @@ -41,9 +43,10 @@ public class GathererSets implements Iterable { private static File outDir; - private static final int DAYS_BEFORE_RELEASE_TO_DOWNLOAD = +14; // Try to load the symbolsBasic eralies 14 days before release date + private static final int DAYS_BEFORE_RELEASE_TO_DOWNLOAD = +14; // Try to load the symbolsBasic 14 days before release date private static final Logger logger = Logger.getLogger(GathererSets.class); + // TODO: find all possible sets from ExpansionRepository instead custom private static final String[] symbolsBasic = {"10E", "9ED", "8ED", "7ED", "6ED", "5ED", "4ED", "3ED", "2ED", "LEB", "LEA", "HOP", "ARN", "ATQ", "LEG", "DRK", "FEM", "HML", @@ -61,14 +64,16 @@ public class GathererSets implements Iterable { "TSP", "TSB", "PLC", "FUT", "LRW", "MOR", "SHM", "EVE", - "MED", "ME2", "ME3", "ME4", + "ME2", "ME3", "ME4", "POR", "P02", "PTK", "ARC", "DD3EVG", - "W16", "W17", + "W16", "W17", // "PALP" -- Gatherer does not have the set Asia Pacific Land Program // "ATH" -- has cards from many sets, symbol does not exist on gatherer // "CP", "DPA", "PELP", "PGPX", "PGRU", "H17", "JR", "SWS", // need to fix - "H09", "PD2", "PD3", "UNH", "CM1", "V11", "A25", "UST", "IMA", "DD2", "EVG", "DDC", "DDE", "DDD", "CHR", "G18", "GVL", "S00", "S99", "UGL" // ok + "H09", "PD2", "PD3", "UNH", "CM1", "V11", "A25", "UST", "IMA", "DD2", + "EVG", "DDC", "DDE", "DDD", "CHR", "G18", "GVL", "S00", "S99", "UGL", + "BTD" // ok // current testing }; @@ -98,21 +103,25 @@ public class GathererSets implements Iterable { "GNT", "UMA", "GRN", "RNA", "WAR", "MH1", "M20", - "C19", "ELD", "MB1", "GN2", "J20", "THB", "UND", "C20", "IKO", "M21", + "C19", "ELD", "MB1", "GN2", "THB", "UND", "C20", "IKO", "M21", "JMP", "2XM", "ZNR", "KLR", "CMR", "KHC", "KHM", "TSR", "STX", "STA", "C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID", - "NEC", "YNEO", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "YDMU", "40K", "GN3", - "UNF", "BRO", "BRC", "BOT", "30A", "J22", "SCD", "DMR", "ONE", "ONC", + "NEC", "YNEO", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "40K", "GN3", + "UNF", "BRO", "BRC", "BOT", "J22", "DMR", "ONE", "ONC", "MOM", "MOC", "MUL", "MAT", "LTR", "CMM", "WOE", "WHO", "RVR", "WOT", "WOC", "SPG", "LCI", "LCC", "REX", "PIP", "MKM", "MKC", "CLU", "OTJ", - "OTC", "OTP", "BIG", "MH3", "M3C", "ACR", "BLB" + "OTC", "OTP", "BIG", "MH3", "M3C", "ACR", "BLB", "BLC", "DSK", "DSC", + "MB2", "FDN", "INR", "J25", "DRC", "DFT", "TDC", "TDM", "FCA", "FIC", + "FIN", "SIS", "SIR", "SLD", "AKR", "MD1", "ANB", "LTC", "BRR", "HA1", + "HA2", "HA3", "HA4", "HA5", "ZNC", "EOE", "EOC", "SPE", "TLA", "EOS" // "HHO", "ANA" -- do not exist on gatherer }; private static final String[] symbolsOnlyMyth = { - "DRB", "V09", "V10", "V12", "V13", "V14", "V15", "V16", "V17", "EXP", "MED" + "DRB", "V09", "V10", "V12", "V13", "V14", "V15", "V16", "V17", "EXP", "MED", "ZNE" // "HTR16" does not exist }; + private static final String[] symbolsOnlySpecial = { "MPS", "MP2" }; @@ -130,6 +139,7 @@ public class GathererSets implements Iterable { codeReplacements.put("APC", "AP"); codeReplacements.put("ARN", "AN"); codeReplacements.put("ATQ", "AQ"); + codeReplacements.put("BTD", "BD"); codeReplacements.put("CMA", "CM1"); codeReplacements.put("CHR", "CH"); codeReplacements.put("DVD", "DD3_DVD"); @@ -165,14 +175,16 @@ public class GathererSets implements Iterable { codeReplacements.put("UGIN", "FRF_UGIN"); codeReplacements.put("UGL", "UG"); codeReplacements.put("ULG", "GU"); + codeReplacements.put("UNF", "UNFS"); codeReplacements.put("USG", "UZ"); codeReplacements.put("VIS", "VI"); codeReplacements.put("WTH", "WL"); + codeReplacements.put("YMID", "Y22"); + codeReplacements.put("YNEO", "Y22NEO"); } public GathererSets() { - - outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS); + outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS_RARITY_DEFAULT_PATH); if (!outDir.exists()) { outDir.mkdirs(); @@ -287,9 +299,9 @@ public class GathererSets implements Iterable { canDownload = false; if (exp != null && exp.getReleaseDate().before(compareDate)) { canDownload = true; - jobs.add(generateDownloadJob(symbol, "C", "C")); - jobs.add(generateDownloadJob(symbol, "U", "U")); - jobs.add(generateDownloadJob(symbol, "R", "R")); + jobs.add(generateDownloadJob(symbol, "C", "common")); + jobs.add(generateDownloadJob(symbol, "U", "uncommon")); + jobs.add(generateDownloadJob(symbol, "R", "rare")); } CheckSearchResult(symbol, exp, canDownload, true, true, true, false); } @@ -299,10 +311,10 @@ public class GathererSets implements Iterable { canDownload = false; if (exp != null && exp.getReleaseDate().before(compareDate)) { canDownload = true; - jobs.add(generateDownloadJob(symbol, "C", "C")); - jobs.add(generateDownloadJob(symbol, "U", "U")); - jobs.add(generateDownloadJob(symbol, "R", "R")); - jobs.add(generateDownloadJob(symbol, "M", "M")); + jobs.add(generateDownloadJob(symbol, "C", "common")); + jobs.add(generateDownloadJob(symbol, "U", "uncommon")); + jobs.add(generateDownloadJob(symbol, "R", "rare")); + jobs.add(generateDownloadJob(symbol, "M", "mythic")); } CheckSearchResult(symbol, exp, canDownload, true, true, true, true); } @@ -312,7 +324,7 @@ public class GathererSets implements Iterable { canDownload = false; if (exp != null && exp.getReleaseDate().before(compareDate)) { canDownload = true; - jobs.add(generateDownloadJob(symbol, "M", "M")); + jobs.add(generateDownloadJob(symbol, "M", "mythic")); } CheckSearchResult(symbol, exp, canDownload, false, false, false, true); } @@ -322,7 +334,7 @@ public class GathererSets implements Iterable { canDownload = false; if (exp != null && exp.getReleaseDate().before(compareDate)) { canDownload = true; - jobs.add(generateDownloadJob(symbol, "M", "S")); + jobs.add(generateDownloadJob(symbol, "M", "special")); } CheckSearchResult(symbol, exp, canDownload, false, false, false, true); } @@ -334,11 +346,22 @@ public class GathererSets implements Iterable { } private DownloadJob generateDownloadJob(String set, String rarity, String urlRarity) { - File dst = new File(outDir, set + '-' + rarity + ".jpg"); + File dst = new File(outDir, set + '-' + rarity + ".png"); if (codeReplacements.containsKey(set)) { set = codeReplacements.get(set); } - String url = "https://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + urlRarity; + // example: + // - small: https://gatherer-static.wizards.com/set_symbols/FIN/small-common-FIN.png + // - big: https://gatherer-static.wizards.com/set_symbols/FIN/large-rare-FIN.png + + String useSet = set.toUpperCase(Locale.ENGLISH); + String useSize = "large"; // allow: small, large + String url = String.format("https://gatherer-static.wizards.com/set_symbols/%s/%s-%s-%s.png", + useSet, + useSize, + urlRarity, + useSet + ); return new DownloadJob(set + '-' + rarity, url, toFile(dst), false); } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java index b3704e3820d..1bd36368647 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java @@ -75,7 +75,7 @@ public class GathererSymbols implements Iterable { continue; } String symbol = sym.replaceAll("/", ""); - File dst = new File(dir, symbol + ".gif"); + File dst = new File(dir, symbol + ".png"); // workaround for miss icons on Gatherer (no cards with it, so no icons) // TODO: comment and try download without workaround, keep fix for symbols with "Resource not found" error diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java index ab56977114d..972724d952c 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java @@ -478,8 +478,8 @@ public enum GrabbagImageSource implements CardImageSource { // Emblems singleLinks.put("SWS/Emblem Obi-Wan Kenobi", "Qyc10aT.png"); - singleLinks.put("SWS/Aurra Sing", "BLWbVJC.png"); - singleLinks.put("SWS/Yoda", "zH0sYxg.png"); + singleLinks.put("SWS/Emblem Aurra Sing", "BLWbVJC.png"); + singleLinks.put("SWS/Emblem Yoda", "zH0sYxg.png"); singleLinks.put("SWS/Emblem Luke Skywalker", "kHELZDJ.jpeg"); // Tokens 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 5a23faff7d7..b132bfb77e2 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 @@ -590,6 +590,7 @@ public class ScryfallImageSupportCards { add("FCA"); // Final Fantasy: Through the Ages add("EOE"); // Edge of Eternities add("EOC"); // Edge of Eternities Commander + add("EOS"); // Edge of Eternities: Stellar Sights add("SPE"); // Marvel's Spider-Man Eternal add("TLA"); // Avatar: The Last Airbender 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 49894969311..1d07e69f7af 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 @@ -832,12 +832,18 @@ public class ScryfallImageSupportTokens { put("SLD/Food/3", "https://api.scryfall.com/cards/sld/2011?format=image"); put("SLD/Food/4", "https://api.scryfall.com/cards/sld/2012?format=image"); put("SLD/Food/5", "https://api.scryfall.com/cards/sld/2013?format=image"); + put("SLD/Food/6", "https://api.scryfall.com/cards/sld/2064?format=image"); put("SLD/Goblin", "https://api.scryfall.com/cards/sld/219?format=image"); 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", "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"); + put("SLD/Shapeshifter/1", "https://api.scryfall.com/cards/sld/1906?format=image"); + put("SLD/Shapeshifter/2", "https://api.scryfall.com/cards/sld/1907?format=image"); + put("SLD/Shapeshifter/3", "https://api.scryfall.com/cards/sld/1908?format=image"); + put("SLD/Shapeshifter/4", "https://api.scryfall.com/cards/sld/1909?format=image"); put("SLD/Shrine", "https://api.scryfall.com/cards/sld/1835?format=image"); put("SLD/Spirit/1", "https://api.scryfall.com/cards/sld/1341?format=image"); put("SLD/Spirit/2", "https://api.scryfall.com/cards/sld/1852?format=image"); @@ -846,6 +852,8 @@ public class ScryfallImageSupportTokens { put("SLD/Treasure/2", "https://api.scryfall.com/cards/sld/1736/en?format=image"); put("SLD/Treasure/3", "https://api.scryfall.com/cards/sld/1507/en?format=image"); put("SLD/Treasure/4", "https://api.scryfall.com/cards/sld/153/en?format=image"); + put("SLD/Treasure/5", "https://api.scryfall.com/cards/sld/2065/en?format=image"); + put("SLD/Treasure/6", "https://api.scryfall.com/cards/sld/2094/en?format=image"); put("SLD/Walker/1", "https://api.scryfall.com/cards/sld/148/en?format=image"); put("SLD/Walker/2", "https://api.scryfall.com/cards/sld/149/en?format=image"); put("SLD/Walker/3", "https://api.scryfall.com/cards/sld/150/en?format=image"); @@ -2172,23 +2180,16 @@ public class ScryfallImageSupportTokens { put("WOE/Young Hero", "https://api.scryfall.com/cards/twoe/16/en?format=image"); // WOC - put("WOC/Cat/1", "https://api.scryfall.com/cards/twoc/6/en?format=image"); - put("WOC/Cat/2", "https://api.scryfall.com/cards/twoc/5/en?format=image"); - put("WOC/Elephant", "https://api.scryfall.com/cards/twoc/13/en?format=image"); put("WOC/Faerie", "https://api.scryfall.com/cards/twoc/10/en?format=image"); put("WOC/Faerie Rogue/1", "https://api.scryfall.com/cards/twoc/11/en?format=image"); put("WOC/Faerie Rogue/2", "https://api.scryfall.com/cards/twoc/16/en?format=image"); - put("WOC/Human Monk", "https://api.scryfall.com/cards/twoc/14/en?format=image"); put("WOC/Human Soldier", "https://api.scryfall.com/cards/twoc/7/en?format=image"); put("WOC/Monster", "https://api.scryfall.com/cards/twoc/1/en?format=image"); - put("WOC/Ox", "https://api.scryfall.com/cards/twoc/8/en?format=image"); put("WOC/Pegasus", "https://api.scryfall.com/cards/twoc/9/en?format=image"); put("WOC/Pirate", "https://api.scryfall.com/cards/twoc/12/en?format=image"); put("WOC/Royal", "https://api.scryfall.com/cards/twoc/2/en?format=image"); put("WOC/Saproling", "https://api.scryfall.com/cards/twoc/15/en?format=image"); - put("WOC/Sorcerer", "https://api.scryfall.com/cards/twoc/3/en?format=image"); - put("WOC/Spirit", "https://api.scryfall.com/cards/twoc/17/en?format=image"); - put("WOC/Virtuous", "https://api.scryfall.com/cards/twoc/3/en?format=image"); + put("WOC/Virtuous", "https://api.scryfall.com/cards/twoc/2/en?format=image"); // WHO put("WHO/Alien", "https://api.scryfall.com/cards/twho/2?format=image"); @@ -2553,7 +2554,8 @@ public class ScryfallImageSupportTokens { put("DSK/Primo, the Indivisible", "https://api.scryfall.com/cards/tdsk/14?format=image"); put("DSK/Shard", "https://api.scryfall.com/cards/tdsk/2?format=image"); put("DSK/Spider", "https://api.scryfall.com/cards/tdsk/12?format=image"); - put("DSK/Spirit", "https://api.scryfall.com/cards/tdsk/8?format=image"); + put("DSK/Spirit/1", "https://api.scryfall.com/cards/tdsk/6?format=image"); + put("DSK/Spirit/2", "https://api.scryfall.com/cards/tdsk/8?format=image"); put("DSK/Treasure", "https://api.scryfall.com/cards/tdsk/15?format=image"); // DSC @@ -2783,6 +2785,36 @@ public class ScryfallImageSupportTokens { put("FIC/The Blackjack", "https://api.scryfall.com/cards/tfic/8/en?format=image"); put("FIC/Clue", "https://api.scryfall.com/cards/tfic/9/en?format=image"); + // EOE + put("EOE/Drone", "https://api.scryfall.com/cards/teoe/3?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"); + put("EOE/Lander/3", "https://api.scryfall.com/cards/teoe/6?format=image"); + put("EOE/Lander/4", "https://api.scryfall.com/cards/teoe/7?format=image"); + put("EOE/Lander/5", "https://api.scryfall.com/cards/teoe/8?format=image"); + put("EOE/Munitions", "https://api.scryfall.com/cards/teoe/9?format=image"); + put("EOE/Robot", "https://api.scryfall.com/cards/teoe/10?format=image"); + put("EOE/Sliver", "https://api.scryfall.com/cards/teoe/1?format=image"); + + // EOC + put("EOC/Beast/1", "https://api.scryfall.com/cards/teoc/5/en?format=image"); + put("EOC/Beast/2", "https://api.scryfall.com/cards/teoc/6/en?format=image"); + put("EOC/Bird", "https://api.scryfall.com/cards/teoc/3/en?format=image"); + put("EOC/Clue", "https://api.scryfall.com/cards/teoc/10/en?format=image"); + put("EOC/Elemental/1", "https://api.scryfall.com/cards/teoc/7/en?format=image"); + put("EOC/Elemental/2", "https://api.scryfall.com/cards/teoc/8/en?format=image"); + put("EOC/Gnome", "https://api.scryfall.com/cards/teoc/11/en?format=image"); + put("EOC/Golem/1", "https://api.scryfall.com/cards/teoc/12/en?format=image"); + put("EOC/Golem/2", "https://api.scryfall.com/cards/teoc/13/en?format=image"); + put("EOC/Golem/3", "https://api.scryfall.com/cards/teoc/14/en?format=image"); + put("EOC/Incubator", "https://api.scryfall.com/cards/teoc/15/en?format=image&face=front"); + put("EOC/Insect", "https://api.scryfall.com/cards/teoc/4/en?format=image"); + put("EOC/Pest", "https://api.scryfall.com/cards/teoc/9/en?format=image"); + put("EOC/Phyrexian", "https://api.scryfall.com/cards/teoc/15/en?format=image&face=back"); + put("EOC/Shapeshifter", "https://api.scryfall.com/cards/teoc/2/en?format=image"); + put("EOC/Thopter", "https://api.scryfall.com/cards/teoc/16/en?format=image"); + // JVC put("JVC/Elemental Shaman", "https://api.scryfall.com/cards/tjvc/4?format=image"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index a8fb8ddc6c1..704395e0378 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -157,7 +157,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements @Override public boolean isNeedCancel() { - return this.needCancel || (this.errorCount > MAX_ERRORS_COUNT_BEFORE_CANCEL) || Thread.interrupted(); + return this.needCancel || (this.errorCount > MAX_ERRORS_COUNT_BEFORE_CANCEL) || Thread.currentThread().isInterrupted(); } private void setNeedCancel(boolean needCancel) { diff --git a/Mage.Common/pom.xml b/Mage.Common/pom.xml index 36fe0dc82ca..d2935eb53cc 100644 --- a/Mage.Common/pom.xml +++ b/Mage.Common/pom.xml @@ -66,7 +66,6 @@ org.apache.commons commons-lang3 - test diff --git a/Mage.Common/src/main/java/mage/cards/action/impl/EmptyCallback.java b/Mage.Common/src/main/java/mage/cards/action/impl/EmptyCallback.java deleted file mode 100644 index 3da223590e6..00000000000 --- a/Mage.Common/src/main/java/mage/cards/action/impl/EmptyCallback.java +++ /dev/null @@ -1,63 +0,0 @@ -package mage.cards.action.impl; - -import mage.cards.action.ActionCallback; -import mage.cards.action.TransferData; - -import java.awt.*; -import java.awt.event.MouseEvent; -import java.awt.event.MouseWheelEvent; - -/** - * Callback that does nothing on any action - * - * @author nantuko84 - */ -public class EmptyCallback implements ActionCallback { - - @Override - public void mouseMoved(MouseEvent e, TransferData data) { - } - - @Override - public void mouseDragged(MouseEvent e, TransferData data) { - - } - - @Override - public void mouseEntered(MouseEvent e, TransferData data) { - } - - @Override - public void mouseExited(MouseEvent e, TransferData data) { - } - - @Override - public void mouseWheelMoved(int mouseWheelRotation, TransferData data) { - } - - @Override - public void hideOpenComponents() { - } - - @Override - public void mouseClicked(MouseEvent e, TransferData data, boolean doubleClick) { - } - - @Override - public void mousePressed(MouseEvent e, TransferData data) { - } - - @Override - public void mouseReleased(MouseEvent e, TransferData data) { - } - - @Override - public void popupMenuCard(MouseEvent e, TransferData data) { - - } - - @Override - public void popupMenuPanel(MouseEvent e, Component sourceComponent) { - - } -} diff --git a/Mage.Common/src/main/java/mage/filters/MageBufferedImageOp.java b/Mage.Common/src/main/java/mage/filters/MageBufferedImageOp.java deleted file mode 100644 index f6adc23d756..00000000000 --- a/Mage.Common/src/main/java/mage/filters/MageBufferedImageOp.java +++ /dev/null @@ -1,72 +0,0 @@ -package mage.filters; - -import java.awt.*; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.awt.image.BufferedImageOp; -import java.awt.image.ColorModel; - -/** - * Mage abstract class that implements single-input/single-output - * operations performed on {@link java.awt.image.BufferedImage}. - * - * @author nantuko - */ -public abstract class MageBufferedImageOp implements BufferedImageOp { - - /** - * Creates compatible image for @param src image. - */ - @Override - public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dest) { - if (dest == null) { - dest = src.getColorModel(); - } - return new BufferedImage(dest, dest.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dest.isAlphaPremultiplied(), null); - } - - @Override - public RenderingHints getRenderingHints() { - return null; - } - - @Override - public Rectangle2D getBounds2D(BufferedImage src) { - return new Rectangle(0, 0, src.getWidth(), src.getHeight()); - } - - @Override - public Point2D getPoint2D(Point2D srcPt, Point2D destPt) { - if (destPt == null) { - destPt = new Point2D.Double(); - } - destPt.setLocation(srcPt.getX(), srcPt.getY()); - return destPt; - } - - /** - * Gets ARGB pixels from image. Solves the performance - * issue of BufferedImage.getRGB method. - */ - public int[] getRGB(BufferedImage image, int x, int y, int width, int height, int[] pixels) { - int type = image.getType(); - if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) { - return (int[]) image.getRaster().getDataElements(x, y, width, height, pixels); - } - return image.getRGB(x, y, width, height, pixels, 0, width); - } - - /** - * Sets ARGB pixels in image. Solves the performance - * issue of BufferedImage.setRGB method. - */ - public void setRGB(BufferedImage image, int x, int y, int width, int height, int[] pixels) { - int type = image.getType(); - if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) { - image.getRaster().setDataElements(x, y, width, height, pixels); - } else { - image.setRGB(x, y, width, height, pixels, 0, width); - } - } -} diff --git a/Mage.Common/src/main/java/mage/remote/SessionImpl.java b/Mage.Common/src/main/java/mage/remote/SessionImpl.java index aaf1959dd5a..8d2dad50d4e 100644 --- a/Mage.Common/src/main/java/mage/remote/SessionImpl.java +++ b/Mage.Common/src/main/java/mage/remote/SessionImpl.java @@ -723,7 +723,7 @@ public class SessionImpl implements Session { public Optional getRoomChatId(UUID roomId) { try { if (isConnected()) { - return Optional.of(server.chatFindByRoom(roomId)); + return Optional.ofNullable(server.chatFindByRoom(roomId)); } } catch (MageException ex) { handleMageException(ex); @@ -735,7 +735,7 @@ public class SessionImpl implements Session { public Optional getTableChatId(UUID tableId) { try { if (isConnected()) { - return Optional.of(server.chatFindByTable(tableId)); + return Optional.ofNullable(server.chatFindByTable(tableId)); } } catch (MageException ex) { handleMageException(ex); @@ -747,7 +747,7 @@ public class SessionImpl implements Session { public Optional getGameChatId(UUID gameId) { try { if (isConnected()) { - return Optional.of(server.chatFindByGame(gameId)); + return Optional.ofNullable(server.chatFindByGame(gameId)); } } catch (MageException ex) { handleMageException(ex); @@ -761,7 +761,7 @@ public class SessionImpl implements Session { public Optional getTable(UUID roomId, UUID tableId) { try { if (isConnected()) { - return Optional.of(server.roomGetTableById(roomId, tableId)); + return Optional.ofNullable(server.roomGetTableById(roomId, tableId)); } } catch (MageException ex) { handleMageException(ex); @@ -905,7 +905,7 @@ public class SessionImpl implements Session { public Optional getTournamentChatId(UUID tournamentId) { try { if (isConnected()) { - return Optional.of(server.chatFindByTournament(tournamentId)); + return Optional.ofNullable(server.chatFindByTournament(tournamentId)); } } catch (MageException ex) { handleMageException(ex); diff --git a/Mage.Common/src/main/java/mage/utils/ActionWithUUIDResult.java b/Mage.Common/src/main/java/mage/utils/ActionWithUUIDResult.java deleted file mode 100644 index 1133a0bde24..00000000000 --- a/Mage.Common/src/main/java/mage/utils/ActionWithUUIDResult.java +++ /dev/null @@ -1,11 +0,0 @@ -package mage.utils; - -import mage.view.TableView; - -/** - * Used to write less code for ActionWithResult anonymous classes with UUID return type. - * - * @author noxx - */ -public abstract class ActionWithUUIDResult extends ActionWithNullNegativeResult { -} diff --git a/Mage.Common/src/main/java/mage/utils/testers/AmountTestableResult.java b/Mage.Common/src/main/java/mage/utils/testers/AmountTestableResult.java index 96b3f1892af..5b92ba19745 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/AmountTestableResult.java +++ b/Mage.Common/src/main/java/mage/utils/testers/AmountTestableResult.java @@ -9,7 +9,11 @@ import java.util.List; */ public class AmountTestableResult extends BaseTestableResult { - int amount = 0; + Integer amount = null; + + boolean aiAssertEnabled = false; + int aiAssertMinAmount = 0; + int aiAssertMaxAmount = 0; public void onFinish(String resDebugSource, boolean status, List info, int amount) { this.onFinish(resDebugSource, status, info); @@ -18,12 +22,38 @@ public class AmountTestableResult extends BaseTestableResult { @Override public String getResAssert() { - return null; // TODO: implement + if (!this.aiAssertEnabled) { + return null; + } + + // not finished + if (this.amount == null) { + return null; + } + + if (!this.getResStatus()) { + return String.format("Wrong status: need %s, but get %s", + true, // res must be true all the time + this.getResStatus() + ); + } + + // wrong amount + if (this.amount < this.aiAssertMinAmount || this.amount > this.aiAssertMaxAmount) { + return String.format("Wrong amount: need [%d, %d], but get %d", + this.aiAssertMinAmount, + this.aiAssertMaxAmount, + this.amount + ); + } + + // all fine + return ""; } @Override public void onClear() { super.onClear(); - this.amount = 0; + this.amount = null; } } diff --git a/Mage.Common/src/main/java/mage/utils/testers/AnnounceXTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/AnnounceXTestableDialog.java index 558deedab8d..5d3b6d5fd41 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/AnnounceXTestableDialog.java +++ b/Mage.Common/src/main/java/mage/utils/testers/AnnounceXTestableDialog.java @@ -34,6 +34,15 @@ class AnnounceXTestableDialog extends BaseTestableDialog { this.max = max; } + private AnnounceXTestableDialog aiMustChoose(int minAmount, int maxAmount) { + // require min/max cause AI logic uses random choices + AmountTestableResult res = ((AmountTestableResult) this.getResult()); + res.aiAssertEnabled = true; + res.aiAssertMinAmount = minAmount; + res.aiAssertMaxAmount = maxAmount; + return this; + } + @Override public void showDialog(Player player, Ability source, Game game, Player opponent) { Player choosingPlayer = this.isYou ? player : opponent; @@ -51,17 +60,17 @@ class AnnounceXTestableDialog extends BaseTestableDialog { List isManas = Arrays.asList(false, true); for (boolean isYou : isYous) { for (boolean isMana : isManas) { - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 0)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 1)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 3)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 50)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 500)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 1)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 3)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 50)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 3)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 10)); - runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 10, 10)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 0).aiMustChoose(0, 0)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 1).aiMustChoose(0, 1)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 3).aiMustChoose(0, 3)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 50).aiMustChoose(0, 50)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 500).aiMustChoose(0, 500)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 1).aiMustChoose(1, 1)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 3).aiMustChoose(1, 3)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 50).aiMustChoose(1, 50)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 3).aiMustChoose(3, 3)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 10).aiMustChoose(3, 10)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 10, 10).aiMustChoose(10, 10)); } } } diff --git a/Mage.Common/src/main/java/mage/utils/testers/GetAmountTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/GetAmountTestableDialog.java index 569284c239d..2007af09a56 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/GetAmountTestableDialog.java +++ b/Mage.Common/src/main/java/mage/utils/testers/GetAmountTestableDialog.java @@ -36,6 +36,15 @@ class GetAmountTestableDialog extends BaseTestableDialog { this.max = max; } + private GetAmountTestableDialog aiMustChoose(int minAmount, int maxAmount) { + // require min/max cause AI logic uses random choices + AmountTestableResult res = ((AmountTestableResult) this.getResult()); + res.aiAssertEnabled = true; + res.aiAssertMinAmount = minAmount; + res.aiAssertMaxAmount = maxAmount; + return this; + } + @Override public void showDialog(Player player, Ability source, Game game, Player opponent) { Player choosingPlayer = this.isYou ? player : opponent; @@ -51,17 +60,20 @@ class GetAmountTestableDialog extends BaseTestableDialog { static public void register(TestableDialogsRunner runner) { List isYous = Arrays.asList(false, true); for (boolean isYou : isYous) { - runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 0)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 1)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 3)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 50)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 500)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 1)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 3)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 50)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 3)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 10)); - runner.registerDialog(new GetAmountTestableDialog(isYou, 10, 10)); + // TODO: add good and bad effects: + // - on good: choose random big value + // - on bad: choose lower value + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 0).aiMustChoose(0, 0)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 1).aiMustChoose(0, 1)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 3).aiMustChoose(0, 3)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 50).aiMustChoose(0, 50)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 500).aiMustChoose(0, 500)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 1).aiMustChoose(1, 1)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 3).aiMustChoose(1, 3)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 50).aiMustChoose(1, 50)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 3).aiMustChoose(3, 3)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 10).aiMustChoose(3, 10)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 10, 10).aiMustChoose(10, 10)); } } } diff --git a/Mage.Common/src/main/java/mage/utils/testers/GetMultiAmountTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/GetMultiAmountTestableDialog.java index 869791a6e8a..8f5f89be557 100644 --- a/Mage.Common/src/main/java/mage/utils/testers/GetMultiAmountTestableDialog.java +++ b/Mage.Common/src/main/java/mage/utils/testers/GetMultiAmountTestableDialog.java @@ -53,7 +53,9 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog { } private GetMultiAmountTestableDialog aiMustChoose(Integer... needValues) { - // TODO: AI use default distribution (min possible values), improve someday + // TODO: AI use default distribution: + // - bad effect: min possible values + // - good effect: max possible and distributed values MultiAmountTestableResult res = ((MultiAmountTestableResult) this.getResult()); res.aiAssertEnabled = true; res.aiAssertValues = Arrays.stream(needValues).collect(Collectors.toList()); diff --git a/Mage.Common/src/main/java/mage/view/ChatMessage.java b/Mage.Common/src/main/java/mage/view/ChatMessage.java index adf541553cb..02a5b6db31c 100644 --- a/Mage.Common/src/main/java/mage/view/ChatMessage.java +++ b/Mage.Common/src/main/java/mage/view/ChatMessage.java @@ -25,7 +25,12 @@ public class ChatMessage implements Serializable { } public enum MessageType { - USER_INFO, STATUS, GAME, TALK, WHISPER_FROM, WHISPER_TO + USER_INFO, // system messages + STATUS, // system messages + GAME, // game logs + TALK, // public chat + WHISPER_FROM, // private chat income + WHISPER_TO // private chat outcome } public enum SoundToPlay { diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java index 99168e5ea3b..50468b91552 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AbstractCommander.java @@ -5,11 +5,13 @@ import mage.abilities.Ability; import mage.abilities.common.CanBeYourCommanderAbility; import mage.abilities.common.CommanderChooseColorAbility; import mage.abilities.keyword.CompanionAbility; +import mage.abilities.keyword.StationLevelAbility; import mage.cards.Card; import mage.cards.decks.Constructed; import mage.cards.decks.Deck; import mage.cards.decks.DeckValidatorErrorType; import mage.constants.CardType; +import mage.constants.SubType; import mage.filter.FilterMana; import mage.util.CardUtil; import mage.util.ManaUtil; @@ -51,9 +53,19 @@ public abstract class AbstractCommander extends Constructed { protected abstract boolean checkBanned(Map counts); protected boolean checkCommander(Card commander, Set commanders) { - return commander.hasCardTypeForDeckbuilding(CardType.CREATURE) && commander.isLegendary() - || commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance()) - || (validators.stream().anyMatch(validator -> validator.specialCheck(commander)) && commanders.size() == 2); + if (commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance())) { + return true; + } + if (commander.isLegendary() + && (commander.hasCardTypeForDeckbuilding(CardType.CREATURE) + || commander.hasSubTypeForDeckbuilding(SubType.VEHICLE) + || commander.hasSubTypeForDeckbuilding(SubType.SPACECRAFT) + && CardUtil + .castStream(commander.getAbilities(), StationLevelAbility.class) + .anyMatch(StationLevelAbility::hasPT))) { + return true; + } + return commanders.size() == 2 && validators.stream().anyMatch(validator -> validator.specialCheck(commander)); } protected boolean checkPartners(Set commanders) { diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/CanadianHighlander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/CanadianHighlander.java index e07754bb99c..2c77d86534e 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/CanadianHighlander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/CanadianHighlander.java @@ -19,6 +19,7 @@ public class CanadianHighlander extends Constructed { static { pointMap.put("Ancestral Recall", 8); pointMap.put("Ancient Tomb", 1); + pointMap.put("Balance", 1); pointMap.put("Black Lotus", 7); pointMap.put("Demonic Tutor", 3); pointMap.put("Dig Through Time", 1); @@ -30,19 +31,22 @@ public class CanadianHighlander extends Constructed { pointMap.put("Mana Crypt", 5); pointMap.put("Mana Drain", 1); pointMap.put("Mana Vault", 1); + pointMap.put("Merchant Scroll", 1); + pointMap.put("Minsc & Boo, Timeless Heroes", 1); pointMap.put("Mox Emerald", 3); pointMap.put("Mox Jet", 3); pointMap.put("Mox Pearl", 3); pointMap.put("Mox Ruby", 3); pointMap.put("Mox Sapphire", 3); pointMap.put("Mystical Tutor", 1); + pointMap.put("Nadu, Winged Wisdom", 1); pointMap.put("Natural Order", 1); - pointMap.put("Sol Ring", 4); - pointMap.put("Spellseeker", 1); - pointMap.put("Strip Mine", 2); - pointMap.put("Survival of the Fittest", 1); + pointMap.put("Psychic Frog", 1); + pointMap.put("Reanimate", 1); + pointMap.put("Sol Ring", 3); + pointMap.put("Strip Mine", 1); pointMap.put("Tainted Pact", 1); - pointMap.put("Thassa's Oracle", 7); + pointMap.put("Thassa's Oracle", 6); pointMap.put("Time Vault", 7); pointMap.put("Time Walk", 6); pointMap.put("Tinker", 3); @@ -50,8 +54,11 @@ public class CanadianHighlander extends Constructed { pointMap.put("Treasure Cruise", 1); pointMap.put("True-Name Nemesis", 1); pointMap.put("Underworld Breach", 3); + pointMap.put("Urza's Saga", 1); pointMap.put("Vampiric Tutor", 2); + pointMap.put("White Plume Adventurer", 1); pointMap.put("Wishclaw Talisman", 1); + pointMap.put("Wrenn and Six", 1); } public CanadianHighlander() { diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformUnlimited.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformUnlimited.java new file mode 100644 index 00000000000..8dfcfc03f78 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformUnlimited.java @@ -0,0 +1,34 @@ +package mage.deck; + +import mage.cards.decks.Deck; +import mage.cards.decks.DeckValidator; +import mage.cards.decks.DeckValidatorErrorType; + +/** + * @author resech + */ +public class FreeformUnlimited extends DeckValidator { + + public FreeformUnlimited() { + this("Constructed - Freeform Unlimited", null); + } + + public FreeformUnlimited(String name, String shortName) { + super(name, shortName); + } + + @Override + public int getDeckMinSize() { + return 0; + } + + @Override + public int getSideboardMinSize() { + return 0; + } + + @Override + public boolean validate(Deck deck) { + return true; + } +} diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Historic.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Historic.java index 26a3595c5c6..e6dd75163cb 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Historic.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Historic.java @@ -32,7 +32,6 @@ public class Historic extends Constructed { banned.add("Agent of Treachery"); banned.add("Brainstorm"); banned.add("Channel"); - banned.add("Counterspell"); banned.add("Dark Ritual"); banned.add("Demonic Tutor"); banned.add("Fires of Invention"); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java index 9f591a0db29..0796aaf056d 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java @@ -18,6 +18,14 @@ public class Standard extends Constructed { super("Constructed - Standard"); setCodes.addAll(makeLegalSets()); + + banned.add("Abuelo's Awakening"); + banned.add("Cori-Steel Cutter"); + banned.add("Heartfire Hero"); + banned.add("Hopeless Nightmare"); + banned.add("Monstrous Rage"); + banned.add("This Town Ain't Big Enough"); + banned.add("Up the Beanstalk"); } static List makeLegalSets() { 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 b606a20d75e..f25152f9a90 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 @@ -114,6 +114,13 @@ public class ComputerPlayer6 extends ComputerPlayer { this.actionCache = player.actionCache; } + /** + * Change simulation timeout - used for AI stability tests only + */ + public void setMaxThinkTimeSecs(int maxThinkTimeSecs) { + this.maxThinkTimeSecs = maxThinkTimeSecs; + } + @Override public ComputerPlayer6 copy() { return new ComputerPlayer6(this); @@ -206,10 +213,8 @@ public class ComputerPlayer6 extends ComputerPlayer { logger.trace("Add Action [" + depth + "] " + node.getAbilities().toString() + " a: " + alpha + " b: " + beta); } Game game = node.getGame(); - if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS - && Thread.interrupted()) { - Thread.currentThread().interrupt(); - logger.debug("interrupted"); + if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) { + logger.debug("AI game sim interrupted by timeout"); return GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); } // Condition to stop deeper simulation @@ -431,6 +436,8 @@ public class ComputerPlayer6 extends ComputerPlayer { * @return */ protected Integer addActionsTimed() { + // TODO: all actions added and calculated one by one, + // multithreading do not supported here // run new game simulation in parallel thread FutureTask task = new FutureTask<>(() -> addActions(root, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE)); threadPoolSimulations.execute(task); @@ -446,15 +453,23 @@ public class ComputerPlayer6 extends ComputerPlayer { } } catch (TimeoutException | InterruptedException e) { // AI thinks too long - logger.info("ai simulating - timed out"); + // how-to fix: look at stack info - it can contain bad ability with infinite choose dialog + logger.warn(""); + logger.warn("AI player thinks too long (report it to github):"); + logger.warn(" - player: " + getName()); + logger.warn(" - battlefield size: " + root.game.getBattlefield().getAllPermanents().size()); + logger.warn(" - stack: " + root.game.getStack()); + logger.warn(" - game: " + root.game); + printFreezeNode(root); + logger.warn(""); task.cancel(true); } catch (ExecutionException e) { // game error - logger.error("AI simulation catch game error: " + e, e); + logger.error("AI player catch game error in simulation - " + getName() + " - " + root.game + ": " + e, e); task.cancel(true); // real games: must catch and log // unit tests: must raise again for fast fail - if (this.isTestsMode()) { + if (this.isTestMode() && this.isFastFailInTestMode()) { throw new IllegalStateException("One of the simulated games raise the error: " + e, e); } } catch (Throwable e) { @@ -466,11 +481,33 @@ public class ComputerPlayer6 extends ComputerPlayer { return 0; } + private void printFreezeNode(SimulationNode2 root) { + // print simple tree - there are possible multiple child nodes, but ignore it - same for abilities + List chain = new ArrayList<>(); + SimulationNode2 node = root; + while (node != null) { + if (node.abilities != null && !node.abilities.isEmpty()) { + Ability ability = node.abilities.get(0); + String sourceInfo = CardUtil.getSourceIdName(node.game, ability); + chain.add(String.format("%s: %s", + (sourceInfo.isEmpty() ? "unknown" : sourceInfo), + ability + )); + } + node = node.children == null || node.children.isEmpty() ? null : node.children.get(0); + } + 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 + } + chain.forEach(s -> { + logger.warn(" - " + s); + }); + } + protected int simulatePriority(SimulationNode2 node, Game game, int depth, int alpha, int beta) { - if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS - && Thread.interrupted()) { - Thread.currentThread().interrupt(); - logger.info("interrupted"); + if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) { + logger.debug("AI game sim interrupted by timeout"); return GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); } node.setGameValue(game.getState().getValue(true).hashCode()); @@ -498,9 +535,7 @@ public class ComputerPlayer6 extends ComputerPlayer { int bestValSubNodes = Integer.MIN_VALUE; for (Ability action : allActions) { actionNumber++; - if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS - && Thread.interrupted()) { - Thread.currentThread().interrupt(); + if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) { logger.info("Sim Prio [" + depth + "] -- interrupted"); break; } diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java index 31b9bee41ec..09af732d422 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java @@ -142,7 +142,8 @@ public class ComputerPlayer7 extends ComputerPlayer6 { } } } else { - logger.info('[' + game.getPlayer(playerId).getName() + "][pre] Action: skip"); + // nothing to choose or freeze/infinite game + logger.info("AI player can't find next action: " + getName()); } } else { logger.debug("Next Action exists!"); 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 b9c6f3ff1b8..20a8114e4f2 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 @@ -52,7 +52,7 @@ public class ComputerPlayer extends PlayerImpl { protected static final int PASSIVITY_PENALTY = 5; // Penalty value for doing nothing if some actions are available // debug only: set TRUE to debug simulation's code/games (on false sim thread will be stopped after few secs by timeout) - protected static final boolean COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = true; // DebugUtil.AI_ENABLE_DEBUG_MODE; + public static final boolean COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = false; // DebugUtil.AI_ENABLE_DEBUG_MODE; // AI agents uses game simulation thread for all calcs and it's high CPU consumption // More AI threads - more parallel AI games can be calculate @@ -64,7 +64,7 @@ public class ComputerPlayer extends PlayerImpl { // * use yours CPU cores for best performance // TODO: add server config to control max AI threads (with CPU cores by default) // TODO: rework AI implementation to use multiple sims calculation instead one by one - final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 1;//DebugUtil.AI_ENABLE_DEBUG_MODE ? 1 : 5; + final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 5;//DebugUtil.AI_ENABLE_DEBUG_MODE ? 1 : 5; // remember picked cards for better draft choices @@ -104,7 +104,7 @@ public class ComputerPlayer extends PlayerImpl { @Override public boolean chooseMulligan(Game game) { if (hand.size() < 6 - || isTestsMode() // ignore mulligan in tests + || isTestMode() // ignore mulligan in tests || game.getClass().getName().contains("Momir") // ignore mulligan in Momir games ) { return false; 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 49422bcbdbe..c3d9f25ea22 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 @@ -45,9 +45,8 @@ public class PossibleTargetsSelector { public void findNewTargets(Set fromTargetsList) { // collect new valid targets - List found = target.possibleTargets(abilityControllerId, source, game).stream() + List found = target.possibleTargets(abilityControllerId, source, game, fromTargetsList).stream() .filter(id -> !target.contains(id)) - .filter(id -> fromTargetsList == null || fromTargetsList.contains(id)) .filter(id -> target.canTarget(abilityControllerId, id, source, game)) .map(id -> { Player player = game.getPlayer(id); diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java index 8619ef2aeb8..4d65b8bc61c 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java @@ -196,7 +196,7 @@ public class ComputerPlayerMCTS extends ComputerPlayer { } catch (ExecutionException e) { // real games: must catch and log // unit tests: must raise again for fast fail - if (this.isTestsMode()) { + if (this.isTestMode() && this.isFastFailInTestMode()) { throw new IllegalStateException("One of the simulated games raise the error: " + e, e); } } diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index 883f5ce7aa8..938b9b26bc1 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -198,6 +198,7 @@ + diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index ff104474d0e..0e363c314a5 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -192,11 +192,6 @@ runtime - - org.apache.commons - commons-lang3 - 3.11 - javax.xml.bind jaxb-api @@ -213,12 +208,12 @@ org.apache.commons commons-compress - [1.19,) + 1.27.1 org.apache.shiro shiro-core - 1.8.0 + 1.13.0 javax.mail diff --git a/Mage.Server/release/config-example/config.xml b/Mage.Server/release/config-example/config.xml index 0af7bbff52f..0beb38be164 100644 --- a/Mage.Server/release/config-example/config.xml +++ b/Mage.Server/release/config-example/config.xml @@ -192,6 +192,7 @@ + diff --git a/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java index de1c4e24081..335dc081dc0 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java @@ -4,6 +4,8 @@ import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.constants.Constants; import mage.game.Game; +import mage.game.Table; +import mage.game.tournament.Tournament; import mage.server.game.GameController; import mage.server.managers.ChatManager; import mage.server.managers.ManagerFactory; @@ -40,11 +42,39 @@ public class ChatManagerImpl implements ChatManager { this.managerFactory = managerFactory; } + @Override - public UUID createChatSession(String info) { + public UUID createRoomChatSession(UUID roomId) { + return createChatSession("Room " + roomId) + .withRoom(roomId) + .getChatId(); + } + + @Override + public UUID createTourneyChatSession(Tournament tournament) { + return createChatSession("Tourney " + tournament.getId()) + .withTourney(tournament) + .getChatId(); + } + + @Override + public UUID createTableChatSession(Table table) { + return createChatSession("Table " + table.getId()) + .withTable(table) + .getChatId(); + } + + @Override + public UUID createGameChatSession(Game game) { + return createChatSession("Game " + game.getId()) + .withGame(game) + .getChatId(); + } + + private ChatSession createChatSession(String info) { ChatSession chatSession = new ChatSession(managerFactory, info); chatSessions.put(chatSession.getChatId(), chatSession); - return chatSession.getChatId(); + return chatSession; } @Override @@ -94,7 +124,7 @@ public class ChatManagerImpl implements ChatManager { ChatSession chatSession = chatSessions.get(chatId); Optional user = managerFactory.userManager().getUserByName(userName); if (chatSession != null) { - // special commads + // special commands if (message.startsWith("\\") || message.startsWith("/")) { if (user.isPresent()) { if (!performUserCommand(user.get(), message, chatId, false)) { diff --git a/Mage.Server/src/main/java/mage/server/ChatSession.java b/Mage.Server/src/main/java/mage/server/ChatSession.java index b58222afbfb..229ee885b6f 100644 --- a/Mage.Server/src/main/java/mage/server/ChatSession.java +++ b/Mage.Server/src/main/java/mage/server/ChatSession.java @@ -1,6 +1,9 @@ package mage.server; +import mage.collectors.DataCollectorServices; import mage.game.Game; +import mage.game.Table; +import mage.game.tournament.Tournament; import mage.interfaces.callback.ClientCallback; import mage.interfaces.callback.ClientCallbackMethod; import mage.server.managers.ManagerFactory; @@ -27,6 +30,13 @@ public class ChatSession { private final ManagerFactory managerFactory; private final ReadWriteLock lock = new ReentrantReadWriteLock(); // TODO: no needs due ConcurrentHashMap usage? + // only 1 field must be filled per chat type + // TODO: rework chat sessions to share logic (one server room/lobby + one table/subtable + one games/match) + private UUID roomId = null; + private UUID tourneyId = null; + private UUID tableId = null; + private UUID gameId = null; + private final ConcurrentMap users = new ConcurrentHashMap<>(); // active users private final Set usersHistory = new HashSet<>(); // all users that was here (need for system messages like connection problem) private final UUID chatId; @@ -40,6 +50,26 @@ public class ChatSession { this.info = info; } + public ChatSession withRoom(UUID roomId) { + this.roomId = roomId; + return this; + } + + public ChatSession withTourney(Tournament tournament) { + this.tourneyId = tournament.getId(); + return this; + } + + public ChatSession withTable(Table table) { + this.tableId = table.getId(); + return this; + } + + public ChatSession withGame(Game game) { + this.gameId = game.getId(); + return this; + } + public void join(UUID userId) { managerFactory.userManager().getUser(userId).ifPresent(user -> { if (!users.containsKey(userId)) { @@ -112,9 +142,36 @@ public class ChatSession { // TODO: is it freeze on someone's connection fail/freeze with play multiple games/chats/lobby? // TODO: send messages in another thread?! if (!message.isEmpty()) { + ChatMessage chatMessage = new ChatMessage(userName, message, (withTime ? new Date() : null), game, color, messageType, soundToPlay); + + switch (messageType) { + case USER_INFO: + case STATUS: + case TALK: + if (this.roomId != null) { + DataCollectorServices.getInstance().onChatRoom(this.roomId, userName, message); + } else if (this.tourneyId != null) { + DataCollectorServices.getInstance().onChatTourney(this.tourneyId, userName, message); + } else if (this.tableId != null) { + DataCollectorServices.getInstance().onChatTable(this.tableId, userName, message); + } else if (this.gameId != null) { + DataCollectorServices.getInstance().onChatGame(this.gameId, userName, message); + } + break; + case GAME: + // game logs processing in other place + break; + case WHISPER_FROM: + case WHISPER_TO: + // ignore private messages + break; + default: + throw new IllegalStateException("Unsupported message type " + messageType); + } + + // TODO: wtf, remove all that locks/tries and make it simpler Set clientsToRemove = new HashSet<>(); - ClientCallback clientCallback = new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId, - new ChatMessage(userName, message, (withTime ? new Date() : null), game, color, messageType, soundToPlay)); + ClientCallback clientCallback = new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId, chatMessage); List chatUserIds = new ArrayList<>(); final Lock r = lock.readLock(); r.lock(); diff --git a/Mage.Server/src/main/java/mage/server/MageServerImpl.java b/Mage.Server/src/main/java/mage/server/MageServerImpl.java index dcc20c38256..a0083afc710 100644 --- a/Mage.Server/src/main/java/mage/server/MageServerImpl.java +++ b/Mage.Server/src/main/java/mage/server/MageServerImpl.java @@ -5,6 +5,7 @@ import mage.cards.decks.DeckCardLists; import mage.cards.decks.DeckValidatorFactory; import mage.cards.repository.CardRepository; import mage.cards.repository.ExpansionRepository; +import mage.collectors.DataCollectorServices; import mage.constants.Constants; import mage.constants.ManaType; import mage.constants.PlayerAction; @@ -29,6 +30,7 @@ import mage.server.managers.ManagerFactory; import mage.server.services.impl.FeedbackServiceImpl; import mage.server.tournament.TournamentFactory; import mage.server.util.ServerMessagesUtil; +import mage.util.DebugUtil; import mage.utils.*; import mage.view.*; import mage.view.ChatMessage.MessageColor; @@ -68,6 +70,13 @@ public class MageServerImpl implements MageServer { this.detailsMode = detailsMode; this.callExecutor = managerFactory.threadExecutor().getCallExecutor(); ServerMessagesUtil.instance.getMessages(); + + // additional logs + DataCollectorServices.init( + DebugUtil.SERVER_DATA_COLLECTORS_ENABLE_PRINT_GAME_LOGS, + DebugUtil.SERVER_DATA_COLLECTORS_ENABLE_SAVE_GAME_HISTORY + ); + DataCollectorServices.getInstance().onServerStart(); } @Override diff --git a/Mage.Server/src/main/java/mage/server/RoomImpl.java b/Mage.Server/src/main/java/mage/server/RoomImpl.java index 9221ee0cd38..8b628544e27 100644 --- a/Mage.Server/src/main/java/mage/server/RoomImpl.java +++ b/Mage.Server/src/main/java/mage/server/RoomImpl.java @@ -14,7 +14,7 @@ public abstract class RoomImpl implements Room { public RoomImpl(ChatManager chatManager) { roomId = UUID.randomUUID(); - chatId = chatManager.createChatSession("Room " + roomId); + chatId = chatManager.createRoomChatSession(roomId); } /** diff --git a/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java b/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java index a3fe09ef6b6..76bbb3d755e 100644 --- a/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java @@ -8,7 +8,6 @@ import mage.util.ThreadUtils; import org.apache.log4j.Logger; import org.jboss.remoting.callback.InvokerCallbackHandler; -import javax.annotation.Nonnull; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -29,7 +28,7 @@ public class SessionManagerImpl implements SessionManager { } @Override - public Optional getSession(@Nonnull String sessionId) { + public Optional getSession(String sessionId) { return Optional.ofNullable(sessions.getOrDefault(sessionId, null)); } @@ -180,12 +179,12 @@ public class SessionManagerImpl implements SessionManager { } @Override - public boolean isValidSession(@Nonnull String sessionId) { + public boolean isValidSession(String sessionId) { return sessions.containsKey(sessionId); } @Override - public Optional getUser(@Nonnull String sessionId) { + public Optional getUser(String sessionId) { Session session = sessions.get(sessionId); if (session != null) { return managerFactory.userManager().getUser(sessions.get(sessionId).getUserId()); diff --git a/Mage.Server/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java index b23895416cd..be338040777 100644 --- a/Mage.Server/src/main/java/mage/server/TableController.java +++ b/Mage.Server/src/main/java/mage/server/TableController.java @@ -72,7 +72,7 @@ public class TableController { } this.table = new Table(roomId, options.getGameType(), options.getName(), controllerName, DeckValidatorFactory.instance.createDeckValidator(options.getDeckType()), options.getPlayerTypes(), new TableRecorderImpl(managerFactory.userManager()), match, options.getBannedUsers(), options.isPlaneChase()); - this.chatId = managerFactory.chatManager().createChatSession("Match Table " + table.getId()); + this.chatId = managerFactory.chatManager().createTableChatSession(table); init(); } @@ -94,7 +94,7 @@ public class TableController { } table = new Table(roomId, options.getTournamentType(), options.getName(), controllerName, DeckValidatorFactory.instance.createDeckValidator(options.getMatchOptions().getDeckType()), options.getPlayerTypes(), new TableRecorderImpl(managerFactory.userManager()), tournament, options.getMatchOptions().getBannedUsers(), options.isPlaneChase()); - chatId = managerFactory.chatManager().createChatSession("Tourney table " + table.getId()); + chatId = managerFactory.chatManager().createTableChatSession(table); } private void init() { diff --git a/Mage.Server/src/main/java/mage/server/challenge/ChallengeManager.java b/Mage.Server/src/main/java/mage/server/challenge/ChallengeManager.java deleted file mode 100644 index 1c9b4f57777..00000000000 --- a/Mage.Server/src/main/java/mage/server/challenge/ChallengeManager.java +++ /dev/null @@ -1,25 +0,0 @@ -package mage.server.challenge; - -import mage.constants.Zone; -import mage.game.match.Match; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * C U R R E N T L Y U N U S E D - * - * Loads challenges from scenarios. - * Configure games by initializing starting game board. - */ -public enum ChallengeManager { - - instance; - - public void prepareChallenge(UUID playerId, Match match) { - Map commands = new HashMap<>(); - commands.put(Zone.OUTSIDE, "life:3"); - match.getGame().cheat(playerId, commands); - } -} diff --git a/Mage.Server/src/main/java/mage/server/exceptions/UserNotFoundException.java b/Mage.Server/src/main/java/mage/server/exceptions/UserNotFoundException.java deleted file mode 100644 index 5787b42f6d5..00000000000 --- a/Mage.Server/src/main/java/mage/server/exceptions/UserNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package mage.server.exceptions; - -/** - * Created by igoudt on 14-1-2017. - */ -public class UserNotFoundException extends Exception { -} diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 96ed8b53505..8141658f86f 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -85,11 +85,11 @@ public class GameController implements GameCallback { public GameController(ManagerFactory managerFactory, Game game, ConcurrentMap userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) { this.managerFactory = managerFactory; - gameExecutor = managerFactory.threadExecutor().getGameExecutor(); - responseIdleTimeoutExecutor = managerFactory.threadExecutor().getTimeoutIdleExecutor(); - gameSessionId = UUID.randomUUID(); + this.gameExecutor = managerFactory.threadExecutor().getGameExecutor(); + this.responseIdleTimeoutExecutor = managerFactory.threadExecutor().getTimeoutIdleExecutor(); + this.gameSessionId = UUID.randomUUID(); this.userPlayerMap = userPlayerMap; - chatId = managerFactory.chatManager().createChatSession("Game " + game.getId()); + this.chatId = managerFactory.chatManager().createGameChatSession(game); this.userRequestingRollback = null; this.game = game; this.game.setSaveGame(managerFactory.configSettings().isSaveGameActivated()); diff --git a/Mage.Server/src/main/java/mage/server/managers/ChatManager.java b/Mage.Server/src/main/java/mage/server/managers/ChatManager.java index 7319561890d..056500cae69 100644 --- a/Mage.Server/src/main/java/mage/server/managers/ChatManager.java +++ b/Mage.Server/src/main/java/mage/server/managers/ChatManager.java @@ -1,9 +1,10 @@ package mage.server.managers; import mage.game.Game; +import mage.game.Table; +import mage.game.tournament.Tournament; import mage.server.ChatSession; import mage.server.DisconnectReason; -import mage.server.exceptions.UserNotFoundException; import mage.view.ChatMessage; import java.util.List; @@ -11,7 +12,13 @@ import java.util.UUID; public interface ChatManager { - UUID createChatSession(String info); + UUID createRoomChatSession(UUID roomId); + + UUID createTourneyChatSession(Tournament tournament); + + UUID createTableChatSession(Table table); + + UUID createGameChatSession(Game game); void joinChat(UUID chatId, UUID userId); diff --git a/Mage.Server/src/main/java/mage/server/managers/SessionManager.java b/Mage.Server/src/main/java/mage/server/managers/SessionManager.java index c5d1559b068..181ea1b28a9 100644 --- a/Mage.Server/src/main/java/mage/server/managers/SessionManager.java +++ b/Mage.Server/src/main/java/mage/server/managers/SessionManager.java @@ -7,12 +7,11 @@ import mage.server.Session; import mage.server.User; import org.jboss.remoting.callback.InvokerCallbackHandler; -import javax.annotation.Nonnull; import java.util.Optional; public interface SessionManager { - Optional getSession(@Nonnull String sessionId); + Optional getSession(String sessionId); void createSession(String sessionId, InvokerCallbackHandler callbackHandler); @@ -37,9 +36,9 @@ public interface SessionManager { boolean checkAdminAccess(String sessionId); - boolean isValidSession(@Nonnull String sessionId); + boolean isValidSession(String sessionId); - Optional getUser(@Nonnull String sessionId); + Optional getUser(String sessionId); boolean extendUserSession(String sessionId, String pingInfo); diff --git a/Mage.Server/src/main/java/mage/server/services/LogService.java b/Mage.Server/src/main/java/mage/server/services/LogService.java deleted file mode 100644 index b0edcdfba49..00000000000 --- a/Mage.Server/src/main/java/mage/server/services/LogService.java +++ /dev/null @@ -1,18 +0,0 @@ -package mage.server.services; - -/** - * Responsible for gathering logs and storing them in DB. - * - * @author noxx - */ -@FunctionalInterface -public interface LogService { - - /** - * Logs any information - * - * @param key Log key. Should be the same for the same types of logs. - * @param args Any parameters in string representation. - */ - void log(String key, String... args); -} diff --git a/Mage.Server/src/main/java/mage/server/services/MageService.java b/Mage.Server/src/main/java/mage/server/services/MageService.java deleted file mode 100644 index d80d7568d7e..00000000000 --- a/Mage.Server/src/main/java/mage/server/services/MageService.java +++ /dev/null @@ -1,18 +0,0 @@ -package mage.server.services; - -/** - * Common interface for all services. - * - * @author noxx - */ -public interface MageService { - /** - * Restores data on startup. - */ - void initService(); - - /** - * Dumps data to DB. - */ - void saveData(); -} diff --git a/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java b/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java index 939ee2db5b9..df85a172b05 100644 --- a/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java +++ b/Mage.Server/src/main/java/mage/server/tournament/TournamentController.java @@ -54,7 +54,7 @@ public class TournamentController { public TournamentController(ManagerFactory managerFactory, Tournament tournament, ConcurrentMap userPlayerMap, UUID tableId) { this.managerFactory = managerFactory; this.userPlayerMap = userPlayerMap; - chatId = managerFactory.chatManager().createChatSession("Tournament " + tournament.getId()); + this.chatId = managerFactory.chatManager().createTourneyChatSession(tournament); this.tournament = tournament; this.tableId = tableId; init(); @@ -261,9 +261,7 @@ public class TournamentController { table.setState(TableState.STARTING); tableManager.startTournamentSubMatch(null, table.getId()); tableManager.getMatch(table.getId()).ifPresent(match -> { - match.setTableId(tableId); - pair.setMatch(match); - pair.setTableId(table.getId()); + pair.setMatchAndTable(match, table.getId()); player1.setState(TournamentPlayerState.DUELING); player2.setState(TournamentPlayerState.DUELING); }); @@ -291,9 +289,7 @@ public class TournamentController { table.setState(TableState.STARTING); tableManager.startTournamentSubMatch(null, table.getId()); tableManager.getMatch(table.getId()).ifPresent(match -> { - match.setTableId(tableId); - round.setMatch(match); - round.setTableId(table.getId()); + round.setMatchAndTable(match, table.getId()); for (TournamentPlayer player : round.getAllPlayers()) { player.setState(TournamentPlayerState.DUELING); } diff --git a/Mage.Sets/src/mage/cards/a/AbsoluteVirtue.java b/Mage.Sets/src/mage/cards/a/AbsoluteVirtue.java index ac78f6fcc7e..3fe8e6419f3 100644 --- a/Mage.Sets/src/mage/cards/a/AbsoluteVirtue.java +++ b/Mage.Sets/src/mage/cards/a/AbsoluteVirtue.java @@ -79,7 +79,7 @@ class AbsoluteVirtueAbility extends ProtectionAbility { .ofNullable(source) .map(MageItem::getId) .map(game::getControllerId) - .map(uuid -> !game.getOpponents(this.getControllerId()).contains(uuid)) + .map(uuid -> !game.getOpponents(this.getSourceId()).contains(uuid)) .orElse(true); } } diff --git a/Mage.Sets/src/mage/cards/a/AdagiaWindsweptBastion.java b/Mage.Sets/src/mage/cards/a/AdagiaWindsweptBastion.java new file mode 100644 index 00000000000..ee0756881d9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AdagiaWindsweptBastion.java @@ -0,0 +1,62 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AdagiaWindsweptBastion extends CardImpl { + + public AdagiaWindsweptBastion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.PLANET); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {W}. + this.addAbility(new WhiteManaAbility()); + + // Station + this.addAbility(new StationAbility()); + + // STATION 12+ + // {3}{W}, {T}: Create a token that's a copy of target artifact or enchantment you control, except it's legendary. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new CreateTokenCopyTargetEffect() + .setPermanentModifier(token -> token.addSuperType(SuperType.LEGENDARY)) + .setText("create a token that's a copy of target artifact or enchantment you control, except it's legendary"), + new ManaCostsImpl<>("{3}{W}") + ); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CONTROLLED_ARTIFACT_OR_ENCHANTMENT)); + this.addAbility(new StationLevelAbility(12).withLevelAbility(ability)); + } + + private AdagiaWindsweptBastion(final AdagiaWindsweptBastion card) { + super(card); + } + + @Override + public AdagiaWindsweptBastion copy() { + return new AdagiaWindsweptBastion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AdelineResplendentCathar.java b/Mage.Sets/src/mage/cards/a/AdelineResplendentCathar.java index 4c48907c800..f2a3140c4f5 100644 --- a/Mage.Sets/src/mage/cards/a/AdelineResplendentCathar.java +++ b/Mage.Sets/src/mage/cards/a/AdelineResplendentCathar.java @@ -36,7 +36,7 @@ public final class AdelineResplendentCathar extends CardImpl { // Adeline, Resplendent Cathar's power is equal to the number of creatures you control. this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect( - CreaturesYouControlCount.instance)).addHint(CreaturesYouControlHint.instance) + CreaturesYouControlCount.PLURAL)).addHint(CreaturesYouControlHint.instance) ); // Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control. diff --git a/Mage.Sets/src/mage/cards/a/AerialPredation.java b/Mage.Sets/src/mage/cards/a/AerialPredation.java index e3e9164983e..7eaec60816e 100644 --- a/Mage.Sets/src/mage/cards/a/AerialPredation.java +++ b/Mage.Sets/src/mage/cards/a/AerialPredation.java @@ -3,36 +3,26 @@ package mage.cards.a; -import java.util.UUID; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.GainLifeEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class AerialPredation extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public AerialPredation(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); - // Destroy target creature with flying. You gain 2 life. - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.getSpellAbility().addEffect(new DestroyTargetEffect()); this.getSpellAbility().addEffect(new GainLifeEffect(2)); } diff --git a/Mage.Sets/src/mage/cards/a/AerieOuphes.java b/Mage.Sets/src/mage/cards/a/AerieOuphes.java index 588d018cefd..ecdd30d05d2 100644 --- a/Mage.Sets/src/mage/cards/a/AerieOuphes.java +++ b/Mage.Sets/src/mage/cards/a/AerieOuphes.java @@ -7,34 +7,23 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.PersistAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author jeffwadsworth */ public final class AerieOuphes extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public AerieOuphes(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); this.subtype.add(SubType.OUPHE); this.power = new MageInt(3); @@ -43,7 +32,7 @@ public final class AerieOuphes extends CardImpl { // Sacrifice Aerie Ouphes: Aerie Ouphes deals damage equal to its power to target creature with flying. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE) .setText("it deals damage equal to its power to target creature with flying"), new SacrificeSourceCost()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); // Persist diff --git a/Mage.Sets/src/mage/cards/a/AetherfluxConduit.java b/Mage.Sets/src/mage/cards/a/AetherfluxConduit.java index f4f9316a48e..263ffa68a36 100644 --- a/Mage.Sets/src/mage/cards/a/AetherfluxConduit.java +++ b/Mage.Sets/src/mage/cards/a/AetherfluxConduit.java @@ -1,19 +1,22 @@ package mage.cards.a; +import mage.Mana; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.costs.common.PayEnergyCost; import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; +import mage.counters.CounterType; import mage.filter.StaticFilters; +import mage.game.Controllable; import mage.game.Game; import mage.game.stack.Spell; import mage.players.Player; @@ -34,8 +37,8 @@ public final class AetherfluxConduit extends CardImpl { this.addAbility(new SpellCastControllerTriggeredAbility(new AetherfluxConduitManaEffect(), false)); // {T}, Pay fifty {E}: Draw seven cards. You may cast any number of spells from your hand without paying their mana costs. - final Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(7), new TapSourceCost()); - ability.addCost(new PayEnergyCost(50).setText("Pay fifty {E}")); + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(7), new TapSourceCost()); + ability.addCost(new PayEnergyCost(50).setText("pay fifty {E}")); ability.addEffect(new AetherfluxConduitCastEffect()); this.addAbility(ability); } @@ -68,10 +71,21 @@ class AetherfluxConduitManaEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Optional.ofNullable(this.getValue("spellCast")) + int amount = Optional + .ofNullable(this.getValue("spellCast")) .map(Spell.class::cast) - .ifPresent(spell -> new GetEnergyCountersControllerEffect(spell.getManaValue()).apply(game, source)); - return true; + .map(Spell::getStackAbility) + .map(Ability::getManaCostsToPay) + .map(ManaCost::getUsedManaToPay) + .map(Mana::count) + .orElse(0); + return amount > 0 + && Optional + .ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .filter(player -> player.addCounters(CounterType.ENERGY.createInstance(amount), player.getId(), source, game)) + .isPresent(); } } diff --git a/Mage.Sets/src/mage/cards/a/AethericAmplifier.java b/Mage.Sets/src/mage/cards/a/AethericAmplifier.java index 48737ded8e9..e27b7a83f98 100644 --- a/Mage.Sets/src/mage/cards/a/AethericAmplifier.java +++ b/Mage.Sets/src/mage/cards/a/AethericAmplifier.java @@ -1,12 +1,8 @@ package mage.cards.a; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - import mage.abilities.Ability; import mage.abilities.Mode; -import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; @@ -15,6 +11,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.TimingRule; import mage.counters.Counter; import mage.counters.CounterType; import mage.game.Game; @@ -22,6 +19,10 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + /** * @author sobiech */ @@ -35,8 +36,9 @@ public final class AethericAmplifier extends CardImpl { // {4}, {T}: Choose one. Activate only as a sorcery. // * Double the number of each kind of counter on target permanent. - final Ability ability = new ActivateAsSorceryActivatedAbility(new AethericAmplifierDoublePermanentEffect(), new GenericManaCost(4)) - .withShowActivateText(false); + Ability ability = new SimpleActivatedAbility( + new AethericAmplifierDoublePermanentEffect(), new GenericManaCost(4) + ).setTiming(TimingRule.SORCERY); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetPermanent()); ability.getModes().setChooseText("choose one. Activate only as a sorcery."); @@ -145,5 +147,3 @@ class AethericAmplifierDoubleControllerEffect extends OneShotEffect { return new AethericAmplifierDoubleControllerEffect(this); } } - - diff --git a/Mage.Sets/src/mage/cards/a/AetherworksMarvel.java b/Mage.Sets/src/mage/cards/a/AetherworksMarvel.java index 575bf9cc12c..d14c57f5605 100644 --- a/Mage.Sets/src/mage/cards/a/AetherworksMarvel.java +++ b/Mage.Sets/src/mage/cards/a/AetherworksMarvel.java @@ -35,7 +35,7 @@ public final class AetherworksMarvel extends CardImpl { this.addAbility(new PutIntoGraveFromBattlefieldAllTriggeredAbility( new GetEnergyCountersControllerEffect(1), false, StaticFilters.FILTER_CONTROLLED_A_PERMANENT, false - )); + ).setTriggerPhrase("Whenever a permanent you control is put into a graveyard, ")); // {T}, Pay {E}{E}{E}{E}{E}{E}: Look at the top six cards of your library. // You may cast a card from among them without paying its mana cost. diff --git a/Mage.Sets/src/mage/cards/a/AettirAndPriwen.java b/Mage.Sets/src/mage/cards/a/AettirAndPriwen.java index 2cb9a46c063..fa06d1f2ae8 100644 --- a/Mage.Sets/src/mage/cards/a/AettirAndPriwen.java +++ b/Mage.Sets/src/mage/cards/a/AettirAndPriwen.java @@ -61,18 +61,22 @@ class AettirAndPriwenEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = source.getSourcePermanentIfItStillExists(game); + Permanent permanent = Optional + .ofNullable(source.getSourcePermanentIfItStillExists(game)) + .map(Permanent::getAttachedTo) + .map(game::getPermanent) + .orElse(null); if (permanent == null) { return false; } - int life = Optional - .ofNullable(source) + Optional.ofNullable(source) .map(Controllable::getControllerId) .map(game::getPlayer) .map(Player::getLife) - .orElse(0); - permanent.getPower().setModifiedBaseValue(life); - permanent.getToughness().setModifiedBaseValue(life); + .ifPresent(life -> { + permanent.getPower().setModifiedBaseValue(life); + permanent.getToughness().setModifiedBaseValue(life); + }); return true; } } diff --git a/Mage.Sets/src/mage/cards/a/AgentOfAcquisitions.java b/Mage.Sets/src/mage/cards/a/AgentOfAcquisitions.java index 88cd9f907c8..bdbd264c6e9 100644 --- a/Mage.Sets/src/mage/cards/a/AgentOfAcquisitions.java +++ b/Mage.Sets/src/mage/cards/a/AgentOfAcquisitions.java @@ -32,7 +32,7 @@ public final class AgentOfAcquisitions extends CardImpl { // Instead of drafting a card from a booster pack, you may draft each card in that booster pack, one at a time. If you do, turn Agent of Acquisitions face down and you can’t draft cards for the rest of this draft round. this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Instead of drafting a card from a booster pack, " - + "you may draft each card in that booster pack, one at a time. If you do, turn Agent of Acquisitions face down and " + + "you may draft each card in that booster pack, one at a time. If you do, turn {this} face down and " + "you can't draft cards for the rest of this draft round - not implemented."))); } diff --git a/Mage.Sets/src/mage/cards/a/AirServant.java b/Mage.Sets/src/mage/cards/a/AirServant.java index da105691266..e12b1dc0b7c 100644 --- a/Mage.Sets/src/mage/cards/a/AirServant.java +++ b/Mage.Sets/src/mage/cards/a/AirServant.java @@ -2,7 +2,6 @@ package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -13,33 +12,27 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public final class AirServant extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public AirServant(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); this.subtype.add(SubType.ELEMENTAL); this.power = new MageInt(4); this.toughness = new MageInt(3); + this.addAbility(FlyingAbility.getInstance()); + Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new ManaCostsImpl<>("{2}{U}")); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AjaniNacatlAvenger.java b/Mage.Sets/src/mage/cards/a/AjaniNacatlAvenger.java index 597fec72c1c..750cdcbb659 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniNacatlAvenger.java +++ b/Mage.Sets/src/mage/cards/a/AjaniNacatlAvenger.java @@ -110,7 +110,7 @@ class AjaniNacatlAvengerZeroEffect extends OneShotEffect { } ReflexiveTriggeredAbility reflexive = new ReflexiveTriggeredAbility( - new DamageTargetEffect(CreaturesYouControlCount.instance), + new DamageTargetEffect(CreaturesYouControlCount.PLURAL), false, "When you do, if you control a red permanent other than {this}, " + "he deals damage equal to the number of creatures you control to any target.", diff --git a/Mage.Sets/src/mage/cards/a/AjaniWiseCounselor.java b/Mage.Sets/src/mage/cards/a/AjaniWiseCounselor.java index 1f9e4d4c22d..b63a6439454 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniWiseCounselor.java +++ b/Mage.Sets/src/mage/cards/a/AjaniWiseCounselor.java @@ -31,7 +31,7 @@ public final class AjaniWiseCounselor extends CardImpl { this.setStartingLoyalty(5); // +2: You gain 1 life for each creature you control. - this.addAbility(new LoyaltyAbility(new GainLifeEffect(CreaturesYouControlCount.instance) + this.addAbility(new LoyaltyAbility(new GainLifeEffect(CreaturesYouControlCount.PLURAL) .setText("you gain 1 life for each creature you control"), 2)); // −3: Creatures you control get +2/+2 until end of turn. diff --git a/Mage.Sets/src/mage/cards/a/AllFatesScroll.java b/Mage.Sets/src/mage/cards/a/AllFatesScroll.java new file mode 100644 index 00000000000..9cb91de2ae9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AllFatesScroll.java @@ -0,0 +1,46 @@ +package mage.cards.a; + +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.dynamicvalue.common.DifferentlyNamedPermanentCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AllFatesScroll extends CardImpl { + + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS); + + public AllFatesScroll(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + + // {7}, {T}, Sacrifice this artifact: Draw X cards, where X is the number of differently named lands you control. + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(xValue), new GenericManaCost(7)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability.addHint(xValue.getHint())); + } + + private AllFatesScroll(final AllFatesScroll card) { + super(card); + } + + @Override + public AllFatesScroll copy() { + return new AllFatesScroll(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AllFatesStalker.java b/Mage.Sets/src/mage/cards/a/AllFatesStalker.java new file mode 100644 index 00000000000..040667496c7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AllFatesStalker.java @@ -0,0 +1,55 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AllFatesStalker extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("non-Assassin creature"); + + static { + filter.add(Predicates.not(SubType.ASSASSIN.getPredicate())); + } + + public AllFatesStalker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.DRIX); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When this creature enters, exile up to one target non-Assassin creature until this creature leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + + // Warp {1}{W} + this.addAbility(new WarpAbility(this, "{1}{W}")); + } + + private AllFatesStalker(final AllFatesStalker card) { + super(card); + } + + @Override + public AllFatesStalker copy() { + return new AllFatesStalker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AlpharaelStonechosen.java b/Mage.Sets/src/mage/cards/a/AlpharaelStonechosen.java new file mode 100644 index 00000000000..3e2a1437f80 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AlpharaelStonechosen.java @@ -0,0 +1,49 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.LoseHalfLifeTargetEffect; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AlpharaelStonechosen extends CardImpl { + + public AlpharaelStonechosen(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.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Ward--Discard a card at random. + this.addAbility(new WardAbility(new DiscardCardCost(true))); + + // Void -- Whenever Alpharael attacks, if a nonland permanent left the battlefield this turn or a spell was warped this turn, defending player loses half their life, rounded up. + this.addAbility(new AttacksTriggeredAbility( + new LoseHalfLifeTargetEffect() + .setText("defending player loses half their life, rounded up"), + false, null, SetTargetPointer.PLAYER + ).withInterveningIf(VoidCondition.instance).setAbilityWord(AbilityWord.VOID).addHint(VoidCondition.getHint()), new VoidWatcher()); + } + + private AlpharaelStonechosen(final AlpharaelStonechosen card) { + super(card); + } + + @Override + public AlpharaelStonechosen copy() { + return new AlpharaelStonechosen(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AngelOfRenewal.java b/Mage.Sets/src/mage/cards/a/AngelOfRenewal.java index 19b2082087b..4c516216a24 100644 --- a/Mage.Sets/src/mage/cards/a/AngelOfRenewal.java +++ b/Mage.Sets/src/mage/cards/a/AngelOfRenewal.java @@ -27,7 +27,7 @@ public final class AngelOfRenewal extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // When Angel of Renewal enters the battlefield, you gain 1 life for each creature you control. - this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(CreaturesYouControlCount.instance).setText("you gain 1 life for each creature you control"))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(CreaturesYouControlCount.PLURAL).setText("you gain 1 life for each creature you control"))); } private AngelOfRenewal(final AngelOfRenewal card) { diff --git a/Mage.Sets/src/mage/cards/a/AngelicExaltation.java b/Mage.Sets/src/mage/cards/a/AngelicExaltation.java index 16abc7d43d6..d8518b98d36 100644 --- a/Mage.Sets/src/mage/cards/a/AngelicExaltation.java +++ b/Mage.Sets/src/mage/cards/a/AngelicExaltation.java @@ -21,7 +21,7 @@ public final class AngelicExaltation extends CardImpl { // Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of creatures you control. this.addAbility(new AttacksAloneControlledTriggeredAbility( - new BoostTargetEffect(CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn), + new BoostTargetEffect(CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn), true, false).addHint(CreaturesYouControlHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/a/AnticausalVestige.java b/Mage.Sets/src/mage/cards/a/AnticausalVestige.java new file mode 100644 index 00000000000..2fecd9a07b7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AnticausalVestige.java @@ -0,0 +1,71 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.abilities.hint.common.LandsYouControlHint; +import mage.abilities.keyword.WarpAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AnticausalVestige extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard( + "a permanent card with mana value less than or equal to the number of lands you control" + ); + + static { + filter.add(AnticausalVestigePredicate.instance); + } + + public AnticausalVestige(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}"); + + this.subtype.add(SubType.ELDRAZI); + this.power = new MageInt(7); + this.toughness = new MageInt(5); + + // When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped. + Ability ability = new LeavesBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1)); + ability.addEffect(new PutCardFromHandOntoBattlefieldEffect(filter, false, true).concatBy(", then")); + this.addAbility(ability.addHint(LandsYouControlHint.instance)); + + // Warp {4} + this.addAbility(new WarpAbility(this, "{4}")); + } + + private AnticausalVestige(final AnticausalVestige card) { + super(card); + } + + @Override + public AnticausalVestige copy() { + return new AnticausalVestige(this); + } +} + +enum AnticausalVestigePredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input.getObject().getManaValue() + <= LandsYouControlCount.instance.calculate(game, input.getSource(), null); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AppealAuthority.java b/Mage.Sets/src/mage/cards/a/AppealAuthority.java index 11eec0ecb20..256824011fd 100644 --- a/Mage.Sets/src/mage/cards/a/AppealAuthority.java +++ b/Mage.Sets/src/mage/cards/a/AppealAuthority.java @@ -32,7 +32,7 @@ public final class AppealAuthority extends SplitCard { // Until end of turn, target creature gains trample and gets +X/+X, where X is the number of creatures you control. getLeftHalfCard().getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn) .setText("Until end of turn, target creature gains trample")); - getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn) + getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn) .setText("and gets +X/+X, where X is the number of creatures you control")); getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); getLeftHalfCard().getSpellAbility().addHint(CreaturesYouControlHint.instance); diff --git a/Mage.Sets/src/mage/cards/a/ArashiTheSkyAsunder.java b/Mage.Sets/src/mage/cards/a/ArashiTheSkyAsunder.java index 2099b9ab64e..c68fa52dc09 100644 --- a/Mage.Sets/src/mage/cards/a/ArashiTheSkyAsunder.java +++ b/Mage.Sets/src/mage/cards/a/ArashiTheSkyAsunder.java @@ -1,7 +1,6 @@ package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -11,31 +10,23 @@ import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.ChannelAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ArashiTheSkyAsunder extends CardImpl { - static final private FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public ArashiTheSkyAsunder(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.SPIRIT); @@ -45,11 +36,11 @@ public final class ArashiTheSkyAsunder extends CardImpl { // {X}{G}, {tap}: Arashi, the Sky Asunder deals X damage to target creature with flying. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(GetXValue.instance), new ManaCostsImpl<>("{X}{G}")); ability.addCost(new TapSourceCost()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); // Channel - {X}{G}{G}, Discard Arashi: Arashi deals X damage to each creature with flying. - this.addAbility(new ChannelAbility("{X}{G}{G}", new DamageAllEffect(GetXValue.instance, filter))); + this.addAbility(new ChannelAbility("{X}{G}{G}", new DamageAllEffect(GetXValue.instance, StaticFilters.FILTER_CREATURE_FLYING))); } private ArashiTheSkyAsunder(final ArashiTheSkyAsunder card) { diff --git a/Mage.Sets/src/mage/cards/a/ArchenemysCharm.java b/Mage.Sets/src/mage/cards/a/ArchenemysCharm.java new file mode 100644 index 00000000000..b22a968a80f --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArchenemysCharm.java @@ -0,0 +1,62 @@ +package mage.cards.a; + +import mage.abilities.Mode; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +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.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreatureOrPlaneswalker; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ArchenemysCharm extends CardImpl { + + private static final FilterCard filter = new FilterCard("creature and/or planeswalker cards from your graveyard"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + CardType.PLANESWALKER.getPredicate() + )); + } + + public ArchenemysCharm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}{B}{B}"); + + // Choose one -- + // * Exile target creature or planeswalker. + this.getSpellAbility().addEffect(new ExileTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); + + // * Return one or two target creature and/or planeswalker cards from your graveyard to your hand. + this.getSpellAbility().addMode(new Mode(new ReturnFromGraveyardToHandTargetEffect()) + .addTarget(new TargetCardInYourGraveyard(1, 2, filter))); + + // * Put two +1/+1 counters on target creature you control. It gains lifelink until end of turn. + this.getSpellAbility().addMode(new Mode(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2))) + .addEffect(new GainAbilityTargetEffect(LifelinkAbility.getInstance()) + .setText("It gains lifelink until end of turn")) + .addTarget(new TargetControlledCreaturePermanent())); + } + + private ArchenemysCharm(final ArchenemysCharm card) { + super(card); + } + + @Override + public ArchenemysCharm copy() { + return new ArchenemysCharm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AshnodsAltar.java b/Mage.Sets/src/mage/cards/a/AshnodsAltar.java index 699a7735a7b..1fb11e997e0 100644 --- a/Mage.Sets/src/mage/cards/a/AshnodsAltar.java +++ b/Mage.Sets/src/mage/cards/a/AshnodsAltar.java @@ -12,7 +12,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -26,7 +25,7 @@ public final class AshnodsAltar extends CardImpl { // Sacrifice a creature: Add {C}{C}. SacrificeTargetCost cost = new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE); this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, - new BasicManaEffect(Mana.ColorlessMana(2), CreaturesYouControlCount.instance), + new BasicManaEffect(Mana.ColorlessMana(2), CreaturesYouControlCount.PLURAL), cost)); } diff --git a/Mage.Sets/src/mage/cards/a/AssaultBattery.java b/Mage.Sets/src/mage/cards/a/AssaultBattery.java index 4c7a4ddba94..58bd8fa7d39 100644 --- a/Mage.Sets/src/mage/cards/a/AssaultBattery.java +++ b/Mage.Sets/src/mage/cards/a/AssaultBattery.java @@ -1,7 +1,6 @@ package mage.cards.a; -import java.util.UUID; import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DamageTargetEffect; @@ -12,6 +11,8 @@ import mage.constants.SpellAbilityType; import mage.game.permanent.token.ElephantToken; import mage.target.common.TargetAnyTarget; +import java.util.UUID; + public final class AssaultBattery extends SplitCard { public AssaultBattery(UUID ownerId, CardSetInfo setInfo) { @@ -20,7 +21,7 @@ public final class AssaultBattery extends SplitCard { // Assault // Assault deals 2 damage to any target. Effect effect = new DamageTargetEffect(2); - effect.setText("Assault deals 2 damage to any target"); + effect.setText("{this} deals 2 damage to any target"); getLeftHalfCard().getSpellAbility().addEffect(effect); getLeftHalfCard().getSpellAbility().addTarget(new TargetAnyTarget()); diff --git a/Mage.Sets/src/mage/cards/a/AssimilateEssence.java b/Mage.Sets/src/mage/cards/a/AssimilateEssence.java index 1e3852eee1b..29d2cff7dc6 100644 --- a/Mage.Sets/src/mage/cards/a/AssimilateEssence.java +++ b/Mage.Sets/src/mage/cards/a/AssimilateEssence.java @@ -1,18 +1,13 @@ package mage.cards.a; -import mage.abilities.Ability; -import mage.abilities.costs.Cost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CounterUnlessPaysEffect; import mage.abilities.effects.keyword.IncubateEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.filter.FilterSpell; import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.players.Player; import mage.target.TargetSpell; import java.util.UUID; @@ -35,7 +30,10 @@ public final class AssimilateEssence extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); // Counter target creature or battle spell unless its controller pays {4}. If they do, you incubate 2. - this.getSpellAbility().addEffect(new AssimilateEssenceEffect()); + this.getSpellAbility().addEffect( + new CounterUnlessPaysEffect(new GenericManaCost(4)) + .withIfTheyDo(new IncubateEffect(2).setText("you incubate 2")) + ); this.getSpellAbility().addTarget(new TargetSpell(filter)); } @@ -47,36 +45,4 @@ public final class AssimilateEssence extends CardImpl { public AssimilateEssence copy() { return new AssimilateEssence(this); } -} - -class AssimilateEssenceEffect extends OneShotEffect { - - AssimilateEssenceEffect() { - super(Outcome.Benefit); - staticText = "counter target creature or battle spell unless its controller pays {4}. If they do, you incubate 2"; - } - - private AssimilateEssenceEffect(final AssimilateEssenceEffect effect) { - super(effect); - } - - @Override - public AssimilateEssenceEffect copy() { - return new AssimilateEssenceEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - UUID targetId = getTargetPointer().getFirst(game, source); - Player player = game.getPlayer(game.getControllerId(targetId)); - Cost cost = new GenericManaCost(4); - if (player == null - || !cost.canPay(source, source, player.getId(), game) - || !player.chooseUse(outcome, "Pay {4}?", source, game) - || !cost.pay(source, game, source, player.getId(), false)) { - game.getStack().counter(targetId, source, game); - return true; - } - return IncubateEffect.doIncubate(2, source.getControllerId(), game, source); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/a/AstelliReclaimer.java b/Mage.Sets/src/mage/cards/a/AstelliReclaimer.java new file mode 100644 index 00000000000..b3a03f92a74 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AstelliReclaimer.java @@ -0,0 +1,80 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WarpAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.PermanentPredicate; +import mage.game.Game; +import mage.target.common.TargetCardInYourGraveyard; +import mage.watchers.common.ManaPaidSourceWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AstelliReclaimer extends CardImpl { + + private static final FilterCard filter = new FilterCard( + "noncreature, nonland permanent card with mana value X or less" + ); + + static { + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + filter.add(Predicates.not(CardType.LAND.getPredicate())); + filter.add(PermanentPredicate.instance); + filter.add(AstelliReclaimerPredicate.instance); + } + + public AstelliReclaimer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + + this.subtype.add(SubType.ANGEL); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, return target noncreature, nonland permanent card with mana value X or less from your graveyard to the battlefield, where X is the amount of mana spent to cast this creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect() + .setText("return target noncreature, nonland permanent card with mana value X or less from " + + "your graveyard to the battlefield, where X is the amount of mana spent to cast this creature")); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + // Warp {2}{W} + this.addAbility(new WarpAbility(this, "{2}{W}")); + } + + private AstelliReclaimer(final AstelliReclaimer card) { + super(card); + } + + @Override + public AstelliReclaimer copy() { + return new AstelliReclaimer(this); + } +} + +enum AstelliReclaimerPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input.getObject().getManaValue() <= ManaPaidSourceWatcher.getTotalPaid(input.getSourceId(), game); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AstralDragon.java b/Mage.Sets/src/mage/cards/a/AstralDragon.java index 59640b2b6a5..cc954e8cca9 100644 --- a/Mage.Sets/src/mage/cards/a/AstralDragon.java +++ b/Mage.Sets/src/mage/cards/a/AstralDragon.java @@ -9,8 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -20,12 +19,6 @@ import java.util.UUID; */ public final class AstralDragon extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public AstralDragon(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{U}{U}"); @@ -37,14 +30,13 @@ public final class AstralDragon extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Project Image — When Astral Dragon enters the battlefield, create two tokens that are copies of target noncreature permanent, except they're 3/3 Dragon creatures in addition to their other types, and they have flying. - CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect( + Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenCopyTargetEffect( null, CardType.CREATURE, false, 2, false, - false, null, 3, 3, true); - effect.setText("create two tokens that are copies of target noncreature permanent, " + - "except they're 3/3 Dragon creatures in addition to their other types, and they have flying"); - effect.withAdditionalSubType(SubType.DRAGON); - Ability ability = new EntersBattlefieldTriggeredAbility(effect); - ability.addTarget(new TargetPermanent(filter)); + false, null, 3, 3, true + ).withAdditionalSubType(SubType.DRAGON) + .setText("create two tokens that are copies of target noncreature permanent, " + + "except they're 3/3 Dragon creatures in addition to their other types, and they have flying")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); this.addAbility(ability.withFlavorWord("Project Image")); } diff --git a/Mage.Sets/src/mage/cards/a/AtmosphericGreenhouse.java b/Mage.Sets/src/mage/cards/a/AtmosphericGreenhouse.java new file mode 100644 index 00000000000..a2c47a84658 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AtmosphericGreenhouse.java @@ -0,0 +1,54 @@ +package mage.cards.a; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AtmosphericGreenhouse extends CardImpl { + + public AtmosphericGreenhouse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{G}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, put a +1/+1 counter on each creature you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ))); + + // Station + this.addAbility(new StationAbility()); + + // STATION 8+ + // Flying + // Trample + // 5/4 + this.addAbility(new StationLevelAbility(8) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(TrampleAbility.getInstance()) + .withPT(5, 4)); + } + + private AtmosphericGreenhouse(final AtmosphericGreenhouse card) { + super(card); + } + + @Override + public AtmosphericGreenhouse copy() { + return new AtmosphericGreenhouse(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AtomicMicrosizer.java b/Mage.Sets/src/mage/cards/a/AtomicMicrosizer.java new file mode 100644 index 00000000000..cdf501abb19 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AtomicMicrosizer.java @@ -0,0 +1,52 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksAttachedTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; +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.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AtomicMicrosizer extends CardImpl { + + public AtomicMicrosizer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{U}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +1/+0. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(1, 0))); + + // Whenever equipped creature attacks, choose up to one target creature. That creature can't be blocked this turn and has base power and toughness 1/1 until end of turn. + Ability ability = new AttacksAttachedTriggeredAbility(new CantBeBlockedTargetEffect() + .setText("choose up to one target creature. That creature can't be blocked this turn")); + ability.addEffect(new SetBasePowerToughnessTargetEffect(1, 1, Duration.EndOfTurn) + .setText("and has base power and toughness 1/1 until end of turn")); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private AtomicMicrosizer(final AtomicMicrosizer card) { + super(card); + } + + @Override + public AtomicMicrosizer copy() { + return new AtomicMicrosizer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AudienceWithTrostani.java b/Mage.Sets/src/mage/cards/a/AudienceWithTrostani.java index ad9aa3dcaea..39b46b05a0d 100644 --- a/Mage.Sets/src/mage/cards/a/AudienceWithTrostani.java +++ b/Mage.Sets/src/mage/cards/a/AudienceWithTrostani.java @@ -1,19 +1,14 @@ package mage.cards.a; -import mage.MageObject; -import mage.abilities.Ability; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.hint.Hint; -import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.permanent.PermanentToken; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; import mage.game.permanent.token.PlantToken; import java.util.UUID; @@ -23,14 +18,22 @@ import java.util.UUID; */ public final class AudienceWithTrostani extends CardImpl { + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creature tokens you control"); + + static { + filter.add(TokenPredicate.TRUE); + } + + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(filter); + public AudienceWithTrostani(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); // Create a 0/1 green Plant creature token, then draw cards equal to the number of differently named creature tokens you control. this.getSpellAbility().addEffect(new CreateTokenEffect(new PlantToken())); - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(AudienceWithTrostaniValue.instance) + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(xValue) .setText(", then draw cards equal to the number of differently named creature tokens you control")); - this.getSpellAbility().addHint(AudienceWithTrostaniValue.getHint()); + this.getSpellAbility().addHint(xValue.getHint()); } private AudienceWithTrostani(final AudienceWithTrostani card) { @@ -42,46 +45,3 @@ public final class AudienceWithTrostani extends CardImpl { return new AudienceWithTrostani(this); } } - -enum AudienceWithTrostaniValue implements DynamicValue { - instance; - private static final Hint hint = new ValueHint( - "Different names among creature tokens 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_CREATURE, - sourceAbility.getControllerId(), sourceAbility, game - ) - .stream() - .filter(PermanentToken.class::isInstance) - .map(MageObject::getName) - .filter(s -> !s.isEmpty()) - .distinct() - .mapToInt(x -> 1) - .sum(); - } - - @Override - public AudienceWithTrostaniValue copy() { - return this; - } - - @Override - public String getMessage() { - return ""; - } - - @Override - public String toString() { - return "1"; - } -} diff --git a/Mage.Sets/src/mage/cards/a/AurraSingBaneOfJedi.java b/Mage.Sets/src/mage/cards/a/AurraSingBaneOfJedi.java index 7cb81874763..381813a1b21 100644 --- a/Mage.Sets/src/mage/cards/a/AurraSingBaneOfJedi.java +++ b/Mage.Sets/src/mage/cards/a/AurraSingBaneOfJedi.java @@ -44,7 +44,7 @@ public final class AurraSingBaneOfJedi extends CardImpl { ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); - // -4: Target player gets an emblem wiht "Whenever a nontoken creature you control leave the battlefied, discard a card.". + // -4: Target player gets an emblem with "Whenever a nontoken creature you control leave the battlefied, discard a card.". ability = new LoyaltyAbility(new GetEmblemTargetPlayerEffect(new AurraSingBaneOfJediEmblem()), -4); ability.addTarget(new TargetPlayer()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/a/AuxiliaryBoosters.java b/Mage.Sets/src/mage/cards/a/AuxiliaryBoosters.java new file mode 100644 index 00000000000..7143bb4d480 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AuxiliaryBoosters.java @@ -0,0 +1,52 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenAttachSourceEffect; +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.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.RobotToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AuxiliaryBoosters extends CardImpl { + + public AuxiliaryBoosters(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When this Equipment enters, create a 2/2 colorless Robot artifact creature token and attach this Equipment to it. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenAttachSourceEffect(new RobotToken()))); + + // Equipped creature gets +1/+2 and has flying. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 2)); + ability.addEffect(new GainAbilityAttachedEffect( + FlyingAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText("and has flying")); + this.addAbility(ability); + + // Equip {3} + this.addAbility(new EquipAbility(3)); + } + + private AuxiliaryBoosters(final AuxiliaryBoosters card) { + super(card); + } + + @Override + public AuxiliaryBoosters copy() { + return new AuxiliaryBoosters(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AwakenedAmalgam.java b/Mage.Sets/src/mage/cards/a/AwakenedAmalgam.java index 9a2accffcf6..e96bc37e198 100644 --- a/Mage.Sets/src/mage/cards/a/AwakenedAmalgam.java +++ b/Mage.Sets/src/mage/cards/a/AwakenedAmalgam.java @@ -1,29 +1,25 @@ - package mage.cards.a; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class AwakenedAmalgam extends CardImpl { + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS); + public AwakenedAmalgam(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}"); @@ -32,8 +28,9 @@ public final class AwakenedAmalgam extends CardImpl { this.toughness = new MageInt(0); // Awakened Amalgam's power and toughness are each equal to the number of differently named lands you control. - DynamicValue value = (new AwakenedAmalgamLandNamesCount()); - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(value))); + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SetBasePowerToughnessSourceEffect(xValue) + ).addHint(xValue.getHint())); } private AwakenedAmalgam(final AwakenedAmalgam card) { @@ -45,35 +42,3 @@ public final class AwakenedAmalgam extends CardImpl { return new AwakenedAmalgam(this); } } - -class AwakenedAmalgamLandNamesCount implements DynamicValue { - - public AwakenedAmalgamLandNamesCount() { - } - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - Set landNames = new HashSet<>(); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(sourceAbility.getControllerId())) { - if (permanent.isLand(game)) { - landNames.add(permanent.getName()); - } - } - return landNames.size(); - } - - @Override - public AwakenedAmalgamLandNamesCount copy() { - return this; - } - - @Override - public String toString() { - return "1"; - } - - @Override - public String getMessage() { - return "differently named lands you control"; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BalothPrime.java b/Mage.Sets/src/mage/cards/b/BalothPrime.java new file mode 100644 index 00000000000..36f0ce7a90a --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BalothPrime.java @@ -0,0 +1,66 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.TapSourceEffect; +import mage.abilities.effects.common.UntapSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.BeastToken2; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BalothPrime extends CardImpl { + + public BalothPrime(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(10); + this.toughness = new MageInt(10); + + // This creature enters tapped with six stun counters on it. (If a permanent with a stun counter would become untapped, remove one from it instead.) + Ability ability = new EntersBattlefieldAbility( + new TapSourceEffect(true), "tapped with six stun counters on it. " + + "(If a permanent with a stun counter would become untapped, remove one from it instead.)" + ); + ability.addEffect(new AddCountersSourceEffect(CounterType.STUN.createInstance(6))); + this.addAbility(ability); + + // Whenever you sacrifice a land, create a tapped 4/4 green Beast creature token and untap this creature. + ability = new SacrificePermanentTriggeredAbility( + new CreateTokenEffect(new BeastToken2(), 1, true), StaticFilters.FILTER_LAND + ); + ability.addEffect(new UntapSourceEffect().concatBy("and")); + this.addAbility(ability); + + // {4}, Sacrifice a land: You gain 2 life. + ability = new SimpleActivatedAbility(new GainLifeEffect(2), new GenericManaCost(4)); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_LAND)); + this.addAbility(ability); + } + + private BalothPrime(final BalothPrime card) { + super(card); + } + + @Override + public BalothPrime copy() { + return new BalothPrime(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BambooGroveArcher.java b/Mage.Sets/src/mage/cards/b/BambooGroveArcher.java index d7e63242f5b..fef3144e564 100644 --- a/Mage.Sets/src/mage/cards/b/BambooGroveArcher.java +++ b/Mage.Sets/src/mage/cards/b/BambooGroveArcher.java @@ -5,14 +5,12 @@ import mage.abilities.Ability; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.ChannelAbility; import mage.abilities.keyword.DefenderAbility; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -22,12 +20,6 @@ import java.util.UUID; */ public final class BambooGroveArcher extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public BambooGroveArcher(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{1}{G}"); @@ -44,7 +36,7 @@ public final class BambooGroveArcher extends CardImpl { // Channel — {4}{G}, Discard Bamboo Grove Archer: Destroy target creature with flying. Ability ability = new ChannelAbility("{4}{G}", new DestroyTargetEffect()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/b/BattleHymn.java b/Mage.Sets/src/mage/cards/b/BattleHymn.java index 8efdd484b81..f1e2743a47c 100644 --- a/Mage.Sets/src/mage/cards/b/BattleHymn.java +++ b/Mage.Sets/src/mage/cards/b/BattleHymn.java @@ -18,7 +18,7 @@ public final class BattleHymn extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); // Add {R} for each creature you control. - this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.RedMana(1), CreaturesYouControlCount.instance)); + this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.RedMana(1), CreaturesYouControlCount.SINGULAR)); } private BattleHymn(final BattleHymn card) { diff --git a/Mage.Sets/src/mage/cards/b/BattleSquadron.java b/Mage.Sets/src/mage/cards/b/BattleSquadron.java index 0eba186c1fc..333943417f9 100644 --- a/Mage.Sets/src/mage/cards/b/BattleSquadron.java +++ b/Mage.Sets/src/mage/cards/b/BattleSquadron.java @@ -29,7 +29,7 @@ public final class BattleSquadron extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Battle Squadron's power and toughness are each equal to the number of creatures you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance)) + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL)) .addHint(CreaturesYouControlHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/b/BeamsawProspector.java b/Mage.Sets/src/mage/cards/b/BeamsawProspector.java new file mode 100644 index 00000000000..97ecbba74b0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BeamsawProspector.java @@ -0,0 +1,39 @@ +package mage.cards.b; + +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.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BeamsawProspector extends CardImpl { + + public BeamsawProspector(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // When this creature dies, create a Lander token. + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new LanderToken()))); + } + + private BeamsawProspector(final BeamsawProspector card) { + super(card); + } + + @Override + public BeamsawProspector copy() { + return new BeamsawProspector(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BeyondTheQuiet.java b/Mage.Sets/src/mage/cards/b/BeyondTheQuiet.java new file mode 100644 index 00000000000..3196a0cedd8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BeyondTheQuiet.java @@ -0,0 +1,42 @@ +package mage.cards.b; + +import mage.abilities.effects.common.ExileAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BeyondTheQuiet extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("creatures and Spacecraft"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.SPACECRAFT.getPredicate() + )); + } + + public BeyondTheQuiet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}{W}"); + + // Exile all creatures and Spacecraft. + this.getSpellAbility().addEffect(new ExileAllEffect(filter)); + } + + private BeyondTheQuiet(final BeyondTheQuiet card) { + super(card); + } + + @Override + public BeyondTheQuiet copy() { + return new BeyondTheQuiet(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BioengineeredFuture.java b/Mage.Sets/src/mage/cards/b/BioengineeredFuture.java new file mode 100644 index 00000000000..c6bc5fe3a0d --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BioengineeredFuture.java @@ -0,0 +1,144 @@ +package mage.cards.b; + +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.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.WatcherScope; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.LanderToken; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BioengineeredFuture extends CardImpl { + + public BioengineeredFuture(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}{G}"); + + // When this enchantment enters, create a Lander token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new LanderToken()))); + + // Each creature you control enters with an additional +1/+1 counter on it for each land that entered the battlefield under your control this turn. + this.addAbility(new SimpleStaticAbility(new BioengineeredFutureEffect()) + .addHint(BioengineeredFutureHint.instance), new BioengineeredFutureWatcher()); + } + + private BioengineeredFuture(final BioengineeredFuture card) { + super(card); + } + + @Override + public BioengineeredFuture copy() { + return new BioengineeredFuture(this); + } +} + +class BioengineeredFutureEffect extends ReplacementEffectImpl { + + BioengineeredFutureEffect() { + super(Duration.WhileOnBattlefield, Outcome.BoostCreature); + staticText = "each creature you control enters with an additional +1/+1 counter on it " + + "for each land that entered the battlefield under your control this turn"; + } + + private BioengineeredFutureEffect(final BioengineeredFutureEffect effect) { + super(effect); + } + + @Override + public BioengineeredFutureEffect copy() { + return new BioengineeredFutureEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget(); + return permanent != null + && permanent.isControlledBy(source.getControllerId()) + && permanent.isCreature(game); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget(); + int count = BioengineeredFutureWatcher.getCount(game, source); + if (creature != null && count > 0) { + creature.addCounters( + CounterType.P1P1.createInstance(count), source.getControllerId(), + source, game, event.getAppliedEffects() + ); + } + return false; + } +} + +class BioengineeredFutureWatcher extends Watcher { + + private final Map map = new HashMap<>(); + + BioengineeredFutureWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { + return; + } + Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget(); + if (permanent != null && permanent.isLand(game)) { + map.compute(permanent.getControllerId(), CardUtil::setOrIncrementValue); + } + } + + @Override + public void reset() { + super.reset(); + map.clear(); + } + + static int getCount(Game game, Ability source) { + return game + .getState() + .getWatcher(BioengineeredFutureWatcher.class) + .map + .getOrDefault(source.getControllerId(), 0); + } +} + +enum BioengineeredFutureHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + return "Lands that entered under your control this turn: " + BioengineeredFutureWatcher.getCount(game, ability); + } + + @Override + public Hint copy() { + return this; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BiomechanEngineer.java b/Mage.Sets/src/mage/cards/b/BiomechanEngineer.java new file mode 100644 index 00000000000..ca0624d1f6c --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BiomechanEngineer.java @@ -0,0 +1,49 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.LanderToken; +import mage.game.permanent.token.RobotToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BiomechanEngineer extends CardImpl { + + public BiomechanEngineer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When this creature enters, create a Lander token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new LanderToken()))); + + // {8}: Draw two cards and create a 2/2 colorless Robot artifact creature token. + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(2), new GenericManaCost(8)); + ability.addEffect(new CreateTokenEffect(new RobotToken()).concatBy("and")); + this.addAbility(ability); + } + + private BiomechanEngineer(final BiomechanEngineer card) { + super(card); + } + + @Override + public BiomechanEngineer copy() { + return new BiomechanEngineer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BiosynthicBurst.java b/Mage.Sets/src/mage/cards/b/BiosynthicBurst.java new file mode 100644 index 00000000000..05c5b7e7c58 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BiosynthicBurst.java @@ -0,0 +1,42 @@ +package mage.cards.b; + +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BiosynthicBurst extends CardImpl { + + public BiosynthicBurst(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // Put a +1/+1 counter on target creature you control. It gains reach, trample, and indestructible until end of turn. Untap it. + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(ReachAbility.getInstance()).setText("it gains reach")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance()).setText(", trample")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(IndestructibleAbility.getInstance()).setText(", and indestructible until end of turn")); + this.getSpellAbility().addEffect(new UntapTargetEffect("untap it")); + } + + private BiosynthicBurst(final BiosynthicBurst card) { + super(card); + } + + @Override + public BiosynthicBurst copy() { + return new BiosynthicBurst(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BiotechSpecialist.java b/Mage.Sets/src/mage/cards/b/BiotechSpecialist.java new file mode 100644 index 00000000000..3a070aa68cc --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BiotechSpecialist.java @@ -0,0 +1,51 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.LanderToken; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BiotechSpecialist extends CardImpl { + + public BiotechSpecialist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.SCIENTIST); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // When this creature enters, create a Lander token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new LanderToken()))); + + // Whenever you sacrifice an artifact, this creature deals 2 damage to target opponent. + Ability ability = new SacrificePermanentTriggeredAbility( + new DamageTargetEffect(2), StaticFilters.FILTER_PERMANENT_ARTIFACT + ); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private BiotechSpecialist(final BiotechSpecialist card) { + super(card); + } + + @Override + public BiotechSpecialist copy() { + return new BiotechSpecialist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BladeOfTheSwarm.java b/Mage.Sets/src/mage/cards/b/BladeOfTheSwarm.java new file mode 100644 index 00000000000..75aa25c944e --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BladeOfTheSwarm.java @@ -0,0 +1,60 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.target.common.TargetCardInExile; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BladeOfTheSwarm extends CardImpl { + + private static final FilterCard filter = new FilterCard("exiled card with warp"); + + static { + filter.add(new AbilityPredicate(WarpAbility.class)); + } + + public BladeOfTheSwarm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // When this creature enters, choose one -- + // * Put two +1/+1 counters on this creature. + Ability ability = new EntersBattlefieldTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)) + ); + + // * Put target exiled card with warp on the bottom of its owner's library. + ability.addMode(new Mode(new PutOnLibraryTargetEffect(false)) + .addTarget(new TargetCardInExile(filter))); + this.addAbility(ability); + } + + private BladeOfTheSwarm(final BladeOfTheSwarm card) { + super(card); + } + + @Override + public BladeOfTheSwarm copy() { + return new BladeOfTheSwarm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BloomingStinger.java b/Mage.Sets/src/mage/cards/b/BloomingStinger.java new file mode 100644 index 00000000000..f43cc4ad3c1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BloomingStinger.java @@ -0,0 +1,47 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.DeathtouchAbility; +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 BloomingStinger extends CardImpl { + + public BloomingStinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.SCORPION); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // When this creature enters, another target creature you control gains deathtouch until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainAbilityTargetEffect(DeathtouchAbility.getInstance())); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + } + + private BloomingStinger(final BloomingStinger card) { + super(card); + } + + @Override + public BloomingStinger copy() { + return new BloomingStinger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BoosterTutor.java b/Mage.Sets/src/mage/cards/b/BoosterTutor.java index 99c9ca6c21b..de4d70bdc37 100644 --- a/Mage.Sets/src/mage/cards/b/BoosterTutor.java +++ b/Mage.Sets/src/mage/cards/b/BoosterTutor.java @@ -91,7 +91,7 @@ class BoosterTutorEffect extends OneShotEffect { game.loadCards(cardsToLoad, controller.getId()); CardsImpl cards = new CardsImpl(); cards.addAllCards(boosterPack); - if (controller.choose(Outcome.Benefit, cards, targetCard, source, game)) { + if (controller.choose(Outcome.PutCardInPlay, cards, targetCard, source, game)) { Card card = game.getCard(targetCard.getFirstTarget()); if (card != null) { controller.moveCards(card, Zone.HAND, source, game); diff --git a/Mage.Sets/src/mage/cards/b/BowOfNylea.java b/Mage.Sets/src/mage/cards/b/BowOfNylea.java index 967859d2bb2..757d99258fa 100644 --- a/Mage.Sets/src/mage/cards/b/BowOfNylea.java +++ b/Mage.Sets/src/mage/cards/b/BowOfNylea.java @@ -1,6 +1,5 @@ package mage.cards.b; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.SimpleActivatedAbility; @@ -13,30 +12,26 @@ import mage.abilities.effects.common.PutOnLibraryTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.keyword.DeathtouchAbility; -import mage.abilities.keyword.FlyingAbility; -import mage.cards.*; -import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SuperType; import mage.counters.CounterType; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; import mage.target.TargetPermanent; import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class BowOfNylea extends CardImpl { - private static final FilterCreaturePermanent filterFlying = new FilterCreaturePermanent("creature with flying"); - static { - filterFlying.add(new AbilityPredicate(FlyingAbility.class)); - } - public BowOfNylea(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT,CardType.ARTIFACT},"{1}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.ARTIFACT}, "{1}{G}{G}"); this.supertype.add(SuperType.LEGENDARY); // Attacking creatures you control have deathtouch. @@ -50,7 +45,7 @@ public final class BowOfNylea extends CardImpl { ability.addCost(new TapSourceCost()); // or Bow of Nylea deals 2 damage to target creature with flying; Mode mode = new Mode(new DamageTargetEffect(2)); - mode.addTarget(new TargetPermanent(filterFlying)); + mode.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); ability.addMode(mode); // or you gain 3 life; mode = new Mode(new GainLifeEffect(3)); diff --git a/Mage.Sets/src/mage/cards/b/Bramblecrush.java b/Mage.Sets/src/mage/cards/b/Bramblecrush.java index 95bab148a8f..0bd4a81d24f 100644 --- a/Mage.Sets/src/mage/cards/b/Bramblecrush.java +++ b/Mage.Sets/src/mage/cards/b/Bramblecrush.java @@ -1,34 +1,25 @@ - package mage.cards.b; -import java.util.UUID; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; +import java.util.UUID; + /** - * * @author North */ public final class Bramblecrush extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public Bramblecrush(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{G}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}{G}"); // Destroy target noncreature permanent. - this.getSpellAbility().addTarget(new TargetPermanent(filter)); this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); } private Bramblecrush(final Bramblecrush card) { diff --git a/Mage.Sets/src/mage/cards/b/BranchingBolt.java b/Mage.Sets/src/mage/cards/b/BranchingBolt.java index f9da1d99e24..6457f0efe34 100644 --- a/Mage.Sets/src/mage/cards/b/BranchingBolt.java +++ b/Mage.Sets/src/mage/cards/b/BranchingBolt.java @@ -1,21 +1,16 @@ package mage.cards.b; -import mage.abilities.Ability; import mage.abilities.Mode; -import mage.abilities.effects.OneShotEffect; 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.Outcome; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -24,11 +19,9 @@ import java.util.UUID; */ public final class BranchingBolt extends CardImpl { - private static final FilterCreaturePermanent filterFlying = new FilterCreaturePermanent("creature with flying"); private static final FilterCreaturePermanent filterNotFlying = new FilterCreaturePermanent("creature without flying"); static { - filterFlying.add(new AbilityPredicate(FlyingAbility.class)); filterNotFlying.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); } @@ -40,7 +33,7 @@ public final class BranchingBolt extends CardImpl { this.getSpellAbility().getModes().setMaxModes(2); // Branching Bolt deals 3 damage to target creature with flying; this.getSpellAbility().addEffect(new DamageTargetEffect(3)); - this.getSpellAbility().addTarget(new TargetPermanent(filterFlying).withChooseHint("deals 3 damage, without flying")); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING).withChooseHint("deals 3 damage, without flying")); // or Branching Bolt deals 3 damage to target creature without flying. Mode mode = new Mode(new DamageTargetEffect(3)); mode.addTarget(new TargetPermanent(filterNotFlying).withChooseHint("deals 3 damage, without flying")); @@ -56,34 +49,3 @@ public final class BranchingBolt extends CardImpl { return new BranchingBolt(this); } } - -class BranchingBoltEffect extends OneShotEffect { - - BranchingBoltEffect() { - super(Outcome.Damage); - this.staticText = "{this} deals 3 damage to target creature with flying and to target creature without flying"; - } - - private BranchingBoltEffect(final BranchingBoltEffect effect) { - super(effect); - } - - @Override - public BranchingBoltEffect copy() { - return new BranchingBoltEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null) { - permanent.damage(3, source.getSourceId(), source, game, false, true); - } - - permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - if (permanent != null) { - permanent.damage(3, source.getSourceId(), source, game, false, true); - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BrightspearZealot.java b/Mage.Sets/src/mage/cards/b/BrightspearZealot.java new file mode 100644 index 00000000000..539494f14df --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrightspearZealot.java @@ -0,0 +1,65 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BrightspearZealot extends CardImpl { + + public BrightspearZealot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // This creature gets +2/+0 as long as you've cast two or more spells this turn. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostSourceEffect(2, 0, Duration.WhileOnBattlefield), + BrightspearZealotCondition.instance, "{this} gets +2/+0 as long " + + "as you've cast two or more spells this turn" + ))); + } + + private BrightspearZealot(final BrightspearZealot card) { + super(card); + } + + @Override + public BrightspearZealot copy() { + return new BrightspearZealot(this); + } +} + +enum BrightspearZealotCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getState() + .getWatcher(SpellsCastWatcher.class) + .getSpellsCastThisTurn(source.getControllerId()) + .size() >= 2; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BroodguardElite.java b/Mage.Sets/src/mage/cards/b/BroodguardElite.java new file mode 100644 index 00000000000..ce951750071 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BroodguardElite.java @@ -0,0 +1,52 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; +import mage.abilities.effects.common.PutSourceCountersOnTargetEffect; +import mage.abilities.keyword.WarpAbility; +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 BroodguardElite extends CardImpl { + + public BroodguardElite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}{G}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // This creature enters with X+1/+1 counters on it. + this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance()))); + + // When this creature leaves the battlefield, put its counters on target creature you control. + Ability ability = new LeavesBattlefieldTriggeredAbility(new PutSourceCountersOnTargetEffect()); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + + // Warp {X}{G} + this.addAbility(new WarpAbility(this, "{X}{G}")); + } + + private BroodguardElite(final BroodguardElite card) { + super(card); + } + + @Override + public BroodguardElite copy() { + return new BroodguardElite(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BrutalizerExarch.java b/Mage.Sets/src/mage/cards/b/BrutalizerExarch.java index ec16f001d68..127581eac58 100644 --- a/Mage.Sets/src/mage/cards/b/BrutalizerExarch.java +++ b/Mage.Sets/src/mage/cards/b/BrutalizerExarch.java @@ -1,41 +1,29 @@ - package mage.cards.b; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PutOnLibraryTargetEffect; import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; -import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; -import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author North */ public final class BrutalizerExarch extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public BrutalizerExarch(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}"); this.subtype.add(SubType.PHYREXIAN); this.subtype.add(SubType.CLERIC); @@ -44,12 +32,13 @@ public final class BrutalizerExarch extends CardImpl { // When Brutalizer Exarch enters the battlefield, choose one // - Search your library for a creature card, reveal it, then shuffle your library and put that card on top of it; - TargetCardInLibrary target = new TargetCardInLibrary(new FilterCreatureCard("a creature card")); - Ability ability = new EntersBattlefieldTriggeredAbility(new SearchLibraryPutOnLibraryEffect(target, true), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new SearchLibraryPutOnLibraryEffect( + new TargetCardInLibrary(new FilterCreatureCard("a creature card")), true + ), false); + // or put target noncreature permanent on the bottom of its owner's library. - Mode mode = new Mode(new BrutalizerExarchEffect2()); - mode.addTarget(new TargetPermanent(filter)); - ability.addMode(mode); + ability.addMode(new Mode(new PutOnLibraryTargetEffect(false)) + .addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE))); this.addAbility(ability); } @@ -62,30 +51,3 @@ public final class BrutalizerExarch extends CardImpl { return new BrutalizerExarch(this); } } - -class BrutalizerExarchEffect2 extends OneShotEffect { - - public BrutalizerExarchEffect2() { - super(Outcome.Removal); - this.staticText = "put target noncreature permanent on the bottom of its owner's library"; - } - - private BrutalizerExarchEffect2(final BrutalizerExarchEffect2 effect) { - super(effect); - } - - @Override - public BrutalizerExarchEffect2 copy() { - return new BrutalizerExarchEffect2(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - Player controller = game.getPlayer(source.getControllerId()); - if (permanent != null && controller != null) { - return controller.putCardsOnBottomOfLibrary(permanent, game, source); - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BurningPalmEfreet.java b/Mage.Sets/src/mage/cards/b/BurningPalmEfreet.java index 99f881cc6ac..eb0c14a14c4 100644 --- a/Mage.Sets/src/mage/cards/b/BurningPalmEfreet.java +++ b/Mage.Sets/src/mage/cards/b/BurningPalmEfreet.java @@ -1,7 +1,6 @@ package mage.cards.b; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -9,28 +8,21 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.LoseAbilityTargetEffect; import mage.abilities.keyword.FlyingAbility; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class BurningPalmEfreet extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public BurningPalmEfreet(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); @@ -43,7 +35,7 @@ public final class BurningPalmEfreet extends CardImpl { ability.addEffect(new LoseAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn) .setText("and that creature loses flying until end of turn") ); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/b/BurrowguardMentor.java b/Mage.Sets/src/mage/cards/b/BurrowguardMentor.java index ae3a5475edd..90296137ed9 100644 --- a/Mage.Sets/src/mage/cards/b/BurrowguardMentor.java +++ b/Mage.Sets/src/mage/cards/b/BurrowguardMentor.java @@ -32,7 +32,7 @@ public final class BurrowguardMentor extends CardImpl { // Burrowguard Mentor's power and toughness are each equal to the number of creatures you control. this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance) + Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL) ).addHint(CreaturesYouControlHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/b/BygoneColossus.java b/Mage.Sets/src/mage/cards/b/BygoneColossus.java new file mode 100644 index 00000000000..1c6ed0ba743 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BygoneColossus.java @@ -0,0 +1,37 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.keyword.WarpAbility; +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 BygoneColossus extends CardImpl { + + public BygoneColossus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{9}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.GIANT); + this.power = new MageInt(9); + this.toughness = new MageInt(9); + + // Warp {3} + this.addAbility(new WarpAbility(this, "{3}")); + } + + private BygoneColossus(final BygoneColossus card) { + super(card); + } + + @Override + public BygoneColossus copy() { + return new BygoneColossus(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CabarettiCharm.java b/Mage.Sets/src/mage/cards/c/CabarettiCharm.java index 3c30af0dc40..4294f8c2a06 100644 --- a/Mage.Sets/src/mage/cards/c/CabarettiCharm.java +++ b/Mage.Sets/src/mage/cards/c/CabarettiCharm.java @@ -28,7 +28,7 @@ public final class CabarettiCharm extends CardImpl { // Choose one — // • Cabaretti Charm deals damage equal to the number of creatures you control to target creature or planeswalker. - this.getSpellAbility().addEffect(new DamageTargetEffect(CreaturesYouControlCount.instance) + this.getSpellAbility().addEffect(new DamageTargetEffect(CreaturesYouControlCount.PLURAL) .setText("{this} deals damage equal to the number of creatures you control to target creature or planeswalker")); this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); this.getSpellAbility().addHint(CreaturesYouControlHint.instance); diff --git a/Mage.Sets/src/mage/cards/c/CanopySurge.java b/Mage.Sets/src/mage/cards/c/CanopySurge.java index 5480c699a8f..db363effba3 100644 --- a/Mage.Sets/src/mage/cards/c/CanopySurge.java +++ b/Mage.Sets/src/mage/cards/c/CanopySurge.java @@ -1,39 +1,31 @@ package mage.cards.c; -import java.util.UUID; import mage.abilities.condition.common.KickedCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.DamageEverythingEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.KickerAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LoneFox */ public final class CanopySurge extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public CanopySurge(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); // Kicker {2} this.addAbility(new KickerAbility("{2}")); // Canopy Surge deals 1 damage to each creature with flying and each player. If Canopy Surge was kicked, it deals 4 damage to each creature with flying and each player instead. - this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new DamageEverythingEffect(4, filter), - new DamageEverythingEffect(1, filter), KickedCondition.ONCE, - "{this} deals 1 damage to each creature with flying and each player. If this spell was kicked, it deals 4 damage to each creature with flying and each player instead.")); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new DamageEverythingEffect(4, StaticFilters.FILTER_CREATURE_FLYING), + new DamageEverythingEffect(1, StaticFilters.FILTER_CREATURE_FLYING), KickedCondition.ONCE, + "{this} deals 1 damage to each creature with flying and each player. If this spell was kicked, it deals 4 damage to each creature with flying and each player instead.")); } private CanopySurge(final CanopySurge card) { diff --git a/Mage.Sets/src/mage/cards/c/CarnivalCarnage.java b/Mage.Sets/src/mage/cards/c/CarnivalCarnage.java index cc765727975..8ff9875068c 100644 --- a/Mage.Sets/src/mage/cards/c/CarnivalCarnage.java +++ b/Mage.Sets/src/mage/cards/c/CarnivalCarnage.java @@ -1,17 +1,12 @@ package mage.cards.c; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetControllerEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.discard.DiscardTargetEffect; import mage.cards.CardSetInfo; import mage.cards.SplitCard; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SpellAbilityType; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetCreatureOrPlaneswalker; import mage.target.common.TargetOpponent; @@ -27,7 +22,8 @@ public final class CarnivalCarnage extends SplitCard { // Carnival // Carnival deals 1 damage to target creature or planeswalker and 1 damage to that permanent's controller. - this.getLeftHalfCard().getSpellAbility().addEffect(new CarnivalEffect()); + this.getLeftHalfCard().getSpellAbility().addEffect(new DamageTargetEffect(1)); + this.getLeftHalfCard().getSpellAbility().addEffect(new DamageTargetControllerEffect(1).setText("and 1 damage to that permanent's controller")); this.getLeftHalfCard().getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); // Carnage @@ -48,35 +44,3 @@ public final class CarnivalCarnage extends SplitCard { return new CarnivalCarnage(this); } } - -class CarnivalEffect extends OneShotEffect { - - CarnivalEffect() { - super(Outcome.Benefit); - staticText = "{this} deals 1 damage to target creature or planeswalker " + - "and 1 damage to that permanent's controller"; - } - - private CarnivalEffect(final CarnivalEffect effect) { - super(effect); - } - - @Override - public CarnivalEffect copy() { - return new CarnivalEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent == null) { - return false; - } - permanent.damage(1, source.getSourceId(), source, game); - Player player = game.getPlayer(permanent.getControllerId()); - if (player != null) { - player.damage(1, source.getSourceId(), source, game); - } - return true; - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/CentaurArcher.java b/Mage.Sets/src/mage/cards/c/CentaurArcher.java index 5eda6670a21..e109c004410 100644 --- a/Mage.Sets/src/mage/cards/c/CentaurArcher.java +++ b/Mage.Sets/src/mage/cards/c/CentaurArcher.java @@ -1,37 +1,27 @@ 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.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.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author anonymous */ public final class CentaurArcher extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public CentaurArcher(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{R}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}"); this.subtype.add(SubType.CENTAUR); this.subtype.add(SubType.ARCHER); @@ -40,7 +30,7 @@ public final class CentaurArcher extends CardImpl { // {tap}: Centaur Archer deals 1 damage to target creature with flying. Ability activatedAbility = new SimpleActivatedAbility(new DamageTargetEffect(1), new TapSourceCost()); - activatedAbility.addTarget(new TargetPermanent(filter)); + activatedAbility.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(activatedAbility); } diff --git a/Mage.Sets/src/mage/cards/c/CerebralDownload.java b/Mage.Sets/src/mage/cards/c/CerebralDownload.java new file mode 100644 index 00000000000..d7e427f5e9d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CerebralDownload.java @@ -0,0 +1,68 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.common.ArtifactYouControlHint; +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 java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CerebralDownload extends CardImpl { + + public CerebralDownload(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{U}"); + + // Surveil X, where X is the number of artifacts you control. Then draw three cards. + this.getSpellAbility().addEffect(new CerebralDownloadEffect()); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(3).concatBy("Then")); + this.getSpellAbility().addHint(ArtifactYouControlHint.instance); + } + + private CerebralDownload(final CerebralDownload card) { + super(card); + } + + @Override + public CerebralDownload copy() { + return new CerebralDownload(this); + } +} + +class CerebralDownloadEffect extends OneShotEffect { + + CerebralDownloadEffect() { + super(Outcome.Benefit); + staticText = "surveil X, where X is the number of artifacts you control"; + } + + private CerebralDownloadEffect(final CerebralDownloadEffect effect) { + super(effect); + } + + @Override + public CerebralDownloadEffect copy() { + return new CerebralDownloadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .filter(player -> player.surveil( + ArtifactYouControlCount.instance.calculate(game, source, this), source, game + )) + .isPresent(); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChainOfAcid.java b/Mage.Sets/src/mage/cards/c/ChainOfAcid.java index fc6f674bb20..93fe16a9805 100644 --- a/Mage.Sets/src/mage/cards/c/ChainOfAcid.java +++ b/Mage.Sets/src/mage/cards/c/ChainOfAcid.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; @@ -10,8 +8,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.Spell; @@ -19,24 +16,19 @@ import mage.players.Player; import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class ChainOfAcid extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public ChainOfAcid(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); // Destroy target noncreature permanent. Then that permanent's controller may copy this spell and may choose a new target for that copy. this.getSpellAbility().addEffect(new ChainOfAcidEffect()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); } private ChainOfAcid(final ChainOfAcid card) { diff --git a/Mage.Sets/src/mage/cards/c/ChancellorOfTheForge.java b/Mage.Sets/src/mage/cards/c/ChancellorOfTheForge.java index a63ff787882..8b975523988 100644 --- a/Mage.Sets/src/mage/cards/c/ChancellorOfTheForge.java +++ b/Mage.Sets/src/mage/cards/c/ChancellorOfTheForge.java @@ -36,7 +36,7 @@ public final class ChancellorOfTheForge extends CardImpl { this.addAbility(new ChancellorAbility(new ChancellorOfTheForgeDelayedTriggeredAbility(), abilityText)); // When Chancellor of the Forge enters the battlefield, create X 1/1 red Goblin creature tokens with haste, where X is the number of creatures you control. - this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new PhyrexianGoblinHasteToken(), CreaturesYouControlCount.instance), false) + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new PhyrexianGoblinHasteToken(), CreaturesYouControlCount.PLURAL), false) .addHint(CreaturesYouControlHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/c/ChargeOfTheMites.java b/Mage.Sets/src/mage/cards/c/ChargeOfTheMites.java index 0bdea508ff8..ffa44cb6174 100644 --- a/Mage.Sets/src/mage/cards/c/ChargeOfTheMites.java +++ b/Mage.Sets/src/mage/cards/c/ChargeOfTheMites.java @@ -23,7 +23,7 @@ public final class ChargeOfTheMites extends CardImpl { // Choose one-- // * Charge of the Mites deals damage equal to the number of creatures you control to target creature or planeswalker. - this.getSpellAbility().addEffect(new DamageTargetEffect(CreaturesYouControlCount.instance) + this.getSpellAbility().addEffect(new DamageTargetEffect(CreaturesYouControlCount.PLURAL) .setText("{this} deals damage equal to the number of creatures you control to target creature or planeswalker")); this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); this.getSpellAbility().addHint(CreaturesYouControlHint.instance); diff --git a/Mage.Sets/src/mage/cards/c/ChooseYourWeapon.java b/Mage.Sets/src/mage/cards/c/ChooseYourWeapon.java index 7d60685c5da..98a41a400f8 100644 --- a/Mage.Sets/src/mage/cards/c/ChooseYourWeapon.java +++ b/Mage.Sets/src/mage/cards/c/ChooseYourWeapon.java @@ -5,15 +5,12 @@ import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; @@ -26,12 +23,6 @@ import java.util.UUID; */ public final class ChooseYourWeapon extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public ChooseYourWeapon(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); @@ -43,7 +34,7 @@ public final class ChooseYourWeapon extends CardImpl { // • Archery — This spell deals 5 damage to target creature with flying. Mode mode = new Mode(new DamageTargetEffect(5, "this spell")); - mode.addTarget(new TargetPermanent(filter)); + mode.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.getSpellAbility().addMode(mode.withFlavorWord("Archery")); } diff --git a/Mage.Sets/src/mage/cards/c/ChoraleOfTheVoid.java b/Mage.Sets/src/mage/cards/c/ChoraleOfTheVoid.java new file mode 100644 index 00000000000..0b39cf6ad7c --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChoraleOfTheVoid.java @@ -0,0 +1,89 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksAttachedTriggeredAbility; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.SacrificeSourceUnlessConditionEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.watchers.common.VoidWatcher; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChoraleOfTheVoid extends CardImpl { + + private static final FilterCard filter = new FilterCreatureCard("creature card from defending player's graveyard"); + + static { + filter.add(ChoraleOfTheVoidPredicate.instance); + } + + public ChoraleOfTheVoid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature you control + TargetPermanent auraTarget = new TargetControlledCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Whenever enchanted creature attacks, put target creature card from defending player's graveyard onto the battlefield under your control tapped and attacking. + Ability ability = new AttacksAttachedTriggeredAbility( + new ReturnFromGraveyardToBattlefieldTargetEffect(true, true), + AttachmentType.AURA, false + ); + ability.addTarget(new TargetCardInGraveyard(filter)); + this.addAbility(ability); + + // Void -- At the beginning of your end step, sacrifice this Aura unless a nonland permanent left the battlefield this turn or a spell was warped this turn. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new SacrificeSourceUnlessConditionEffect(VoidCondition.instance) + ).setAbilityWord(AbilityWord.VOID).addHint(VoidCondition.getHint()), new VoidWatcher()); + } + + private ChoraleOfTheVoid(final ChoraleOfTheVoid card) { + super(card); + } + + @Override + public ChoraleOfTheVoid copy() { + return new ChoraleOfTheVoid(this); + } +} + +enum ChoraleOfTheVoidPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return Optional + .ofNullable(input) + .map(ObjectSourcePlayer::getSource) + .map(source -> source.getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .map(attachmentId -> game.getCombat().getDefendingPlayerId(attachmentId, game)) + .filter(playerId -> input.getObject().isOwnedBy(playerId)) + .isPresent(); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChorusOfMight.java b/Mage.Sets/src/mage/cards/c/ChorusOfMight.java index 86ab6a7b3b7..3b6d1cab55d 100644 --- a/Mage.Sets/src/mage/cards/c/ChorusOfMight.java +++ b/Mage.Sets/src/mage/cards/c/ChorusOfMight.java @@ -23,7 +23,7 @@ public final class ChorusOfMight extends CardImpl { // Until end of turn, target creature gets +1/+1 for each creature you control and gains trample. this.getSpellAbility().addTarget(new TargetCreaturePermanent()); - this.getSpellAbility().addEffect(new BoostTargetEffect(CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn) + this.getSpellAbility().addEffect(new BoostTargetEffect(CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn) .setText("until end of turn, target creature gets +1/+1 for each creature you control") ); this.getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn) diff --git a/Mage.Sets/src/mage/cards/c/ChromeCompanion.java b/Mage.Sets/src/mage/cards/c/ChromeCompanion.java new file mode 100644 index 00000000000..ca4b07a39b2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChromeCompanion.java @@ -0,0 +1,49 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChromeCompanion extends CardImpl { + + public ChromeCompanion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.subtype.add(SubType.DOG); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Whenever this creature becomes tapped, you gain 1 life. + this.addAbility(new BecomesTappedSourceTriggeredAbility(new GainLifeEffect(1))); + + // {2}, {T}: Put target card from a graveyard on the bottom of its owner's library. + Ability ability = new SimpleActivatedAbility(new PutOnLibraryTargetEffect(false), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCardInGraveyard()); + this.addAbility(ability); + } + + private ChromeCompanion(final ChromeCompanion card) { + super(card); + } + + @Override + public ChromeCompanion copy() { + return new ChromeCompanion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CircleOfFlame.java b/Mage.Sets/src/mage/cards/c/CircleOfFlame.java index fb8c50281f6..dd30ff8cd15 100644 --- a/Mage.Sets/src/mage/cards/c/CircleOfFlame.java +++ b/Mage.Sets/src/mage/cards/c/CircleOfFlame.java @@ -30,7 +30,7 @@ public final class CircleOfFlame extends CardImpl { // Whenever a creature without flying attacks you or a planeswalker you control, Circle of Flame deals 1 damage to that creature. - this.addAbility(new AttacksAllTriggeredAbility(new DamageTargetEffect(1), false, + this.addAbility(new AttacksAllTriggeredAbility(new DamageTargetEffect(1).withTargetDescription("that creature"), false, filter, SetTargetPointer.PERMANENT, true)); } diff --git a/Mage.Sets/src/mage/cards/c/ClanDefiance.java b/Mage.Sets/src/mage/cards/c/ClanDefiance.java index a3d04ea8ba7..609475d0019 100644 --- a/Mage.Sets/src/mage/cards/c/ClanDefiance.java +++ b/Mage.Sets/src/mage/cards/c/ClanDefiance.java @@ -7,11 +7,11 @@ import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetPlayerOrPlaneswalker; import java.util.UUID; @@ -21,11 +21,9 @@ import java.util.UUID; */ public final class ClanDefiance extends CardImpl { - static final private FilterCreaturePermanent filterFlying = new FilterCreaturePermanent("creature with flying"); static final private FilterCreaturePermanent filterWithoutFlying = new FilterCreaturePermanent("creature without flying"); static { - filterFlying.add(new AbilityPredicate(FlyingAbility.class)); filterWithoutFlying.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); } @@ -37,7 +35,7 @@ public final class ClanDefiance extends CardImpl { this.getSpellAbility().getModes().setMaxModes(3); // Clan Defiance deals X damage to target creature with flying; this.getSpellAbility().addEffect(new DamageTargetEffect(GetXValue.instance)); - this.getSpellAbility().addTarget(new TargetPermanent(filterFlying).withChooseHint("deals X damage, with flying")); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING).withChooseHint("deals X damage, with flying")); // Clan Defiance deals X damage to target creature without flying; Mode mode1 = new Mode(new DamageTargetEffect(GetXValue.instance)); mode1.addTarget(new TargetPermanent(filterWithoutFlying).withChooseHint("deals X damage, without flying")); diff --git a/Mage.Sets/src/mage/cards/c/ClawsOfWirewood.java b/Mage.Sets/src/mage/cards/c/ClawsOfWirewood.java index fb6c2330ab6..c8dabe2fce9 100644 --- a/Mage.Sets/src/mage/cards/c/ClawsOfWirewood.java +++ b/Mage.Sets/src/mage/cards/c/ClawsOfWirewood.java @@ -1,40 +1,33 @@ package mage.cards.c; -import java.util.UUID; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.keyword.CyclingAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class ClawsOfWirewood extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public ClawsOfWirewood(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); // Claws of Wirewood deals 3 damage to each creature with flying and each player. - this.getSpellAbility().addEffect(new DamageAllEffect(3, filter)); + this.getSpellAbility().addEffect(new DamageAllEffect(3, StaticFilters.FILTER_CREATURE_FLYING)); Effect effect = new DamagePlayersEffect(3); effect.setText("and each player"); this.getSpellAbility().addEffect(effect); + // Cycling {2} this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}"))); } diff --git a/Mage.Sets/src/mage/cards/c/ClipWings.java b/Mage.Sets/src/mage/cards/c/ClipWings.java index 40e5ea7c351..3f7c10acc26 100644 --- a/Mage.Sets/src/mage/cards/c/ClipWings.java +++ b/Mage.Sets/src/mage/cards/c/ClipWings.java @@ -1,31 +1,23 @@ package mage.cards.c; -import java.util.UUID; import mage.abilities.effects.common.SacrificeOpponentsEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author fireshoes */ public final class ClipWings extends CardImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } public ClipWings(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Each opponent sacrifices a creature with flying. - this.getSpellAbility().addEffect(new SacrificeOpponentsEffect(filter) + this.getSpellAbility().addEffect(new SacrificeOpponentsEffect(StaticFilters.FILTER_CREATURE_FLYING) .setText("each opponent sacrifices a creature of their choice with flying")); } diff --git a/Mage.Sets/src/mage/cards/c/CloseEncounter.java b/Mage.Sets/src/mage/cards/c/CloseEncounter.java new file mode 100644 index 00000000000..012920b6b5b --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CloseEncounter.java @@ -0,0 +1,175 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CloseEncounter extends CardImpl { + + public CloseEncounter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // As an additional cost to cast this spell, choose a creature you control or a warped creature card you own in exile. + this.getSpellAbility().addCost(new CloseEncounterCost()); + + // Close Encounter deals damage equal to the power of the chosen creature or card to target creature. + this.getSpellAbility().addEffect(new DamageTargetEffect(CloseEncounterValue.instance) + .setText("{this} deals damage equal to the power of the chosen creature or card to target creature")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private CloseEncounter(final CloseEncounter card) { + super(card); + } + + @Override + public CloseEncounter copy() { + return new CloseEncounter(this); + } +} + +enum CloseEncounterValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return Optional + .ofNullable((Card) effect.getValue("closeEncounterCost")) + .map(MageObject::getPower) + .map(MageInt::getValue) + .orElse(0); + } + + @Override + public CloseEncounterValue copy() { + return this; + } + + @Override + public String getMessage() { + return "the power of the chosen creature or card"; + } + + @Override + public String toString() { + return "1"; + } +} + +class CloseEncounterCost extends CostImpl { + + private static final FilterCard filterCard = new FilterCreatureCard("warped creature card you own in exile"); + + public CloseEncounterCost() { + super(); + this.text = "choose a creature you control or a warped creature card you own in exile"; + } + + private CloseEncounterCost(final CloseEncounterCost cost) { + super(cost); + } + + @Override + public CloseEncounterCost copy() { + return new CloseEncounterCost(this); + } + + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + return Optional + .ofNullable(CardUtil.getExileZoneId(WarpAbility.makeWarpString(controllerId), game)) + .map(game.getExile()::getExileZone) + .filter(exileZone -> !exileZone.getCards(filterCard, game).isEmpty()) + .isPresent() + || game + .getBattlefield() + .contains(StaticFilters.FILTER_CONTROLLED_CREATURE, controllerId, source, game, 1); + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + Player player = game.getPlayer(controllerId); + if (player == null) { + paid = false; + return paid; + } + boolean hasPermanent = game + .getBattlefield() + .contains(StaticFilters.FILTER_CONTROLLED_CREATURE, controllerId, source, game, 1); + boolean hasWarp = Optional + .ofNullable(CardUtil.getExileZoneId(WarpAbility.makeWarpString(controllerId), game)) + .map(game.getExile()::getExileZone) + .filter(exileZone -> !exileZone.getCards(filterCard, game).isEmpty()) + .isPresent(); + boolean usePermanent; + if (hasPermanent && hasWarp) { + usePermanent = player.chooseUse( + Outcome.Neutral, "Choose a creature you control or a warped creature you own in exile?", + null, "Choose controlled", "Choose from exile", source, game + ); + } else if (hasPermanent) { + usePermanent = true; + } else if (hasWarp) { + usePermanent = false; + } else { + paid = false; + return paid; + } + if (usePermanent) { + TargetPermanent target = new TargetControlledCreaturePermanent(); + target.withNotTarget(true); + player.choose(Outcome.Neutral, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + paid = false; + return paid; + } + game.informPlayers(player.getLogName() + " chooses " + permanent.getLogName() + " on the battlefield"); + source.getEffects().setValue("closeEncounterCost", permanent); + paid = true; + return true; + } + TargetCard target = new TargetCardInExile( + filterCard, CardUtil.getExileZoneId(WarpAbility.makeWarpString(controllerId), game) + ); + player.choose(Outcome.Neutral, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + paid = false; + return paid; + } + game.informPlayers(player.getLogName() + " chooses " + card.getLogName() + " from exile"); + source.getEffects().setValue("closeEncounterCost", card); + paid = true; + return paid; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CloudsculptTechnician.java b/Mage.Sets/src/mage/cards/c/CloudsculptTechnician.java new file mode 100644 index 00000000000..f4eceed3997 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CloudsculptTechnician.java @@ -0,0 +1,55 @@ + +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class CloudsculptTechnician extends CardImpl { + + private static final Condition condition = + new PermanentsOnTheBattlefieldCondition(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT); + + public CloudsculptTechnician(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + this.subtype.add(SubType.JELLYFISH); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // As long as you control an artifact, this creature gets +1/+0. + this.addAbility(new SimpleStaticAbility( + new ConditionalContinuousEffect( + new BoostSourceEffect(1, 0, Duration.WhileOnBattlefield), + condition, "As long as you control an artifact, this creature gets +1/+0" + ) + )); + } + + private CloudsculptTechnician(final CloudsculptTechnician card) { + super(card); + } + + @Override + public CloudsculptTechnician copy() { + return new CloudsculptTechnician(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/Cloudthresher.java b/Mage.Sets/src/mage/cards/c/Cloudthresher.java index 7b16baa23f2..a3a02cefc8b 100644 --- a/Mage.Sets/src/mage/cards/c/Cloudthresher.java +++ b/Mage.Sets/src/mage/cards/c/Cloudthresher.java @@ -1,7 +1,6 @@ package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -9,28 +8,22 @@ import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.keyword.EvokeAbility; import mage.abilities.keyword.FlashAbility; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class Cloudthresher extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Cloudthresher(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{G}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}{G}{G}"); this.subtype.add(SubType.ELEMENTAL); this.power = new MageInt(7); @@ -38,12 +31,15 @@ public final class Cloudthresher extends CardImpl { // Flash this.addAbility(FlashAbility.getInstance()); + // Reach this.addAbility(ReachAbility.getInstance()); + // When Cloudthresher enters the battlefield, it deals 2 damage to each creature with flying and each player. - Ability ability = new EntersBattlefieldTriggeredAbility(new DamageAllEffect(2, "it", filter)); + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageAllEffect(2, "it", StaticFilters.FILTER_CREATURE_FLYING)); ability.addEffect(new DamagePlayersEffect(2).setText("and each player")); this.addAbility(ability); + // Evoke {2}{G}{G} this.addAbility(new EvokeAbility("{2}{G}{G}")); } diff --git a/Mage.Sets/src/mage/cards/c/CodecrackerHound.java b/Mage.Sets/src/mage/cards/c/CodecrackerHound.java new file mode 100644 index 00000000000..ff8e71a9131 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CodecrackerHound.java @@ -0,0 +1,44 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CodecrackerHound extends CardImpl { + + public CodecrackerHound(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.DOG); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // When this creature enters, look at the top two cards of your library. Put one into your hand and the other into your graveyard. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new LookLibraryAndPickControllerEffect(2, 1, PutCards.HAND, PutCards.GRAVEYARD) + )); + + // Warp {2}{U} + this.addAbility(new WarpAbility(this, "{2}{U}")); + } + + private CodecrackerHound(final CodecrackerHound card) { + super(card); + } + + @Override + public CodecrackerHound copy() { + return new CodecrackerHound(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CollectedConjuring.java b/Mage.Sets/src/mage/cards/c/CollectedConjuring.java index b838db5ce86..c38757e772b 100644 --- a/Mage.Sets/src/mage/cards/c/CollectedConjuring.java +++ b/Mage.Sets/src/mage/cards/c/CollectedConjuring.java @@ -76,6 +76,8 @@ class CollectedConjuringEffect extends OneShotEffect { } Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, 6)); controller.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); + cards.retainZone(Zone.EXILED, game); CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, filter, 2); controller.putCardsOnBottomOfLibrary(cards, game, source, false); return true; diff --git a/Mage.Sets/src/mage/cards/c/CollisionColossus.java b/Mage.Sets/src/mage/cards/c/CollisionColossus.java index 74a9473184c..5f627a704b4 100644 --- a/Mage.Sets/src/mage/cards/c/CollisionColossus.java +++ b/Mage.Sets/src/mage/cards/c/CollisionColossus.java @@ -3,16 +3,13 @@ package mage.cards.c; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardSetInfo; import mage.cards.SplitCard; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SpellAbilityType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -23,19 +20,13 @@ import java.util.UUID; */ public final class CollisionColossus extends SplitCard { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public CollisionColossus(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R/G}", "{R}{G}", SpellAbilityType.SPLIT); // Collision // Collision deals 6 damage to target creature with flying. this.getLeftHalfCard().getSpellAbility().addEffect(new DamageTargetEffect(6)); - this.getLeftHalfCard().getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getLeftHalfCard().getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); // Colossus // Target creature gets +4/+2 and gains trample until end of turn. diff --git a/Mage.Sets/src/mage/cards/c/CometCrawler.java b/Mage.Sets/src/mage/cards/c/CometCrawler.java new file mode 100644 index 00000000000..a83fc1dbfdc --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CometCrawler.java @@ -0,0 +1,49 @@ +package mage.cards.c; + +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.continuous.BoostSourceEffect; +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.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CometCrawler extends CardImpl { + + public CometCrawler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever this creature attacks, you may sacrifice another creature or artifact. If you do, this creature gets +2/+0 until end of turn. + this.addAbility(new AttacksTriggeredAbility(new DoIfCostPaid( + new BoostSourceEffect(2, 0, Duration.EndOfTurn), + new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT) + ))); + } + + private CometCrawler(final CometCrawler card) { + super(card); + } + + @Override + public CometCrawler copy() { + return new CometCrawler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CommandBridge.java b/Mage.Sets/src/mage/cards/c/CommandBridge.java new file mode 100644 index 00000000000..52bcb979169 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CommandBridge.java @@ -0,0 +1,43 @@ +package mage.cards.c; + +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CommandBridge extends CardImpl { + + public CommandBridge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When this land enters, sacrifice it unless you tap an untapped permanent you control. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SacrificeSourceUnlessPaysEffect(new TapTargetCost(StaticFilters.FILTER_CONTROLLED_UNTAPPED_PERMANENT)) + )); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + } + + private CommandBridge(final CommandBridge card) { + super(card); + } + + @Override + public CommandBridge copy() { + return new CommandBridge(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ConformerShuriken.java b/Mage.Sets/src/mage/cards/c/ConformerShuriken.java index 7a551915287..ad4eaea674c 100644 --- a/Mage.Sets/src/mage/cards/c/ConformerShuriken.java +++ b/Mage.Sets/src/mage/cards/c/ConformerShuriken.java @@ -86,7 +86,7 @@ class ConformerShurikenEffect extends OneShotEffect { .map(game::getPermanent) .map(MageObject::getPower) .map(MageInt::getValue) - .map(x -> permanent.getPower().getValue() - x) + .map(targetsPower -> targetsPower - permanent.getPower().getValue()) .filter(x -> x > 0 && permanent.addCounters(CounterType.P1P1.createInstance(x), source, game)) .isPresent(); } diff --git a/Mage.Sets/src/mage/cards/c/ConsultTheStarCharts.java b/Mage.Sets/src/mage/cards/c/ConsultTheStarCharts.java new file mode 100644 index 00000000000..6a2cfcbbe25 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ConsultTheStarCharts.java @@ -0,0 +1,44 @@ +package mage.cards.c; + +import mage.abilities.condition.common.KickedCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.ManaSpentToCastCount; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.KickerAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ConsultTheStarCharts extends CardImpl { + + public ConsultTheStarCharts(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Kicker {1}{U} + this.addAbility(new KickerAbility("{1}{U}")); + + // 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), + 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" + )); + } + + private ConsultTheStarCharts(final ConsultTheStarCharts card) { + super(card); + } + + @Override + public ConsultTheStarCharts copy() { + return new ConsultTheStarCharts(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CoordinatedManeuver.java b/Mage.Sets/src/mage/cards/c/CoordinatedManeuver.java index afd5cb71f31..0fe64cb6a99 100644 --- a/Mage.Sets/src/mage/cards/c/CoordinatedManeuver.java +++ b/Mage.Sets/src/mage/cards/c/CoordinatedManeuver.java @@ -23,7 +23,7 @@ public final class CoordinatedManeuver extends CardImpl { // Choose one -- // * Coordinated Maneuver deals damage equal to the number of creatures you control to target creature or planeswalker. - this.getSpellAbility().addEffect(new DamageTargetEffect(CreaturesYouControlCount.instance) + this.getSpellAbility().addEffect(new DamageTargetEffect(CreaturesYouControlCount.PLURAL) .setText("{this} deals damage equal to the number of creatures you control to target creature or planeswalker")); this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); this.getSpellAbility().addHint(CreaturesYouControlHint.instance); diff --git a/Mage.Sets/src/mage/cards/c/CorrosiveGale.java b/Mage.Sets/src/mage/cards/c/CorrosiveGale.java index 97b5cffea5c..75ea401177b 100644 --- a/Mage.Sets/src/mage/cards/c/CorrosiveGale.java +++ b/Mage.Sets/src/mage/cards/c/CorrosiveGale.java @@ -1,33 +1,24 @@ package mage.cards.c; -import java.util.UUID; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author North */ public final class CorrosiveGale extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public CorrosiveGale(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{X}{G/P}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{G/P}"); - - this.getSpellAbility().addEffect(new DamageAllEffect(GetXValue.instance, filter)); + this.getSpellAbility().addEffect(new DamageAllEffect(GetXValue.instance, StaticFilters.FILTER_CREATURE_FLYING)); } private CorrosiveGale(final CorrosiveGale card) { diff --git a/Mage.Sets/src/mage/cards/c/Cosmogoyf.java b/Mage.Sets/src/mage/cards/c/Cosmogoyf.java new file mode 100644 index 00000000000..ac336b28f9d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/Cosmogoyf.java @@ -0,0 +1,76 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessPlusOneSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Cosmogoyf extends CardImpl { + + public Cosmogoyf(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{G}"); + + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.LHURGOYF); + this.power = new MageInt(0); + this.toughness = new MageInt(1); + + // This creature's power is equal to the number of cards you own in exile and its toughness is equal to that number plus 1. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SetBasePowerToughnessPlusOneSourceEffect(CosmogoyfValue.instance) + ).addHint(CosmogoyfValue.instance.getHint())); + } + + private Cosmogoyf(final Cosmogoyf card) { + super(card); + } + + @Override + public Cosmogoyf copy() { + return new Cosmogoyf(this); + } +} + +enum CosmogoyfValue implements DynamicValue { + instance; + private static final Hint hint = new ValueHint("Card you own in exile", instance); + + public static Hint getHint() { + return hint; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game.getState().getExile().getAllCards(game, sourceAbility.getControllerId()).size(); + } + + @Override + public CosmogoyfValue copy() { + return this; + } + + @Override + public String getMessage() { + return "cards you own in exile"; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CosmograndZenith.java b/Mage.Sets/src/mage/cards/c/CosmograndZenith.java new file mode 100644 index 00000000000..8183b318673 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CosmograndZenith.java @@ -0,0 +1,51 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +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 mage.game.permanent.token.HumanSoldierToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CosmograndZenith extends CardImpl { + + public CosmograndZenith(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Whenever you cast your second spell each turn, choose one -- + // * Create two 1/1 white Human Soldier creature tokens. + Ability ability = new CastSecondSpellTriggeredAbility(new CreateTokenEffect(new HumanSoldierToken(), 2)); + + // * Put a +1/+1 counter on each creature you control. + ability.addMode(new Mode(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ))); + this.addAbility(ability); + } + + private CosmograndZenith(final CosmograndZenith card) { + super(card); + } + + @Override + public CosmograndZenith copy() { + return new CosmograndZenith(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CourtOfEmbereth.java b/Mage.Sets/src/mage/cards/c/CourtOfEmbereth.java index 2ee2e52d19b..ec950c739ab 100644 --- a/Mage.Sets/src/mage/cards/c/CourtOfEmbereth.java +++ b/Mage.Sets/src/mage/cards/c/CourtOfEmbereth.java @@ -36,7 +36,7 @@ public final class CourtOfEmbereth extends CardImpl { new CreateTokenEffect(new Knight31RedToken()) ); ability.addEffect(new ConditionalOneShotEffect( - new DamagePlayersEffect(CreaturesYouControlCount.instance, TargetController.OPPONENT) + new DamagePlayersEffect(CreaturesYouControlCount.PLURAL, TargetController.OPPONENT) .setText("{this} deals X damage to each opponent, where X is the number of creatures you control"), MonarchIsSourceControllerCondition.instance ).concatBy("Then")); diff --git a/Mage.Sets/src/mage/cards/c/CrashLanding.java b/Mage.Sets/src/mage/cards/c/CrashLanding.java index c075f6df546..fb375ac55bf 100644 --- a/Mage.Sets/src/mage/cards/c/CrashLanding.java +++ b/Mage.Sets/src/mage/cards/c/CrashLanding.java @@ -1,7 +1,6 @@ package mage.cards.c; -import java.util.UUID; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.DamageTargetEffect; @@ -12,24 +11,21 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class CrashLanding extends CardImpl { private static final FilterControlledPermanent filter = new FilterControlledPermanent("Forests you control"); - private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("creature with flying"); static { filter.add(SubType.FOREST.getPredicate()); - filter2.add(new AbilityPredicate(FlyingAbility.class)); } public CrashLanding(UUID ownerId, CardSetInfo setInfo) { @@ -40,7 +36,7 @@ public final class CrashLanding extends CardImpl { this.getSpellAbility().addEffect(new LoseAbilityTargetEffect( FlyingAbility.getInstance(), Duration.EndOfTurn)); this.getSpellAbility().addEffect(new DamageTargetEffect(amount).setText("{this} deals damage to that creature equal to the number of Forests you control")); - this.getSpellAbility().addTarget(new TargetPermanent(filter2)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private CrashLanding(final CrashLanding card) { diff --git a/Mage.Sets/src/mage/cards/c/CraterhoofBehemoth.java b/Mage.Sets/src/mage/cards/c/CraterhoofBehemoth.java index 61f68c52bed..2c74a17b113 100644 --- a/Mage.Sets/src/mage/cards/c/CraterhoofBehemoth.java +++ b/Mage.Sets/src/mage/cards/c/CraterhoofBehemoth.java @@ -37,7 +37,7 @@ public final class CraterhoofBehemoth extends CardImpl { TrampleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES ).setText("creatures you control gain trample")); ability.addEffect(new BoostControlledEffect( - CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, + CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES, false ).setText("and get +X/+X until end of turn, where X is the number of creatures you control")); ability.addHint(CreaturesYouControlHint.instance); diff --git a/Mage.Sets/src/mage/cards/c/CrusaderOfOdric.java b/Mage.Sets/src/mage/cards/c/CrusaderOfOdric.java index 038f0e4750a..0a4d84fdb42 100644 --- a/Mage.Sets/src/mage/cards/c/CrusaderOfOdric.java +++ b/Mage.Sets/src/mage/cards/c/CrusaderOfOdric.java @@ -27,7 +27,7 @@ public final class CrusaderOfOdric extends CardImpl { this.toughness = new MageInt(0); // Crusader of Odric's power and toughness are each equal to the number of creatures you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance)) + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL)) .addHint(CreaturesYouControlHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/c/CrushingCanopy.java b/Mage.Sets/src/mage/cards/c/CrushingCanopy.java index 7ca22a17ec1..0b4f3530ac5 100644 --- a/Mage.Sets/src/mage/cards/c/CrushingCanopy.java +++ b/Mage.Sets/src/mage/cards/c/CrushingCanopy.java @@ -1,38 +1,30 @@ package mage.cards.c; -import java.util.UUID; import mage.abilities.Mode; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetEnchantmentPermanent; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class CrushingCanopy extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public CrushingCanopy(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); // Choose one -- // * Destroy target creature with flying. - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.getSpellAbility().addEffect(new DestroyTargetEffect()); + // * Destroy target enchantment. Mode mode = new Mode(new DestroyTargetEffect()); mode.addTarget(new TargetEnchantmentPermanent()); diff --git a/Mage.Sets/src/mage/cards/c/CrushingVines.java b/Mage.Sets/src/mage/cards/c/CrushingVines.java index 43cc576d3d8..5f01d70cce4 100644 --- a/Mage.Sets/src/mage/cards/c/CrushingVines.java +++ b/Mage.Sets/src/mage/cards/c/CrushingVines.java @@ -1,37 +1,27 @@ package mage.cards.c; -import java.util.UUID; import mage.abilities.Mode; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetArtifactPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author North */ public final class CrushingVines extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public CrushingVines(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); // Choose one - Destroy target creature with flying; or destroy target artifact. - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.getSpellAbility().addEffect(new DestroyTargetEffect()); Mode mode = new Mode(new DestroyTargetEffect()); mode.addTarget(new TargetArtifactPermanent()); diff --git a/Mage.Sets/src/mage/cards/c/CryogenRelic.java b/Mage.Sets/src/mage/cards/c/CryogenRelic.java new file mode 100644 index 00000000000..1fc6de476cc --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CryogenRelic.java @@ -0,0 +1,53 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrLeavesSourceTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CryogenRelic extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("tapped creature"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + public CryogenRelic(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{U}"); + + // When this artifact enters or leaves the battlefield, draw a card. + this.addAbility(new EntersBattlefieldOrLeavesSourceTriggeredAbility(new DrawCardSourceControllerEffect(1), false)); + + // {1}{U}, Sacrifice this artifact: Put a stun counter on up to one target tapped creature. + Ability ability = new SimpleActivatedAbility(new AddCountersTargetEffect(CounterType.STUN.createInstance()), new ManaCostsImpl<>("{1}{U}")); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + } + + private CryogenRelic(final CryogenRelic card) { + super(card); + } + + @Override + public CryogenRelic copy() { + return new CryogenRelic(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/Cryoshatter.java b/Mage.Sets/src/mage/cards/c/Cryoshatter.java new file mode 100644 index 00000000000..d5cb2bc016f --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/Cryoshatter.java @@ -0,0 +1,53 @@ + +package mage.cards.c; + +import mage.abilities.common.BecomesTappedAttachedTriggeredAbility; +import mage.abilities.common.DealtDamageAttachedTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.meta.OrTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author Cryoshatter + */ +public final class Cryoshatter extends CardImpl { + + public Cryoshatter(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.UnboostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature gets -5/-0. + this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(-5, 0))); + + // When enchanted creature becomes tapped or is dealt damage, destroy it. + this.addAbility(new OrTriggeredAbility(Zone.BATTLEFIELD, new DestroyTargetEffect().setText("destroy it"), false, + "When enchanted creature becomes tapped or is dealt damage, ", + new BecomesTappedAttachedTriggeredAbility(null, "enchanted creature", false, SetTargetPointer.PERMANENT), + new DealtDamageAttachedTriggeredAbility(Zone.BATTLEFIELD, null, false, SetTargetPointer.PERMANENT))); + } + + private Cryoshatter(final Cryoshatter card) { + super(card); + } + + @Override + public Cryoshatter copy() { + return new Cryoshatter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CurseOfInertia.java b/Mage.Sets/src/mage/cards/c/CurseOfInertia.java index ee6c5120633..c31c6e391dc 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfInertia.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfInertia.java @@ -13,11 +13,11 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.TargetPlayer; +import mage.target.targetadjustment.DefineByTriggerTargetAdjuster; import java.util.UUID; @@ -56,6 +56,7 @@ class CurseOfInertiaTriggeredAbility extends TriggeredAbilityImpl { public CurseOfInertiaTriggeredAbility() { super(Zone.BATTLEFIELD, new CurseOfInertiaTapOrUntapTargetEffect(), false); + setTargetAdjuster(DefineByTriggerTargetAdjuster.instance); } private CurseOfInertiaTriggeredAbility(final CurseOfInertiaTriggeredAbility ability) { @@ -75,7 +76,8 @@ class CurseOfInertiaTriggeredAbility extends TriggeredAbilityImpl { && game.getCombat().getPlayerDefenders(game, false).contains(enchantment.getAttachedTo())) { TargetPermanent target = new TargetPermanent(); target.setTargetController(game.getCombat().getAttackingPlayerId()); - addTarget(target); + this.getTargets().clear(); + this.addTarget(target); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/c/CutPropulsion.java b/Mage.Sets/src/mage/cards/c/CutPropulsion.java new file mode 100644 index 00000000000..82b1c83d080 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CutPropulsion.java @@ -0,0 +1,65 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CutPropulsion extends CardImpl { + + public CutPropulsion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + // Target creature deals damage to itself equal to its power. If that creature has flying, it deals twice that much damage to itself instead. + this.getSpellAbility().addEffect(new CutPropulsionEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private CutPropulsion(final CutPropulsion card) { + super(card); + } + + @Override + public CutPropulsion copy() { + return new CutPropulsion(this); + } +} + +class CutPropulsionEffect extends OneShotEffect { + + CutPropulsionEffect() { + super(Outcome.Benefit); + staticText = "target creature deals damage to itself equal to its power. " + + "If that creature has flying, it deals twice that much damage to itself instead"; + } + + private CutPropulsionEffect(final CutPropulsionEffect effect) { + super(effect); + } + + @Override + public CutPropulsionEffect copy() { + return new CutPropulsionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + int damage = (permanent.getAbilities(game).containsClass(FlyingAbility.class) ? 2 : 1) * permanent.getPower().getValue(); + return damage > 0 && permanent.damage(damage, permanent.getId(), source, game) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CutRibbons.java b/Mage.Sets/src/mage/cards/c/CutRibbons.java index 46f1fa21685..b2e78bcde6d 100644 --- a/Mage.Sets/src/mage/cards/c/CutRibbons.java +++ b/Mage.Sets/src/mage/cards/c/CutRibbons.java @@ -1,6 +1,5 @@ package mage.cards.c; -import java.util.UUID; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; @@ -11,6 +10,8 @@ import mage.constants.CardType; import mage.constants.SpellAbilityType; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * @author Stravant */ @@ -22,7 +23,7 @@ public final class CutRibbons extends SplitCard { // Cut // Cut deals 4 damage to target creature. getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); - getLeftHalfCard().getSpellAbility().addEffect(new DamageTargetEffect(4).setText("Cut deals 4 damage to target creature")); + getLeftHalfCard().getSpellAbility().addEffect(new DamageTargetEffect(4).setText("{this} deals 4 damage to target creature")); // to // Ribbons diff --git a/Mage.Sets/src/mage/cards/d/DanceWithCalamity.java b/Mage.Sets/src/mage/cards/d/DanceWithCalamity.java index 5d947b3f73a..3b111b5bd05 100644 --- a/Mage.Sets/src/mage/cards/d/DanceWithCalamity.java +++ b/Mage.Sets/src/mage/cards/d/DanceWithCalamity.java @@ -78,6 +78,8 @@ class DanceWithCalamityEffect extends OneShotEffect { player.moveCards(card, Zone.EXILED, source, game); cards.add(card); } + game.processAction(); + cards.retainZone(Zone.EXILED, game); if (cards .getCards(game) .stream() diff --git a/Mage.Sets/src/mage/cards/d/DarkEndurance.java b/Mage.Sets/src/mage/cards/d/DarkEndurance.java new file mode 100644 index 00000000000..1071668405e --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DarkEndurance.java @@ -0,0 +1,53 @@ +package mage.cards.d; + +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.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.filter.common.FilterBlockingCreature; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DarkEndurance extends CardImpl { + + private static final Condition condition = new SourceTargetsPermanentCondition(new FilterBlockingCreature()); + + public DarkEndurance(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); + + // This spell costs {1} less to cast if it targets a blocking creature. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(1, condition).setCanWorksOnStackOnly(true) + ).setRuleAtTheTop(true)); + + // Target creature gets +2/+0 and gains indestructible until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect( + 2, 0, Duration.EndOfTurn + ).setText("Target creature gets +2/+0")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains indestructible until end of turn")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private DarkEndurance(final DarkEndurance card) { + super(card); + } + + @Override + public DarkEndurance copy() { + return new DarkEndurance(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DauntlessScrapbot.java b/Mage.Sets/src/mage/cards/d/DauntlessScrapbot.java new file mode 100644 index 00000000000..fe4b49ec47b --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DauntlessScrapbot.java @@ -0,0 +1,47 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileGraveyardAllPlayersEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.StaticFilters; +import mage.game.permanent.token.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DauntlessScrapbot extends CardImpl { + + public DauntlessScrapbot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // When this creature enters, exile each opponent's graveyard. Create a Lander token. + Ability ability = new EntersBattlefieldTriggeredAbility( + new ExileGraveyardAllPlayersEffect(StaticFilters.FILTER_CARD, TargetController.OPPONENT) + .setText("exile each opponent's graveyard") + ); + ability.addEffect(new CreateTokenEffect(new LanderToken())); + this.addAbility(ability); + } + + private DauntlessScrapbot(final DauntlessScrapbot card) { + super(card); + } + + @Override + public DauntlessScrapbot copy() { + return new DauntlessScrapbot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DauthiVoidwalker.java b/Mage.Sets/src/mage/cards/d/DauthiVoidwalker.java index 1f6df5b4fad..81ff05d7be1 100644 --- a/Mage.Sets/src/mage/cards/d/DauthiVoidwalker.java +++ b/Mage.Sets/src/mage/cards/d/DauthiVoidwalker.java @@ -147,7 +147,7 @@ class DauthiVoidwalkerPlayEffect extends OneShotEffect { game.addEffect(new PlayFromNotOwnHandZoneTargetEffect( Zone.EXILED, TargetController.YOU, Duration.EndOfTurn, true, false ).setTargetPointer(new FixedTarget(card, game)), source); - game.informPlayers(player.getLogName() + " chose " + card.getLogName()); + game.informPlayers(game.getObject(source).getLogName() + ": " + player.getLogName() + " has chosen " + card.getLogName()); return true; } } diff --git a/Mage.Sets/src/mage/cards/d/DawnsireSunstarDreadnought.java b/Mage.Sets/src/mage/cards/d/DawnsireSunstarDreadnought.java new file mode 100644 index 00000000000..3b523d6b091 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DawnsireSunstarDreadnought.java @@ -0,0 +1,54 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.target.common.TargetCreatureOrPlaneswalker; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DawnsireSunstarDreadnought extends CardImpl { + + public DawnsireSunstarDreadnought(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 10+ + // Whenever you attack, Dawnsire deals 100 damage to up to one target creature or planeswalker. + Ability ability = new AttacksWithCreaturesTriggeredAbility(new DamageTargetEffect(100), 1); + ability.addTarget(new TargetCreatureOrPlaneswalker(0, 1)); + this.addAbility(new StationLevelAbility(10).withLevelAbility(ability)); + + // STATION 20+ + // Flying + // 20/20 + this.addAbility(new StationLevelAbility(20) + .withLevelAbility(FlyingAbility.getInstance()) + .withPT(20, 20)); + } + + private DawnsireSunstarDreadnought(final DawnsireSunstarDreadnought card) { + super(card); + } + + @Override + public DawnsireSunstarDreadnought copy() { + return new DawnsireSunstarDreadnought(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DawnstrikeVanguard.java b/Mage.Sets/src/mage/cards/d/DawnstrikeVanguard.java new file mode 100644 index 00000000000..89229a7b49f --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DawnstrikeVanguard.java @@ -0,0 +1,56 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.condition.common.TwoTappedCreaturesCondition; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.LifelinkAbility; +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 mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DawnstrikeVanguard extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("creature you control other than this creature"); + + static { + filter.add(AnotherPredicate.instance); + } + + public DawnstrikeVanguard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on each creature you control other than this creature. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter) + ).withInterveningIf(TwoTappedCreaturesCondition.instance).addHint(TwoTappedCreaturesCondition.getHint())); + } + + private DawnstrikeVanguard(final DawnstrikeVanguard card) { + super(card); + } + + @Override + public DawnstrikeVanguard copy() { + return new DawnstrikeVanguard(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DaybreakRanger.java b/Mage.Sets/src/mage/cards/d/DaybreakRanger.java index cc653c12f4d..7aa01edb49f 100644 --- a/Mage.Sets/src/mage/cards/d/DaybreakRanger.java +++ b/Mage.Sets/src/mage/cards/d/DaybreakRanger.java @@ -6,17 +6,13 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.WerewolfFrontTriggeredAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -25,12 +21,6 @@ import java.util.UUID; */ public final class DaybreakRanger extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public DaybreakRanger(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.subtype.add(SubType.HUMAN); @@ -45,8 +35,9 @@ public final class DaybreakRanger extends CardImpl { // {tap}: Daybreak Ranger deals 2 damage to target creature with flying. Ability activatedAbility = new SimpleActivatedAbility(new DamageTargetEffect(2), new TapSourceCost()); - activatedAbility.addTarget(new TargetPermanent(filter)); + activatedAbility.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(activatedAbility); + // At the beginning of each upkeep, if no spells were cast last turn, transform Daybreak Ranger. this.addAbility(new TransformAbility()); this.addAbility(new WerewolfFrontTriggeredAbility()); diff --git a/Mage.Sets/src/mage/cards/d/DeadGone.java b/Mage.Sets/src/mage/cards/d/DeadGone.java index 6bbf1065378..6f67087d7be 100644 --- a/Mage.Sets/src/mage/cards/d/DeadGone.java +++ b/Mage.Sets/src/mage/cards/d/DeadGone.java @@ -6,7 +6,6 @@ import mage.cards.CardSetInfo; import mage.cards.SplitCard; import mage.constants.CardType; import mage.constants.SpellAbilityType; -import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -24,7 +23,7 @@ public final class DeadGone extends SplitCard { // Dead // Dead deals 2 damage to target creature. - getLeftHalfCard().getSpellAbility().addEffect(new DamageTargetEffect(2, "Dead")); + getLeftHalfCard().getSpellAbility().addEffect(new DamageTargetEffect(2)); getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); // Gone diff --git a/Mage.Sets/src/mage/cards/d/DeadshotMinotaur.java b/Mage.Sets/src/mage/cards/d/DeadshotMinotaur.java index 8ebaa2c9e90..e709da017b2 100644 --- a/Mage.Sets/src/mage/cards/d/DeadshotMinotaur.java +++ b/Mage.Sets/src/mage/cards/d/DeadshotMinotaur.java @@ -1,48 +1,38 @@ package mage.cards.d; -import java.util.UUID; 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.CyclingAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author jonubuu */ public final class DeadshotMinotaur extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public DeadshotMinotaur(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{R}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}"); this.subtype.add(SubType.MINOTAUR); - - this.power = new MageInt(3); this.toughness = new MageInt(4); // When Deadshot Minotaur enters the battlefield, it deals 3 damage to target creature with flying. Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(3, "it"), false); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); + // Cycling {RG} this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{R/G}"))); } diff --git a/Mage.Sets/src/mage/cards/d/DebrisFieldCrusher.java b/Mage.Sets/src/mage/cards/d/DebrisFieldCrusher.java new file mode 100644 index 00000000000..ff824eb791c --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DebrisFieldCrusher.java @@ -0,0 +1,59 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DebrisFieldCrusher extends CardImpl { + + public DebrisFieldCrusher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{R}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, it deals 3 damage to any target. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(3)); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + + // Station + this.addAbility(new StationAbility()); + + // STATION 8+ + // Flying + // {1}{R}: This Spacecraft gets +2/+0 until end of turn. + // 1/5 + this.addAbility(new StationLevelAbility(8) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(new SimpleActivatedAbility( + new BoostSourceEffect(2, 0, Duration.EndOfTurn), new ManaCostsImpl<>("{1}{R}") + )) + .withPT(1, 5)); + } + + private DebrisFieldCrusher(final DebrisFieldCrusher card) { + super(card); + } + + @Override + public DebrisFieldCrusher copy() { + return new DebrisFieldCrusher(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DecodeTransmissions.java b/Mage.Sets/src/mage/cards/d/DecodeTransmissions.java new file mode 100644 index 00000000000..7e9b55d24d6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DecodeTransmissions.java @@ -0,0 +1,45 @@ +package mage.cards.d; + +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DecodeTransmissions extends CardImpl { + + public DecodeTransmissions(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // You draw two cards and lose 2 life. + // Void -- If a nonland permanent left the battlefield this turn or a spell was warped this turn, instead you draw two cards and each opponent loses 2 life. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2, true)); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new LoseLifeOpponentsEffect(2), new LoseLifeSourceControllerEffect(2), + VoidCondition.instance, "and lose 2 life.
" + AbilityWord.VOID.formatWord() + + "If a nonland permanent left the battlefield this turn or a spell was warped this turn, " + + "instead you draw two cards and each opponent loses 2 life" + )); + this.getSpellAbility().addHint(VoidCondition.getHint()); + this.getSpellAbility().addWatcher(new VoidWatcher()); + } + + private DecodeTransmissions(final DecodeTransmissions card) { + super(card); + } + + @Override + public DecodeTransmissions copy() { + return new DecodeTransmissions(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DenyTheWitch.java b/Mage.Sets/src/mage/cards/d/DenyTheWitch.java index e54075fc0b4..9cb0a28c8c1 100644 --- a/Mage.Sets/src/mage/cards/d/DenyTheWitch.java +++ b/Mage.Sets/src/mage/cards/d/DenyTheWitch.java @@ -22,7 +22,7 @@ public final class DenyTheWitch extends CardImpl { // Counter target spell, activated ability, or triggered ability. Its controller loses life equal to the number of creatures you control. this.getSpellAbility().addEffect(new CounterTargetEffect().setText("Counter target spell, activated ability, or triggered ability")); this.getSpellAbility().addTarget(new TargetStackObject()); - this.getSpellAbility().addEffect(new LoseLifeTargetControllerEffect(CreaturesYouControlCount.instance) + this.getSpellAbility().addEffect(new LoseLifeTargetControllerEffect(CreaturesYouControlCount.PLURAL) .setText("Its controller loses life equal to the number of creatures you control")); this.getSpellAbility().addHint(CreaturesYouControlHint.instance); } diff --git a/Mage.Sets/src/mage/cards/d/Depressurize.java b/Mage.Sets/src/mage/cards/d/Depressurize.java new file mode 100644 index 00000000000..d2b5577eb5f --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/Depressurize.java @@ -0,0 +1,67 @@ +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.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class Depressurize extends CardImpl { + + public Depressurize(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); + + // Target creature gets -3/-0 until end of turn. Then if that creature's power is 0 or less, destroy it. + this.getSpellAbility().addEffect(new BoostTargetEffect(-3, 0, Duration.EndOfTurn)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new DepressurizeTargetEffect()); + } + + private Depressurize(final Depressurize card) { + super(card); + } + + @Override + public Depressurize copy() { + return new Depressurize(this); + } +} + +class DepressurizeTargetEffect extends OneShotEffect { + + DepressurizeTargetEffect() { + super(Outcome.Benefit); + staticText = "Then if that creature's power is 0 or less, destroy it"; + } + + private DepressurizeTargetEffect(final DepressurizeTargetEffect effect) { + super(effect); + } + + @Override + public DepressurizeTargetEffect copy() { + return new DepressurizeTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null || permanent.getPower().getValue() > 0) { + return false; + } + + permanent.destroy(source, game); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DepthshakerTitan.java b/Mage.Sets/src/mage/cards/d/DepthshakerTitan.java new file mode 100644 index 00000000000..44a47f8dceb --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DepthshakerTitan.java @@ -0,0 +1,86 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.abilities.effects.common.continuous.AddCardTypeTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.MeleeAbility; +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.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DepthshakerTitan extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledArtifactPermanent("noncreature artifacts you control"); + + static { + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + } + + public DepthshakerTitan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{5}{R}{R}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // When this creature enters, any number of target noncreature artifacts you control become 3/3 artifact creatures. Sacrifice them at the beginning of the next end step. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCardTypeTargetEffect( + Duration.Custom, CardType.ARTIFACT, CardType.CREATURE + ).setText("any number of target noncreature artifacts you control")); + ability.addEffect(new SetBasePowerToughnessTargetEffect( + 3, 3, Duration.Custom + ).setText(" become 3/3 artifact creatures")); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect( + new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new SacrificeTargetEffect().setText("sacrifice those artifacts") + ), true + ).withCopyToPointer(true).setText("Sacrifice them at the beginning of the next end step")); + ability.addTarget(new TargetPermanent(0, Integer.MAX_VALUE, filter)); + this.addAbility(ability); + + // Each artifact creature you control has melee, trample, and haste. + ability = new SimpleStaticAbility(new GainAbilityControlledEffect( + new MeleeAbility(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_ARTIFACT_CREATURE + ).setText("each artifact creature you control has melee")); + ability.addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_ARTIFACT_CREATURE + ).setText(", trample")); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_ARTIFACT_CREATURE + ).setText(", and haste")); + this.addAbility(ability); + } + + private DepthshakerTitan(final DepthshakerTitan card) { + super(card); + } + + @Override + public DepthshakerTitan copy() { + return new DepthshakerTitan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DesculptingBlast.java b/Mage.Sets/src/mage/cards/d/DesculptingBlast.java new file mode 100644 index 00000000000..13ecce8e7cd --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DesculptingBlast.java @@ -0,0 +1,73 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.DroneToken2; +import mage.players.Player; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DesculptingBlast extends CardImpl { + + public DesculptingBlast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Return target nonland permanent to its owner's hand. If it was attacking, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying." + this.getSpellAbility().addEffect(new DesculptingBlastEffect()); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + } + + private DesculptingBlast(final DesculptingBlast card) { + super(card); + } + + @Override + public DesculptingBlast copy() { + return new DesculptingBlast(this); + } +} + +class DesculptingBlastEffect extends OneShotEffect { + + DesculptingBlastEffect() { + super(Outcome.Benefit); + staticText = "return target nonland permanent to its owner's hand. If it was attacking, " + + "create a 1/1 colorless Drone artifact creature token with flying and " + + "\"This token can block only creatures with flying.\""; + } + + private DesculptingBlastEffect(final DesculptingBlastEffect effect) { + super(effect); + } + + @Override + public DesculptingBlastEffect copy() { + return new DesculptingBlastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (player == null || permanent == null) { + return false; + } + boolean flag = permanent.isAttacking(); + player.moveCards(permanent, Zone.HAND, source, game); + if (flag) { + new DroneToken2().putOntoBattlefield(1, game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DestructorDragon.java b/Mage.Sets/src/mage/cards/d/DestructorDragon.java index ccba40c5270..ba785d7d5f7 100644 --- a/Mage.Sets/src/mage/cards/d/DestructorDragon.java +++ b/Mage.Sets/src/mage/cards/d/DestructorDragon.java @@ -1,7 +1,6 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DiesSourceTriggeredAbility; @@ -11,32 +10,28 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; +import java.util.UUID; + /** - * * @author fireshoes */ public final class DestructorDragon extends CardImpl { - - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } public DestructorDragon(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); this.subtype.add(SubType.DRAGON); this.power = new MageInt(4); this.toughness = new MageInt(4); // Flying this.addAbility(FlyingAbility.getInstance()); + // When Destructor Dragon dies, destroy target noncreature permanent. - Ability ability = new DiesSourceTriggeredAbility(new DestroyTargetEffect(), false); - ability.addTarget(new TargetPermanent(filter)); + Ability ability = new DiesSourceTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DevastatingOnslaught.java b/Mage.Sets/src/mage/cards/d/DevastatingOnslaught.java new file mode 100644 index 00000000000..363a739303f --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DevastatingOnslaught.java @@ -0,0 +1,72 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DevastatingOnslaught extends CardImpl { + + public DevastatingOnslaught(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{X}{R}"); + + // Create X tokens that are copies of target artifact or creature you control. Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step. + this.getSpellAbility().addEffect(new DevastatingOnslaughtEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE)); + } + + private DevastatingOnslaught(final DevastatingOnslaught card) { + super(card); + } + + @Override + public DevastatingOnslaught copy() { + return new DevastatingOnslaught(this); + } +} + +class DevastatingOnslaughtEffect extends OneShotEffect { + + DevastatingOnslaughtEffect() { + super(Outcome.Benefit); + staticText = "create X tokens that are copies of target artifact or creature you control. " + + "Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step"; + } + + private DevastatingOnslaughtEffect(final DevastatingOnslaughtEffect effect) { + super(effect); + } + + @Override + public DevastatingOnslaughtEffect copy() { + return new DevastatingOnslaughtEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + int xValue = GetXValue.instance.calculate(game, source, this); + if (permanent == null || xValue < 1) { + return false; + } + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(); + effect.setHasHaste(true); + effect.setSavedPermanent(permanent); + effect.apply(game, source); + effect.sacrificeTokensCreatedAtNextEndStep(game, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DionBahamutsDominant.java b/Mage.Sets/src/mage/cards/d/DionBahamutsDominant.java index 169722a42e3..1916be7d1ce 100644 --- a/Mage.Sets/src/mage/cards/d/DionBahamutsDominant.java +++ b/Mage.Sets/src/mage/cards/d/DionBahamutsDominant.java @@ -47,7 +47,7 @@ public final class DionBahamutsDominant extends CardImpl { MyTurnCondition.instance, "during your turn, {this}" )); ability.addEffect(new ConditionalContinuousEffect( - new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.WhileControlled, filter), + new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield, filter), MyTurnCondition.instance, "and other Knights you control have flying" )); this.addAbility(ability.withFlavorWord("Dragonfire Dive")); diff --git a/Mage.Sets/src/mage/cards/d/DiplomaticRelations.java b/Mage.Sets/src/mage/cards/d/DiplomaticRelations.java new file mode 100644 index 00000000000..d0fe0dfeb8e --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DiplomaticRelations.java @@ -0,0 +1,40 @@ +package mage.cards.d; + +import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DiplomaticRelations extends CardImpl { + + public DiplomaticRelations(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); + + // Target creature gets +1/+0 and gains vigilance until end of turn. It deals damage equal to its power to target creature an opponent controls. + this.getSpellAbility().addEffect(new BoostTargetEffect(1, 0)); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(VigilanceAbility.getInstance()) + .setText("and gains vigilance until end of turn")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect("it")); + this.getSpellAbility().addTarget(new TargetOpponentsCreaturePermanent()); + } + + private DiplomaticRelations(final DiplomaticRelations card) { + super(card); + } + + @Override + public DiplomaticRelations copy() { + return new DiplomaticRelations(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DivertDisaster.java b/Mage.Sets/src/mage/cards/d/DivertDisaster.java new file mode 100644 index 00000000000..d8cc9a6be37 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DivertDisaster.java @@ -0,0 +1,38 @@ +package mage.cards.d; + +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CounterUnlessPaysEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.LanderToken; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class DivertDisaster extends CardImpl { + + public DivertDisaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Counter target spell unless its controller pays {2}. If they do, you create a Lander token. + this.getSpellAbility().addEffect( + new CounterUnlessPaysEffect(new GenericManaCost(2)) + .withIfTheyDo(new CreateTokenEffect(new LanderToken()).setText("you create a Lander token")) + ); + this.getSpellAbility().addTarget(new TargetSpell()); + } + + private DivertDisaster(final DivertDisaster card) { + super(card); + } + + @Override + public DivertDisaster copy() { + return new DivertDisaster(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DizzyingGaze.java b/Mage.Sets/src/mage/cards/d/DizzyingGaze.java index 6a8e61e8df0..f2cddd74871 100644 --- a/Mage.Sets/src/mage/cards/d/DizzyingGaze.java +++ b/Mage.Sets/src/mage/cards/d/DizzyingGaze.java @@ -1,41 +1,30 @@ package mage.cards.d; -import java.util.UUID; -import mage.constants.SubType; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; -import mage.constants.Outcome; import mage.abilities.keyword.EnchantAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +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.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; /** - * * @author jeffwadsworth */ public final class DizzyingGaze extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public DizzyingGaze(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}"); @@ -45,18 +34,12 @@ public final class DizzyingGaze extends CardImpl { TargetPermanent auraTarget = new TargetControlledCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // {R}: Enchanted creature deals 1 damage to target creature with flying. - Ability ability2 = new SimpleActivatedAbility(new DamageTargetEffect(1), new ManaCostsImpl<>("{R}")); - ability2.addTarget(new TargetPermanent(filter)); - this.addAbility(new SimpleStaticAbility( - new GainAbilityAttachedEffect( - ability2, - AttachmentType.AURA, - Duration.WhileOnBattlefield))); - + Ability ability = new SimpleActivatedAbility(new DizzyingGazeEffect(), new ManaCostsImpl<>("{R}")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); + this.addAbility(ability); } private DizzyingGaze(final DizzyingGaze card) { @@ -68,3 +51,31 @@ public final class DizzyingGaze extends CardImpl { return new DizzyingGaze(this); } } + +class DizzyingGazeEffect extends OneShotEffect { + + DizzyingGazeEffect() { + super(Outcome.Benefit); + staticText = "enchanted creature deals 1 damage to target creature with flying"; + } + + private DizzyingGazeEffect(final DizzyingGazeEffect effect) { + super(effect); + } + + @Override + public DizzyingGazeEffect copy() { + return new DizzyingGazeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); + return creature != null + && Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .filter(permanentId -> creature.damage(1, permanentId, source, game) > 0) + .isPresent(); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DockworkerDrone.java b/Mage.Sets/src/mage/cards/d/DockworkerDrone.java new file mode 100644 index 00000000000..b6efa4f61cb --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DockworkerDrone.java @@ -0,0 +1,50 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +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.counters.CounterType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DockworkerDrone extends CardImpl { + + public DockworkerDrone(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // This creature enters with a +1/+1 counter on it. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + "with a +1/+1 counter on it" + )); + + // When this creature dies, put its counters on target creature you control. + Ability ability = new DiesSourceTriggeredAbility(new PutSourceCountersOnTargetEffect()); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private DockworkerDrone(final DockworkerDrone card) { + super(card); + } + + @Override + public DockworkerDrone copy() { + return new DockworkerDrone(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DontMakeASound.java b/Mage.Sets/src/mage/cards/d/DontMakeASound.java index 4a40f281698..7934615b8b4 100644 --- a/Mage.Sets/src/mage/cards/d/DontMakeASound.java +++ b/Mage.Sets/src/mage/cards/d/DontMakeASound.java @@ -1,19 +1,13 @@ package mage.cards.d; -import mage.abilities.Ability; -import mage.abilities.costs.Cost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CounterUnlessPaysEffect; +import mage.abilities.effects.keyword.SurveilEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.game.Game; -import mage.game.stack.Spell; -import mage.players.Player; import mage.target.TargetSpell; -import java.util.Optional; import java.util.UUID; /** @@ -25,7 +19,10 @@ public final class DontMakeASound extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); // Counter target spell unless its controller pays {2}. If they do, surveil 2. - this.getSpellAbility().addEffect(new DontMakeASoundEffect()); + this.getSpellAbility().addEffect( + new CounterUnlessPaysEffect(new GenericManaCost(2)) + .withIfTheyDo(new SurveilEffect(2)) + ); this.getSpellAbility().addTarget(new TargetSpell()); } @@ -37,42 +34,4 @@ public final class DontMakeASound extends CardImpl { public DontMakeASound copy() { return new DontMakeASound(this); } -} - -class DontMakeASoundEffect extends OneShotEffect { - - DontMakeASoundEffect() { - super(Outcome.Benefit); - staticText = "counter target spell unless its controller pays {2}. If they do, surveil 2"; - } - - private DontMakeASoundEffect(final DontMakeASoundEffect effect) { - super(effect); - } - - @Override - public DontMakeASoundEffect copy() { - return new DontMakeASoundEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); - if (spell == null) { - return false; - } - Player player = game.getPlayer(spell.getControllerId()); - if (player == null) { - return game.getStack().counter(spell.getId(), source, game); - } - Cost cost = new GenericManaCost(2); - if (!cost.canPay(source, source, player.getId(), game) - || !player.chooseUse(outcome, "Pay {2}?", source, game) - || !cost.pay(source, game, source, player.getId(), false)) { - return game.getStack().counter(spell.getId(), source, game); - } - Optional.ofNullable(game.getPlayer(source.getControllerId())) - .ifPresent(p -> p.surveil(2, source, game)); - return true; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DorotheasRetribution.java b/Mage.Sets/src/mage/cards/d/DorotheasRetribution.java index 459825e644d..f884b920053 100644 --- a/Mage.Sets/src/mage/cards/d/DorotheasRetribution.java +++ b/Mage.Sets/src/mage/cards/d/DorotheasRetribution.java @@ -91,7 +91,7 @@ class DorotheasRetributionEffect extends OneShotEffect { game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility( new SacrificeTargetEffect() .setTargetPointer(new FixedTargets(token, game)) - .setText("sacrifce that token") + .setText("sacrifice that token") ), source); return true; } diff --git a/Mage.Sets/src/mage/cards/d/Downdraft.java b/Mage.Sets/src/mage/cards/d/Downdraft.java index f4400e93aee..b2b164e03b6 100644 --- a/Mage.Sets/src/mage/cards/d/Downdraft.java +++ b/Mage.Sets/src/mage/cards/d/Downdraft.java @@ -1,7 +1,6 @@ package mage.cards.d; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; @@ -13,35 +12,28 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author fireshoes */ public final class Downdraft extends CardImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } public Downdraft(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); // {G}: Target creature loses flying until end of turn. Ability ability = new SimpleActivatedAbility( - new LoseAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), + new LoseAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), new ManaCostsImpl<>("{G}")); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); - + // Sacrifice Downdraft: Downdraft deals 2 damage to each creature with flying. - this.addAbility(new SimpleActivatedAbility(new DamageAllEffect(2, "it", filter), new SacrificeSourceCost())); + this.addAbility(new SimpleActivatedAbility(new DamageAllEffect(2, "it", StaticFilters.FILTER_CREATURE_FLYING), new SacrificeSourceCost())); } private Downdraft(final Downdraft card) { diff --git a/Mage.Sets/src/mage/cards/d/DreamEater.java b/Mage.Sets/src/mage/cards/d/DreamEater.java index 5c728566480..6ad226cde45 100644 --- a/Mage.Sets/src/mage/cards/d/DreamEater.java +++ b/Mage.Sets/src/mage/cards/d/DreamEater.java @@ -5,6 +5,7 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.delayed.ReflexiveTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.effects.keyword.SurveilEffect; import mage.abilities.keyword.FlashAbility; @@ -42,8 +43,9 @@ public final class DreamEater extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Dream Eater enters the battlefield, surveil 4. When you do, you may return target nonland permanent an opponent controls to its owner's hand. - Ability ability = new EntersBattlefieldTriggeredAbility(new SurveilEffect(4)); + Ability ability = new EntersBattlefieldTriggeredAbility(new SurveilEffect(4, false)); ability.addEffect(new DreamEaterEffect()); + ability.addEffect(new InfoEffect("(To surveil 4, look at the top four cards of your library, then put any number of them into your graveyard and the rest on top of your library in any order.)")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DrillTooDeep.java b/Mage.Sets/src/mage/cards/d/DrillTooDeep.java new file mode 100644 index 00000000000..42345b18dc4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DrillTooDeep.java @@ -0,0 +1,54 @@ +package mage.cards.d; + +import mage.abilities.Mode; +import mage.abilities.effects.common.DestroyTargetEffect; +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.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class DrillTooDeep extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("Spacecraft or Planet you control"); + + static { + filter.add(Predicates.or( + SubType.SPACECRAFT.getPredicate(), + SubType.PLANET.getPredicate() + )); + } + + public DrillTooDeep(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Choose one — + // • Put five charge counters on target Spacecraft or Planet you control + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.CHARGE.createInstance(5))); + this.getSpellAbility().addTarget(new TargetControlledPermanent(filter)); + + // • Destroy target artifact. + Mode mode = new Mode(new DestroyTargetEffect()); + mode.addTarget(new TargetArtifactPermanent()); + this.getSpellAbility().addMode(mode); + } + + private DrillTooDeep(final DrillTooDeep card) { + super(card); + } + + @Override + public DrillTooDeep copy() { + return new DrillTooDeep(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DrixFatemaker.java b/Mage.Sets/src/mage/cards/d/DrixFatemaker.java new file mode 100644 index 00000000000..1c26fc07c2c --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DrixFatemaker.java @@ -0,0 +1,65 @@ +package mage.cards.d; + +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.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DrixFatemaker extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("each creature you control with a +1/+1 counter on it"); + + static { + filter.add(CounterType.P1P1.getPredicate()); + } + + public DrixFatemaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.DRIX); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + 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); + + // Each creature you control with a +1/+1 counter on it has trample. + this.addAbility(new SimpleStaticAbility(new GainAbilityAllEffect( + TrampleAbility.getInstance(), Duration.WhileOnBattlefield, filter + ))); + + // Warp {1}{G} + this.addAbility(new WarpAbility(this, "{1}{G}")); + } + + private DrixFatemaker(final DrixFatemaker card) { + super(card); + } + + @Override + public DrixFatemaker copy() { + return new DrixFatemaker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DrownInFilth.java b/Mage.Sets/src/mage/cards/d/DrownInFilth.java index 2f484b35b54..d6af3f0f4e1 100644 --- a/Mage.Sets/src/mage/cards/d/DrownInFilth.java +++ b/Mage.Sets/src/mage/cards/d/DrownInFilth.java @@ -1,7 +1,6 @@ package mage.cards.d; -import java.util.UUID; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; @@ -15,6 +14,8 @@ import mage.constants.Duration; import mage.filter.common.FilterLandCard; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author LevelX2 @@ -31,7 +32,7 @@ public final class DrownInFilth extends CardImpl { effect.setText("Choose target creature. Mill four cards"); this.getSpellAbility().addEffect(effect); DynamicValue landCards = new SignInversionDynamicValue(new CardsInControllerGraveyardCount(new FilterLandCard())); - this.getSpellAbility().addEffect(new BoostTargetEffect(landCards, landCards, Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new BoostTargetEffect(landCards, landCards, Duration.EndOfTurn).withTargetDescription(", then that creature")); } private DrownInFilth(final DrownInFilth card) { diff --git a/Mage.Sets/src/mage/cards/d/DualSunAdepts.java b/Mage.Sets/src/mage/cards/d/DualSunAdepts.java new file mode 100644 index 00000000000..c20393d7e38 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DualSunAdepts.java @@ -0,0 +1,46 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DualSunAdepts extends CardImpl { + + public DualSunAdepts(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // {5}: Creatures you control get +1/+1 until end of turn. + this.addAbility(new SimpleActivatedAbility( + new BoostControlledEffect(1, 1, Duration.EndOfTurn), new GenericManaCost(5) + )); + } + + private DualSunAdepts(final DualSunAdepts card) { + super(card); + } + + @Override + public DualSunAdepts copy() { + return new DualSunAdepts(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DualSunTechnique.java b/Mage.Sets/src/mage/cards/d/DualSunTechnique.java new file mode 100644 index 00000000000..cd3faf1e4e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DualSunTechnique.java @@ -0,0 +1,71 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.game.Controllable; +import mage.game.Game; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DualSunTechnique extends CardImpl { + + public DualSunTechnique(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Target creature you control gains double strike until end of turn. If it has a +1/+1 counter on it, draw a card. + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance())); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addEffect(new DualSunTechniqueEffect()); + } + + private DualSunTechnique(final DualSunTechnique card) { + super(card); + } + + @Override + public DualSunTechnique copy() { + return new DualSunTechnique(this); + } +} + +class DualSunTechniqueEffect extends OneShotEffect { + + DualSunTechniqueEffect() { + super(Outcome.Benefit); + staticText = "If it has a +1/+1 counter on it, draw a card"; + } + + private DualSunTechniqueEffect(final DualSunTechniqueEffect effect) { + super(effect); + } + + @Override + public DualSunTechniqueEffect copy() { + return new DualSunTechniqueEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(getTargetPointer().getFirst(game, source)) + .map(game::getPermanent) + .filter(permanent -> permanent.getCounters(game).containsKey(CounterType.P1P1)) + .map(x -> source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .filter(player -> player.drawCards(1, source, game) > 0) + .isPresent(); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DubiousDelicacy.java b/Mage.Sets/src/mage/cards/d/DubiousDelicacy.java new file mode 100644 index 00000000000..ffa80fd6900 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DubiousDelicacy.java @@ -0,0 +1,59 @@ +package mage.cards.d; + +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.LoseLifeTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.token.FoodAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DubiousDelicacy extends CardImpl { + + public DubiousDelicacy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{B}"); + + this.subtype.add(SubType.FOOD); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When this artifact enters, up to one target creature gets -3/-3 until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(-3, -3)); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // {2}, {T}, Sacrifice this artifact: You gain 3 life. + this.addAbility(new FoodAbility()); + + // {2}, {T}, Sacrifice this artifact: Target opponent loses 3 life. + ability = new SimpleActivatedAbility(new LoseLifeTargetEffect(3), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private DubiousDelicacy(final DubiousDelicacy card) { + super(card); + } + + @Override + public DubiousDelicacy copy() { + return new DubiousDelicacy(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DustCorona.java b/Mage.Sets/src/mage/cards/d/DustCorona.java index a9e0f06c6d8..9d8eac60f6a 100644 --- a/Mage.Sets/src/mage/cards/d/DustCorona.java +++ b/Mage.Sets/src/mage/cards/d/DustCorona.java @@ -1,7 +1,6 @@ package mage.cards.d; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.Effect; @@ -9,28 +8,22 @@ import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesAttachedEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; import mage.abilities.keyword.EnchantAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author emerald000 */ public final class DustCorona extends CardImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures with flying"); - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } public DustCorona(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{R}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}"); this.subtype.add(SubType.AURA); // Enchant creature @@ -39,10 +32,10 @@ public final class DustCorona extends CardImpl { this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); Ability ability = new EnchantAbility(auraTarget); this.addAbility(ability); - + // Enchanted creature gets +2/+0 and can't be blocked by creatures with flying. ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 0)); - Effect effect = new CantBeBlockedByCreaturesAttachedEffect(Duration.WhileOnBattlefield, filter, AttachmentType.AURA); + Effect effect = new CantBeBlockedByCreaturesAttachedEffect(Duration.WhileOnBattlefield, StaticFilters.FILTER_CREATURE_FLYING, AttachmentType.AURA); effect.setText("and can't be blocked by creatures with flying"); ability.addEffect(effect); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/d/DwarvenPriest.java b/Mage.Sets/src/mage/cards/d/DwarvenPriest.java index d8b829ee962..c8f8a164a02 100644 --- a/Mage.Sets/src/mage/cards/d/DwarvenPriest.java +++ b/Mage.Sets/src/mage/cards/d/DwarvenPriest.java @@ -25,7 +25,7 @@ public final class DwarvenPriest extends CardImpl { this.toughness = new MageInt(4); // When Dwarven Priest enters the battlefield, you gain 1 life for each creature you control. - this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(CreaturesYouControlCount.instance).setText("you gain 1 life for each creature you control"))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(CreaturesYouControlCount.PLURAL).setText("you gain 1 life for each creature you control"))); } private DwarvenPriest(final DwarvenPriest card) { diff --git a/Mage.Sets/src/mage/cards/d/DyadrineSynthesisAmalgam.java b/Mage.Sets/src/mage/cards/d/DyadrineSynthesisAmalgam.java new file mode 100644 index 00000000000..482233cc3b1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DyadrineSynthesisAmalgam.java @@ -0,0 +1,114 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.dynamicvalue.common.ManaSpentToCastCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfCostPaid; +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.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.token.RobotToken; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DyadrineSynthesisAmalgam extends CardImpl { + + public DyadrineSynthesisAmalgam(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{X}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CONSTRUCT); + this.power = new MageInt(0); + this.toughness = new MageInt(1); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Dyadrine enters with a number of +1/+1 counters on it equal to the amount of mana spent to cast it. + this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect( + CounterType.P1P1.createInstance(), ManaSpentToCastCount.instance, true + ), "with a number of +1/+1 counters on it equal to the amount of mana spent to cast it")); + + // Whenever you attack, you may remove a +1/+1 counter from each of two creatures you control. If you do, draw a card and create a 2/2 colorless Robot artifact creature token. + this.addAbility(new AttacksWithCreaturesTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), new DyadrineSynthesisAmalgamCost() + ).addEffect(new CreateTokenEffect(new RobotToken()).concatBy("and")), 1)); + } + + private DyadrineSynthesisAmalgam(final DyadrineSynthesisAmalgam card) { + super(card); + } + + @Override + public DyadrineSynthesisAmalgam copy() { + return new DyadrineSynthesisAmalgam(this); + } +} + +class DyadrineSynthesisAmalgamCost extends CostImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(CounterType.P1P1.getPredicate()); + } + + DyadrineSynthesisAmalgamCost() { + super(); + setText("remove a +1/+1 counter from each of two creatures you control"); + } + + private DyadrineSynthesisAmalgamCost(final DyadrineSynthesisAmalgamCost cost) { + super(cost); + } + + @Override + public DyadrineSynthesisAmalgamCost copy() { + return new DyadrineSynthesisAmalgamCost(this); + } + + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + return game.getBattlefield().contains(filter, controllerId, source, game, 2); + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + Player player = game.getPlayer(controllerId); + if (player == null) { + return paid; + } + TargetPermanent target = new TargetPermanent(2, filter); + target.withNotTarget(true); + target.withChooseHint("to remove a +1/+1 counter from"); + player.choose(Outcome.UnboostCreature, target, source, game); + for (UUID permanentId : target.getTargets()) { + Optional.ofNullable(permanentId) + .map(game::getPermanent) + .ifPresent(permanent -> permanent.removeCounters(CounterType.P1P1.createInstance(), source, game)); + } + paid = true; + return paid; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EatenBySpiders.java b/Mage.Sets/src/mage/cards/e/EatenBySpiders.java index 7dddb3e0b6b..0e37c34a264 100644 --- a/Mage.Sets/src/mage/cards/e/EatenBySpiders.java +++ b/Mage.Sets/src/mage/cards/e/EatenBySpiders.java @@ -1,42 +1,32 @@ package mage.cards.e; -import java.util.LinkedList; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.LinkedList; +import java.util.UUID; /** - * * @author North */ public final class EatenBySpiders extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public EatenBySpiders(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); // Destroy target creature with flying and all Equipment attached to that creature. this.getSpellAbility().addEffect(new EatenBySpidersEffect()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private EatenBySpiders(final EatenBySpiders card) { diff --git a/Mage.Sets/src/mage/cards/e/EchoStorm.java b/Mage.Sets/src/mage/cards/e/EchoStorm.java index 86a7a380bc7..a76a6297ced 100644 --- a/Mage.Sets/src/mage/cards/e/EchoStorm.java +++ b/Mage.Sets/src/mage/cards/e/EchoStorm.java @@ -1,6 +1,5 @@ package mage.cards.e; -import java.util.UUID; import mage.abilities.effects.common.CreateTokenCopyTargetEffect; import mage.abilities.keyword.CommanderStormAbility; import mage.cards.CardImpl; @@ -8,6 +7,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.target.common.TargetArtifactPermanent; +import java.util.UUID; + /** * * @author TheElk801 @@ -18,7 +19,7 @@ public final class EchoStorm extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}{U}"); // When you cast this spell, copy it for each time you've cast your commander from the command zone this game. You may choose new targets for the copies. - this.addAbility(new CommanderStormAbility()); + this.addAbility(new CommanderStormAbility(true)); // Create a token that's a copy of target artifact. this.getSpellAbility().addEffect(new CreateTokenCopyTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/e/EdgeRover.java b/Mage.Sets/src/mage/cards/e/EdgeRover.java new file mode 100644 index 00000000000..c4d40c6f3e2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EdgeRover.java @@ -0,0 +1,44 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.CreateTokenAllEffect; +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.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EdgeRover extends CardImpl { + + public EdgeRover(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // When this creature dies, each player creates a Lander token. + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenAllEffect(new LanderToken(), TargetController.ANY))); + } + + private EdgeRover(final EdgeRover card) { + super(card); + } + + @Override + public EdgeRover copy() { + return new EdgeRover(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElderOfLaurels.java b/Mage.Sets/src/mage/cards/e/ElderOfLaurels.java index 3570f857829..4295c3a5d9e 100644 --- a/Mage.Sets/src/mage/cards/e/ElderOfLaurels.java +++ b/Mage.Sets/src/mage/cards/e/ElderOfLaurels.java @@ -30,7 +30,7 @@ public final class ElderOfLaurels extends CardImpl { // {3}{G}: Target creature gets +X/+X until end of turn, where X is the number of creatures you control. SimpleActivatedAbility ability = new SimpleActivatedAbility( - new BoostTargetEffect(CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn), + new BoostTargetEffect(CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn), new ManaCostsImpl<>("{3}{G}")); ability.addTarget(new TargetCreaturePermanent()); ability.addHint(CreaturesYouControlHint.instance); diff --git a/Mage.Sets/src/mage/cards/e/ElegyAcolyte.java b/Mage.Sets/src/mage/cards/e/ElegyAcolyte.java new file mode 100644 index 00000000000..a952eec0082 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElegyAcolyte.java @@ -0,0 +1,59 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.keyword.LifelinkAbility; +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.filter.StaticFilters; +import mage.game.permanent.token.RobotToken; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ElegyAcolyte extends CardImpl { + + public ElegyAcolyte(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever one or more creatures you control deal combat damage to a player, you draw a card and lose 1 life. + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility( + new DrawCardSourceControllerEffect(1, true), StaticFilters.FILTER_CONTROLLED_CREATURES + ); + ability.addEffect(new LoseLifeSourceControllerEffect(1).setText("and lose 1 life")); + this.addAbility(ability); + + // Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, create a 2/2 colorless Robot artifact creature token. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new CreateTokenEffect(new RobotToken())) + .withInterveningIf(VoidCondition.instance).setAbilityWord(AbilityWord.VOID).addHint(VoidCondition.getHint()), new VoidWatcher()); + } + + private ElegyAcolyte(final ElegyAcolyte card) { + super(card); + } + + @Override + public ElegyAcolyte copy() { + return new ElegyAcolyte(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElvishSkysweeper.java b/Mage.Sets/src/mage/cards/e/ElvishSkysweeper.java index be06a8f9757..10fcdc0fb26 100644 --- a/Mage.Sets/src/mage/cards/e/ElvishSkysweeper.java +++ b/Mage.Sets/src/mage/cards/e/ElvishSkysweeper.java @@ -1,38 +1,26 @@ package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; import mage.target.TargetPermanent; -import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author Loki */ public final class ElvishSkysweeper extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public ElvishSkysweeper(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); this.subtype.add(SubType.ELF); @@ -44,7 +32,7 @@ public final class ElvishSkysweeper extends CardImpl { // {4}{G}, Sacrifice a creature: Destroy target creature with flying. Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new ManaCostsImpl<>("{4}{G}")); ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE)); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/EmbraceOblivion.java b/Mage.Sets/src/mage/cards/e/EmbraceOblivion.java new file mode 100644 index 00000000000..f0d32b67e15 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EmbraceOblivion.java @@ -0,0 +1,49 @@ +package mage.cards.e; + +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EmbraceOblivion extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("creature or Spacecraft"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.SPACECRAFT.getPredicate() + )); + } + + public EmbraceOblivion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}"); + + // As an additional cost to cast this spell, sacrifice an artifact or creature. + this.getSpellAbility().addCost(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); + + // Destroy target creature or Spacecraft. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private EmbraceOblivion(final EmbraceOblivion card) { + super(card); + } + + @Override + public EmbraceOblivion copy() { + return new EmbraceOblivion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EmergencyEject.java b/Mage.Sets/src/mage/cards/e/EmergencyEject.java new file mode 100644 index 00000000000..4cd370e4dd7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EmergencyEject.java @@ -0,0 +1,35 @@ +package mage.cards.e; + +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.LanderToken; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EmergencyEject extends CardImpl { + + public EmergencyEject(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}"); + + // Destroy target nonland permanent. Its controller creates a Lander token. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetEffect(new LanderToken())); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + } + + private EmergencyEject(final EmergencyEject card) { + super(card); + } + + @Override + public EmergencyEject copy() { + return new EmergencyEject(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EmergentUltimatum.java b/Mage.Sets/src/mage/cards/e/EmergentUltimatum.java index e9f2af707bc..8a7830e7c05 100644 --- a/Mage.Sets/src/mage/cards/e/EmergentUltimatum.java +++ b/Mage.Sets/src/mage/cards/e/EmergentUltimatum.java @@ -79,6 +79,8 @@ class EmergentUltimatumEffect extends OneShotEffect { boolean searched = player.searchLibrary(targetCardInLibrary, source, game); Cards cards = new CardsImpl(targetCardInLibrary.getTargets()); player.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); + cards.retainZone(Zone.EXILED, game); if (cards.isEmpty()) { if (searched) { player.shuffleLibrary(source, game); diff --git a/Mage.Sets/src/mage/cards/e/EmissaryEscort.java b/Mage.Sets/src/mage/cards/e/EmissaryEscort.java new file mode 100644 index 00000000000..5290d60cd92 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EmissaryEscort.java @@ -0,0 +1,44 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EmissaryEscort extends CardImpl { + + public EmissaryEscort(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // This creature gets +X/+0 where X is the greatest mana value among other artifacts you control. + this.addAbility(new SimpleStaticAbility(new BoostSourceEffect( + GreatestAmongPermanentsValue.MANAVALUE_OTHER_CONTROLLED_ARTIFACTS, + StaticValue.get(0), Duration.WhileOnBattlefield + )).addHint(GreatestAmongPermanentsValue.MANAVALUE_OTHER_CONTROLLED_ARTIFACTS.getHint())); + } + + private EmissaryEscort(final EmissaryEscort card) { + super(card); + } + + @Override + public EmissaryEscort copy() { + return new EmissaryEscort(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EmpyrialStorm.java b/Mage.Sets/src/mage/cards/e/EmpyrialStorm.java index af364064bd6..ebc08a010e3 100644 --- a/Mage.Sets/src/mage/cards/e/EmpyrialStorm.java +++ b/Mage.Sets/src/mage/cards/e/EmpyrialStorm.java @@ -1,6 +1,5 @@ package mage.cards.e; -import java.util.UUID; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.keyword.CommanderStormAbility; import mage.cards.CardImpl; @@ -8,6 +7,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.game.permanent.token.AngelToken; +import java.util.UUID; + /** * * @author TheElk801 @@ -18,7 +19,7 @@ public final class EmpyrialStorm extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{W}{W}"); // When you cast this spell, copy it for each time you've cast your commander from the command zone this game. - this.addAbility(new CommanderStormAbility()); + this.addAbility(new CommanderStormAbility(false)); // Create a 4/4 white Angel creature token with flying. this.getSpellAbility().addEffect(new CreateTokenEffect(new AngelToken())); diff --git a/Mage.Sets/src/mage/cards/e/EntropicBattlecruiser.java b/Mage.Sets/src/mage/cards/e/EntropicBattlecruiser.java new file mode 100644 index 00000000000..22ceb05bf83 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EntropicBattlecruiser.java @@ -0,0 +1,105 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.DiscardsACardOpponentTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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.game.Game; +import mage.players.Player; +import mage.target.common.TargetDiscard; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EntropicBattlecruiser extends CardImpl { + + public EntropicBattlecruiser(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{B}"); + + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 1+ + // Whenever an opponent discards a card, they lose 3 life. + this.addAbility(new StationLevelAbility(1).withLevelAbility(new DiscardsACardOpponentTriggeredAbility( + new LoseLifeTargetEffect(3).setText("they lose 3 life"), false + ))); + + // STATION 8+ + // Flying + // Deathtouch + // Whenever this Spacecraft attacks, each opponent discards a card. Each opponent who doesn't loses 3 life. + // 3/10 + this.addAbility(new StationLevelAbility(8) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(DeathtouchAbility.getInstance()) + .withLevelAbility(new AttacksTriggeredAbility(new EntropicBattlecruiserEffect())) + .withPT(3, 10)); + } + + private EntropicBattlecruiser(final EntropicBattlecruiser card) { + super(card); + } + + @Override + public EntropicBattlecruiser copy() { + return new EntropicBattlecruiser(this); + } +} + +class EntropicBattlecruiserEffect extends OneShotEffect { + + EntropicBattlecruiserEffect() { + super(Outcome.Benefit); + staticText = "each opponent discards a card. Each opponent who doesn't loses 3 life"; + } + + private EntropicBattlecruiserEffect(final EntropicBattlecruiserEffect effect) { + super(effect); + } + + @Override + public EntropicBattlecruiserEffect copy() { + return new EntropicBattlecruiserEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Map map = new HashMap<>(); + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(opponentId); + if (player == null || player.getHand().isEmpty()) { + continue; + } + TargetDiscard target = new TargetDiscard(opponentId); + player.choose(Outcome.Discard, target, source, game); + map.put(opponentId, game.getCard(target.getFirstTarget())); + } + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(opponentId); + if (player != null && !player.discard( + map.getOrDefault(opponentId, null), false, source, game + )) { + player.loseLife(3, game, source, false); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EpicExperiment.java b/Mage.Sets/src/mage/cards/e/EpicExperiment.java index a3a596dcce7..7ad32fff925 100644 --- a/Mage.Sets/src/mage/cards/e/EpicExperiment.java +++ b/Mage.Sets/src/mage/cards/e/EpicExperiment.java @@ -67,6 +67,8 @@ class EpicExperimentEffect extends OneShotEffect { } Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, CardUtil.getSourceCostsTag(game, source, "X", 0))); controller.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); + cards.retainZone(Zone.EXILED, game); FilterCard filter = new FilterInstantOrSorceryCard(); filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, CardUtil.getSourceCostsTag(game, source, "X", 0) + 1)); CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, filter); diff --git a/Mage.Sets/src/mage/cards/e/EsixFractalBloom.java b/Mage.Sets/src/mage/cards/e/EsixFractalBloom.java index 23ce2aff151..a960764eafa 100644 --- a/Mage.Sets/src/mage/cards/e/EsixFractalBloom.java +++ b/Mage.Sets/src/mage/cards/e/EsixFractalBloom.java @@ -24,10 +24,8 @@ import mage.target.TargetPermanent; import mage.util.functions.CopyApplier; import mage.util.functions.CopyTokenFunction; import mage.util.functions.EmptyCopyApplier; -import mage.watchers.Watcher; +import mage.watchers.common.CreatedTokenWatcher; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -47,7 +45,7 @@ public final class EsixFractalBloom extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // The first time you would create one or more tokens during each of your turns, you may instead choose a creature other than Esix, Fractal Bloom and create that many tokens that are copies of that creature. - this.addAbility(new SimpleStaticAbility(new EsixFractalBloomEffect()), new EsixFractalBloomWatcher()); + this.addAbility(new SimpleStaticAbility(new EsixFractalBloomEffect()), new CreatedTokenWatcher()); } private EsixFractalBloom(final EsixFractalBloom card) { @@ -88,7 +86,7 @@ class EsixFractalBloomEffect extends ReplacementEffectImpl { public boolean applies(GameEvent event, Ability source, Game game) { return source.isControlledBy(event.getPlayerId()) && game.isActivePlayer(source.getControllerId()) - && !EsixFractalBloomWatcher.checkPlayer(source.getControllerId(), game) + && !CreatedTokenWatcher.checkPlayer(source.getControllerId(), game) && game.getBattlefield().count( filter, source.getControllerId(), source, game ) > 0; @@ -149,30 +147,3 @@ class EsixFractalBloomEffect extends ReplacementEffectImpl { return token; } } - -class EsixFractalBloomWatcher extends Watcher { - - private final Set createdThisTurn = new HashSet<>(); - - EsixFractalBloomWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.CREATED_TOKEN) { - createdThisTurn.add(event.getPlayerId()); - } - } - - @Override - public void reset() { - super.reset(); - createdThisTurn.clear(); - } - - static boolean checkPlayer(UUID playerId, Game game) { - EsixFractalBloomWatcher watcher = game.getState().getWatcher(EsixFractalBloomWatcher.class); - return watcher != null && watcher.createdThisTurn.contains(playerId); - } -} diff --git a/Mage.Sets/src/mage/cards/e/EtaliPrimalConqueror.java b/Mage.Sets/src/mage/cards/e/EtaliPrimalConqueror.java index 1a919e9d546..8330ea611f9 100644 --- a/Mage.Sets/src/mage/cards/e/EtaliPrimalConqueror.java +++ b/Mage.Sets/src/mage/cards/e/EtaliPrimalConqueror.java @@ -91,6 +91,8 @@ class EtaliPrimalConquerorEffect extends OneShotEffect { } } } + game.processAction(); + cards.retainZone(Zone.EXILED, game); CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, StaticFilters.FILTER_CARD); return true; } diff --git a/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java b/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java index f16e32a1b6c..c4997deae4a 100644 --- a/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java +++ b/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java @@ -29,8 +29,7 @@ public final class EtaliPrimalStorm extends CardImpl { this.power = new MageInt(6); this.toughness = new MageInt(6); - // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, - // then you may cast any number of nonland cards exiled this way without paying their mana costs. + // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, then you may cast any number of nonland cards exiled this way without paying their mana costs. this.addAbility(new AttacksTriggeredAbility(new EtaliPrimalStormEffect(), false)); } @@ -78,6 +77,8 @@ class EtaliPrimalStormEffect extends OneShotEffect { } } controller.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); + cards.retainZone(Zone.EXILED, game); CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, filter); return true; } diff --git a/Mage.Sets/src/mage/cards/e/Ettercap.java b/Mage.Sets/src/mage/cards/e/Ettercap.java index dfc21a00fdd..692fea5aa27 100644 --- a/Mage.Sets/src/mage/cards/e/Ettercap.java +++ b/Mage.Sets/src/mage/cards/e/Ettercap.java @@ -2,15 +2,12 @@ package mage.cards.e; import mage.MageInt; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.AdventureCard; 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.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -20,12 +17,6 @@ import java.util.UUID; */ public final class Ettercap extends AdventureCard { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Ettercap(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.INSTANT}, "{4}{G}", "Web Shot", "{2}{G}"); @@ -40,7 +31,7 @@ public final class Ettercap extends AdventureCard { // Web Shot // Destroy target creature with flying. this.getSpellCard().getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellCard().getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellCard().getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.finalizeAdventure(); } diff --git a/Mage.Sets/src/mage/cards/e/EumidianHatchery.java b/Mage.Sets/src/mage/cards/e/EumidianHatchery.java new file mode 100644 index 00000000000..6b4aff16a44 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EumidianHatchery.java @@ -0,0 +1,50 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.common.PutIntoGraveFromBattlefieldSourceTriggeredAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.mana.BlackManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.game.permanent.token.XiraBlackInsectToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EumidianHatchery extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(CounterType.HATCHLING); + + public EumidianHatchery(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}, Pay 1 life: Add {B}. Put a hatchling counter on this land. + Ability ability = new BlackManaAbility(); + ability.addCost(new PayLifeCost(1)); + ability.addEffect(new AddCountersSourceEffect(CounterType.HATCHLING.createInstance())); + this.addAbility(ability); + + // When this land is put into a graveyard from the battlefield, for each hatchling counter on it, create a 1/1 black Insect creature token with flying. + this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility( + new CreateTokenEffect(new XiraBlackInsectToken(), xValue) + .setText("for each hatchling counter on it, create a 1/1 black Insect creature token with flying") + )); + } + + private EumidianHatchery(final EumidianHatchery card) { + super(card); + } + + @Override + public EumidianHatchery copy() { + return new EumidianHatchery(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EumidianTerrabotanist.java b/Mage.Sets/src/mage/cards/e/EumidianTerrabotanist.java new file mode 100644 index 00000000000..fa517f903f0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EumidianTerrabotanist.java @@ -0,0 +1,38 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.common.GainLifeEffect; +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 EumidianTerrabotanist extends CardImpl { + + public EumidianTerrabotanist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Landfall -- Whenever a land you control enters, you gain 1 life. + this.addAbility(new LandfallAbility(new GainLifeEffect(1))); + } + + private EumidianTerrabotanist(final EumidianTerrabotanist card) { + super(card); + } + + @Override + public EumidianTerrabotanist copy() { + return new EumidianTerrabotanist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EumidianWastewaker.java b/Mage.Sets/src/mage/cards/e/EumidianWastewaker.java new file mode 100644 index 00000000000..18f4001cdaa --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EumidianWastewaker.java @@ -0,0 +1,149 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.EncoreAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.CanBeSacrificedPredicate; +import mage.game.Game; +import mage.game.Ownerable; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetDiscard; + +import java.util.*; + +/** + * @author TheElk801 + */ +public final class EumidianWastewaker extends CardImpl { + + public EumidianWastewaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Whenever this creature attacks, you and defending player each discard a card or sacrifice a permanent. You draw a card for each land card put into a graveyard this way. + this.addAbility(new AttacksTriggeredAbility( + new EumidianWastewakerEffect(), false, null, SetTargetPointer.PLAYER + )); + + // Encore {6}{B}{B} + this.addAbility(new EncoreAbility(new ManaCostsImpl<>("{6}{B}{B}"))); + } + + private EumidianWastewaker(final EumidianWastewaker card) { + super(card); + } + + @Override + public EumidianWastewaker copy() { + return new EumidianWastewaker(this); + } +} + +class EumidianWastewakerEffect extends OneShotEffect { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent(); + + static { + filter.add(CanBeSacrificedPredicate.instance); + } + + EumidianWastewakerEffect() { + super(Outcome.Benefit); + staticText = "you and defending player each discard a card or sacrifice a permanent. " + + "You draw a card for each land card put into a graveyard this way"; + } + + private EumidianWastewakerEffect(final EumidianWastewakerEffect effect) { + super(effect); + } + + @Override + public EumidianWastewakerEffect copy() { + return new EumidianWastewakerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player defendingPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (controller == null) { + return false; + } + Set toSacrifice = new HashSet<>(); + Set toDiscard = new HashSet<>(); + for (Player player : Arrays.asList(controller, defendingPlayer)) { + if (player == null) { + continue; + } + boolean canDiscard = !player.getHand().isEmpty(); + boolean canSacrifice = game.getBattlefield().contains(filter, player.getId(), source, game, 1); + if (canSacrifice && !canDiscard) { + chooseSacrifice(player, toSacrifice, game, source); + } else if (!canSacrifice && canDiscard) { + chooseDiscard(player, toDiscard, game, source); + } else if (!canSacrifice && !canSacrifice) { + continue; + } else if (player.chooseUse( + Outcome.Sacrifice, "Sacrifice a permanent or discard a card?", + null, "Sacrifice", "Discard", source, game + )) { + chooseSacrifice(player, toSacrifice, game, source); + } else { + chooseDiscard(player, toDiscard, game, source); + } + } + toSacrifice.removeIf(Objects::isNull); + toDiscard.removeIf(Objects::isNull); + Set lands = new HashSet<>(); + for (Permanent permanent : toSacrifice) { + permanent.sacrifice(source, game); + Card card = permanent.getMainCard(); + if (card != null && card.isLand(game) && Zone.GRAVEYARD.match(game.getState().getZone(card.getId()))) { + lands.add(card.getId()); + } + } + for (Card card : toDiscard) { + Optional.ofNullable(card) + .map(Ownerable::getOwnerId) + .map(game::getPlayer) + .map(player -> player.discard(card, false, source, game)); + if (card.isLand(game) && Zone.GRAVEYARD.match(game.getState().getZone(card.getId()))) { + lands.add(card.getId()); + } + } + Optional.of(lands) + .map(Set::size) + .filter(x -> x > 0) + .ifPresent(x -> controller.drawCards(x, source, game)); + return true; + } + + static void chooseSacrifice(Player player, Set toSacrifice, Game game, Ability source) { + TargetPermanent target = new TargetControlledPermanent(filter); + target.withChooseHint("to sacrifice"); + target.withNotTarget(true); + player.choose(Outcome.Sacrifice, target, source, game); + toSacrifice.add(game.getPermanent(target.getFirstTarget())); + } + + static void chooseDiscard(Player player, Set toDiscard, Game game, Ability source) { + TargetDiscard target = new TargetDiscard(player.getId()); + player.choose(Outcome.Discard, target, source, game); + toDiscard.add(game.getCard(target.getFirstTarget())); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EusocialEngineering.java b/Mage.Sets/src/mage/cards/e/EusocialEngineering.java new file mode 100644 index 00000000000..2b75aa5c333 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EusocialEngineering.java @@ -0,0 +1,36 @@ +package mage.cards.e; + +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.RobotToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EusocialEngineering extends CardImpl { + + public EusocialEngineering(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}{G}"); + + // Landfall -- Whenever a land you control enters, create a 2/2 colorless Robot artifact creature token. + this.addAbility(new LandfallAbility(new CreateTokenEffect(new RobotToken()))); + + // Warp {1}{G} + this.addAbility(new WarpAbility(this, "{1}{G}")); + } + + private EusocialEngineering(final EusocialEngineering card) { + super(card); + } + + @Override + public EusocialEngineering copy() { + return new EusocialEngineering(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EvendoBrushrazer.java b/Mage.Sets/src/mage/cards/e/EvendoBrushrazer.java new file mode 100644 index 00000000000..5203720d090 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EvendoBrushrazer.java @@ -0,0 +1,110 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.PermanentToken; +import mage.util.CardUtil; +import mage.watchers.common.PermanentsSacrificedWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EvendoBrushrazer extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("nontoken permanent"); + + static { + filter.add(TokenPredicate.FALSE); + } + + public EvendoBrushrazer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you sacrifice a nontoken permanent, exile the top card of your library. + this.addAbility(new SacrificePermanentTriggeredAbility( + new ExileCardsFromTopOfLibraryControllerEffect(1, true), filter + )); + + // During your turn, as long as you've sacrificed a nontoken permanent this turn, you may play cards exiled with this creature. + this.addAbility(new SimpleStaticAbility(new EvendoBrushrazerEffect()), new PermanentsSacrificedWatcher()); + + // {T}, Sacrifice a land: Add {R}{R}. + Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.RedMana(2), new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_LAND)); + this.addAbility(ability); + } + + private EvendoBrushrazer(final EvendoBrushrazer card) { + super(card); + } + + @Override + public EvendoBrushrazer copy() { + return new EvendoBrushrazer(this); + } +} + +class EvendoBrushrazerEffect extends AsThoughEffectImpl { + + EvendoBrushrazerEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "during your turn, as long as you've sacrificed a nontoken " + + "permanent this turn, you may play cards exiled with this creature"; + } + + private EvendoBrushrazerEffect(final EvendoBrushrazerEffect effect) { + super(effect); + } + + @Override + public EvendoBrushrazerEffect copy() { + return new EvendoBrushrazerEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + return source.isControlledBy(affectedControllerId) + && game.isActivePlayer(affectedControllerId) + && !game + .getState() + .getWatcher(PermanentsSacrificedWatcher.class) + .getThisTurnSacrificedPermanents(affectedControllerId) + .stream() + .allMatch(PermanentToken.class::isInstance) + && game + .getExile() + .getExileZone(CardUtil.getExileZoneId( + game, source.getSourceId(), + game.getState().getZoneChangeCounter(source.getSourceId()) + )) + .contains(CardUtil.getMainCardId(game, objectId)); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EvendoWakingHaven.java b/Mage.Sets/src/mage/cards/e/EvendoWakingHaven.java new file mode 100644 index 00000000000..ebc6a4b191e --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EvendoWakingHaven.java @@ -0,0 +1,57 @@ +package mage.cards.e; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.hint.common.CreaturesYouControlHint; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.abilities.mana.DynamicManaAbility; +import mage.abilities.mana.GreenManaAbility; +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 EvendoWakingHaven extends CardImpl { + + public EvendoWakingHaven(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.PLANET); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {G}. + this.addAbility(new GreenManaAbility()); + + // Station + this.addAbility(new StationAbility()); + + // STATION 12+ + // {G}, {T}: Add {G} for each creature you control. + Ability ability = new DynamicManaAbility( + Mana.GreenMana(1), CreaturesYouControlCount.SINGULAR, new ManaCostsImpl<>("{G}") + ); + ability.addCost(new TapSourceCost()); + this.addAbility(new StationLevelAbility(12).withLevelAbility(ability).addHint(CreaturesYouControlHint.instance)); + } + + private EvendoWakingHaven(final EvendoWakingHaven card) { + super(card); + } + + @Override + public EvendoWakingHaven copy() { + return new EvendoWakingHaven(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EverythingamajigE.java b/Mage.Sets/src/mage/cards/e/EverythingamajigE.java index b33912a7fa9..622c4e89520 100644 --- a/Mage.Sets/src/mage/cards/e/EverythingamajigE.java +++ b/Mage.Sets/src/mage/cards/e/EverythingamajigE.java @@ -26,8 +26,6 @@ import mage.game.Game; import mage.game.stack.Spell; import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInLibrary; -import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetControlledPermanent; import java.util.UUID; @@ -47,7 +45,7 @@ public final class EverythingamajigE extends CardImpl { // Sacrifice a creature: Add {C}{C} to your mana pool. SacrificeTargetCost cost = new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE); this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, - new BasicManaEffect(Mana.ColorlessMana(2), CreaturesYouControlCount.instance), + new BasicManaEffect(Mana.ColorlessMana(2), CreaturesYouControlCount.PLURAL), cost)); // Urza's Hot Tub diff --git a/Mage.Sets/src/mage/cards/e/ExaltedSunborn.java b/Mage.Sets/src/mage/cards/e/ExaltedSunborn.java new file mode 100644 index 00000000000..8586a8973ca --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExaltedSunborn.java @@ -0,0 +1,50 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.replacement.CreateTwiceThatManyTokensEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.WarpAbility; +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 ExaltedSunborn extends CardImpl { + + public ExaltedSunborn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + + this.subtype.add(SubType.ANGEL); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // If one or more tokens would be created under your control, twice that many of those tokens are created instead. + this.addAbility(new SimpleStaticAbility(new CreateTwiceThatManyTokensEffect())); + + // Warp {1}{W} + this.addAbility(new WarpAbility(this, "{1}{W}")); + } + + private ExaltedSunborn(final ExaltedSunborn card) { + super(card); + } + + @Override + public ExaltedSunborn copy() { + return new ExaltedSunborn(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExosuitSavior.java b/Mage.Sets/src/mage/cards/e/ExosuitSavior.java new file mode 100644 index 00000000000..269348d6ba4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExosuitSavior.java @@ -0,0 +1,55 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ExosuitSavior extends CardImpl { + + private static final FilterControlledPermanent filter = + new FilterControlledPermanent("other target permanent you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + public ExosuitSavior(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, return up to one other target permanent you control to its owner's hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect()); + ability.addTarget(new TargetControlledPermanent(0, 1, filter, false)); + this.addAbility(ability); + } + + private ExosuitSavior(final ExosuitSavior card) { + super(card); + } + + @Override + public ExosuitSavior copy() { + return new ExosuitSavior(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExplorationBroodship.java b/Mage.Sets/src/mage/cards/e/ExplorationBroodship.java new file mode 100644 index 00000000000..19775075c82 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExplorationBroodship.java @@ -0,0 +1,62 @@ +package mage.cards.e; + +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterPermanentCard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ExplorationBroodship extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard("a permanent spell"); + + public ExplorationBroodship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{G}"); + + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 3+ + // You may play an additional land on each of your turns. + this.addAbility(new StationLevelAbility(3).withLevelAbility(new SimpleStaticAbility( + new PlayAdditionalLandsControllerEffect(1, Duration.WhileOnBattlefield) + ))); + + // STATION 8+ + // Flying + // Once during each of your turns, you may cast a permanent spell from your graveyard by sacrificing a land in addition to paying its other costs. + // 4/4 + this.addAbility(new StationLevelAbility(8) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility( + filter, new SacrificeTargetCost(StaticFilters.FILTER_LAND).setText("sacrificing a land") + )) + .withPT(4, 4)); + } + + private ExplorationBroodship(final ExplorationBroodship card) { + super(card); + } + + @Override + public ExplorationBroodship copy() { + return new ExplorationBroodship(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExplosiveVegetation.java b/Mage.Sets/src/mage/cards/e/ExplosiveVegetation.java index 0e31e2e7db7..eed679af881 100644 --- a/Mage.Sets/src/mage/cards/e/ExplosiveVegetation.java +++ b/Mage.Sets/src/mage/cards/e/ExplosiveVegetation.java @@ -1,16 +1,15 @@ - package mage.cards.e; -import java.util.UUID; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author Loki */ public final class ExplosiveVegetation extends CardImpl { @@ -19,7 +18,9 @@ public final class ExplosiveVegetation extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); // Search your library for up to two basic land cards and put them onto the battlefield tapped. Then shuffle your library. - this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 2, new FilterBasicLandCard("basic land cards")), true)); + this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect( + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS), true + )); } private ExplosiveVegetation(final ExplosiveVegetation card) { diff --git a/Mage.Sets/src/mage/cards/e/ExtinguisherBattleship.java b/Mage.Sets/src/mage/cards/e/ExtinguisherBattleship.java new file mode 100644 index 00000000000..3fb73e6548a --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExtinguisherBattleship.java @@ -0,0 +1,57 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageAllEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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 ExtinguisherBattleship extends CardImpl { + + public ExtinguisherBattleship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{8}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, destroy target noncreature permanent. Then this Spacecraft deals 4 damage to each creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); + ability.addEffect(new DamageAllEffect(4, StaticFilters.FILTER_PERMANENT_CREATURE).concatBy("Then")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); + this.addAbility(ability); + + // Station + this.addAbility(new StationAbility()); + + // STATION 5+ + // Flying + // Trample + // 10/10 + this.addAbility(new StationLevelAbility(5) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(TrampleAbility.getInstance()) + .withPT(10, 10)); + } + + private ExtinguisherBattleship(final ExtinguisherBattleship card) { + super(card); + } + + @Override + public ExtinguisherBattleship copy() { + return new ExtinguisherBattleship(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EzurisArchers.java b/Mage.Sets/src/mage/cards/e/EzurisArchers.java index 95d98e62d44..1f102ae4bbd 100644 --- a/Mage.Sets/src/mage/cards/e/EzurisArchers.java +++ b/Mage.Sets/src/mage/cards/e/EzurisArchers.java @@ -1,35 +1,26 @@ package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.BlocksCreatureTriggeredAbility; import mage.abilities.effects.common.continuous.BoostSourceEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author North */ public final class EzurisArchers extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public EzurisArchers(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); this.subtype.add(SubType.ELF); this.subtype.add(SubType.ARCHER); @@ -40,7 +31,7 @@ public final class EzurisArchers extends CardImpl { this.addAbility(ReachAbility.getInstance()); // Whenever Ezuri's Archers blocks a creature with flying, Ezuri's Archers gets +3/+0 until end of turn. - this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(3, 0, Duration.EndOfTurn), filter, false)); + this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(3, 0, Duration.EndOfTurn), StaticFilters.FILTER_CREATURE_FLYING, false)); } private EzurisArchers(final EzurisArchers card) { diff --git a/Mage.Sets/src/mage/cards/f/FallersFaithful.java b/Mage.Sets/src/mage/cards/f/FallersFaithful.java new file mode 100644 index 00000000000..cfbb5d93758 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FallersFaithful.java @@ -0,0 +1,84 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FallersFaithful extends CardImpl { + + public FallersFaithful(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // When this creature enters, destroy up to one other target creature. If that creature wasn't dealt damage this turn, its controller draws two cards. + Ability ability = new EntersBattlefieldTriggeredAbility(new FallersFaithfulEffect()); + ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_ANOTHER_TARGET_CREATURE)); + this.addAbility(ability); + } + + private FallersFaithful(final FallersFaithful card) { + super(card); + } + + @Override + public FallersFaithful copy() { + return new FallersFaithful(this); + } +} + +class FallersFaithfulEffect extends OneShotEffect { + + FallersFaithfulEffect() { + super(Outcome.Benefit); + staticText = "destroy up to one other target creature. If that creature " + + "wasn't dealt damage this turn, its controller draws two cards"; + } + + private FallersFaithfulEffect(final FallersFaithfulEffect effect) { + super(effect); + } + + @Override + public FallersFaithfulEffect copy() { + return new FallersFaithfulEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + boolean flag = permanent.getDealtDamageByThisTurn().isEmpty(); + permanent.destroy(source, game); + game.processAction(); + if (!flag) { + Optional.ofNullable(permanent) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.drawCards(2, source, game)); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FamishedWorldsire.java b/Mage.Sets/src/mage/cards/f/FamishedWorldsire.java new file mode 100644 index 00000000000..2fa2496862f --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FamishedWorldsire.java @@ -0,0 +1,104 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ShuffleLibrarySourceEffect; +import mage.abilities.keyword.DevourAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledLandPermanent; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class FamishedWorldsire extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledLandPermanent("land"); + + public FamishedWorldsire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}{G}"); + + this.subtype.add(SubType.LEVIATHAN); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Ward {3} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{3}"))); + + // Devour land 3 + this.addAbility(new DevourAbility(3, filter)); + + // When this creature enters, look at the top X cards of your library, where X is this creature's power. Put any number of land cards from among them onto the battlefield tapped, then shuffle. + Ability ability = new EntersBattlefieldTriggeredAbility(new FamishedWorldsireEffect()); + ability.addEffect(new ShuffleLibrarySourceEffect().setText(", then shuffle")); + this.addAbility(ability); + } + + private FamishedWorldsire(final FamishedWorldsire card) { + super(card); + } + + @Override + public FamishedWorldsire copy() { + return new FamishedWorldsire(this); + } +} + +class FamishedWorldsireEffect extends OneShotEffect { + + FamishedWorldsireEffect() { + super(Outcome.PutLandInPlay); + staticText = "look at the top X cards of your library, where X is this creature's power. " + + "Put any number of land cards from among them onto the battlefield tapped"; + } + + private FamishedWorldsireEffect(final FamishedWorldsireEffect effect) { + super(effect); + } + + @Override + public FamishedWorldsireEffect copy() { + return new FamishedWorldsireEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int amount = SourcePermanentPowerValue.NOT_NEGATIVE.calculate(game, source, this); + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, amount)); + cards.retainZone(Zone.LIBRARY, game); + if (cards.isEmpty()) { + return false; + } + TargetCard target = new TargetCardInLibrary(0, Integer.MAX_VALUE, StaticFilters.FILTER_CARD_LANDS); + target.withChooseHint("to put onto the battlefield tapped"); + player.choose(outcome, cards, target, source, game); + player.moveCards( + new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, + game, true, false, false, null + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FarrelsMantle.java b/Mage.Sets/src/mage/cards/f/FarrelsMantle.java index 945e1488a63..596abc2e441 100644 --- a/Mage.Sets/src/mage/cards/f/FarrelsMantle.java +++ b/Mage.Sets/src/mage/cards/f/FarrelsMantle.java @@ -21,6 +21,7 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.DefineByTriggerTargetAdjuster; import java.util.UUID; @@ -58,6 +59,7 @@ class FarrelsMantleTriggeredAbility extends TriggeredAbilityImpl { FarrelsMantleTriggeredAbility() { super(Zone.BATTLEFIELD, null, false); + setTargetAdjuster(DefineByTriggerTargetAdjuster.instance); } private FarrelsMantleTriggeredAbility(final FarrelsMantleTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/f/FeastOfFlesh.java b/Mage.Sets/src/mage/cards/f/FeastOfFlesh.java index bed772ebc9a..d0c3c6cdef6 100644 --- a/Mage.Sets/src/mage/cards/f/FeastOfFlesh.java +++ b/Mage.Sets/src/mage/cards/f/FeastOfFlesh.java @@ -32,7 +32,7 @@ public final class FeastOfFlesh extends CardImpl { // Feast of Flesh deals X damage to target creature and you gain X life, where X is 1 plus the number of cards named Feast of Flesh in all graveyards. IntPlusDynamicValue value = new IntPlusDynamicValue(1, new CardsInAllGraveyardsCount(filter)); Effect effect1 = new DamageTargetEffect(value); - effect1.setText("Feast of Flesh deals X damage to target creature"); + effect1.setText("{this} deals X damage to target creature"); Effect effect2 = new GainLifeEffect(value); effect2.setText("and you gain X life, where X is 1 plus the number of cards named Feast of Flesh in all graveyards"); this.getSpellAbility().addEffect(effect1); diff --git a/Mage.Sets/src/mage/cards/f/FellGravship.java b/Mage.Sets/src/mage/cards/f/FellGravship.java new file mode 100644 index 00000000000..cacb2c6d99e --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FellGravship.java @@ -0,0 +1,102 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FellGravship extends CardImpl { + + public FellGravship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{B}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, mill three cards, then return a creature or Spacecraft card from your graveyard to your hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(3)); + ability.addEffect(new FellGravshipEffect()); + this.addAbility(ability); + + // Station + this.addAbility(new StationAbility()); + + // STATION 8+ + // Flying + // Lifelink + // 3/2 + this.addAbility(new StationLevelAbility(8) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(LifelinkAbility.getInstance()) + .withPT(3, 2)); + } + + private FellGravship(final FellGravship card) { + super(card); + } + + @Override + public FellGravship copy() { + return new FellGravship(this); + } +} + +class FellGravshipEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard("creature or Spacecraft card"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.SPACECRAFT.getPredicate() + )); + } + + FellGravshipEffect() { + super(Outcome.Benefit); + staticText = ", then return a creature or Spacecraft card from your graveyard to your hand"; + } + + private FellGravshipEffect(final FellGravshipEffect effect) { + super(effect); + } + + @Override + public FellGravshipEffect copy() { + return new FellGravshipEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getGraveyard().count(filter, game) < 1) { + return false; + } + TargetCard target = new TargetCardInYourGraveyard(filter); + target.withNotTarget(true); + player.choose(outcome, player.getGraveyard(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + return card != null && player.moveCards(card, Zone.HAND, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FellThePheasant.java b/Mage.Sets/src/mage/cards/f/FellThePheasant.java index 788685d81a0..0c6a42e543e 100644 --- a/Mage.Sets/src/mage/cards/f/FellThePheasant.java +++ b/Mage.Sets/src/mage/cards/f/FellThePheasant.java @@ -2,13 +2,10 @@ package mage.cards.f; import mage.abilities.effects.common.CreateTokenEffect; 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.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.game.permanent.token.FoodToken; import mage.target.TargetPermanent; @@ -19,20 +16,13 @@ import java.util.UUID; */ public final class FellThePheasant extends CardImpl { - - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public FellThePheasant(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Fell the Pheasant deals 5 damage to target creature with flying. Create a Food token. this.getSpellAbility().addEffect(new DamageTargetEffect(5)); this.getSpellAbility().addEffect(new CreateTokenEffect(new FoodToken())); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private FellThePheasant(final FellThePheasant card) { diff --git a/Mage.Sets/src/mage/cards/f/FesteringThicket.java b/Mage.Sets/src/mage/cards/f/FesteringThicket.java new file mode 100644 index 00000000000..b5ea5fcbaee --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FesteringThicket.java @@ -0,0 +1,45 @@ +package mage.cards.f; + +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.CyclingAbility; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.GreenManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FesteringThicket extends CardImpl { + + public FesteringThicket(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.SWAMP); + this.subtype.add(SubType.FOREST); + + // ({T}: Add {B} or {G}.) + this.addAbility(new BlackManaAbility()); + this.addAbility(new GreenManaAbility()); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // Cycling {2} + this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private FesteringThicket(final FesteringThicket card) { + super(card); + } + + @Override + public FesteringThicket copy() { + return new FesteringThicket(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FeveredSuspicion.java b/Mage.Sets/src/mage/cards/f/FeveredSuspicion.java index 9032c3b34c9..2f9ecf9032b 100644 --- a/Mage.Sets/src/mage/cards/f/FeveredSuspicion.java +++ b/Mage.Sets/src/mage/cards/f/FeveredSuspicion.java @@ -78,6 +78,7 @@ class FeveredSuspicionEffect extends OneShotEffect { } } controller.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); nonlands.retainZone(Zone.EXILED, game); CardUtil.castMultipleWithAttributeForFree( controller, source, game, nonlands, diff --git a/Mage.Sets/src/mage/cards/f/FieldOfTheDead.java b/Mage.Sets/src/mage/cards/f/FieldOfTheDead.java index 448e161a1ad..652eed8372c 100644 --- a/Mage.Sets/src/mage/cards/f/FieldOfTheDead.java +++ b/Mage.Sets/src/mage/cards/f/FieldOfTheDead.java @@ -4,7 +4,9 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTappedAbility; import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; import mage.abilities.condition.Condition; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.Hint; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -12,7 +14,6 @@ import mage.constants.CardType; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.token.ZombieToken; -import mage.players.Player; import java.util.UUID; @@ -33,7 +34,7 @@ public final class FieldOfTheDead extends CardImpl { // Whenever Field of the Dead or another land you control enters, if you control seven or more lands with different names, create a 2/2 black Zombie creature token. this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility( new CreateTokenEffect(new ZombieToken()), StaticFilters.FILTER_LAND, false, true - ).withInterveningIf(FieldOfTheDeadCondition.instance)); + ).withInterveningIf(FieldOfTheDeadCondition.instance).addHint(FieldOfTheDeadCondition.getHint())); } private FieldOfTheDead(final FieldOfTheDead card) { @@ -49,20 +50,15 @@ public final class FieldOfTheDead extends CardImpl { enum FieldOfTheDeadCondition implements Condition { instance; + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS); + + static Hint getHint() { + return xValue.getHint(); + } + @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - return game - .getBattlefield() - .getAllActivePermanents(StaticFilters.FILTER_LAND, source.getControllerId(), game) - .stream() - .map(permanent -> permanent.getName()) - .filter(s -> s.length() > 0) - .distinct() - .count() > 6; + return xValue.calculate(game, source, null) >= 7; } @Override diff --git a/Mage.Sets/src/mage/cards/f/Firespout.java b/Mage.Sets/src/mage/cards/f/Firespout.java index 6f894b1fd13..c8ed77f2799 100644 --- a/Mage.Sets/src/mage/cards/f/Firespout.java +++ b/Mage.Sets/src/mage/cards/f/Firespout.java @@ -8,7 +8,7 @@ import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.ColoredManaSymbol; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; @@ -21,23 +21,20 @@ import java.util.UUID; public final class Firespout extends CardImpl { private static final FilterCreaturePermanent filter1 = new FilterCreaturePermanent("creature without flying"); - private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("creature with flying"); static { filter1.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); - filter2.add(new AbilityPredicate(FlyingAbility.class)); } public Firespout(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R/G}"); - // Firespout deals 3 damage to each creature without flying if {R} was spent to cast Firespout and 3 damage to each creature with flying if {G} was spent to cast it. this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new DamageAllEffect(3, filter1), ManaWasSpentCondition.RED, "{this} deals 3 damage to each creature without flying if {R} was spent to cast this spell")); this.getSpellAbility().addEffect(new ConditionalOneShotEffect( - new DamageAllEffect(3, filter2), + new DamageAllEffect(3, StaticFilters.FILTER_CREATURE_FLYING), ManaWasSpentCondition.GREEN, "and 3 damage to each creature with flying if {G} was spent to cast this spell. (Do both if {R}{G} was spent.)")); } diff --git a/Mage.Sets/src/mage/cards/f/FlightDeckCoordinator.java b/Mage.Sets/src/mage/cards/f/FlightDeckCoordinator.java new file mode 100644 index 00000000000..993db29c872 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlightDeckCoordinator.java @@ -0,0 +1,40 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.condition.common.TwoTappedCreaturesCondition; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +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 FlightDeckCoordinator extends CardImpl { + + public FlightDeckCoordinator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // At the beginning of your end step, if you control two or more tapped creatures, you gain 2 life. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new GainLifeEffect(2)) + .withInterveningIf(TwoTappedCreaturesCondition.instance).addHint(TwoTappedCreaturesCondition.getHint())); + } + + private FlightDeckCoordinator(final FlightDeckCoordinator card) { + super(card); + } + + @Override + public FlightDeckCoordinator copy() { + return new FlightDeckCoordinator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FlourishingStrike.java b/Mage.Sets/src/mage/cards/f/FlourishingStrike.java index fa4d511a980..0609d8156a5 100644 --- a/Mage.Sets/src/mage/cards/f/FlourishingStrike.java +++ b/Mage.Sets/src/mage/cards/f/FlourishingStrike.java @@ -4,13 +4,11 @@ import mage.abilities.Mode; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.keyword.EntwineAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -21,20 +19,13 @@ import java.util.UUID; */ public final class FlourishingStrike extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public FlourishingStrike(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); - // Choose one — // • Flourishing Strike deals 5 damage to target creature with flying. this.getSpellAbility().addEffect(new DamageTargetEffect(5)); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); // • Target creature gets +3/+3 until end of turn. Mode mode = new Mode(new BoostTargetEffect(3, 3, Duration.EndOfTurn)); diff --git a/Mage.Sets/src/mage/cards/f/FlubsTheFool.java b/Mage.Sets/src/mage/cards/f/FlubsTheFool.java index 4eb42eba9b2..1e0162a1d5c 100644 --- a/Mage.Sets/src/mage/cards/f/FlubsTheFool.java +++ b/Mage.Sets/src/mage/cards/f/FlubsTheFool.java @@ -1,20 +1,21 @@ package mage.cards.f; -import java.util.UUID; - import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.PlayLandOrCastSpellTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.HellbentCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect; import mage.abilities.effects.common.discard.DiscardControllerEffect; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.game.Game; -import mage.game.events.GameEvent; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; /** * @author Cguy7777 @@ -32,10 +33,14 @@ public final class FlubsTheFool extends CardImpl { // You may play an additional land on each of your turns. this.addAbility(new SimpleStaticAbility( - new PlayAdditionalLandsControllerEffect(1, Duration.WhileOnBattlefield))); + new PlayAdditionalLandsControllerEffect(1, Duration.WhileOnBattlefield) + )); // Whenever you play a land or cast a spell, draw a card if you have no cards in hand. Otherwise, discard a card. - this.addAbility(new FlubsTheFoolTriggeredAbility()); + this.addAbility(new PlayLandOrCastSpellTriggeredAbility(new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(1), new DiscardControllerEffect(1), + HellbentCondition.instance, "draw a card if you have no cards in hand. Otherwise, discard a card" + ))); } private FlubsTheFool(final FlubsTheFool card) { @@ -47,34 +52,3 @@ public final class FlubsTheFool extends CardImpl { return new FlubsTheFool(this); } } - -class FlubsTheFoolTriggeredAbility extends TriggeredAbilityImpl { - - FlubsTheFoolTriggeredAbility() { - super(Zone.BATTLEFIELD, new ConditionalOneShotEffect( - new DrawCardSourceControllerEffect(1), - new DiscardControllerEffect(1), - HellbentCondition.instance, - "draw a card if you have no cards in hand. Otherwise, discard a card")); - setTriggerPhrase("Whenever you play a land or cast a spell, "); - } - - private FlubsTheFoolTriggeredAbility(final FlubsTheFoolTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LAND_PLAYED || event.getType() == GameEvent.EventType.SPELL_CAST; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(controllerId); - } - - @Override - public FlubsTheFoolTriggeredAbility copy() { - return new FlubsTheFoolTriggeredAbility(this); - } -} diff --git a/Mage.Sets/src/mage/cards/f/FocusFire.java b/Mage.Sets/src/mage/cards/f/FocusFire.java new file mode 100644 index 00000000000..c6a096b8240 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FocusFire.java @@ -0,0 +1,57 @@ +package mage.cards.f; + +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.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetAttackingOrBlockingCreature; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FocusFire extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.SPACECRAFT.getPredicate() + )); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, 2); + private static final Hint hint = new ValueHint( + "Creatures and/or Spacecraft you control", new PermanentsOnBattlefieldCount(filter) + ); + + public FocusFire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Focus Fire deals X damage to target attacking or blocking creature, where X is 2 plus the number of creatures and/or Spacecraft you control. + this.getSpellAbility().addEffect(new DamageTargetEffect(xValue) + .setText("{this} deals X damage to target attacking or blocking creature, " + + "where X is 2 plus the number of creatures and/or Spacecraft you control")); + this.getSpellAbility().addTarget(new TargetAttackingOrBlockingCreature()); + this.getSpellAbility().addHint(hint); + } + + private FocusFire(final FocusFire card) { + super(card); + } + + @Override + public FocusFire copy() { + return new FocusFire(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForcedLanding.java b/Mage.Sets/src/mage/cards/f/ForcedLanding.java index fe46b1c5cec..f2e58398187 100644 --- a/Mage.Sets/src/mage/cards/f/ForcedLanding.java +++ b/Mage.Sets/src/mage/cards/f/ForcedLanding.java @@ -1,12 +1,10 @@ package mage.cards.f; import mage.abilities.effects.common.PutOnLibraryTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -16,19 +14,12 @@ import java.util.UUID; */ public final class ForcedLanding extends CardImpl { - private static final FilterCreaturePermanent filter - = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public ForcedLanding(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Put target creature with flying on the bottom of its owner's library. this.getSpellAbility().addEffect(new PutOnLibraryTargetEffect(false)); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private ForcedLanding(final ForcedLanding card) { diff --git a/Mage.Sets/src/mage/cards/f/FoundryChampion.java b/Mage.Sets/src/mage/cards/f/FoundryChampion.java index ac2e5a41684..f84bf6a86f5 100644 --- a/Mage.Sets/src/mage/cards/f/FoundryChampion.java +++ b/Mage.Sets/src/mage/cards/f/FoundryChampion.java @@ -14,7 +14,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; import mage.target.common.TargetAnyTarget; import java.util.UUID; @@ -33,7 +32,7 @@ public final class FoundryChampion extends CardImpl { this.toughness = new MageInt(4); //When Foundry Champion enters the battlefield, it deals damage to any target equal to the number of creatures you control. - Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(CreaturesYouControlCount.instance, "it")); + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(CreaturesYouControlCount.PLURAL, "it")); ability.addTarget(new TargetAnyTarget()); ability.addHint(CreaturesYouControlHint.instance); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/f/FowlStrike.java b/Mage.Sets/src/mage/cards/f/FowlStrike.java index ce423d2a787..50f9b33cc5c 100644 --- a/Mage.Sets/src/mage/cards/f/FowlStrike.java +++ b/Mage.Sets/src/mage/cards/f/FowlStrike.java @@ -2,15 +2,12 @@ package mage.cards.f; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReinforceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -19,18 +16,12 @@ import java.util.UUID; */ public final class FowlStrike extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public FowlStrike(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Destroy target creature with flying. this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); // Reinforce 2--{2}{G} this.addAbility(new ReinforceAbility(2, new ManaCostsImpl<>("{2}{G}"))); diff --git a/Mage.Sets/src/mage/cards/f/FrenziedBaloth.java b/Mage.Sets/src/mage/cards/f/FrenziedBaloth.java new file mode 100644 index 00000000000..4a937cf3c6c --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrenziedBaloth.java @@ -0,0 +1,90 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CantBeCounteredSourceAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.CantBeCounteredControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterSpell; +import mage.filter.common.FilterCreatureSpell; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.PreventDamageEvent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FrenziedBaloth extends CardImpl { + + private static final FilterSpell filter = new FilterCreatureSpell("creature spells"); + + public FrenziedBaloth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{G}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // This spell can't be countered. + this.addAbility(new CantBeCounteredSourceAbility()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Creature spells you control can't be countered. + this.addAbility(new SimpleStaticAbility(new CantBeCounteredControlledEffect(filter, Duration.WhileOnBattlefield))); + + // Combat damage can't be prevented. + this.addAbility(new SimpleStaticAbility(new FrenziedBalothEffect())); + } + + private FrenziedBaloth(final FrenziedBaloth card) { + super(card); + } + + @Override + public FrenziedBaloth copy() { + return new FrenziedBaloth(this); + } +} + +class FrenziedBalothEffect extends ContinuousRuleModifyingEffectImpl { + + FrenziedBalothEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "combat damage can't be prevented"; + } + + private FrenziedBalothEffect(final FrenziedBalothEffect effect) { + super(effect); + } + + @Override + public FrenziedBalothEffect copy() { + return new FrenziedBalothEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.PREVENT_DAMAGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return ((PreventDamageEvent) event).isCombatDamage(); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FrontlineRush.java b/Mage.Sets/src/mage/cards/f/FrontlineRush.java index 4cab716fcb2..6e15bba07eb 100644 --- a/Mage.Sets/src/mage/cards/f/FrontlineRush.java +++ b/Mage.Sets/src/mage/cards/f/FrontlineRush.java @@ -26,7 +26,7 @@ public final class FrontlineRush extends CardImpl { // * Target creature gets +X/+X until end of turn, where X is the number of creatures you control. this.getSpellAbility().addMode(new Mode(new BoostTargetEffect( - CreaturesYouControlCount.instance, CreaturesYouControlCount.instance + CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL )).addTarget(new TargetCreaturePermanent())); } diff --git a/Mage.Sets/src/mage/cards/f/FrontlineWarRager.java b/Mage.Sets/src/mage/cards/f/FrontlineWarRager.java new file mode 100644 index 00000000000..efe24764f6a --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrontlineWarRager.java @@ -0,0 +1,44 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.common.TwoTappedCreaturesCondition; +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.counters.CounterType; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class FrontlineWarRager extends CardImpl { + + public FrontlineWarRager(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.KAVU); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on this creature. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + ).withInterveningIf(TwoTappedCreaturesCondition.instance); + this.addAbility(ability.addHint(TwoTappedCreaturesCondition.getHint())); + } + + private FrontlineWarRager(final FrontlineWarRager card) { + super(card); + } + + @Override + public FrontlineWarRager copy() { + return new FrontlineWarRager(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FrostwebSpider.java b/Mage.Sets/src/mage/cards/f/FrostwebSpider.java index fa470eef0c8..135ae2fa560 100644 --- a/Mage.Sets/src/mage/cards/f/FrostwebSpider.java +++ b/Mage.Sets/src/mage/cards/f/FrostwebSpider.java @@ -1,14 +1,12 @@ package mage.cards.f; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.BlocksCreatureTriggeredAbility; import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -16,20 +14,15 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.counters.CounterType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; + /** - * * @author L_J */ public final class FrostwebSpider extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public FrostwebSpider(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.supertype.add(SuperType.SNOW); @@ -43,7 +36,7 @@ public final class FrostwebSpider extends CardImpl { // Whenever Frostweb Spider blocks a creature with flying, put a +1/+1 counter on Frostweb Spider at end of combat. Effect effect = new CreateDelayedTriggeredAbilityEffect(new AtTheEndOfCombatDelayedTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())), true); effect.setText("put a +1/+1 counter on {this} at end of combat"); - this.addAbility(new BlocksCreatureTriggeredAbility(effect, filter,false)); + this.addAbility(new BlocksCreatureTriggeredAbility(effect, StaticFilters.FILTER_CREATURE_FLYING, false)); } private FrostwebSpider(final FrostwebSpider card) { @@ -54,4 +47,4 @@ public final class FrostwebSpider extends CardImpl { public FrostwebSpider copy() { return new FrostwebSpider(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/f/FullBore.java b/Mage.Sets/src/mage/cards/f/FullBore.java new file mode 100644 index 00000000000..77b88483b32 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FullBore.java @@ -0,0 +1,79 @@ +package mage.cards.f; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.Collections; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FullBore extends CardImpl { + + public FullBore(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); + + // Target creature you control gets +3/+2 until end of turn. If that creature was cast for its warp cost, it also gains trample and haste until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(3, 2)); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addEffect(new FullBoreEffect()); + } + + private FullBore(final FullBore card) { + super(card); + } + + @Override + public FullBore copy() { + return new FullBore(this); + } +} + +class FullBoreEffect extends OneShotEffect { + + FullBoreEffect() { + super(Outcome.Benefit); + staticText = "If that creature was cast for its warp cost, it also gains trample and haste until end of turn"; + } + + private FullBoreEffect(final FullBoreEffect effect) { + super(effect); + } + + @Override + public FullBoreEffect copy() { + return new FullBoreEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null + || !game + .getPermanentCostsTags() + .getOrDefault(new MageObjectReference(permanent, game, -1), Collections.emptyMap()) + .containsKey(WarpAbility.WARP_ACTIVATION_VALUE_KEY)) { + return false; + } + game.addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance()) + .setTargetPointer(new FixedTarget(permanent, game)), source); + game.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance()) + .setTargetPointer(new FixedTarget(permanent, game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FungalColossus.java b/Mage.Sets/src/mage/cards/f/FungalColossus.java new file mode 100644 index 00000000000..03bce5fa5fe --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FungalColossus.java @@ -0,0 +1,45 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; +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.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FungalColossus extends CardImpl { + + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS); + + public FungalColossus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{G}"); + + this.subtype.add(SubType.FUNGUS); + this.subtype.add(SubType.BEAST); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // This spell costs {X} less to cast, where X is the number of differently named lands you control. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(xValue) + ).addHint(xValue.getHint()).setRuleAtTheTop(true)); + } + + private FungalColossus(final FungalColossus card) { + super(card); + } + + @Override + public FungalColossus copy() { + return new FungalColossus(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FuryStorm.java b/Mage.Sets/src/mage/cards/f/FuryStorm.java index eef623e689e..412d0f3a185 100644 --- a/Mage.Sets/src/mage/cards/f/FuryStorm.java +++ b/Mage.Sets/src/mage/cards/f/FuryStorm.java @@ -1,6 +1,5 @@ package mage.cards.f; -import java.util.UUID; import mage.abilities.effects.common.CopyTargetStackObjectEffect; import mage.abilities.keyword.CommanderStormAbility; import mage.cards.CardImpl; @@ -9,6 +8,8 @@ import mage.constants.CardType; import mage.filter.StaticFilters; import mage.target.TargetSpell; +import java.util.UUID; + /** * * @author TheElk801 @@ -19,7 +20,7 @@ public final class FuryStorm extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}{R}"); // When you cast this spell, copy it for each time you've cast your commander from the command zone this game. You may choose new targets for the copies. - this.addAbility(new CommanderStormAbility()); + this.addAbility(new CommanderStormAbility(true)); // Copy target instant or sorcery spell. You may choose new targets for the copy. this.getSpellAbility().addEffect(new CopyTargetStackObjectEffect()); diff --git a/Mage.Sets/src/mage/cards/g/GaeasCradle.java b/Mage.Sets/src/mage/cards/g/GaeasCradle.java index 3bbdfc2daff..8d91c3432df 100644 --- a/Mage.Sets/src/mage/cards/g/GaeasCradle.java +++ b/Mage.Sets/src/mage/cards/g/GaeasCradle.java @@ -1,38 +1,29 @@ - package mage.cards.g; -import java.util.UUID; import mage.Mana; -import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.hint.Hint; -import mage.abilities.hint.ValueHint; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.hint.common.CreaturesYouControlHint; import mage.abilities.mana.DynamicManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SuperType; -import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author Backfir3 */ public final class GaeasCradle extends CardImpl { - private static final Hint hint = new ValueHint( - "Number of creatures you control", new PermanentsOnBattlefieldCount(StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED) - ); - public GaeasCradle(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); this.supertype.add(SuperType.LEGENDARY); // {T}: Add {G} for each creature you control. - DynamicManaAbility ability = new DynamicManaAbility( - Mana.GreenMana(1), - new PermanentsOnBattlefieldCount(StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED) - ); - this.addAbility(ability.addHint(hint)); + this.addAbility(new DynamicManaAbility( + Mana.GreenMana(1), CreaturesYouControlCount.SINGULAR + ).addHint(CreaturesYouControlHint.instance)); } private GaeasCradle(final GaeasCradle card) { diff --git a/Mage.Sets/src/mage/cards/g/GalacticWayfarer.java b/Mage.Sets/src/mage/cards/g/GalacticWayfarer.java new file mode 100644 index 00000000000..7fb6e3daa56 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GalacticWayfarer.java @@ -0,0 +1,39 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GalacticWayfarer extends CardImpl { + + public GalacticWayfarer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When this creature enters, create a Lander token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new LanderToken()))); + } + + private GalacticWayfarer(final GalacticWayfarer card) { + super(card); + } + + @Override + public GalacticWayfarer copy() { + return new GalacticWayfarer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GaleForce.java b/Mage.Sets/src/mage/cards/g/GaleForce.java index 5c453b553be..6fdd7b91746 100644 --- a/Mage.Sets/src/mage/cards/g/GaleForce.java +++ b/Mage.Sets/src/mage/cards/g/GaleForce.java @@ -1,31 +1,22 @@ - - package mage.cards.g; -import java.util.UUID; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** * @author Loki */ public final class GaleForce extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public GaleForce(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}"); - this.getSpellAbility().addEffect(new DamageAllEffect(5, filter)); + this.getSpellAbility().addEffect(new DamageAllEffect(5, StaticFilters.FILTER_CREATURE_FLYING)); } private GaleForce(final GaleForce card) { diff --git a/Mage.Sets/src/mage/cards/g/GalvanizingSawship.java b/Mage.Sets/src/mage/cards/g/GalvanizingSawship.java new file mode 100644 index 00000000000..33fca150779 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GalvanizingSawship.java @@ -0,0 +1,45 @@ +package mage.cards.g; + +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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 GalvanizingSawship extends CardImpl { + + public GalvanizingSawship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}{R}"); + + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 3+ + // Flying + // Haste + // 6/5 + this.addAbility(new StationLevelAbility(3) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(HasteAbility.getInstance()) + .withPT(6, 5)); + } + + private GalvanizingSawship(final GalvanizingSawship card) { + super(card); + } + + @Override + public GalvanizingSawship copy() { + return new GalvanizingSawship(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GeistHonoredMonk.java b/Mage.Sets/src/mage/cards/g/GeistHonoredMonk.java index 8c296b05c31..a9567b5fd61 100644 --- a/Mage.Sets/src/mage/cards/g/GeistHonoredMonk.java +++ b/Mage.Sets/src/mage/cards/g/GeistHonoredMonk.java @@ -33,7 +33,7 @@ public final class GeistHonoredMonk extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // Geist-Honored Monk's power and toughness are each equal to the number of creatures you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance)) + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL)) .addHint(CreaturesYouControlHint.instance)); // When Geist-Honored Monk enters the battlefield, create two 1/1 white Spirit creature tokens with flying. diff --git a/Mage.Sets/src/mage/cards/g/GeistcatchersRig.java b/Mage.Sets/src/mage/cards/g/GeistcatchersRig.java index 48ef5dc2ea2..860a41d4a3a 100644 --- a/Mage.Sets/src/mage/cards/g/GeistcatchersRig.java +++ b/Mage.Sets/src/mage/cards/g/GeistcatchersRig.java @@ -1,42 +1,34 @@ package mage.cards.g; -import java.util.UUID; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author North */ public final class GeistcatchersRig extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public GeistcatchersRig(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},"{6}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{6}"); this.subtype.add(SubType.CONSTRUCT); this.power = new MageInt(4); this.toughness = new MageInt(5); + // When Geistcatcher's Rig enters the battlefield, you may have it deal 4 damage to target creature with flying. Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(4), true); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GenePollinator.java b/Mage.Sets/src/mage/cards/g/GenePollinator.java new file mode 100644 index 00000000000..385de243b67 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GenePollinator.java @@ -0,0 +1,44 @@ +package mage.cards.g; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +/** + * @author vcommero + */ +public final class GenePollinator extends CardImpl { + + public GenePollinator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.INSECT); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // {T}, Tapped an untapped permanent you control: Add one mana of any color. + Ability ability = new AnyColorManaAbility(); + ability.addCost(new TapTargetCost(StaticFilters.FILTER_CONTROLLED_UNTAPPED_PERMANENT)); + this.addAbility(ability); + } + + private GenePollinator(final GenePollinator card) { + super(card); + } + + @Override + public Card copy() { + return new GenePollinator(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/g/GenemorphImago.java b/Mage.Sets/src/mage/cards/g/GenemorphImago.java new file mode 100644 index 00000000000..a0c9138e2c6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GenemorphImago.java @@ -0,0 +1,63 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LandfallAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; +import mage.abilities.hint.common.LandsYouControlHint; +import mage.abilities.keyword.FlyingAbility; +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.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GenemorphImago extends CardImpl { + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition( + StaticFilters.FILTER_LANDS, ComparisonType.MORE_THAN, 5, true + ); + + public GenemorphImago(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Landfall -- Whenever a land you control enters, target creature has base power and toughness 3/3 until end of turn. If you control six or more lands, that creature has base power and toughness 6/6 until end of turn instead. + Ability ability = new LandfallAbility(new ConditionalOneShotEffect( + new AddContinuousEffectToGame(new SetBasePowerToughnessTargetEffect(6, 6, Duration.EndOfTurn)), + new AddContinuousEffectToGame(new SetBasePowerToughnessTargetEffect(3, 3, Duration.EndOfTurn)), + condition, "target creature has base power and toughness 3/3 until end of turn. " + + "If you control six or more lands, that creature has base power and toughness 6/6 until end of turn instead" + )); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability.addHint(LandsYouControlHint.instance)); + } + + private GenemorphImago(final GenemorphImago card) { + super(card); + } + + @Override + public GenemorphImago copy() { + return new GenemorphImago(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GeneralLeoCristophe.java b/Mage.Sets/src/mage/cards/g/GeneralLeoCristophe.java index a42d9e34429..2b044116cfe 100644 --- a/Mage.Sets/src/mage/cards/g/GeneralLeoCristophe.java +++ b/Mage.Sets/src/mage/cards/g/GeneralLeoCristophe.java @@ -45,7 +45,7 @@ public final class GeneralLeoCristophe extends CardImpl { Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter)); ability.addEffect(new AddCountersSourceEffect( - CounterType.P1P1.createInstance(), CreaturesYouControlCount.instance + CounterType.P1P1.createInstance(), CreaturesYouControlCount.PLURAL ).setText("Then put a +1/+1 counter on {this} for each creature you control")); this.addAbility(ability.addHint(CreaturesYouControlHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/g/GenesisStorm.java b/Mage.Sets/src/mage/cards/g/GenesisStorm.java index 29d6f997dd5..2a52032ae23 100644 --- a/Mage.Sets/src/mage/cards/g/GenesisStorm.java +++ b/Mage.Sets/src/mage/cards/g/GenesisStorm.java @@ -1,20 +1,17 @@ package mage.cards.g; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.CommanderStormAbility; -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.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** * * @author TheElk801 @@ -25,7 +22,7 @@ public final class GenesisStorm extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}{G}"); // When you cast this spell, copy it for each time you've cast your commander from the command zone this game. - this.addAbility(new CommanderStormAbility()); + this.addAbility(new CommanderStormAbility(false)); // Reveal cards from the top of your library until you reveal a nonland permanent card. You may put that card onto the battlefield. Then put all cards revealed this way that weren't put onto the battlefield on the bottom of your library in a random order. this.getSpellAbility().addEffect(new GenesisStormEffect()); diff --git a/Mage.Sets/src/mage/cards/g/GerminatingWurm.java b/Mage.Sets/src/mage/cards/g/GerminatingWurm.java new file mode 100644 index 00000000000..dec5d058f0d --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GerminatingWurm.java @@ -0,0 +1,42 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.WarpAbility; +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 GerminatingWurm extends CardImpl { + + public GerminatingWurm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.WURM); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // When this creature enters, you gain 2 life. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(2))); + + // Warp {1}{G} + this.addAbility(new WarpAbility(this, "{1}{G}")); + } + + private GerminatingWurm(final GerminatingWurm card) { + super(card); + } + + @Override + public GerminatingWurm copy() { + return new GerminatingWurm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GetALegUp.java b/Mage.Sets/src/mage/cards/g/GetALegUp.java index e2063a004f4..dba214ae241 100644 --- a/Mage.Sets/src/mage/cards/g/GetALegUp.java +++ b/Mage.Sets/src/mage/cards/g/GetALegUp.java @@ -23,7 +23,7 @@ public final class GetALegUp extends CardImpl { // Until end of turn, target creature gets +1/+1 for each creature you control and gains reach. this.getSpellAbility().addTarget(new TargetCreaturePermanent()); - this.getSpellAbility().addEffect(new BoostTargetEffect(CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn) + this.getSpellAbility().addEffect(new BoostTargetEffect(CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn) .setText("until end of turn, target creature gets +1/+1 for each creature you control")); this.getSpellAbility().addEffect(new GainAbilityTargetEffect(ReachAbility.getInstance(), Duration.EndOfTurn) diff --git a/Mage.Sets/src/mage/cards/g/GhostlyDancers.java b/Mage.Sets/src/mage/cards/g/GhostlyDancers.java new file mode 100644 index 00000000000..7458b643734 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GhostlyDancers.java @@ -0,0 +1,87 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.abilityword.EerieAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.Spirit31Token; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GhostlyDancers extends CardImpl { + + public GhostlyDancers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, return an enchantment card from your graveyard to your hand or unlock a locked door of a Room you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GhostlyDancersEffect())); + + // Eerie -- Whenever an enchantment you control enters and whenever you fully unlock a Room, create a 3/1 white Spirit creature token with flying. + this.addAbility(new EerieAbility(new CreateTokenEffect(new Spirit31Token()))); + } + + private GhostlyDancers(final GhostlyDancers card) { + super(card); + } + + @Override + public GhostlyDancers copy() { + return new GhostlyDancers(this); + } +} + +class GhostlyDancersEffect extends OneShotEffect { + + GhostlyDancersEffect() { + super(Outcome.Benefit); + staticText = "return an enchantment card from your graveyard to your hand or unlock a locked door of a Room you control"; + } + + private GhostlyDancersEffect(final GhostlyDancersEffect effect) { + super(effect); + } + + @Override + public GhostlyDancersEffect copy() { + return new GhostlyDancersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + // TODO: 7/7/25 this needs to be refactored when rooms are implemented + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getGraveyard().count(StaticFilters.FILTER_CARD_ENCHANTMENT, game) < 1) { + return false; + } + TargetCard target = new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_ENCHANTMENT); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + return card != null && player.moveCards(card, Zone.HAND, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GiantsGrasp.java b/Mage.Sets/src/mage/cards/g/GiantsGrasp.java index 87f1342896a..bcb6ccb268f 100644 --- a/Mage.Sets/src/mage/cards/g/GiantsGrasp.java +++ b/Mage.Sets/src/mage/cards/g/GiantsGrasp.java @@ -1,24 +1,23 @@ package mage.cards.g; -import java.util.UUID; - -import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.common.continuous.GainControlTargetEffect; -import mage.constants.Duration; -import mage.constants.SubType; import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.AttachEffect; -import mage.constants.Outcome; -import mage.filter.common.FilterControlledPermanent; -import mage.target.TargetPermanent; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; 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.common.FilterControlledPermanent; +import mage.target.TargetPermanent; import mage.target.common.TargetNonlandPermanent; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class GiantsGrasp extends CardImpl { @@ -40,7 +39,7 @@ public final class GiantsGrasp extends CardImpl { // When Giant's Grasp enters the battlefield, gain control of target nonland permanent for as long as Giant's Grasp remains on the battlefield. GainControlTargetEffect controlEffect = new GainControlTargetEffect(Duration.UntilSourceLeavesBattlefield); - controlEffect.setText("gain control of target nonland permanent for as long as Giant's Grasp remains on the battlefield"); + controlEffect.setText("gain control of target nonland permanent for as long as {this} remains on the battlefield"); ability = new EntersBattlefieldTriggeredAbility(controlEffect); ability.addTarget(new TargetNonlandPermanent()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/g/GigastormTitan.java b/Mage.Sets/src/mage/cards/g/GigastormTitan.java new file mode 100644 index 00000000000..3998f813f8d --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GigastormTitan.java @@ -0,0 +1,42 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CastAnotherSpellThisTurnCondition; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GigastormTitan extends CardImpl { + + public GigastormTitan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // This spell costs {3} less to cast if you've cast another spell this turn. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(3, CastAnotherSpellThisTurnCondition.instance) + .setCanWorksOnStackOnly(true) + ).setRuleAtTheTop(true).addHint(CastAnotherSpellThisTurnCondition.instance.getHint())); + } + + private GigastormTitan(final GigastormTitan card) { + super(card); + } + + @Override + public GigastormTitan copy() { + return new GigastormTitan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GilgameshMasterAtArms.java b/Mage.Sets/src/mage/cards/g/GilgameshMasterAtArms.java index f4ee4c91e24..d9a92bffc6a 100644 --- a/Mage.Sets/src/mage/cards/g/GilgameshMasterAtArms.java +++ b/Mage.Sets/src/mage/cards/g/GilgameshMasterAtArms.java @@ -163,7 +163,7 @@ class GilgameshMasterAtArmsAttachEffect extends OneShotEffect { break; } FilterPermanent filterPermanent = new FilterPermanent("Equipment"); - filter.add(new PermanentReferenceInCollectionPredicate(permanents, game)); + filterPermanent.add(new PermanentReferenceInCollectionPredicate(permanents, game)); return Optional .of(new TargetPermanent(0, 1, filterPermanent, true)) .map(t -> { diff --git a/Mage.Sets/src/mage/cards/g/GimbalGremlinProdigy.java b/Mage.Sets/src/mage/cards/g/GimbalGremlinProdigy.java index c15322785a5..44deb8cfc66 100644 --- a/Mage.Sets/src/mage/cards/g/GimbalGremlinProdigy.java +++ b/Mage.Sets/src/mage/cards/g/GimbalGremlinProdigy.java @@ -1,25 +1,27 @@ package mage.cards.g; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.hint.Hint; import mage.abilities.keyword.TrampleAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.predicate.permanent.TokenPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.game.permanent.token.GremlinArtifactToken; import mage.game.permanent.token.Token; -import java.util.Objects; import java.util.UUID; /** @@ -43,9 +45,8 @@ public final class GimbalGremlinProdigy extends CardImpl { ))); // At the beginning of your end step, create a 0/0 red Gremlin artifact creature token. Put X +1/+1 counters on it, where X is the number of differently named artifact tokens you control. - this.addAbility(new BeginningOfEndStepTriggeredAbility( - new GimbalGremlinProdigyEffect() - )); + this.addAbility(new BeginningOfEndStepTriggeredAbility(new GimbalGremlinProdigyEffect()) + .addHint(GimbalGremlinProdigyEffect.getHint())); } private GimbalGremlinProdigy(final GimbalGremlinProdigy card) { @@ -60,6 +61,18 @@ public final class GimbalGremlinProdigy extends CardImpl { class GimbalGremlinProdigyEffect extends OneShotEffect { + private static final FilterPermanent filter = new FilterControlledArtifactPermanent("artifact tokens you control"); + + static { + filter.add(TokenPredicate.TRUE); + } + + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(filter); + + static final Hint getHint() { + return xValue.getHint(); + } + GimbalGremlinProdigyEffect() { super(Outcome.Benefit); staticText = "create a 0/0 red Gremlin artifact creature token. Put X +1/+1 counters on it, " + @@ -79,20 +92,7 @@ class GimbalGremlinProdigyEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Token token = new GremlinArtifactToken(); token.putOntoBattlefield(1, game, source); - int amount = game - .getBattlefield() - .getActivePermanents( - StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT, - source.getControllerId(), source, game - ) - .stream() - .filter(PermanentToken.class::isInstance) - .map(MageObject::getName) - .filter(Objects::nonNull) - .filter(s -> !s.isEmpty()) - .distinct() - .mapToInt(i -> 1) - .sum(); + int amount = xValue.calculate(game, source, this); for (UUID tokenId : token.getLastAddedTokenIds()) { Permanent permanent = game.getPermanent(tokenId); if (permanent != null) { diff --git a/Mage.Sets/src/mage/cards/g/GixYawgmothPraetor.java b/Mage.Sets/src/mage/cards/g/GixYawgmothPraetor.java index 6fa05c7b7ff..f9811fed4a4 100644 --- a/Mage.Sets/src/mage/cards/g/GixYawgmothPraetor.java +++ b/Mage.Sets/src/mage/cards/g/GixYawgmothPraetor.java @@ -144,6 +144,7 @@ class GixYawgmothPraetorExileEffect extends OneShotEffect { int xValue = GetXValue.instance.calculate(game, source, this); Set toExile = opponent.getLibrary().getTopCards(game, xValue); controller.moveCards(toExile, Zone.EXILED, source, game); + game.processAction(); Cards cards = new CardsImpl(toExile); cards.retainZone(Zone.EXILED, game); CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, StaticFilters.FILTER_CARD, Integer.MAX_VALUE, null, true); diff --git a/Mage.Sets/src/mage/cards/g/GlacierGodmaw.java b/Mage.Sets/src/mage/cards/g/GlacierGodmaw.java new file mode 100644 index 00000000000..0dd3c7f86f6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GlacierGodmaw.java @@ -0,0 +1,64 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +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.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GlacierGodmaw extends CardImpl { + + public GlacierGodmaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}"); + + this.subtype.add(SubType.LEVIATHAN); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When this creature enters, create a Lander token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new LanderToken()))); + + // Landfall -- Whenever a land you control enters, creatures you control get +1/+1 and gain vigilance and haste until end of turn. + Ability ability = new LandfallAbility(new BoostControlledEffect( + 1, 1, Duration.EndOfTurn + ).setText("creatures you control get +1/+1")); + ability.addEffect(new GainAbilityControlledEffect( + VigilanceAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("and gain vigilance")); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("and haste until end of turn")); + this.addAbility(ability); + } + + private GlacierGodmaw(final GlacierGodmaw card) { + super(card); + } + + @Override + public GlacierGodmaw copy() { + return new GlacierGodmaw(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GlitteringMassif.java b/Mage.Sets/src/mage/cards/g/GlitteringMassif.java new file mode 100644 index 00000000000..52e4bc31365 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GlitteringMassif.java @@ -0,0 +1,45 @@ +package mage.cards.g; + +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.CyclingAbility; +import mage.abilities.mana.RedManaAbility; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GlitteringMassif extends CardImpl { + + public GlitteringMassif(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.MOUNTAIN); + this.subtype.add(SubType.PLAINS); + + // ({T}: Add {R} or {W}.) + this.addAbility(new RedManaAbility()); + this.addAbility(new WhiteManaAbility()); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // Cycling {2} + this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private GlitteringMassif(final GlitteringMassif card) { + super(card); + } + + @Override + public GlitteringMassif copy() { + return new GlitteringMassif(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GloomwidowsFeast.java b/Mage.Sets/src/mage/cards/g/GloomwidowsFeast.java index fef48fffef7..8df43d7c397 100644 --- a/Mage.Sets/src/mage/cards/g/GloomwidowsFeast.java +++ b/Mage.Sets/src/mage/cards/g/GloomwidowsFeast.java @@ -1,40 +1,31 @@ package mage.cards.g; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.SpiderToken; import mage.target.TargetPermanent; +import java.util.UUID; + /** - * * @author jeffwadsworth */ public final class GloomwidowsFeast extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("target creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public GloomwidowsFeast(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{3}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{G}"); // Destroy target creature with flying. If that creature was blue or black, create a 1/2 green Spider creature token with reach. this.getSpellAbility().addEffect(new GloomwidowsFeastEffect()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } diff --git a/Mage.Sets/src/mage/cards/g/GoblinLyre.java b/Mage.Sets/src/mage/cards/g/GoblinLyre.java index ab861f85f8f..5246972d660 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinLyre.java +++ b/Mage.Sets/src/mage/cards/g/GoblinLyre.java @@ -9,7 +9,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.players.Player; @@ -46,7 +45,7 @@ class GoblinLyreEffect extends OneShotEffect { GoblinLyreEffect() { super(Outcome.Damage); this.staticText = "Flip a coin. If you win the flip, {this} deals damage to target opponent or planeswalker equal to the number of creatures you control. " - + "If you lose the flip, Goblin Lyre deals damage to you equal to the number of creatures that opponent or that planeswalker's controller controls"; + + "If you lose the flip, {this} deals damage to you equal to the number of creatures that opponent or that planeswalker's controller controls"; } private GoblinLyreEffect(final GoblinLyreEffect effect) { @@ -64,7 +63,7 @@ class GoblinLyreEffect extends OneShotEffect { Player opponent = game.getPlayerOrPlaneswalkerController(getTargetPointer().getFirst(game, source)); if (controller != null) { if (controller.flipCoin(source, game, true)) { - int damage = CreaturesYouControlCount.instance.calculate(game, source, this); + int damage = CreaturesYouControlCount.PLURAL.calculate(game, source, this); if (opponent != null) { return game.damagePlayerOrPermanent(source.getFirstTarget(), damage, source.getSourceId(), source, game, false, true) > 0; } diff --git a/Mage.Sets/src/mage/cards/g/GoblinSkycutter.java b/Mage.Sets/src/mage/cards/g/GoblinSkycutter.java index 4cfbe13ee95..09eff010452 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinSkycutter.java +++ b/Mage.Sets/src/mage/cards/g/GoblinSkycutter.java @@ -1,7 +1,6 @@ package mage.cards.g; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -12,27 +11,18 @@ import mage.abilities.keyword.FlyingAbility; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author LoneFox - * */ public final class GoblinSkycutter extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public GoblinSkycutter(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); this.subtype.add(SubType.GOBLIN); @@ -43,7 +33,7 @@ public final class GoblinSkycutter extends CardImpl { // Sacrifice Goblin Skycutter: Goblin Skycutter deals 2 damage to target creature with flying. That creature loses flying until end of turn. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(2, "it"), new SacrificeSourceCost()); ability.addEffect(new LoseAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn).setText("that creature loses flying until end of turn")); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GrapeshotCatapult.java b/Mage.Sets/src/mage/cards/g/GrapeshotCatapult.java index ae8d84dce82..9adf2a38c37 100644 --- a/Mage.Sets/src/mage/cards/g/GrapeshotCatapult.java +++ b/Mage.Sets/src/mage/cards/g/GrapeshotCatapult.java @@ -1,37 +1,27 @@ package mage.cards.g; -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.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.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author anonymous */ public final class GrapeshotCatapult extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public GrapeshotCatapult(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},"{4}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}"); this.subtype.add(SubType.CONSTRUCT); this.power = new MageInt(2); @@ -39,7 +29,7 @@ public final class GrapeshotCatapult extends CardImpl { // {tap}: Grapeshot Catapult deals 1 damage to target creature with flying. Ability activatedAbility = new SimpleActivatedAbility(new DamageTargetEffect(1), new TapSourceCost()); - activatedAbility.addTarget(new TargetPermanent(filter)); + activatedAbility.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(activatedAbility); } diff --git a/Mage.Sets/src/mage/cards/g/GravbladeHeavy.java b/Mage.Sets/src/mage/cards/g/GravbladeHeavy.java new file mode 100644 index 00000000000..c0f921a78c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GravbladeHeavy.java @@ -0,0 +1,58 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class GravbladeHeavy extends CardImpl { + + private static final Condition condition = + new PermanentsOnTheBattlefieldCondition(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT); + + public GravbladeHeavy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // As long as you control an artifact, this creature gets +1/+0 and has deathtouch. + Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostSourceEffect(1, 0, Duration.WhileOnBattlefield), + condition, "as long as you control an artifact, {this} gets +1/+0" + )); + ability.addEffect(new ConditionalContinuousEffect( + new GainAbilitySourceEffect( + DeathtouchAbility.getInstance(), Duration.WhileOnBattlefield + ), condition, "and has deathtouch" + )); + this.addAbility(ability); + } + + private GravbladeHeavy(final GravbladeHeavy card) { + super(card); + } + + @Override + public GravbladeHeavy copy() { + return new GravbladeHeavy(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GravePeril.java b/Mage.Sets/src/mage/cards/g/GravePeril.java index ab662134f50..5db7ed7787f 100644 --- a/Mage.Sets/src/mage/cards/g/GravePeril.java +++ b/Mage.Sets/src/mage/cards/g/GravePeril.java @@ -1,6 +1,5 @@ package mage.cards.g; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.effects.Effect; @@ -16,14 +15,15 @@ import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.UUID; + /** - * * @author emerald000 */ public final class GravePeril extends CardImpl { public GravePeril(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); // When a nonblack creature enters the battlefield, sacrifice Grave Peril. If you do, destroy that creature. this.addAbility(new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new GravePerilEffect(), StaticFilters.FILTER_PERMANENT_CREATURE_NON_BLACK, false, SetTargetPointer.PERMANENT).setTriggerPhrase("When a nonblack creature enters, ")); @@ -43,7 +43,7 @@ class GravePerilEffect extends OneShotEffect { GravePerilEffect() { super(Outcome.DestroyPermanent); - this.staticText = "sacrifice Grave Peril. If you do, destroy that creature"; + this.staticText = "sacrifice {this}. If you do, destroy that creature"; } private GravePerilEffect(final GravePerilEffect effect) { diff --git a/Mage.Sets/src/mage/cards/g/Gravkill.java b/Mage.Sets/src/mage/cards/g/Gravkill.java new file mode 100644 index 00000000000..3819898e36d --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/Gravkill.java @@ -0,0 +1,44 @@ +package mage.cards.g; + +import mage.abilities.effects.common.ExileTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Gravkill extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("creature or Spacecraft"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.SPACECRAFT.getPredicate() + )); + } + + public Gravkill(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}"); + + // Exile target creature or Spacecraft. + this.getSpellAbility().addEffect(new ExileTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private Gravkill(final Gravkill card) { + super(card); + } + + @Override + public Gravkill copy() { + return new Gravkill(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GravpackMonoist.java b/Mage.Sets/src/mage/cards/g/GravpackMonoist.java new file mode 100644 index 00000000000..02ef9892fed --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GravpackMonoist.java @@ -0,0 +1,43 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.RobotToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GravpackMonoist extends CardImpl { + + public GravpackMonoist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature dies, create a tapped 2/2 colorless Robot artifact creature token. + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new RobotToken(), 1, true))); + } + + private GravpackMonoist(final GravpackMonoist card) { + super(card); + } + + @Override + public GravpackMonoist copy() { + return new GravpackMonoist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GreaterGargadon.java b/Mage.Sets/src/mage/cards/g/GreaterGargadon.java index 73ea8940947..6afb1ceb7ef 100644 --- a/Mage.Sets/src/mage/cards/g/GreaterGargadon.java +++ b/Mage.Sets/src/mage/cards/g/GreaterGargadon.java @@ -1,7 +1,6 @@ package mage.cards.g; -import java.util.UUID; import mage.MageInt; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.costs.common.SacrificeTargetCost; @@ -18,7 +17,8 @@ import mage.counters.CounterType; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.Predicates; import mage.game.Game; -import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; /** * @@ -81,6 +81,6 @@ class GreaterGargadonAbility extends ActivatedAbilityImpl { @Override public String getRule() { - return super.getRule() + " Activate only if Greater Gargadon is suspended."; + return super.getRule() + " Activate only if {this} is suspended."; } } diff --git a/Mage.Sets/src/mage/cards/g/GruulCharm.java b/Mage.Sets/src/mage/cards/g/GruulCharm.java index b5b63e7d715..80816652a97 100644 --- a/Mage.Sets/src/mage/cards/g/GruulCharm.java +++ b/Mage.Sets/src/mage/cards/g/GruulCharm.java @@ -1,6 +1,5 @@ package mage.cards.g; -import java.util.UUID; import mage.abilities.Mode; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.combat.CantBlockAllEffect; @@ -12,29 +11,28 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.TargetController; import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; +import java.util.UUID; + /** - * * @author jeffwadsworth */ public final class GruulCharm extends CardImpl { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures without flying"); private static final FilterPermanent filter2 = new FilterPermanent("permanents you own"); - private static final FilterCreaturePermanent filter3 = new FilterCreaturePermanent("creature with flying"); static { filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); filter2.add(TargetController.YOU.getOwnerPredicate()); - filter3.add(new AbilityPredicate(FlyingAbility.class)); } public GruulCharm(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{R}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}{G}"); // Choose one - Creatures without flying can't block this turn; this.getSpellAbility().addEffect(new CantBlockAllEffect(filter, Duration.EndOfTurn)); @@ -43,7 +41,7 @@ public final class GruulCharm extends CardImpl { this.getSpellAbility().addMode(new Mode(new GainControlAllEffect(Duration.Custom, filter2))); // or Gruul Charm deals 3 damage to each creature with flying. - this.getSpellAbility().addMode(new Mode(new DamageAllEffect(3, filter3))); + this.getSpellAbility().addMode(new Mode(new DamageAllEffect(3, StaticFilters.FILTER_CREATURE_FLYING))); } private GruulCharm(final GruulCharm card) { diff --git a/Mage.Sets/src/mage/cards/h/Halfdane.java b/Mage.Sets/src/mage/cards/h/Halfdane.java index 27530170b0d..322991b2baf 100644 --- a/Mage.Sets/src/mage/cards/h/Halfdane.java +++ b/Mage.Sets/src/mage/cards/h/Halfdane.java @@ -3,20 +3,18 @@ package mage.cards.h; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -26,12 +24,6 @@ import java.util.UUID; */ public final class Halfdane extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("target creature other than Halfdane"); - - static { - filter.add(AnotherPredicate.instance); - } - public Halfdane(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{U}{B}"); this.supertype.add(SuperType.LEGENDARY); @@ -41,7 +33,7 @@ public final class Halfdane extends CardImpl { // At the beginning of your upkeep, change Halfdane's base power and toughness to the power and toughness of target creature other than Halfdane until the end of your next upkeep. Ability ability = new BeginningOfUpkeepTriggeredAbility(new HalfdaneUpkeepEffect()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE)); this.addAbility(ability); } @@ -59,7 +51,7 @@ class HalfdaneUpkeepEffect extends OneShotEffect { HalfdaneUpkeepEffect() { super(Outcome.Detriment); - this.staticText = "change {this}'s base power and toughness to the power and toughness of target creature other than Halfdane until the end of your next upkeep"; + this.staticText = "change {this}'s base power and toughness to the power and toughness of target creature other than {this} until the end of your next upkeep"; } private HalfdaneUpkeepEffect(final HalfdaneUpkeepEffect effect) { diff --git a/Mage.Sets/src/mage/cards/h/HaliyaAscendantCadet.java b/Mage.Sets/src/mage/cards/h/HaliyaAscendantCadet.java new file mode 100644 index 00000000000..70fde5d251a --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HaliyaAscendantCadet.java @@ -0,0 +1,63 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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.FilterControlledCreaturePermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HaliyaAscendantCadet extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("creatures you control with +1/+1 counters on them"); + + static { + filter.add(CounterType.P1P1.getPredicate()); + } + + public HaliyaAscendantCadet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever Haliya enters or attacks, put a +1/+1 counter on target creature you control. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()) + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + + // Whenever one or more creatures you control with +1/+1 counters on them deal combat damage to a player, draw a card. + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( + new DrawCardSourceControllerEffect(1), filter + )); + } + + private HaliyaAscendantCadet(final HaliyaAscendantCadet card) { + super(card); + } + + @Override + public HaliyaAscendantCadet copy() { + return new HaliyaAscendantCadet(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HaliyaGuidedByLight.java b/Mage.Sets/src/mage/cards/h/HaliyaGuidedByLight.java new file mode 100644 index 00000000000..d940cdb0ae5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HaliyaGuidedByLight.java @@ -0,0 +1,65 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.YouGainedLifeCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.ControllerGainedLifeCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.WarpAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.watchers.common.PlayerGainedLifeWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HaliyaGuidedByLight extends CardImpl { + + private static final Condition condition = new YouGainedLifeCondition(ComparisonType.MORE_THAN, 2); + + public HaliyaGuidedByLight(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.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever Haliya or another creature or artifact you control enters, you gain 1 life. + this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility( + new GainLifeEffect(1), + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT, + false, false + )); + + // At the beginning of your end step, draw a card if you've gained 3 or more life this turn. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(1), condition, + "draw a card if you've gained 3 or more life this turn" + )).addHint(ControllerGainedLifeCount.getHint()), new PlayerGainedLifeWatcher()); + + // Warp {W} + this.addAbility(new WarpAbility(this, "{W}")); + } + + private HaliyaGuidedByLight(final HaliyaGuidedByLight card) { + super(card); + } + + @Override + public HaliyaGuidedByLight copy() { + return new HaliyaGuidedByLight(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HallOfStormGiants.java b/Mage.Sets/src/mage/cards/h/HallOfStormGiants.java index 40150e6404e..28e9a107083 100644 --- a/Mage.Sets/src/mage/cards/h/HallOfStormGiants.java +++ b/Mage.Sets/src/mage/cards/h/HallOfStormGiants.java @@ -1,7 +1,5 @@ package mage.cards.h; -import java.util.UUID; - import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.condition.Condition; @@ -14,13 +12,17 @@ import mage.abilities.keyword.WardAbility; import mage.abilities.mana.BlueManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Duration; +import mage.constants.SubType; import mage.filter.common.FilterLandPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.permanent.token.custom.CreatureToken; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class HallOfStormGiants extends CardImpl { @@ -51,10 +53,10 @@ public final class HallOfStormGiants extends CardImpl { .withSubType(SubType.GIANT) .withAbility(new WardAbility(new GenericManaCost(3))), CardType.LAND, Duration.EndOfTurn).setText( - "Until end of turn, Hall of Storm Giants becomes a 7/7 blue Giant creature with ward {3}. " + - "It's still a land. " + - "(Whenever it becomes the target of a spell or ability an opponent controls, " + - "counter it unless that player pays {3}.)"), + "Until end of turn, {this} becomes a 7/7 blue Giant creature with ward {3}. " + + "It's still a land. " + + "(Whenever it becomes the target of a spell or ability an opponent controls, " + + "counter it unless that player pays {3}.)"), new ManaCostsImpl<>("{5}{U}"))); } diff --git a/Mage.Sets/src/mage/cards/h/HammerheimDeadeye.java b/Mage.Sets/src/mage/cards/h/HammerheimDeadeye.java index dcd68bafae4..7f20fcd8f37 100644 --- a/Mage.Sets/src/mage/cards/h/HammerheimDeadeye.java +++ b/Mage.Sets/src/mage/cards/h/HammerheimDeadeye.java @@ -1,36 +1,28 @@ package mage.cards.h; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.EchoAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.Target; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class HammerheimDeadeye extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public HammerheimDeadeye(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); this.subtype.add(SubType.GIANT); this.subtype.add(SubType.WARRIOR); @@ -39,9 +31,10 @@ public final class HammerheimDeadeye extends CardImpl { // Echo {5}{R} this.addAbility(new EchoAbility("{5}{R}")); + // When Hammerheim Deadeye enters the battlefield, destroy target creature with flying. Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); - Target target = new TargetPermanent(filter); + Target target = new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING); ability.addTarget(target); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/h/HardlightContainment.java b/Mage.Sets/src/mage/cards/h/HardlightContainment.java new file mode 100644 index 00000000000..8c1c9499d1e --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HardlightContainment.java @@ -0,0 +1,59 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.WardAbility; +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.filter.StaticFilters; +import mage.target.TargetPermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HardlightContainment extends CardImpl { + + public HardlightContainment(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant artifact you control + TargetPermanent auraTarget = new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When this Aura enters, exile target creature an opponent controls until this Aura leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + + // Enchanted permanent has ward {1}. + this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect( + new WardAbility(new GenericManaCost(1)), AttachmentType.AURA + ).setText("enchanted permanent has ward {1}"))); + } + + private HardlightContainment(final HardlightContainment card) { + super(card); + } + + @Override + public HardlightContainment copy() { + return new HardlightContainment(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HarshSustenance.java b/Mage.Sets/src/mage/cards/h/HarshSustenance.java index 219aad9437d..c78ee91b341 100644 --- a/Mage.Sets/src/mage/cards/h/HarshSustenance.java +++ b/Mage.Sets/src/mage/cards/h/HarshSustenance.java @@ -21,11 +21,11 @@ public final class HarshSustenance extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}{B}"); // Harsh Sustenance deals X damage to any target and you gain X life, where X is the number of creatures you control. - Effect effect = new DamageTargetEffect(CreaturesYouControlCount.instance); + Effect effect = new DamageTargetEffect(CreaturesYouControlCount.PLURAL); effect.setText("{this} deals X damage to any target"); getSpellAbility().addEffect(effect); getSpellAbility().addTarget(new TargetAnyTarget()); - effect = new GainLifeEffect(CreaturesYouControlCount.instance); + effect = new GainLifeEffect(CreaturesYouControlCount.PLURAL); effect.setText("and you gain X life, where X is the number of creatures you control"); getSpellAbility().addEffect(effect); getSpellAbility().addHint(CreaturesYouControlHint.instance); diff --git a/Mage.Sets/src/mage/cards/h/HazoretsUndyingFury.java b/Mage.Sets/src/mage/cards/h/HazoretsUndyingFury.java index 2355abcea95..4bd75592ac4 100644 --- a/Mage.Sets/src/mage/cards/h/HazoretsUndyingFury.java +++ b/Mage.Sets/src/mage/cards/h/HazoretsUndyingFury.java @@ -79,6 +79,8 @@ class HazoretsUndyingFuryEffect extends OneShotEffect { // move cards from library to exile Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, 4)); controller.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); + cards.retainZone(Zone.EXILED, game); // cast the possible cards without paying the mana CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, filter); return true; diff --git a/Mage.Sets/src/mage/cards/h/HearthhullTheWorldseed.java b/Mage.Sets/src/mage/cards/h/HearthhullTheWorldseed.java new file mode 100644 index 00000000000..4abf065d43f --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HearthhullTheWorldseed.java @@ -0,0 +1,69 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect; +import mage.abilities.keyword.*; +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 HearthhullTheWorldseed extends CardImpl { + + public HearthhullTheWorldseed(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{B}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 2+ + // {1}, {T}, Sacrifice a land: Draw two cards. You may play an additional land this turn. + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(2), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_LAND)); + ability.addEffect(new PlayAdditionalLandsControllerEffect(1, Duration.EndOfTurn)); + this.addAbility(new StationLevelAbility(2).withLevelAbility(ability)); + + // STATION 8+ + // Flying + // Vigilance + // Haste + // Whenever you sacrifice a land, each opponent loses 2 life. + // 6/7 + this.addAbility(new StationLevelAbility(8) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(VigilanceAbility.getInstance()) + .withLevelAbility(HasteAbility.getInstance()) + .withLevelAbility(new SacrificePermanentTriggeredAbility( + new LoseLifeOpponentsEffect(2), StaticFilters.FILTER_LAND + )) + .withPT(6, 7)); + } + + private HearthhullTheWorldseed(final HearthhullTheWorldseed card) { + super(card); + } + + @Override + public HearthhullTheWorldseed copy() { + return new HearthhullTheWorldseed(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HeavenEarth.java b/Mage.Sets/src/mage/cards/h/HeavenEarth.java index 02536851d55..159e17c449f 100644 --- a/Mage.Sets/src/mage/cards/h/HeavenEarth.java +++ b/Mage.Sets/src/mage/cards/h/HeavenEarth.java @@ -1,6 +1,5 @@ package mage.cards.h; -import java.util.UUID; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.keyword.AftermathAbility; @@ -9,21 +8,21 @@ import mage.cards.CardSetInfo; import mage.cards.SplitCard; import mage.constants.CardType; import mage.constants.SpellAbilityType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; +import java.util.UUID; + /** - * * @author Styxo */ public final class HeavenEarth extends SplitCard { - private static final FilterCreaturePermanent filterFlying = new FilterCreaturePermanent("creature with flying"); private static final FilterCreaturePermanent filterWithouFlying = new FilterCreaturePermanent("creature without flying"); static { - filterFlying.add(new AbilityPredicate(FlyingAbility.class)); filterWithouFlying.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); } @@ -32,7 +31,7 @@ public final class HeavenEarth extends SplitCard { // Falling // Falling deals X damage to each creature with flying. - getLeftHalfCard().getSpellAbility().addEffect(new DamageAllEffect(GetXValue.instance, filterFlying)); + getLeftHalfCard().getSpellAbility().addEffect(new DamageAllEffect(GetXValue.instance, StaticFilters.FILTER_CREATURE_FLYING)); // to // Earth diff --git a/Mage.Sets/src/mage/cards/h/HeliodsEmissary.java b/Mage.Sets/src/mage/cards/h/HeliodsEmissary.java index 18f4430cf9d..f1f9dc10b63 100644 --- a/Mage.Sets/src/mage/cards/h/HeliodsEmissary.java +++ b/Mage.Sets/src/mage/cards/h/HeliodsEmissary.java @@ -1,7 +1,6 @@ package mage.cards.h; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksAttachedTriggeredAbility; @@ -10,17 +9,13 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; import mage.abilities.keyword.BestowAbility; +import mage.abilities.meta.OrTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Zone; -import mage.filter.StaticFilters; -import mage.target.Target; +import mage.constants.*; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE; @@ -31,7 +26,7 @@ import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE; public final class HeliodsEmissary extends CardImpl { public HeliodsEmissary(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT,CardType.CREATURE},"{3}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{3}{W}"); this.subtype.add(SubType.ELK); this.power = new MageInt(3); @@ -40,16 +35,14 @@ public final class HeliodsEmissary extends CardImpl { // Bestow {6}{W} this.addAbility(new BestowAbility(this, "{6}{W}")); // Whenever Heliod's Emissary or enchanted creature attacks, tap target creature an opponent controls. - Ability ability = new AttacksTriggeredAbility(new TapTargetEffect(), false); - Target target = new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE); - ability.addTarget(target); - this.addAbility(ability); - ability = new AttacksAttachedTriggeredAbility(new TapTargetEffect(), AttachmentType.AURA, false); - target = new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE); - ability.addTarget(target); + Ability ability = new OrTriggeredAbility(Zone.BATTLEFIELD, new TapTargetEffect(), + false, "Whenever {this} or enchanted creature attacks, ", + new AttacksTriggeredAbility(null, false), + new AttacksAttachedTriggeredAbility(null, AttachmentType.AURA, false)); + ability.addTarget(new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE)); this.addAbility(ability); // Enchanted creature gets +3/+3. - this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(3,3, Duration.WhileOnBattlefield))); + this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(3, 3, Duration.WhileOnBattlefield))); } private HeliodsEmissary(final HeliodsEmissary card) { diff --git a/Mage.Sets/src/mage/cards/h/Helldozer.java b/Mage.Sets/src/mage/cards/h/Helldozer.java index df448a2acd9..8358a6d1b46 100644 --- a/Mage.Sets/src/mage/cards/h/Helldozer.java +++ b/Mage.Sets/src/mage/cards/h/Helldozer.java @@ -1,6 +1,5 @@ package mage.cards.h; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -10,15 +9,15 @@ import mage.abilities.effects.OneShotEffect; 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.target.common.TargetLandPermanent; +import java.util.UUID; + /** - * * @author jeffwadsworth */ public final class Helldozer extends CardImpl { @@ -55,7 +54,7 @@ class HelldozerEffect extends OneShotEffect { HelldozerEffect() { super(Outcome.DestroyPermanent); - this.staticText = "Destroy target land. If that land was nonbasic, untap Helldozer"; + this.staticText = "Destroy target land. If that land was nonbasic, untap {this}"; } private HelldozerEffect(final HelldozerEffect effect) { diff --git a/Mage.Sets/src/mage/cards/h/HemosymbicMite.java b/Mage.Sets/src/mage/cards/h/HemosymbicMite.java new file mode 100644 index 00000000000..e8209be491c --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HemosymbicMite.java @@ -0,0 +1,45 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HemosymbicMite extends CardImpl { + + public HemosymbicMite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.MITE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever this creature becomes tapped, another target creature you control gets +X/+X until end of turn, where X is this creature's power. + Ability ability = new BecomesTappedSourceTriggeredAbility(new BoostTargetEffect( + SourcePermanentPowerValue.NOT_NEGATIVE, SourcePermanentPowerValue.NOT_NEGATIVE + )); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + } + + private HemosymbicMite(final HemosymbicMite card) { + super(card); + } + + @Override + public HemosymbicMite copy() { + return new HemosymbicMite(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HighRiseSawjack.java b/Mage.Sets/src/mage/cards/h/HighRiseSawjack.java index 5e35ae1eaec..0d5834f3ad7 100644 --- a/Mage.Sets/src/mage/cards/h/HighRiseSawjack.java +++ b/Mage.Sets/src/mage/cards/h/HighRiseSawjack.java @@ -3,15 +3,13 @@ package mage.cards.h; import mage.MageInt; import mage.abilities.common.BlocksCreatureTriggeredAbility; import mage.abilities.effects.common.continuous.BoostSourceEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import java.util.UUID; @@ -20,14 +18,8 @@ import java.util.UUID; */ public final class HighRiseSawjack extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public HighRiseSawjack(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.subtype.add(SubType.ELF); this.subtype.add(SubType.CITIZEN); @@ -38,7 +30,7 @@ public final class HighRiseSawjack extends CardImpl { this.addAbility(ReachAbility.getInstance()); // Whenever High-Rise Sawjack blocks a creature with flying, High-Rise Sawjack gets +2/+0 until end of turn. - this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn), filter, false)); + this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn), StaticFilters.FILTER_CREATURE_FLYING, false)); } private HighRiseSawjack(final HighRiseSawjack card) { diff --git a/Mage.Sets/src/mage/cards/h/HitRun.java b/Mage.Sets/src/mage/cards/h/HitRun.java index d919874b146..22a49b8fb56 100644 --- a/Mage.Sets/src/mage/cards/h/HitRun.java +++ b/Mage.Sets/src/mage/cards/h/HitRun.java @@ -12,13 +12,10 @@ import mage.constants.Outcome; import mage.constants.SpellAbilityType; import mage.filter.StaticFilters; import mage.filter.common.FilterAttackingCreature; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPlayer; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetSacrifice; import mage.target.targetpointer.FixedTarget; @@ -57,7 +54,7 @@ class HitEffect extends OneShotEffect { HitEffect() { super(Outcome.DestroyPermanent); - this.staticText = "Target player sacrifices an artifact or creature. Hit deals damage to that player equal to that permanent's mana value"; + this.staticText = "Target player sacrifices an artifact or creature. {this} deals damage to that player equal to that permanent's mana value"; } private HitEffect(final HitEffect effect) { diff --git a/Mage.Sets/src/mage/cards/h/HobbitsSting.java b/Mage.Sets/src/mage/cards/h/HobbitsSting.java index c858d8685b3..e7223c8e174 100644 --- a/Mage.Sets/src/mage/cards/h/HobbitsSting.java +++ b/Mage.Sets/src/mage/cards/h/HobbitsSting.java @@ -22,7 +22,7 @@ import java.util.UUID; public final class HobbitsSting extends CardImpl { private static final DynamicValue xValue = new AdditiveDynamicValue( - CreaturesYouControlCount.instance, + CreaturesYouControlCount.PLURAL, new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.FOOD)) ); private static final Hint hint = new ValueHint("Creatures and Foods you control", xValue); diff --git a/Mage.Sets/src/mage/cards/h/HoldForRansom.java b/Mage.Sets/src/mage/cards/h/HoldForRansom.java index 705da1e0c95..c03c39ae508 100644 --- a/Mage.Sets/src/mage/cards/h/HoldForRansom.java +++ b/Mage.Sets/src/mage/cards/h/HoldForRansom.java @@ -1,26 +1,29 @@ package mage.cards.h; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.combat.CantAttackBlockAttachedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; -import mage.constants.*; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.common.TargetCreaturePermanent; -import mage.abilities.effects.common.AttachEffect; -import mage.target.TargetPermanent; 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.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author weirddan455 @@ -81,7 +84,7 @@ class HoldForRansomSacrificeEffect extends OneShotEffect { HoldForRansomSacrificeEffect() { super(Outcome.Sacrifice); - this.staticText = "Hold for Ransom's controller sacrifices it and draws a card"; + this.staticText = "{this}'s controller sacrifices it and draws a card"; } private HoldForRansomSacrificeEffect(final HoldForRansomSacrificeEffect effect) { diff --git a/Mage.Sets/src/mage/cards/h/Honor.java b/Mage.Sets/src/mage/cards/h/Honor.java new file mode 100644 index 00000000000..0ecc12a7244 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/Honor.java @@ -0,0 +1,37 @@ +package mage.cards.h; + +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Honor extends CardImpl { + + public Honor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}"); + + // Put a +1/+1 counter on target creature. + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // Draw a card. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); + } + + private Honor(final Honor card) { + super(card); + } + + @Override + public Honor copy() { + return new Honor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java b/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java new file mode 100644 index 00000000000..a88998a3287 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java @@ -0,0 +1,57 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterBySubtypeCard; +import mage.game.permanent.token.HumanSoldierToken; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HonoredKnightCaptain extends CardImpl { + + private static final FilterCard filter = new FilterBySubtypeCard(SubType.EQUIPMENT); + + public HonoredKnightCaptain(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When this creature enters, create a 1/1 white Human Soldier creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HumanSoldierToken()))); + + // {4}{W}{W}, Sacrifice this creature: Search your library for an Equipment card, put it onto the battlefield, then shuffle. + Ability ability = new SimpleActivatedAbility( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter)), new ManaCostsImpl<>("{4}{W}{W}") + ); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private HonoredKnightCaptain(final HonoredKnightCaptain card) { + super(card); + } + + @Override + public HonoredKnightCaptain copy() { + return new HonoredKnightCaptain(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HorizonExplorer.java b/Mage.Sets/src/mage/cards/h/HorizonExplorer.java new file mode 100644 index 00000000000..7000bf65814 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HorizonExplorer.java @@ -0,0 +1,49 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.EnterUntappedAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HorizonExplorer extends CardImpl { + + public HorizonExplorer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Lands you control enter untapped. + this.addAbility(new SimpleStaticAbility(new EnterUntappedAllEffect(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS))); + + // Whenever you attack a player, create a Lander token. + this.addAbility(new AttacksPlayerWithCreaturesTriggeredAbility( + new CreateTokenEffect(new LanderToken()), + StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED, SetTargetPointer.NONE + )); + } + + private HorizonExplorer(final HorizonExplorer card) { + super(card); + } + + @Override + public HorizonExplorer copy() { + return new HorizonExplorer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HornOfValhalla.java b/Mage.Sets/src/mage/cards/h/HornOfValhalla.java index 6120a9664e7..e2056406e7d 100644 --- a/Mage.Sets/src/mage/cards/h/HornOfValhalla.java +++ b/Mage.Sets/src/mage/cards/h/HornOfValhalla.java @@ -30,7 +30,7 @@ public final class HornOfValhalla extends AdventureCard { // Equipped creature gets +1/+1 for each creature you control. this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect( - CreaturesYouControlCount.instance, CreaturesYouControlCount.instance + CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL ).setText("equipped creature gets +1/+1 for each creature you control")).addHint(CreaturesYouControlHint.instance)); // Equip {3} diff --git a/Mage.Sets/src/mage/cards/h/HowlingGale.java b/Mage.Sets/src/mage/cards/h/HowlingGale.java index c4e058056c9..ed811ea9995 100644 --- a/Mage.Sets/src/mage/cards/h/HowlingGale.java +++ b/Mage.Sets/src/mage/cards/h/HowlingGale.java @@ -1,40 +1,32 @@ package mage.cards.h; -import java.util.UUID; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.keyword.FlashbackAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.TimingRule; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class HowlingGale extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public HowlingGale(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Howling Gale deals 1 damage to each creature with flying and each player. - this.getSpellAbility().addEffect(new DamageAllEffect(1, filter)); + this.getSpellAbility().addEffect(new DamageAllEffect(1, StaticFilters.FILTER_CREATURE_FLYING)); Effect effect = new DamagePlayersEffect(1); effect.setText("and each player"); this.getSpellAbility().addEffect(effect); + // Flashback {1}{G} this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{1}{G}"))); } diff --git a/Mage.Sets/src/mage/cards/h/Hullcarver.java b/Mage.Sets/src/mage/cards/h/Hullcarver.java new file mode 100644 index 00000000000..7f542c4d505 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/Hullcarver.java @@ -0,0 +1,37 @@ +package mage.cards.h; + +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 Hullcarver extends CardImpl { + + public Hullcarver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{B}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + } + + private Hullcarver(final Hullcarver card) { + super(card); + } + + @Override + public Hullcarver copy() { + return new Hullcarver(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HundredHandedOne.java b/Mage.Sets/src/mage/cards/h/HundredHandedOne.java index 2ce2d04b673..5fd007c5523 100644 --- a/Mage.Sets/src/mage/cards/h/HundredHandedOne.java +++ b/Mage.Sets/src/mage/cards/h/HundredHandedOne.java @@ -1,7 +1,6 @@ package mage.cards.h; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -15,9 +14,10 @@ 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.Zone; +import mage.constants.SubType; + +import java.util.UUID; /** * @@ -40,11 +40,11 @@ public final class HundredHandedOne extends CardImpl { // As long as Hundred-Handed One is monstrous, it has reach and can block an additional ninety-nine creatures each combat. ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect( - new GainAbilitySourceEffect(ReachAbility.getInstance(), Duration.WhileOnBattlefield), + new GainAbilitySourceEffect(ReachAbility.getInstance(), Duration.WhileOnBattlefield), MonstrousCondition.instance, - "As long as Hundred-Handed One is monstrous, it has reach"); + "As long as {this} is monstrous, it has reach"); ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect( - new CanBlockAdditionalCreatureEffect(99), + new CanBlockAdditionalCreatureEffect(99), MonstrousCondition.instance, "and can block an additional ninety-nine creatures each combat"); Ability ability = new SimpleStaticAbility(effect1); diff --git a/Mage.Sets/src/mage/cards/h/HurlyBurly.java b/Mage.Sets/src/mage/cards/h/HurlyBurly.java index b78d98fd474..002d41c3afd 100644 --- a/Mage.Sets/src/mage/cards/h/HurlyBurly.java +++ b/Mage.Sets/src/mage/cards/h/HurlyBurly.java @@ -1,35 +1,34 @@ package mage.cards.h; -import java.util.UUID; import mage.abilities.Mode; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; +import java.util.UUID; + /** - * * @author Loki */ public final class HurlyBurly extends CardImpl { private static final FilterCreaturePermanent filterWithoutFlying = new FilterCreaturePermanent("creature without flying"); - private static final FilterCreaturePermanent filterWithFlying = new FilterCreaturePermanent("creature with flying"); static { filterWithoutFlying.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); - filterWithFlying.add(new AbilityPredicate(FlyingAbility.class)); } public HurlyBurly(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); - this.getSpellAbility().addEffect(new DamageAllEffect(1, filterWithFlying)); + this.getSpellAbility().addEffect(new DamageAllEffect(1, StaticFilters.FILTER_CREATURE_FLYING)); Mode mode = new Mode(new DamageAllEffect(1, filterWithoutFlying)); this.getSpellAbility().addMode(mode); } diff --git a/Mage.Sets/src/mage/cards/h/Hurricane.java b/Mage.Sets/src/mage/cards/h/Hurricane.java index cb196c777c8..06466406ba4 100644 --- a/Mage.Sets/src/mage/cards/h/Hurricane.java +++ b/Mage.Sets/src/mage/cards/h/Hurricane.java @@ -1,34 +1,25 @@ package mage.cards.h; -import java.util.UUID; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageEverythingEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author Quercitron */ public final class Hurricane extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Hurricane(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{X}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{G}"); // Hurricane deals X damage to each creature with flying and each player. - this.getSpellAbility().addEffect(new DamageEverythingEffect(GetXValue.instance, filter)); + this.getSpellAbility().addEffect(new DamageEverythingEffect(GetXValue.instance, StaticFilters.FILTER_CREATURE_FLYING)); } private Hurricane(final Hurricane card) { diff --git a/Mage.Sets/src/mage/cards/h/Hylderblade.java b/Mage.Sets/src/mage/cards/h/Hylderblade.java new file mode 100644 index 00000000000..2586689aaf4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/Hylderblade.java @@ -0,0 +1,53 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Hylderblade extends CardImpl { + + public Hylderblade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{B}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +3/+1. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(3, 1))); + + // Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, attach this Equipment to target creature you control. + Ability ability = new BeginningOfEndStepTriggeredAbility(new AttachEffect( + Outcome.BoostCreature, "attach {this} to target creature you control" + )).withInterveningIf(VoidCondition.instance); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability.setAbilityWord(AbilityWord.VOID).addHint(VoidCondition.getHint()), new VoidWatcher()); + + // Equip {4} + this.addAbility(new EquipAbility(4)); + } + + private Hylderblade(final Hylderblade card) { + super(card); + } + + @Override + public Hylderblade copy() { + return new Hylderblade(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HymnOfTheFaller.java b/Mage.Sets/src/mage/cards/h/HymnOfTheFaller.java new file mode 100644 index 00000000000..d2860d0e8a4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HymnOfTheFaller.java @@ -0,0 +1,47 @@ +package mage.cards.h; + +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HymnOfTheFaller extends CardImpl { + + public HymnOfTheFaller(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + // Surveil 1, then you draw a card and lose 1 life. + this.getSpellAbility().addEffect(new SurveilEffect(1, false)); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1, true).concatBy(", then")); + this.getSpellAbility().addEffect(new LoseLifeSourceControllerEffect(1).setText("and lose 1 life")); + + // Void -- If a nonland permanent left the battlefield this turn or a spell was warped this turn, draw another card. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(1), VoidCondition.instance, + "
" + AbilityWord.VOID.formatWord() + "If a nonland permanent left the battlefield " + + "this turn or a spell was warped this turn, draw another card" + )); + this.getSpellAbility().addHint(VoidCondition.getHint()); + this.getSpellAbility().addWatcher(new VoidWatcher()); + } + + private HymnOfTheFaller(final HymnOfTheFaller card) { + super(card); + } + + @Override + public HymnOfTheFaller copy() { + return new HymnOfTheFaller(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IcecaveCrasher.java b/Mage.Sets/src/mage/cards/i/IcecaveCrasher.java new file mode 100644 index 00000000000..8b2000b826d --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IcecaveCrasher.java @@ -0,0 +1,42 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IcecaveCrasher extends CardImpl { + + public IcecaveCrasher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Landfall -- Whenever a land you control enters, this creature gets +1/+0 until end of turn. + this.addAbility(new LandfallAbility(new BoostSourceEffect(1, 0, Duration.EndOfTurn))); + } + + private IcecaveCrasher(final IcecaveCrasher card) { + super(card); + } + + @Override + public IcecaveCrasher copy() { + return new IcecaveCrasher(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IcetillExplorer.java b/Mage.Sets/src/mage/cards/i/IcetillExplorer.java new file mode 100644 index 00000000000..19ed3e18206 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IcetillExplorer.java @@ -0,0 +1,48 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.common.LandfallAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect; +import mage.abilities.effects.common.ruleModifying.PlayFromGraveyardControllerEffect; +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 IcetillExplorer extends CardImpl { + + public IcetillExplorer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // You may play an additional land on each of your turns. + this.addAbility(new SimpleStaticAbility(new PlayAdditionalLandsControllerEffect(1, Duration.WhileOnBattlefield))); + + // You may play lands from your graveyard. + this.addAbility(new SimpleStaticAbility(PlayFromGraveyardControllerEffect.playLands())); + + // Landfall -- Whenever a land you control enters, mill a card. + this.addAbility(new LandfallAbility(new MillCardsControllerEffect(1))); + } + + private IcetillExplorer(final IcetillExplorer card) { + super(card); + } + + @Override + public IcetillExplorer copy() { + return new IcetillExplorer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IfhBiffEfreet.java b/Mage.Sets/src/mage/cards/i/IfhBiffEfreet.java index 9d5a8908f82..d928c40cb76 100644 --- a/Mage.Sets/src/mage/cards/i/IfhBiffEfreet.java +++ b/Mage.Sets/src/mage/cards/i/IfhBiffEfreet.java @@ -1,7 +1,6 @@ package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -15,33 +14,26 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.TargetController; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author fireshoes */ public final class IfhBiffEfreet extends CardImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } public IfhBiffEfreet(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.subtype.add(SubType.EFREET); this.power = new MageInt(3); this.toughness = new MageInt(3); // Flying this.addAbility(FlyingAbility.getInstance()); - + // {G}: Ifh-Biff Efreet deals 1 damage to each creature with flying and each player. Any player may activate this ability. - SimpleActivatedAbility ability = new SimpleActivatedAbility(new DamageAllEffect(1, filter), new ManaCostsImpl<>("{G}")); + SimpleActivatedAbility ability = new SimpleActivatedAbility(new DamageAllEffect(1, StaticFilters.FILTER_CREATURE_FLYING), new ManaCostsImpl<>("{G}")); Effect effect = new DamagePlayersEffect(1); effect.setText("and each player"); ability.addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/i/IllusionaryInformant.java b/Mage.Sets/src/mage/cards/i/IllusionaryInformant.java index 2a386666fcc..9be533acf14 100644 --- a/Mage.Sets/src/mage/cards/i/IllusionaryInformant.java +++ b/Mage.Sets/src/mage/cards/i/IllusionaryInformant.java @@ -1,14 +1,14 @@ package mage.cards.i; import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.InfoEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.InfoEffect; import java.util.UUID; @@ -28,10 +28,10 @@ public final class IllusionaryInformant extends CardImpl { // TODO: Draft specific abilities not implemented // Draft Illusionary Informant face up. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Draft Illusionary Informant face up - not implemented."))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Draft {this} face up - not implemented."))); // During the draft, you may turn Illusionary Informant face down. If you do, look at the next card drafted by a player of your choice. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("During the draft, you may turn Illusionary Informant face down. If you do, " + this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("During the draft, you may turn {this} face down. If you do, " + "look at the next card drafted by a player of your choice - not implemented."))); // Flying diff --git a/Mage.Sets/src/mage/cards/i/IllvoiGaleblade.java b/Mage.Sets/src/mage/cards/i/IllvoiGaleblade.java new file mode 100644 index 00000000000..dab83b1d4cf --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IllvoiGaleblade.java @@ -0,0 +1,45 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.token.ClueAbility; +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 IllvoiGaleblade extends CardImpl { + + public IllvoiGaleblade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}"); + + this.subtype.add(SubType.JELLYFISH); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // {2}, Sacrifice this creature: Draw a card. + this.addAbility(new ClueAbility(true)); + } + + private IllvoiGaleblade(final IllvoiGaleblade card) { + super(card); + } + + @Override + public IllvoiGaleblade copy() { + return new IllvoiGaleblade(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IllvoiInfiltrator.java b/Mage.Sets/src/mage/cards/i/IllvoiInfiltrator.java new file mode 100644 index 00000000000..46487a4f53c --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IllvoiInfiltrator.java @@ -0,0 +1,72 @@ + +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.CantBeBlockedSourceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class IllvoiInfiltrator extends CardImpl { + + public IllvoiInfiltrator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.JELLYFISH); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // This creature can't be blocked if you've cast two or more spells this turn. + this.addAbility(new SimpleStaticAbility( + new ConditionalContinuousEffect( + new GainAbilitySourceEffect(new CantBeBlockedSourceAbility(), Duration.WhileOnBattlefield), + IllvoiInfiltratorCondition.instance, + "This creature can't be blocked if you've cast two or more spells this turn" + ) + )); + + // Whenever this creature deals combat damage to a player, draw a card. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new DrawCardSourceControllerEffect(1), false + )); + } + + private IllvoiInfiltrator(final IllvoiInfiltrator card) { + super(card); + } + + @Override + public IllvoiInfiltrator copy() { + return new IllvoiInfiltrator(this); + } +} + +enum IllvoiInfiltratorCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getState() + .getWatcher(SpellsCastWatcher.class) + .getSpellsCastThisTurn(source.getControllerId()) + .size() >= 2; + } +} diff --git a/Mage.Sets/src/mage/cards/i/IllvoiLightJammer.java b/Mage.Sets/src/mage/cards/i/IllvoiLightJammer.java new file mode 100644 index 00000000000..68810679a56 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IllvoiLightJammer.java @@ -0,0 +1,52 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAttachToTarget; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class IllvoiLightJammer extends CardImpl { + + public IllvoiLightJammer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{R}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When this Equipment enters, attach it to target creature you control. That creature gains hexproof until end of turn. + Ability ability = new EntersBattlefieldAttachToTarget(); + ability.addEffect(new GainAbilityTargetEffect(HexproofAbility.getInstance()) + .setText("That creature gains hexproof until end of turn")); + this.addAbility(ability); + + // Equipped creature gets +1/+2. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(1, 2))); + + // Equip {1} + this.addAbility(new EquipAbility(3)); + } + + private IllvoiLightJammer(final IllvoiLightJammer card) { + super(card); + } + + @Override + public IllvoiLightJammer copy() { + return new IllvoiLightJammer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IllvoiOperative.java b/Mage.Sets/src/mage/cards/i/IllvoiOperative.java new file mode 100644 index 00000000000..9fba338885e --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IllvoiOperative.java @@ -0,0 +1,41 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +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 Susucr + */ +public final class IllvoiOperative extends CardImpl { + + public IllvoiOperative(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.JELLYFISH); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Whenever you cast your second spell each turn, put a +1/+1 counter on this creature. + this.addAbility(new CastSecondSpellTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)) + )); + } + + private IllvoiOperative(final IllvoiOperative card) { + super(card); + } + + @Override + public IllvoiOperative copy() { + return new IllvoiOperative(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/ImpelledGiant.java b/Mage.Sets/src/mage/cards/i/ImpelledGiant.java index 4b136e16cec..5a32d0d837f 100644 --- a/Mage.Sets/src/mage/cards/i/ImpelledGiant.java +++ b/Mage.Sets/src/mage/cards/i/ImpelledGiant.java @@ -1,6 +1,5 @@ package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.ObjectColor; import mage.abilities.Ability; @@ -17,23 +16,23 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetControlledPermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author jeffwadsworth */ public final class ImpelledGiant extends CardImpl { - static final private FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("an untapped red creature you control other than Impelled Giant"); + static final private FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("an untapped red creature you control other than {this}"); static { filter.add(TappedPredicate.UNTAPPED); @@ -73,7 +72,7 @@ class ImpelledGiantCost extends CostImpl { public ImpelledGiantCost(TargetControlledPermanent target) { this.target = target; - this.text = "Tap an untapped red creature you control other than Impelled Giant"; + this.text = "Tap an untapped red creature you control other than {this}"; } private ImpelledGiantCost(final ImpelledGiantCost cost) { diff --git a/Mage.Sets/src/mage/cards/i/Incendiary.java b/Mage.Sets/src/mage/cards/i/Incendiary.java index b349674d1a1..1da5f208c95 100644 --- a/Mage.Sets/src/mage/cards/i/Incendiary.java +++ b/Mage.Sets/src/mage/cards/i/Incendiary.java @@ -1,24 +1,25 @@ package mage.cards.i; -import java.util.UUID; -import mage.constants.SubType; -import mage.target.common.TargetCreaturePermanent; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.DiesAttachedTriggeredAbility; import mage.abilities.dynamicvalue.common.CountersSourceCount; import mage.abilities.effects.Effect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; -import mage.constants.Outcome; -import mage.target.TargetPermanent; import mage.abilities.keyword.EnchantAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.counters.CounterType; +import mage.target.TargetPermanent; import mage.target.common.TargetCreatureOrPlayer; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** * @@ -47,7 +48,7 @@ public final class Incendiary extends CardImpl { // When enchanted creature dies, Incendiary deals X damage to any target, where X is the number of fuse counters on Incendiary. Effect effect = new DamageTargetEffect(new CountersSourceCount(CounterType.FUSE)).setText(rule); Ability ability2 = new DiesAttachedTriggeredAbility(effect, "enchanted creature"); - ability.addTarget(new TargetCreatureOrPlayer()); + ability2.addTarget(new TargetCreatureOrPlayer()); this.addAbility(ability2); } diff --git a/Mage.Sets/src/mage/cards/i/InfernalSovereign.java b/Mage.Sets/src/mage/cards/i/InfernalSovereign.java index 05615210eeb..07af6fe2061 100644 --- a/Mage.Sets/src/mage/cards/i/InfernalSovereign.java +++ b/Mage.Sets/src/mage/cards/i/InfernalSovereign.java @@ -1,7 +1,8 @@ package mage.cards.i; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; +import mage.abilities.common.PlayLandOrCastSpellTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; @@ -12,9 +13,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; import java.util.UUID; @@ -39,7 +37,9 @@ public final class InfernalSovereign extends CardImpl { this.addAbility(new SimpleStaticAbility(new SkipDrawStepEffect())); // Whenever you play a land or cast a spell, you draw a card and you lose 1 life. - this.addAbility(new InfernalSovereignTriggeredAbility()); + Ability ability = new PlayLandOrCastSpellTriggeredAbility(new DrawCardSourceControllerEffect(1, true)); + ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); } private InfernalSovereign(final InfernalSovereign card) { @@ -51,31 +51,3 @@ public final class InfernalSovereign extends CardImpl { return new InfernalSovereign(this); } } - -class InfernalSovereignTriggeredAbility extends TriggeredAbilityImpl { - - public InfernalSovereignTriggeredAbility() { - super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1).setText("you draw a card")); - this.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); - setTriggerPhrase("Whenever you play a land or cast a spell, "); - } - - private InfernalSovereignTriggeredAbility(final InfernalSovereignTriggeredAbility ability) { - super(ability); - } - - @Override - public InfernalSovereignTriggeredAbility copy() { - return new InfernalSovereignTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LAND_PLAYED || event.getType() == GameEvent.EventType.SPELL_CAST; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(controllerId); - } -} diff --git a/Mage.Sets/src/mage/cards/i/InfiniteGuidelineStation.java b/Mage.Sets/src/mage/cards/i/InfiniteGuidelineStation.java new file mode 100644 index 00000000000..f77571b35d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InfiniteGuidelineStation.java @@ -0,0 +1,72 @@ +package mage.cards.i; + +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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.MulticoloredPredicate; +import mage.game.permanent.token.RobotToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InfiniteGuidelineStation extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("multicolored permanent you control"); + + static { + filter.add(MulticoloredPredicate.instance); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private static final Hint hint = new ValueHint("Multicolored permanents you control", xValue); + + public InfiniteGuidelineStation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{W}{U}{B}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPACECRAFT); + + // When Infinite Guideline Station enters, create a tapped 2/2 colorless Robot artifact creature token for each multicolored permanent you control. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new CreateTokenEffect(new RobotToken(), xValue, true, false) + ).addHint(hint)); + + // Station + this.addAbility(new StationAbility()); + + // STATION 12+ + // Flying + // Whenever Infinite Guideline Station attacks, draw a card for each multicolored permanent you control. + // 7/15 + this.addAbility(new StationLevelAbility(12) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(new AttacksTriggeredAbility(new DrawCardSourceControllerEffect(xValue))) + .withPT(7, 15)); + } + + private InfiniteGuidelineStation(final InfiniteGuidelineStation card) { + super(card); + } + + @Override + public InfiniteGuidelineStation copy() { + return new InfiniteGuidelineStation(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/Inquisition.java b/Mage.Sets/src/mage/cards/i/Inquisition.java index 5d9e9427d9a..d538d10bb0f 100644 --- a/Mage.Sets/src/mage/cards/i/Inquisition.java +++ b/Mage.Sets/src/mage/cards/i/Inquisition.java @@ -50,7 +50,7 @@ class InquisitionEffect extends OneShotEffect { public InquisitionEffect() { super(Outcome.Exile); - staticText = "Target player reveals their hand. Inquisition deals damage to that player equal to the number of white cards in their hand"; + staticText = "Target player reveals their hand. {this} deals damage to that player equal to the number of white cards in their hand"; } private InquisitionEffect(final InquisitionEffect effect) { diff --git a/Mage.Sets/src/mage/cards/i/InsatiableSkittermaw.java b/Mage.Sets/src/mage/cards/i/InsatiableSkittermaw.java new file mode 100644 index 00000000000..4a1c3855eca --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InsatiableSkittermaw.java @@ -0,0 +1,48 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.MenaceAbility; +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.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InsatiableSkittermaw extends CardImpl { + + public InsatiableSkittermaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Menace + this.addAbility(new MenaceAbility()); + + // Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, put a +1/+1 counter on this creature. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + ).withInterveningIf(VoidCondition.instance).setAbilityWord(AbilityWord.VOID).addHint(VoidCondition.getHint()), new VoidWatcher()); + } + + private InsatiableSkittermaw(final InsatiableSkittermaw card) { + super(card); + } + + @Override + public InsatiableSkittermaw copy() { + return new InsatiableSkittermaw(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InsightEngine.java b/Mage.Sets/src/mage/cards/i/InsightEngine.java new file mode 100644 index 00000000000..3cf8a0a7bac --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InsightEngine.java @@ -0,0 +1,46 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +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.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InsightEngine extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(CounterType.CHARGE); + + public InsightEngine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + // {2}, {T}: Put a charge counter on this artifact, then draw a card for each charge counter on it. + Ability ability = new SimpleActivatedAbility( + new AddCountersSourceEffect(CounterType.CHARGE.createInstance()), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addEffect(new DrawCardSourceControllerEffect(xValue) + .setText(", then draw a card for each charge counter on it")); + this.addAbility(ability); + } + + private InsightEngine(final InsightEngine card) { + super(card); + } + + @Override + public InsightEngine copy() { + return new InsightEngine(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InspiritFlagshipVessel.java b/Mage.Sets/src/mage/cards/i/InspiritFlagshipVessel.java new file mode 100644 index 00000000000..1b36367e0eb --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InspiritFlagshipVessel.java @@ -0,0 +1,106 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +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.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InspiritFlagshipVessel extends CardImpl { + + private static final FilterPermanent filter = new FilterArtifactPermanent("other target artifact"); + + static { + filter.add(AnotherPredicate.instance); + } + + public InspiritFlagshipVessel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{U}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 1+ + // At the beginning of combat on your turn, put your choice of a +1/+1 counter or two charge counters on up to one other target artifact. + Ability ability = new BeginningOfCombatTriggeredAbility(new InspiritFlagshipVesselEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(new StationLevelAbility(1).withLevelAbility(ability)); + + // STATION 8+ + // Flying + // Other artifacts you control have hexproof and indestructible. + // 5/5 + ability = new SimpleStaticAbility(new GainAbilityControlledEffect( + HexproofAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_ARTIFACTS, true + )); + ability.addEffect(new GainAbilityControlledEffect( + IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_ARTIFACTS, true + ).setText("and indestructible")); + this.addAbility(new StationLevelAbility(8) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(ability) + .withPT(5, 5)); + } + + private InspiritFlagshipVessel(final InspiritFlagshipVessel card) { + super(card); + } + + @Override + public InspiritFlagshipVessel copy() { + return new InspiritFlagshipVessel(this); + } +} + +class InspiritFlagshipVesselEffect extends OneShotEffect { + + InspiritFlagshipVesselEffect() { + super(Outcome.Benefit); + staticText = "put your choice of a +1/+1 counter or two charge counters on up to one other target artifact"; + } + + private InspiritFlagshipVesselEffect(final InspiritFlagshipVesselEffect effect) { + super(effect); + } + + @Override + public InspiritFlagshipVesselEffect copy() { + return new InspiritFlagshipVesselEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + return player != null && permanent != null && permanent.addCounters( + player.chooseUse( + Outcome.BoostCreature, "Put a +1/+1 counter or two charge counters on " + + permanent.getLogName() + '?', null, + "one +1/+1 counter", "two charge counters", source, game + ) ? CounterType.P1P1.createInstance() : CounterType.CHARGE.createInstance(2), source, game + ); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IntegrityIntervention.java b/Mage.Sets/src/mage/cards/i/IntegrityIntervention.java index 3c51fb1f7fc..08bce3880fa 100644 --- a/Mage.Sets/src/mage/cards/i/IntegrityIntervention.java +++ b/Mage.Sets/src/mage/cards/i/IntegrityIntervention.java @@ -1,6 +1,5 @@ package mage.cards.i; -import java.util.UUID; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; @@ -12,8 +11,9 @@ import mage.constants.SpellAbilityType; import mage.target.common.TargetAnyTarget; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class IntegrityIntervention extends SplitCard { @@ -33,7 +33,7 @@ public final class IntegrityIntervention extends SplitCard { // Intervention // Intervention deals 3 damage to any target and you gain 3 life. this.getRightHalfCard().getSpellAbility().addEffect( - new DamageTargetEffect(3).setText("Intervention deals 3 damage to any target") + new DamageTargetEffect(3).setText("{this} deals 3 damage to any target") ); this.getRightHalfCard().getSpellAbility().addEffect( new GainLifeEffect(3).setText("and you gain 3 life") diff --git a/Mage.Sets/src/mage/cards/i/InterceptorMechan.java b/Mage.Sets/src/mage/cards/i/InterceptorMechan.java new file mode 100644 index 00000000000..d44291321e9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InterceptorMechan.java @@ -0,0 +1,58 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +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.StaticFilters; +import mage.target.common.TargetCardInYourGraveyard; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InterceptorMechan extends CardImpl { + + public InterceptorMechan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{B}{R}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, return target artifact or creature card from your graveyard to your hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_ARTIFACT_OR_CREATURE)); + this.addAbility(ability); + + // Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, put a +1/+1 counter on this creature. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())) + .withInterveningIf(VoidCondition.instance) + .setAbilityWord(AbilityWord.VOID) + .addHint(VoidCondition.getHint()), new VoidWatcher()); + } + + private InterceptorMechan(final InterceptorMechan card) { + super(card); + } + + @Override + public InterceptorMechan copy() { + return new InterceptorMechan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IntrepidTenderfoot.java b/Mage.Sets/src/mage/cards/i/IntrepidTenderfoot.java new file mode 100644 index 00000000000..14e6dd6ead2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IntrepidTenderfoot.java @@ -0,0 +1,42 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +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 IntrepidTenderfoot extends CardImpl { + + public IntrepidTenderfoot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {3}: Put a +1/+1 counter on this creature. Activate only as a sorcery. + this.addAbility(new ActivateAsSorceryActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new GenericManaCost(3) + )); + } + + private IntrepidTenderfoot(final IntrepidTenderfoot card) { + super(card); + } + + @Override + public IntrepidTenderfoot copy() { + return new IntrepidTenderfoot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasiveManeuvers.java b/Mage.Sets/src/mage/cards/i/InvasiveManeuvers.java new file mode 100644 index 00000000000..54c4528565c --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InvasiveManeuvers.java @@ -0,0 +1,43 @@ +package mage.cards.i; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InvasiveManeuvers extends CardImpl { + + private static final Condition condition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.SPACECRAFT)); + + public InvasiveManeuvers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Invasive Maneuvers deals 3 damage to target creature. It deals 5 damage instead if you control a Spacecraft. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new DamageTargetEffect(5), new DamageTargetEffect(3), condition, + "{this} deals 3 damage to target creature. It deals 5 damage instead if you control a Spacecraft" + )); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private InvasiveManeuvers(final InvasiveManeuvers card) { + super(card); + } + + @Override + public InvasiveManeuvers copy() { + return new InvasiveManeuvers(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IslandOfWakWak.java b/Mage.Sets/src/mage/cards/i/IslandOfWakWak.java index fd027139515..98cb6119a16 100644 --- a/Mage.Sets/src/mage/cards/i/IslandOfWakWak.java +++ b/Mage.Sets/src/mage/cards/i/IslandOfWakWak.java @@ -1,44 +1,34 @@ package mage.cards.i; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author anonymous */ public final class IslandOfWakWak extends CardImpl { - private static final FilterCreaturePermanent filterWithFlying = new FilterCreaturePermanent("creature with flying"); - - static { - filterWithFlying.add(new AbilityPredicate(FlyingAbility.class)); - } - public IslandOfWakWak(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); // {tap}: Target creature with flying has base power 0 until end of turn. Ability ability = new SimpleActivatedAbility(new IslandOfWakWakEffect(), new TapSourceCost()); - ability.addTarget(new TargetPermanent(filterWithFlying)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/i/ItlimocCradleOfTheSun.java b/Mage.Sets/src/mage/cards/i/ItlimocCradleOfTheSun.java index a9f5a6dffcc..30411803120 100644 --- a/Mage.Sets/src/mage/cards/i/ItlimocCradleOfTheSun.java +++ b/Mage.Sets/src/mage/cards/i/ItlimocCradleOfTheSun.java @@ -1,7 +1,7 @@ package mage.cards.i; import mage.Mana; -import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; import mage.abilities.hint.common.CreaturesYouControlHint; import mage.abilities.mana.DynamicManaAbility; import mage.abilities.mana.GreenManaAbility; @@ -9,7 +9,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SuperType; -import mage.filter.StaticFilters; import java.util.UUID; @@ -30,7 +29,9 @@ public final class ItlimocCradleOfTheSun extends CardImpl { this.addAbility(new GreenManaAbility()); // {T}: Add {G} for each creature you control. - this.addAbility(new DynamicManaAbility(Mana.GreenMana(1), new PermanentsOnBattlefieldCount(StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED)).addHint(CreaturesYouControlHint.instance)); + this.addAbility(new DynamicManaAbility( + Mana.GreenMana(1), CreaturesYouControlCount.SINGULAR + ).addHint(CreaturesYouControlHint.instance)); } private ItlimocCradleOfTheSun(final ItlimocCradleOfTheSun card) { diff --git a/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java b/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java index ad4ef1f1cff..22499a1b5fc 100644 --- a/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java +++ b/Mage.Sets/src/mage/cards/j/JaceArchitectOfThought.java @@ -108,6 +108,7 @@ class JaceArchitectOfThoughtEffect3 extends OneShotEffect { controller.moveCards(card, Zone.EXILED, source, game); player.shuffleLibrary(source, game); } + game.processAction(); cards.retainZone(Zone.EXILED, game); CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, StaticFilters.FILTER_CARD); return true; diff --git a/Mage.Sets/src/mage/cards/j/JaggedScarArchers.java b/Mage.Sets/src/mage/cards/j/JaggedScarArchers.java index 28d11bf11bf..f2d2c60297c 100644 --- a/Mage.Sets/src/mage/cards/j/JaggedScarArchers.java +++ b/Mage.Sets/src/mage/cards/j/JaggedScarArchers.java @@ -9,35 +9,30 @@ import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; -import mage.abilities.keyword.FlyingAbility; 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.filter.common.FilterControlledPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author Plopman */ public final class JaggedScarArchers extends CardImpl { - private static final FilterCreaturePermanent flyingCreatureFilter = new FilterCreaturePermanent("creature with flying"); private static final FilterControlledPermanent controlledElvesFilter = new FilterControlledPermanent("Elves you control"); + static { - flyingCreatureFilter.add(new AbilityPredicate(FlyingAbility.class)); controlledElvesFilter.add(SubType.ELF.getPredicate()); } public JaggedScarArchers(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{G}"); this.subtype.add(SubType.ELF); this.subtype.add(SubType.ARCHER); @@ -46,9 +41,10 @@ public final class JaggedScarArchers extends CardImpl { // Jagged-Scar Archers's power and toughness are each equal to the number of Elves you control. this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(new PermanentsOnBattlefieldCount(controlledElvesFilter)))); + // {tap}: Jagged-Scar Archers deals damage equal to its power to target creature with flying. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE).setText("{this} deals damage equal to its power to target creature with flying"), new TapSourceCost()); - ability.addTarget(new TargetPermanent(flyingCreatureFilter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/j/JoragaWarcaller.java b/Mage.Sets/src/mage/cards/j/JoragaWarcaller.java index e7bc1f28bd7..d8460c3e19e 100644 --- a/Mage.Sets/src/mage/cards/j/JoragaWarcaller.java +++ b/Mage.Sets/src/mage/cards/j/JoragaWarcaller.java @@ -1,7 +1,6 @@ package mage.cards.j; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleStaticAbility; @@ -12,21 +11,25 @@ import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.MultikickerAbility; 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.TargetController; import mage.counters.CounterType; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; +import java.util.UUID; + /** - * * @author jeffwadsworth */ public final class JoragaWarcaller extends CardImpl { - - private static final String rule = "Other Elf creatures you control get +1/+1 for each +1/+1 counter on Joraga Warcaller"; - + + private static final String rule = "Other Elf creatures you control get +1/+1 for each +1/+1 counter on {this}"; + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("elf creatures you control"); - + static { filter.add(TargetController.YOU.getControllerPredicate()); filter.add(SubType.ELF.getPredicate()); diff --git a/Mage.Sets/src/mage/cards/j/JubilantMascot.java b/Mage.Sets/src/mage/cards/j/JubilantMascot.java index 4f706c1c689..12dd6115469 100644 --- a/Mage.Sets/src/mage/cards/j/JubilantMascot.java +++ b/Mage.Sets/src/mage/cards/j/JubilantMascot.java @@ -5,13 +5,12 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DoIfCostPaid; -import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.keyword.SupportEffect; 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.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.TargetPermanent; @@ -39,8 +38,7 @@ public final class JubilantMascot extends CardImpl { // At the beginning of combat on your turn, you may pay {3}{W}. If you do, support 2. (Put a +1/+1 counter on each of up to two other target creatures.) Ability ability = new BeginningOfCombatTriggeredAbility( new DoIfCostPaid( - new AddCountersTargetEffect(CounterType.P1P1.createInstance()) - .setText("support 2"), + new SupportEffect(this, 2, true), new ManaCostsImpl<>("{3}{W}") )); ability.addTarget(new TargetPermanent(0, 2, filter)); diff --git a/Mage.Sets/src/mage/cards/j/JunkyoBell.java b/Mage.Sets/src/mage/cards/j/JunkyoBell.java index bd69fa5bf43..9c1f0248b6b 100644 --- a/Mage.Sets/src/mage/cards/j/JunkyoBell.java +++ b/Mage.Sets/src/mage/cards/j/JunkyoBell.java @@ -32,7 +32,7 @@ public final class JunkyoBell extends CardImpl { // At the beginning of your upkeep, you may have target creature you control get +X/+X until end of turn, // where X is the number of creatures you control. If you do, sacrifice that creature at the beginning of the next end step. Ability ability = new BeginningOfUpkeepTriggeredAbility( - new BoostTargetEffect(CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn), true + new BoostTargetEffect(CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn), true ); ability.addTarget(new TargetControlledCreaturePermanent()); ability.addEffect(new JunkyoBellSacrificeEffect()); diff --git a/Mage.Sets/src/mage/cards/k/KalamaxTheStormsire.java b/Mage.Sets/src/mage/cards/k/KalamaxTheStormsire.java index 6cffb041ca4..e64a01d24f1 100644 --- a/Mage.Sets/src/mage/cards/k/KalamaxTheStormsire.java +++ b/Mage.Sets/src/mage/cards/k/KalamaxTheStormsire.java @@ -103,7 +103,7 @@ class KalamaxTheStormsireSpellCastAbility extends SpellCastControllerTriggeredAb @Override public String getRule() { return "Whenever you cast your first instant spell each turn, " + - "if Kalamax, the Stormsire is tapped, " + + "if {this} is tapped, " + "copy that spell. You may choose new targets for the copy."; } } diff --git a/Mage.Sets/src/mage/cards/k/KavLandseeker.java b/Mage.Sets/src/mage/cards/k/KavLandseeker.java new file mode 100644 index 00000000000..7b9eef99637 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KavLandseeker.java @@ -0,0 +1,82 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +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.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.LanderToken; +import mage.game.permanent.token.Token; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class KavLandseeker extends CardImpl { + + public KavLandseeker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.KAVU); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // When this creature enters, create a Lander token. At the beginning of the end step on your next turn, sacrifice that token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new KavLandseekerEffect())); + } + + private KavLandseeker(final KavLandseeker card) { + super(card); + } + + @Override + public KavLandseeker copy() { + return new KavLandseeker(this); + } +} + +class KavLandseekerEffect extends OneShotEffect { + + KavLandseekerEffect() { + super(Outcome.Benefit); + staticText = "create a Lander token. " + + "At the beginning of the end step on your next turn, sacrifice that token"; + } + + private KavLandseekerEffect(final KavLandseekerEffect effect) { + super(effect); + } + + @Override + public KavLandseekerEffect copy() { + return new KavLandseekerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new LanderToken(); + token.putOntoBattlefield(1, game, source, source.getControllerId()); + game.addDelayedTriggeredAbility(new AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility( + new SacrificeTargetEffect() + .setTargetPointer(new FixedTargets(token, game)) + .setText("sacrifice that token"), + GameEvent.EventType.END_TURN_STEP_PRE + ), source); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/k/KavaronHarrier.java b/Mage.Sets/src/mage/cards/k/KavaronHarrier.java new file mode 100644 index 00000000000..14406f5c360 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KavaronHarrier.java @@ -0,0 +1,81 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.SacrificeTargetEffect; +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.token.RobotToken; +import mage.game.permanent.token.Token; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class KavaronHarrier extends CardImpl { + + public KavaronHarrier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{R}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Whenever this creature attacks, you may pay {2}. If you do, create a 2/2 colorless Robot artifact creature token that's tapped and attacking. Sacrifice that token at end of combat. + this.addAbility(new AttacksTriggeredAbility( + new DoIfCostPaid(new KavaronHarrierEffect(), new GenericManaCost(2)) + )); + } + + private KavaronHarrier(final KavaronHarrier card) { + super(card); + } + + @Override + public KavaronHarrier copy() { + return new KavaronHarrier(this); + } +} + + +class KavaronHarrierEffect extends OneShotEffect { + + KavaronHarrierEffect() { + super(Outcome.Benefit); + staticText = "create a 2/2 colorless Robot artifact creature token that's tapped and attacking. " + + "Sacrifice that token at end of combat"; + } + + private KavaronHarrierEffect(final KavaronHarrierEffect effect) { + super(effect); + } + + @Override + public KavaronHarrierEffect copy() { + return new KavaronHarrierEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new RobotToken(); + token.putOntoBattlefield(1, game, source, source.getControllerId(), true, true); + game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility( + new SacrificeTargetEffect() + .setTargetPointer(new FixedTargets(token, game)) + .setText("sacrifice that token") + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KavaronMemorialWorld.java b/Mage.Sets/src/mage/cards/k/KavaronMemorialWorld.java new file mode 100644 index 00000000000..a95a3961021 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KavaronMemorialWorld.java @@ -0,0 +1,69 @@ +package mage.cards.k; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +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.HasteAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.RobotToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KavaronMemorialWorld extends CardImpl { + + public KavaronMemorialWorld(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.PLANET); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {R}. + this.addAbility(new RedManaAbility()); + + // Station + this.addAbility(new StationAbility()); + + // STATION 12+ + // {1}{R}, {T}, Sacrifice a land: Create a 2/2 colorless Robot artifact creature token, then creatures you control get +1/+0 and gain haste until end of turn. + Ability ability = new SimpleActivatedAbility( + new CreateTokenEffect(new RobotToken()), new ManaCostsImpl<>("{1}{R}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_LAND)); + ability.addEffect(new BoostControlledEffect(1, 0, Duration.EndOfTurn) + .setText(", then creatures you control get +1/+0")); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("and gain haste until end of turn")); + this.addAbility(new StationLevelAbility(12).withLevelAbility(ability)); + } + + private KavaronMemorialWorld(final KavaronMemorialWorld card) { + super(card); + } + + @Override + public KavaronMemorialWorld copy() { + return new KavaronMemorialWorld(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KavaronSkywarden.java b/Mage.Sets/src/mage/cards/k/KavaronSkywarden.java new file mode 100644 index 00000000000..a4d73a009cb --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KavaronSkywarden.java @@ -0,0 +1,49 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.ReachAbility; +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.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class KavaronSkywarden extends CardImpl { + + public KavaronSkywarden(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.KAVU); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, put a +1/+1 counter on this creature. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())) + .withInterveningIf(VoidCondition.instance) + .setAbilityWord(AbilityWord.VOID) + .addHint(VoidCondition.getHint()), new VoidWatcher()); + } + + private KavaronSkywarden(final KavaronSkywarden card) { + super(card); + } + + @Override + public KavaronSkywarden copy() { + return new KavaronSkywarden(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KavaronTurbodrone.java b/Mage.Sets/src/mage/cards/k/KavaronTurbodrone.java new file mode 100644 index 00000000000..8fdd27aca55 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KavaronTurbodrone.java @@ -0,0 +1,53 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class KavaronTurbodrone extends CardImpl { + + public KavaronTurbodrone(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // {T}: Target creature you control gets +1/+1 and gains haste until end of turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new BoostTargetEffect(1, 1, Duration.EndOfTurn) + .setText("Target creature gets +1/+1"), + new TapSourceCost() + ); + ability.addEffect(new GainAbilityTargetEffect( + HasteAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains haste until end of turn")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private KavaronTurbodrone(final KavaronTurbodrone card) { + super(card); + } + + @Override + public KavaronTurbodrone copy() { + return new KavaronTurbodrone(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KavuPrimarch.java b/Mage.Sets/src/mage/cards/k/KavuPrimarch.java index 30bc8420716..e0b89e5bd08 100644 --- a/Mage.Sets/src/mage/cards/k/KavuPrimarch.java +++ b/Mage.Sets/src/mage/cards/k/KavuPrimarch.java @@ -1,7 +1,6 @@ package mage.cards.k; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.common.KickedCondition; @@ -14,6 +13,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; +import java.util.UUID; + /** * * @author LevelX2 @@ -36,7 +37,7 @@ public final class KavuPrimarch extends CardImpl { // If Kavu Primarch was kicked, it enters with four +1/+1 counters on it. this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(4)),KickedCondition.ONCE, - "If Kavu Primarch was kicked, it enters with four +1/+1 counters on it.", "")); + "If {this} was kicked, it enters with four +1/+1 counters on it.", "")); } private KavuPrimarch(final KavuPrimarch card) { diff --git a/Mage.Sets/src/mage/cards/k/KefkaDancingMad.java b/Mage.Sets/src/mage/cards/k/KefkaDancingMad.java index c3ab872f77e..7f7954118a2 100644 --- a/Mage.Sets/src/mage/cards/k/KefkaDancingMad.java +++ b/Mage.Sets/src/mage/cards/k/KefkaDancingMad.java @@ -114,6 +114,7 @@ class KefkaDancingMadEffect extends OneShotEffect { return false; } controller.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); cards.retainZone(Zone.EXILED, game); KefkaDancingMadTracker tracker = new KefkaDancingMadTracker(); CardUtil.castMultipleWithAttributeForFree( diff --git a/Mage.Sets/src/mage/cards/k/KingHaraldsRevenge.java b/Mage.Sets/src/mage/cards/k/KingHaraldsRevenge.java index 63fc04c9e6d..8dd4fa98ff6 100644 --- a/Mage.Sets/src/mage/cards/k/KingHaraldsRevenge.java +++ b/Mage.Sets/src/mage/cards/k/KingHaraldsRevenge.java @@ -23,7 +23,7 @@ public final class KingHaraldsRevenge extends CardImpl { // Until end of turn, target creature gets +1/+1 for each creature you control and gains trample. It must be blocked this turn if able. this.getSpellAbility().addEffect(new BoostTargetEffect( - CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn + CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn ).setText("until end of turn, target creature gets +1/+1 for each creature you control")); this.getSpellAbility().addEffect(new GainAbilityTargetEffect( TrampleAbility.getInstance(), Duration.EndOfTurn diff --git a/Mage.Sets/src/mage/cards/k/KingNarfisBetrayal.java b/Mage.Sets/src/mage/cards/k/KingNarfisBetrayal.java index c6174250f9b..5b618f1ad34 100644 --- a/Mage.Sets/src/mage/cards/k/KingNarfisBetrayal.java +++ b/Mage.Sets/src/mage/cards/k/KingNarfisBetrayal.java @@ -127,7 +127,7 @@ class KingNarfisBetrayalSecondEffect extends OneShotEffect { public KingNarfisBetrayalSecondEffect() { super(Outcome.Benefit); - this.staticText = "Until end of turn, you may cast spells from among cards exiled with King Narfi's Betrayal," + + this.staticText = "Until end of turn, you may cast spells from among cards exiled with {this}," + " and you may spend mana as though it were mana of any color to cast those spells"; } diff --git a/Mage.Sets/src/mage/cards/k/KnightLuminary.java b/Mage.Sets/src/mage/cards/k/KnightLuminary.java new file mode 100644 index 00000000000..33d5a5c0f71 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KnightLuminary.java @@ -0,0 +1,43 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.HumanSoldierToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KnightLuminary extends CardImpl { + + public KnightLuminary(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // When this creature enters, create a 1/1 white Human Soldier creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HumanSoldierToken()))); + + // Warp {1}{W} + this.addAbility(new WarpAbility(this, "{1}{W}")); + } + + private KnightLuminary(final KnightLuminary card) { + super(card); + } + + @Override + public KnightLuminary copy() { + return new KnightLuminary(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KorDuelist.java b/Mage.Sets/src/mage/cards/k/KorDuelist.java index f35a918b5a2..c493a238f70 100644 --- a/Mage.Sets/src/mage/cards/k/KorDuelist.java +++ b/Mage.Sets/src/mage/cards/k/KorDuelist.java @@ -1,8 +1,6 @@ package mage.cards.k; -import java.util.List; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -13,19 +11,21 @@ import mage.abilities.keyword.DoubleStrikeAbility; 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 mage.game.Game; import mage.game.permanent.Permanent; +import java.util.List; +import java.util.UUID; + /** * * @author North */ public final class KorDuelist extends CardImpl { - private static final String ruleText = "As long as Kor Duelist is equipped, it has double strike"; + private static final String ruleText = "As long as {this} is equipped, it has double strike"; public KorDuelist(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}"); diff --git a/Mage.Sets/src/mage/cards/k/KothOfTheHammer.java b/Mage.Sets/src/mage/cards/k/KothOfTheHammer.java index 14ee8e04dd6..82dbe08f976 100644 --- a/Mage.Sets/src/mage/cards/k/KothOfTheHammer.java +++ b/Mage.Sets/src/mage/cards/k/KothOfTheHammer.java @@ -41,7 +41,7 @@ public final class KothOfTheHammer extends CardImpl { // +1: Untap target Mountain. It becomes a 4/4 red Elemental creature until end of turn. It's still a land. Ability ability = new LoyaltyAbility(new UntapTargetEffect(), 1); - ability.addEffect(new BecomesCreatureTargetEffect(new KothOfTheHammerToken(), false, true, Duration.EndOfTurn)); + ability.addEffect(new BecomesCreatureTargetEffect(new KothOfTheHammerToken(), false, true, Duration.EndOfTurn).withTargetDescription("It")); ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); @@ -65,7 +65,7 @@ public final class KothOfTheHammer extends CardImpl { class KothOfTheHammerToken extends TokenImpl { public KothOfTheHammerToken() { - super("Elemental", "4/4 red Elemental"); + super("Elemental", "4/4 red Elemental creature"); this.cardType.add(CardType.CREATURE); this.subtype.add(SubType.ELEMENTAL); diff --git a/Mage.Sets/src/mage/cards/k/KotisTheFangkeeper.java b/Mage.Sets/src/mage/cards/k/KotisTheFangkeeper.java index ff609acbfcb..a95d43efa36 100644 --- a/Mage.Sets/src/mage/cards/k/KotisTheFangkeeper.java +++ b/Mage.Sets/src/mage/cards/k/KotisTheFangkeeper.java @@ -80,6 +80,8 @@ class KotisTheFangkeeperEffect extends OneShotEffect { controller.moveCards(cards, Zone.EXILED, source, game); FilterCard filter = new FilterCard(); filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, xValue + 1)); + game.processAction(); + cards.retainZone(Zone.EXILED, game); CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, filter); return true; } diff --git a/Mage.Sets/src/mage/cards/k/KrovikanVampire.java b/Mage.Sets/src/mage/cards/k/KrovikanVampire.java index c33596292f8..026b2777100 100644 --- a/Mage.Sets/src/mage/cards/k/KrovikanVampire.java +++ b/Mage.Sets/src/mage/cards/k/KrovikanVampire.java @@ -126,7 +126,7 @@ class KrovikanVampireInterveningIfCondition implements Condition { @Override public String toString() { - return "if a creature dealt damage by Krovikan Vampire this turn died"; + return "if a creature dealt damage by {this} this turn died"; } } diff --git a/Mage.Sets/src/mage/cards/k/KyloxVisionaryInventor.java b/Mage.Sets/src/mage/cards/k/KyloxVisionaryInventor.java index 871529737e2..00e094f447c 100644 --- a/Mage.Sets/src/mage/cards/k/KyloxVisionaryInventor.java +++ b/Mage.Sets/src/mage/cards/k/KyloxVisionaryInventor.java @@ -110,6 +110,8 @@ class KyloxVisionaryInventorEffect extends OneShotEffect { return true; } player.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); + cards.retainZone(Zone.EXILED, game); CardUtil.castMultipleWithAttributeForFree( player, source, game, cards, StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY diff --git a/Mage.Sets/src/mage/cards/l/LairwatchGiant.java b/Mage.Sets/src/mage/cards/l/LairwatchGiant.java index f4aa0406d1e..04e4a82d0c0 100644 --- a/Mage.Sets/src/mage/cards/l/LairwatchGiant.java +++ b/Mage.Sets/src/mage/cards/l/LairwatchGiant.java @@ -1,7 +1,6 @@ package mage.cards.l; -import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -15,9 +14,10 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; +import java.util.UUID; + /** * * @author Styxo @@ -81,6 +81,6 @@ class LairwatchGiantTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever Lairwatch Giant blocks two or more creatures, it gains first strike until end of turn."; + return "Whenever {this} blocks two or more creatures, it gains first strike until end of turn."; } } diff --git a/Mage.Sets/src/mage/cards/l/LanternFlare.java b/Mage.Sets/src/mage/cards/l/LanternFlare.java index 4b4244a716e..b43fa39e65f 100644 --- a/Mage.Sets/src/mage/cards/l/LanternFlare.java +++ b/Mage.Sets/src/mage/cards/l/LanternFlare.java @@ -32,10 +32,10 @@ public final class LanternFlare extends CardImpl { // Lantern Flare deals X damage to target creature or planeswalker and you gain X life. [X is the number of creatures you control.] this.getSpellAbility().addEffect(new DamageTargetEffect( - CreaturesYouControlCount.instance + CreaturesYouControlCount.PLURAL ).setText("{this} deals X damage to target creature or planeswalker")); this.getSpellAbility().addEffect(new GainLifeEffect( - CreaturesYouControlCount.instance, + CreaturesYouControlCount.PLURAL, "and you gain X life. [X is the number of creatures you control.]" )); this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); diff --git a/Mage.Sets/src/mage/cards/l/LarvalScoutlander.java b/Mage.Sets/src/mage/cards/l/LarvalScoutlander.java new file mode 100644 index 00000000000..2f9177489bf --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LarvalScoutlander.java @@ -0,0 +1,66 @@ +package mage.cards.l; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LarvalScoutlander extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("land or a Lander"); + + static { + filter.add(Predicates.or( + CardType.LAND.getPredicate(), + SubType.LANDER.getPredicate() + )); + } + + public LarvalScoutlander(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{G}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, you may sacrifice a land or a Lander. If you do, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoIfCostPaid( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary( + 0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS + ), true), new SacrificeTargetCost(filter) + ))); + + // Station + this.addAbility(new StationAbility()); + + // STATION 7+ + // Flying + // 3/3 + this.addAbility(new StationLevelAbility(7) + .withLevelAbility(FlyingAbility.getInstance()) + .withPT(3, 3)); + } + + private LarvalScoutlander(final LarvalScoutlander card) { + super(card); + } + + @Override + public LarvalScoutlander copy() { + return new LarvalScoutlander(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LashOut.java b/Mage.Sets/src/mage/cards/l/LashOut.java index addead2ad54..7efb79e5419 100644 --- a/Mage.Sets/src/mage/cards/l/LashOut.java +++ b/Mage.Sets/src/mage/cards/l/LashOut.java @@ -42,7 +42,7 @@ class LashOutEffect extends OneShotEffect { LashOutEffect() { super(Outcome.Damage); - this.staticText = "Lash Out deals 3 damage to target creature. Clash with an opponent. If you win, Lash Out deals 3 damage to that creature's controller"; + this.staticText = "{this} deals 3 damage to target creature. Clash with an opponent. If you win, {this} deals 3 damage to that creature's controller"; } private LashOutEffect(final LashOutEffect effect) { diff --git a/Mage.Sets/src/mage/cards/l/LashwhipPredator.java b/Mage.Sets/src/mage/cards/l/LashwhipPredator.java new file mode 100644 index 00000000000..7b973553163 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LashwhipPredator.java @@ -0,0 +1,62 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.filter.common.FilterOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LashwhipPredator extends CardImpl { + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition( + new FilterOpponentsCreaturePermanent("your opponents control three or more creatures"), + ComparisonType.MORE_THAN, 2 + ); + private static final Hint hint = new ValueHint( + "Creatures your opponents control", + new PermanentsOnBattlefieldCount(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE) + ); + + public LashwhipPredator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.BEAST); + this.power = new MageInt(5); + this.toughness = new MageInt(7); + + // This spell costs {2} less to cast if your opponents control three or more creatures. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(2, condition).setCanWorksOnStackOnly(true) + ).setRuleAtTheTop(true).addHint(hint)); + + // Reach + this.addAbility(ReachAbility.getInstance()); + } + + private LashwhipPredator(final LashwhipPredator card) { + super(card); + } + + @Override + public LashwhipPredator copy() { + return new LashwhipPredator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LastStand.java b/Mage.Sets/src/mage/cards/l/LastStand.java index 80540eff916..5d7e868c46a 100644 --- a/Mage.Sets/src/mage/cards/l/LastStand.java +++ b/Mage.Sets/src/mage/cards/l/LastStand.java @@ -1,7 +1,6 @@ package mage.cards.l; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; @@ -18,6 +17,8 @@ import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author LevelX2 @@ -60,7 +61,7 @@ class LastStandEffect extends OneShotEffect { public LastStandEffect() { super(Outcome.Benefit); - this.staticText = "Target opponent loses 2 life for each Swamp you control. Last Stand deals damage to target creature equal to the number of Mountains you control. Create a 1/1 green Saproling creature token for each Forest you control. You gain 2 life for each Plains you control. Draw a card for each Island you control, then discard that many cards"; + this.staticText = "Target opponent loses 2 life for each Swamp you control. {this} deals damage to target creature equal to the number of Mountains you control. Create a 1/1 green Saproling creature token for each Forest you control. You gain 2 life for each Plains you control. Draw a card for each Island you control, then discard that many cards"; } private LastStandEffect(final LastStandEffect effect) { diff --git a/Mage.Sets/src/mage/cards/l/LeafArrow.java b/Mage.Sets/src/mage/cards/l/LeafArrow.java index b80bd3e4b88..4dc1eb2b305 100644 --- a/Mage.Sets/src/mage/cards/l/LeafArrow.java +++ b/Mage.Sets/src/mage/cards/l/LeafArrow.java @@ -1,35 +1,24 @@ - package mage.cards.l; -import java.util.UUID; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author North */ public final class LeafArrow extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public LeafArrow(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); - - this.getSpellAbility().addTarget(new TargetPermanent(filter)); this.getSpellAbility().addEffect(new DamageTargetEffect(3)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private LeafArrow(final LeafArrow card) { diff --git a/Mage.Sets/src/mage/cards/l/LeapOfFaith.java b/Mage.Sets/src/mage/cards/l/LeapOfFaith.java index cfada756817..7bd62d9e29c 100644 --- a/Mage.Sets/src/mage/cards/l/LeapOfFaith.java +++ b/Mage.Sets/src/mage/cards/l/LeapOfFaith.java @@ -1,7 +1,6 @@ package mage.cards.l; -import java.util.UUID; import mage.abilities.effects.common.PreventDamageToTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.keyword.FlyingAbility; @@ -11,6 +10,8 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author noxx @@ -23,7 +24,7 @@ public final class LeapOfFaith extends CardImpl { // Target creature gains flying until end of turn. Prevent all damage that would be dealt to that creature this turn. this.getSpellAbility().addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn)); - this.getSpellAbility().addEffect(new PreventDamageToTargetEffect(Duration.EndOfTurn, Integer.MAX_VALUE)); + this.getSpellAbility().addEffect(new PreventDamageToTargetEffect(Duration.EndOfTurn, Integer.MAX_VALUE).withTargetDescription("that creature")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/l/Leeches.java b/Mage.Sets/src/mage/cards/l/Leeches.java index 705441d1fab..eb2ee6617f9 100644 --- a/Mage.Sets/src/mage/cards/l/Leeches.java +++ b/Mage.Sets/src/mage/cards/l/Leeches.java @@ -40,7 +40,7 @@ class LeechesEffect extends OneShotEffect { LeechesEffect() { super(Outcome.Benefit); - this.staticText = "Target player loses all poison counters. Leeches deals that much damage to that player"; + this.staticText = "Target player loses all poison counters. {this} deals that much damage to that player"; } private LeechesEffect(final LeechesEffect effect) { diff --git a/Mage.Sets/src/mage/cards/l/LeovoldsOperative.java b/Mage.Sets/src/mage/cards/l/LeovoldsOperative.java index b448f16364b..32bbf0cc942 100644 --- a/Mage.Sets/src/mage/cards/l/LeovoldsOperative.java +++ b/Mage.Sets/src/mage/cards/l/LeovoldsOperative.java @@ -1,14 +1,15 @@ package mage.cards.l; -import java.util.UUID; import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.InfoEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.InfoEffect; + +import java.util.UUID; /** * @@ -27,11 +28,11 @@ public final class LeovoldsOperative extends CardImpl { // TODO: Draft specific abilities not implemented // Draft Leovold’s Operative face up. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Draft Leovold's Operative face up - not implemented."))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Draft {this} face up - not implemented."))); // As you draft a card, you may draft an additional card from that booster pack. If you do, turn Leovold's Operative face down, then pass the next booster pack without drafting a card from it. this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("As you draft a card, you may draft an additional card from that booster pack. " - + "If you do, turn Leovold's Operative face down, then pass the next booster pack without drafting a card from it - not implemented."))); + + "If you do, turn {this} face down, then pass the next booster pack without drafting a card from it - not implemented."))); } private LeovoldsOperative(final LeovoldsOperative card) { diff --git a/Mage.Sets/src/mage/cards/l/Lich.java b/Mage.Sets/src/mage/cards/l/Lich.java index 9c7afe8ef73..d5dbf9136f8 100644 --- a/Mage.Sets/src/mage/cards/l/Lich.java +++ b/Mage.Sets/src/mage/cards/l/Lich.java @@ -36,7 +36,7 @@ public final class Lich extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}{B}{B}{B}"); // As Lich enters the battlefield, you lose life equal to your life total. - this.addAbility(new EntersBattlefieldAbility(new LoseLifeSourceControllerEffect(ControllerLifeCount.instance), null, "As Lich enters the battlefield, you lose life equal to your life total.", null)); + this.addAbility(new EntersBattlefieldAbility(new LoseLifeSourceControllerEffect(ControllerLifeCount.instance), null, "As {this} enters the battlefield, you lose life equal to your life total.", null)); // You don't lose the game for having 0 or less life. this.addAbility(new SimpleStaticAbility(new DontLoseByZeroOrLessLifeEffect(Duration.WhileOnBattlefield))); diff --git a/Mage.Sets/src/mage/cards/l/LifeOfTheParty.java b/Mage.Sets/src/mage/cards/l/LifeOfTheParty.java index 86c13e6ed56..22dc8f18f9e 100644 --- a/Mage.Sets/src/mage/cards/l/LifeOfTheParty.java +++ b/Mage.Sets/src/mage/cards/l/LifeOfTheParty.java @@ -63,7 +63,7 @@ public final class LifeOfTheParty extends CardImpl { // Whenever Life of the Party attacks, it gets +X/+0 until end of turn, where X is the number of creatures you control. this.addAbility(new AttacksTriggeredAbility(new BoostSourceEffect( - CreaturesYouControlCount.instance, StaticValue.get(0), + CreaturesYouControlCount.PLURAL, StaticValue.get(0), Duration.EndOfTurn, "it" ).setText("it gets +X/+0 until end of turn, where X is the number of creatures you control")) .addHint(CreaturesYouControlHint.instance)); diff --git a/Mage.Sets/src/mage/cards/l/LightlessEvangel.java b/Mage.Sets/src/mage/cards/l/LightlessEvangel.java new file mode 100644 index 00000000000..c8cbb888953 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LightlessEvangel.java @@ -0,0 +1,54 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +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.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LightlessEvangel extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("another creature or artifact"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + CardType.ARTIFACT.getPredicate() + )); + } + + public LightlessEvangel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you sacrifice another creature or artifact, put a +1/+1 counter on this creature. + this.addAbility(new SacrificePermanentTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter + )); + } + + private LightlessEvangel(final LightlessEvangel card) { + super(card); + } + + @Override + public LightlessEvangel copy() { + return new LightlessEvangel(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LightningDart.java b/Mage.Sets/src/mage/cards/l/LightningDart.java index 05b1a28aada..c6b8b06ec4f 100644 --- a/Mage.Sets/src/mage/cards/l/LightningDart.java +++ b/Mage.Sets/src/mage/cards/l/LightningDart.java @@ -1,7 +1,6 @@ package mage.cards.l; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -13,6 +12,8 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author Derpthemeus @@ -40,7 +41,7 @@ public final class LightningDart extends CardImpl { public LightningDartEffect() { super(Outcome.Damage); - this.staticText = "Lightning Dart deals 1 damage to target creature. If that creature is white or blue, Lightning Dart deals 4 damage to it instead"; + this.staticText = "{this} deals 1 damage to target creature. If that creature is white or blue, {this} deals 4 damage to it instead"; } private LightningDartEffect(final LightningDartEffect effect) { diff --git a/Mage.Sets/src/mage/cards/l/LightstallInquisitor.java b/Mage.Sets/src/mage/cards/l/LightstallInquisitor.java new file mode 100644 index 00000000000..7a18d9e1b0a --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LightstallInquisitor.java @@ -0,0 +1,185 @@ +package mage.cards.l; + +import mage.MageIdentifier; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.replacement.CardMorEnteringTappedEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.*; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInHand; +import mage.watchers.Watcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class LightstallInquisitor extends CardImpl { + + public LightstallInquisitor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.ANGEL); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When this creature enters, each opponent exiles a card from their hand and may play that card for as long as it remains exiled. Each spell cast this way costs {1} more to cast. Each land played this way enters tapped. + this.addAbility( + new EntersBattlefieldTriggeredAbility(new LightstallInquisitorEffect()) + .setIdentifier(MageIdentifier.LightstallInquisitorAlternateCast), + new LightstallInquisitorWatcher()); + } + + private LightstallInquisitor(final LightstallInquisitor card) { + super(card); + } + + @Override + public LightstallInquisitor copy() { + return new LightstallInquisitor(this); + } +} + +class LightstallInquisitorEffect extends OneShotEffect { + + public LightstallInquisitorEffect() { + super(Outcome.Benefit); + staticText = "each opponent exiles a card from their hand and may play that card for as long as it remains exiled. " + + "Each spell cast this way costs {1} more to cast. Each land played this way enters tapped."; + } + + private LightstallInquisitorEffect(final LightstallInquisitorEffect effect) { + super(effect); + } + + @Override + public LightstallInquisitorEffect copy() { + return new LightstallInquisitorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(playerId); + if (opponent == null || opponent.getHand().isEmpty()) { + continue; + } + TargetCard target = new TargetCardInHand(); + opponent.choose(Outcome.Exile, opponent.getHand(), target, source, game); + Cards cards = new CardsImpl(target.getTargets()); + if (cards.isEmpty()) { + continue; + } + opponent.moveCardsToExile(cards.getCards(game), source, game, true, null, ""); + cards.retainZone(Zone.EXILED, game); + for (Card card : cards.getCards(game)) { + game.addEffect(new LightstallInquisitorAsThoughEffect(playerId, new MageObjectReference(card, game)), source); + } + } + return true; + } + +} + +class LightstallInquisitorAsThoughEffect extends AsThoughEffectImpl { + + private final UUID playerId; + private final MageObjectReference cardMOR; + + LightstallInquisitorAsThoughEffect(UUID playerId, MageObjectReference cardMOR) { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + this.playerId = playerId; + this.cardMOR = cardMOR; + } + + private LightstallInquisitorAsThoughEffect(final LightstallInquisitorAsThoughEffect effect) { + super(effect); + this.playerId = effect.playerId; + this.cardMOR = effect.cardMOR; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public LightstallInquisitorAsThoughEffect copy() { + return new LightstallInquisitorAsThoughEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + Card card = this.cardMOR.getCard(game); + if (card == null) { + // cleanup, the card moved from exile. + discard(); + return false; + } + if (!this.cardMOR.refersTo(objectId, game) + || !playerId.equals(affectedControllerId)) { + return false; + } + Player player = game.getPlayer(affectedControllerId); + if (player == null) { + return false; + } + if (card.getSpellAbility() != null) { + ManaCosts newManaCosts = new ManaCostsImpl<>(); + newManaCosts.addAll(card.getManaCost()); + newManaCosts.add(new GenericManaCost(1)); + player.setCastSourceIdWithAlternateMana( + card.getId(), newManaCosts, card.getSpellAbility().getCosts(), + MageIdentifier.LightstallInquisitorAlternateCast + ); + } + return true; + } +} + + +// Similar to CastFromGraveyardOnceWatcher, this Watcher add a EnteringTapped effects for lands played with the identifier. +class LightstallInquisitorWatcher extends Watcher { + + LightstallInquisitorWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (!GameEvent.EventType.PLAY_LAND.equals(event.getType())) { + return; + } + if (!event.hasApprovingIdentifier(MageIdentifier.LightstallInquisitorAlternateCast)) { + return; + } + // The land enters the battlefield tapped. + Card landCard = game.getCard(event.getTargetId()); + if (landCard == null) { + return; + } + MageObjectReference mor = new MageObjectReference(landCard, game); + game.getState().addEffect( + new CardMorEnteringTappedEffect(mor), + event.getApprovingObject().getApprovingAbility() // ability that approved the cast is the source of the tapping. + ); + } +} + diff --git a/Mage.Sets/src/mage/cards/l/LilianasContract.java b/Mage.Sets/src/mage/cards/l/LilianasContract.java index 77f572540c3..be8b6ba2e5e 100644 --- a/Mage.Sets/src/mage/cards/l/LilianasContract.java +++ b/Mage.Sets/src/mage/cards/l/LilianasContract.java @@ -3,19 +3,19 @@ package mage.cards.l; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.Condition; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; import mage.abilities.effects.common.WinGameSourceControllerEffect; +import mage.abilities.hint.Hint; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; import mage.game.Game; -import mage.game.permanent.Permanent; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -33,7 +33,7 @@ public final class LilianasContract extends CardImpl { // At the beginning of your upkeep, if you control four or more Demons with different names, you win the game. this.addAbility(new BeginningOfUpkeepTriggeredAbility(new WinGameSourceControllerEffect()) - .withInterveningIf(LilianasContractCondition.instance)); + .withInterveningIf(LilianasContractCondition.instance).addHint(LilianasContractCondition.getHint())); } private LilianasContract(final LilianasContract card) { @@ -47,24 +47,18 @@ public final class LilianasContract extends CardImpl { } enum LilianasContractCondition implements Condition { - instance; + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount( + new FilterControlledPermanent(SubType.DEMON, "Demons you control") + ); + + static Hint getHint() { + return xValue.getHint(); + } @Override public boolean apply(Game game, Ability source) { - Set demonNames = new HashSet<>(); - for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { - if (permanent == null - || !permanent.isControlledBy(source.getControllerId()) - || !permanent.hasSubtype(SubType.DEMON, game)) { - continue; - } - demonNames.add(permanent.getName()); - if (demonNames.size() > 3) { - return true; - } - } - return false; + return xValue.calculate(game, source, null) >= 4; } @Override diff --git a/Mage.Sets/src/mage/cards/l/Lithobraking.java b/Mage.Sets/src/mage/cards/l/Lithobraking.java new file mode 100644 index 00000000000..8cd579d78fb --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/Lithobraking.java @@ -0,0 +1,42 @@ +package mage.cards.l; + +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DamageAllEffect; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Lithobraking extends CardImpl { + + public Lithobraking(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + // Create a Lander token. Then you may sacrifice an artifact. When you do, Lithobraking deals 2 damage to each creature. + this.getSpellAbility().addEffect(new CreateTokenEffect(new LanderToken())); + this.getSpellAbility().addEffect(new DoWhenCostPaid( + new ReflexiveTriggeredAbility( + new DamageAllEffect(2, StaticFilters.FILTER_PERMANENT_CREATURE), false + ), new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_ARTIFACT), + "Sacrifice an artifact?" + ).concatBy("Then")); + } + + private Lithobraking(final Lithobraking card) { + super(card); + } + + @Override + public Lithobraking copy() { + return new Lithobraking(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LoadingZone.java b/Mage.Sets/src/mage/cards/l/LoadingZone.java new file mode 100644 index 00000000000..e4fd6e9d85e --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LoadingZone.java @@ -0,0 +1,90 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.keyword.WarpAbility; +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.GameEvent; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LoadingZone extends CardImpl { + + public LoadingZone(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); + + // If one or more counters would be put on a creature, Spacecraft, or Planet you control, twice that many of each of those kinds of counters are put on it instead. + this.addAbility(new SimpleStaticAbility(new LoadingZoneEffect())); + + // Warp {G} + this.addAbility(new WarpAbility(this, "{G}")); + } + + private LoadingZone(final LoadingZone card) { + super(card); + } + + @Override + public LoadingZone copy() { + return new LoadingZone(this); + } +} + +class LoadingZoneEffect extends ReplacementEffectImpl { + + LoadingZoneEffect() { + super(Duration.WhileOnBattlefield, Outcome.BoostCreature, false); + staticText = "if one or more counters would be put on a creature, Spacecraft, or Planet you control, " + + "twice that many of each of those kinds of counters are put on it instead"; + } + + private LoadingZoneEffect(final LoadingZoneEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmountForCounters(CardUtil.overflowMultiply(event.getAmount(), 2), true); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ADD_COUNTERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getAmount() < 1) { + return false; + } + Permanent permanent = Optional + .ofNullable(event) + .map(GameEvent::getTargetId) + .map(game::getPermanent) + .orElse(game.getPermanentEntering(event.getTargetId())); + return permanent != null + && permanent.isControlledBy(source.getControllerId()) + && (permanent.isCreature(game) + || permanent.hasSubtype(SubType.SPACECRAFT, game) + || permanent.hasSubtype(SubType.PLANET, game)); + } + + @Override + public LoadingZoneEffect copy() { + return new LoadingZoneEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LongRangeSensor.java b/Mage.Sets/src/mage/cards/l/LongRangeSensor.java new file mode 100644 index 00000000000..0d0dcd91427 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LongRangeSensor.java @@ -0,0 +1,45 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.keyword.DiscoverEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LongRangeSensor extends CardImpl { + + public LongRangeSensor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R}"); + + // Whenever you attack a player, put a charge counter on this artifact. + this.addAbility(new AttacksPlayerWithCreaturesTriggeredAbility( + new AddCountersSourceEffect(CounterType.CHARGE.createInstance()), SetTargetPointer.NONE + )); + + // {1}, Remove two charge counters from this artifact: Discover 4. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility(new DiscoverEffect(4), new GenericManaCost(1)); + ability.addCost(new RemoveCountersSourceCost(CounterType.CHARGE.createInstance(2))); + this.addAbility(ability); + } + + private LongRangeSensor(final LongRangeSensor card) { + super(card); + } + + @Override + public LongRangeSensor copy() { + return new LongRangeSensor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LoreSeeker.java b/Mage.Sets/src/mage/cards/l/LoreSeeker.java index 5daea234c55..90a43705adf 100644 --- a/Mage.Sets/src/mage/cards/l/LoreSeeker.java +++ b/Mage.Sets/src/mage/cards/l/LoreSeeker.java @@ -1,15 +1,16 @@ package mage.cards.l; -import java.util.UUID; import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.InfoEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.InfoEffect; + +import java.util.UUID; /** * @@ -27,8 +28,8 @@ public final class LoreSeeker extends CardImpl { // TODO: Draft specific abilities not implemented // Reveal Lore Seeker as you draft it. After you draft Lore Seeker, you may add a booster pack to the draft. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Reveal Lore Seeker as you draft it. " - + "After you draft Lore Seeker, you may add a booster pack to the draft - not implemented."))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Reveal {this} as you draft it. " + + "After you draft {this}, you may add a booster pack to the draft - not implemented."))); } private LoreSeeker(final LoreSeeker card) { diff --git a/Mage.Sets/src/mage/cards/l/LostInSpace.java b/Mage.Sets/src/mage/cards/l/LostInSpace.java new file mode 100644 index 00000000000..0c85143fd96 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LostInSpace.java @@ -0,0 +1,35 @@ +package mage.cards.l; + +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; +import mage.abilities.effects.keyword.SurveilEffect; +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 Susucr + */ +public final class LostInSpace extends CardImpl { + + public LostInSpace(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); + + // Target artifact or creature's owner puts it on their choice of the top or bottom of their library. Surveil 1. + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(false)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); + this.getSpellAbility().addEffect(new SurveilEffect(1)); + } + + private LostInSpace(final LostInSpace card) { + super(card); + } + + @Override + public LostInSpace copy() { + return new LostInSpace(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LouisoixsSacrifice.java b/Mage.Sets/src/mage/cards/l/LouisoixsSacrifice.java index 1e01f063bad..e01d3c9339d 100644 --- a/Mage.Sets/src/mage/cards/l/LouisoixsSacrifice.java +++ b/Mage.Sets/src/mage/cards/l/LouisoixsSacrifice.java @@ -1,6 +1,6 @@ package mage.cards.l; -import mage.abilities.costs.CompositeCost; +import mage.abilities.costs.OrCost; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CounterTargetEffect; @@ -33,9 +33,10 @@ public final class LouisoixsSacrifice extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); // As an additional cost to cast this spell, sacrifice a legendary creature or pay {2}. - this.getSpellAbility().addCost(new CompositeCost( + this.getSpellAbility().addCost(new OrCost( + "sacrifice a legendary creature or pay {2}", new SacrificeTargetCost(StaticFilters.FILTER_CREATURE_LEGENDARY), - new GenericManaCost(2), "sacrifice a legendary creature or pay {2}" + new GenericManaCost(2) )); // Counter target activated ability, triggered ability, or noncreature spell. diff --git a/Mage.Sets/src/mage/cards/l/LumenClassFrigate.java b/Mage.Sets/src/mage/cards/l/LumenClassFrigate.java new file mode 100644 index 00000000000..af8fbb1ebe5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LumenClassFrigate.java @@ -0,0 +1,54 @@ +package mage.cards.l; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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 LumenClassFrigate extends CardImpl { + + public LumenClassFrigate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); + + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 2+ + // Other creatures you control get +1/+1. + this.addAbility(new StationLevelAbility(2).withLevelAbility(new SimpleStaticAbility( + new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, true) + ))); + + // STATION 12+ + // Flying + // Lifelink + // 3/5 + this.addAbility(new StationLevelAbility(12) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(LifelinkAbility.getInstance()) + .withPT(3, 5)); + } + + private LumenClassFrigate(final LumenClassFrigate card) { + super(card); + } + + @Override + public LumenClassFrigate copy() { + return new LumenClassFrigate(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LuxArtillery.java b/Mage.Sets/src/mage/cards/l/LuxArtillery.java index 918f5c0e743..1c24e0b66f2 100644 --- a/Mage.Sets/src/mage/cards/l/LuxArtillery.java +++ b/Mage.Sets/src/mage/cards/l/LuxArtillery.java @@ -1,20 +1,18 @@ package mage.cards.l; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.condition.Condition; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.keyword.SunburstAbility; -import mage.cards.Card; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.counters.Counter; -import mage.filter.FilterPermanent; +import mage.counters.Counters; import mage.filter.FilterSpell; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.Spell; @@ -37,11 +35,14 @@ public final class LuxArtillery extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); // Whenever you cast an artifact creature spell, it gains sunburst. - this.addAbility(new SpellCastControllerTriggeredAbility(new LuxArtilleryEffect(this), filter, false, SetTargetPointer.SPELL)); + this.addAbility(new SpellCastControllerTriggeredAbility( + new LuxArtilleryEffect(), filter, false, SetTargetPointer.SPELL + )); // At the beginning of your end step, if there are thirty or more counters among artifacts and creatures you control, Lux Artillery deals 10 damage to each opponent. - this.addAbility(new BeginningOfEndStepTriggeredAbility(new DamagePlayersEffect(10, TargetController.OPPONENT)) - .withInterveningIf(LuxArtilleryCondition.instance)); + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new DamagePlayersEffect(10, TargetController.OPPONENT) + ).withInterveningIf(LuxArtilleryCondition.instance)); } private LuxArtillery(final LuxArtillery card) { @@ -56,21 +57,18 @@ public final class LuxArtillery extends CardImpl { class LuxArtilleryEffect extends ContinuousEffectImpl { - private final Ability ability; - private int zoneChangeCounter; private UUID permanentId; + private int zoneChangeCounter; - LuxArtilleryEffect(Card card) { + LuxArtilleryEffect() { super(Duration.OneUse, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); staticText = "it gains sunburst. (It enters the battlefield with a +1/+1 counter on it for each color of mana spent to cast it.)"; - ability = new SunburstAbility(card); } private LuxArtilleryEffect(final LuxArtilleryEffect effect) { super(effect); - this.ability = effect.ability.copy(); - this.zoneChangeCounter = effect.zoneChangeCounter; this.permanentId = effect.permanentId; + this.zoneChangeCounter = effect.zoneChangeCounter; } @Override @@ -81,26 +79,27 @@ class LuxArtilleryEffect extends ContinuousEffectImpl { @Override public void init(Ability source, Game game) { super.init(source, game); - Spell object = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); + Spell object = game.getSpell(getTargetPointer().getFirst(game, source)); if (object != null) { - zoneChangeCounter = game.getState().getZoneChangeCounter(object.getSourceId()) + 1; permanentId = object.getSourceId(); + zoneChangeCounter = game.getState().getZoneChangeCounter(object.getSourceId()) + 1; } } @Override public boolean apply(Game game, Ability source) { + if (game.getState().getZoneChangeCounter(permanentId) >= zoneChangeCounter) { + discard(); + return false; + } Permanent permanent = game.getPermanent(permanentId); if (permanent != null && permanent.getZoneChangeCounter(game) <= zoneChangeCounter) { permanent.addAbility(new SunburstAbility(permanent), source.getSourceId(), game); - } else { - if (game.getState().getZoneChangeCounter(permanentId) >= zoneChangeCounter) { - discard(); - } - Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); - if (spell != null) { - game.getState().addOtherAbility(spell.getCard(), ability, true); - } + return true; + } + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); + if (spell != null) { + game.getState().addOtherAbility(spell.getCard(), new SunburstAbility(spell), true); } return true; } @@ -109,25 +108,18 @@ class LuxArtilleryEffect extends ContinuousEffectImpl { enum LuxArtilleryCondition implements Condition { instance; - private static final FilterPermanent filter = new FilterPermanent("artifacts and creatures you control"); - - static { - filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(), CardType.CREATURE.getPredicate())); - filter.add(TargetController.YOU.getControllerPredicate()); - } - @Override public boolean apply(Game game, Ability source) { - int totalCounters = 0; - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { - if (permanent == null) { - continue; - } - for (Counter counter : permanent.getCounters(game).values()) { - totalCounters += counter.getCount(); - } - } - return totalCounters >= 30; + return game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE, + source.getControllerId(), source, game + ) + .stream() + .map(permanent -> permanent.getCounters(game)) + .mapToInt(Counters::getTotalCount) + .sum() >= 30; } @Override diff --git a/Mage.Sets/src/mage/cards/l/LuxknightBreacher.java b/Mage.Sets/src/mage/cards/l/LuxknightBreacher.java new file mode 100644 index 00000000000..071fc30f407 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LuxknightBreacher.java @@ -0,0 +1,50 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.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 LuxknightBreacher extends CardImpl { + + private static final DynamicValue xValue + = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT); + private static final Hint hint = new ValueHint("Other creatures and artifacts you control", xValue); + + public LuxknightBreacher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // This creature enters with a +1/+1 counter on it for each other creature and/or artifact you control. + this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect( + CounterType.P1P1.createInstance(0), xValue, true + ), "with a +1/+1 counter on it for each other creature and/or artifact you control").addHint(hint)); + } + + private LuxknightBreacher(final LuxknightBreacher card) { + super(card); + } + + @Override + public LuxknightBreacher copy() { + return new LuxknightBreacher(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LysAlanaBowmaster.java b/Mage.Sets/src/mage/cards/l/LysAlanaBowmaster.java index d031b08afd3..a093279cc39 100644 --- a/Mage.Sets/src/mage/cards/l/LysAlanaBowmaster.java +++ b/Mage.Sets/src/mage/cards/l/LysAlanaBowmaster.java @@ -4,17 +4,14 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterSpell; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -24,11 +21,9 @@ import java.util.UUID; public final class LysAlanaBowmaster extends CardImpl { private static final FilterSpell filterElf = new FilterSpell("an Elf spell"); - private static final FilterCreaturePermanent filterFlying = new FilterCreaturePermanent("creature with flying"); static { filterElf.add(SubType.ELF.getPredicate()); - filterFlying.add(new AbilityPredicate(FlyingAbility.class)); } public LysAlanaBowmaster(UUID ownerId, CardSetInfo setInfo) { @@ -41,7 +36,7 @@ public final class LysAlanaBowmaster extends CardImpl { this.addAbility(ReachAbility.getInstance()); Ability ability = new SpellCastControllerTriggeredAbility(new DamageTargetEffect(2) .setText("{this} deal 2 damage to target creature with flying"), filterElf, true); - ability.addTarget(new TargetPermanent(filterFlying)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheOrder.java b/Mage.Sets/src/mage/cards/m/MagusOfTheOrder.java index 2a76e8cbe86..f2c185a3ce2 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheOrder.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheOrder.java @@ -17,10 +17,9 @@ import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreatureCard; -import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.ColorPredicate; import mage.target.common.TargetCardInLibrary; -import mage.target.common.TargetControlledPermanent; import java.util.UUID; @@ -55,7 +54,7 @@ public final class MagusOfTheOrder extends CardImpl { ability.addCost(new TapSourceCost()); ability.addCost(new CompositeCost( new SacrificeSourceCost(), new SacrificeTargetCost(filter2), - "sacrifice Magus of the Order and another green creature" + "sacrifice {this} and another green creature" )); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java b/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java index 872bc34dd09..5cd12311a23 100644 --- a/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java +++ b/Mage.Sets/src/mage/cards/m/MajesticMyriarch.java @@ -2,7 +2,6 @@ package mage.cards.m; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.MultipliedValue; @@ -12,6 +11,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; import mage.abilities.keyword.*; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -87,7 +87,7 @@ class MajesticMyriarchEffect extends OneShotEffect { MajesticMyriarchEffect() { super(Outcome.BoostCreature); - this.staticText = "if you control a creature with flying, Majestic Myriarch gains flying until end of turn. " + + this.staticText = "if you control a creature with flying, {this} gains flying until end of turn. " + "The same is true for first strike, double strike, deathtouch, haste, hexproof, indestructible, lifelink, menace, reach, trample, and vigilance."; } diff --git a/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java b/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java index 7f41fa32cc8..d01b21e9f6f 100644 --- a/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java +++ b/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java @@ -15,22 +15,32 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.predicate.Predicate; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.TargetCard; import mage.target.TargetPermanent; import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetControlledCreaturePermanent; import java.util.Objects; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; /** * @author TheElk801 */ public final class MantleOfTheAncients extends CardImpl { + private static final FilterCard filter = new FilterPermanentCard(); + + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate() + )); + } public MantleOfTheAncients(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{W}"); @@ -41,11 +51,12 @@ public final class MantleOfTheAncients extends CardImpl { TargetPermanent auraTarget = new TargetControlledCreaturePermanent(); 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 Mantle of the Ancients enters the battlefield, return any number of target Aura and/or Equipment cards from your graveyard to the battlefield attached to enchanted creature. - this.addAbility(new EntersBattlefieldTriggeredAbility(new MantleOfTheAncientsEffect())); + Ability ability = new EntersBattlefieldTriggeredAbility(new MantleOfTheAncientsEffect()); + ability.addTarget(new TargetCardInYourGraveyard(0, Integer.MAX_VALUE, filter)); + this.addAbility(ability); // Enchanted creature gets +1/+1 for each Aura and Equipment attached to it. this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect( @@ -68,8 +79,7 @@ class MantleOfTheAncientsEffect extends OneShotEffect { MantleOfTheAncientsEffect() { super(Outcome.Benefit); staticText = "return any number of target Aura and/or Equipment cards " + - "that could be attached to enchanted creature from your graveyard " + - "to the battlefield attached to enchanted creature"; + "from your graveyard to the battlefield attached to enchanted creature"; } private MantleOfTheAncientsEffect(final MantleOfTheAncientsEffect effect) { @@ -92,46 +102,21 @@ class MantleOfTheAncientsEffect extends OneShotEffect { if (permanent == null) { return false; } - FilterCard filter = new FilterCard("Aura or Equipment card that can be attached to " + permanent.getName()); - filter.add(new MantleOfTheAncientsPredicate(permanent)); - TargetCard target = new TargetCardInYourGraveyard(0, Integer.MAX_VALUE, filter, true); - player.choose(outcome, target, source, game); - Cards cards = new CardsImpl(target.getTargets()); + Set cards = getTargetPointer().getTargets(game, source).stream().map(game::getCard).filter(Objects::nonNull) + .filter(card -> !permanent.cantBeAttachedBy(card, source, game, true)).collect(Collectors.toSet()); if (cards.isEmpty()) { return false; } - cards.getCards(game) - .stream() - .forEach(card -> game.getState().setValue("attachTo:" + card.getId(), permanent)); + + cards.forEach(card -> game.getState().setValue("attachTo:" + card.getId(), permanent)); player.moveCards(cards, Zone.BATTLEFIELD, source, game); - for (UUID cardId : cards) { - permanent.addAttachment(cardId, source, game); - } + Cards movedCards = new CardsImpl(cards); + movedCards.retainZone(Zone.BATTLEFIELD, game); + movedCards.forEach(card -> permanent.addAttachment(card, source, game)); return true; } } -class MantleOfTheAncientsPredicate implements Predicate { - - private final Permanent permanent; - - MantleOfTheAncientsPredicate(Permanent permanent) { - this.permanent = permanent; - } - - @Override - public boolean apply(Card input, Game game) { - if (input.hasSubtype(SubType.AURA, game)) { - return input - .getSpellAbility() - .getTargets() - .stream() - .anyMatch(target -> target.getFilter().match(permanent, game)); - } - return input.hasSubtype(SubType.EQUIPMENT, game); - } -} - enum MantleOfTheAncientsValue implements DynamicValue { instance; diff --git a/Mage.Sets/src/mage/cards/m/MartyrOfAshes.java b/Mage.Sets/src/mage/cards/m/MartyrOfAshes.java index e62bbc29d17..ed45c5c7c95 100644 --- a/Mage.Sets/src/mage/cards/m/MartyrOfAshes.java +++ b/Mage.Sets/src/mage/cards/m/MartyrOfAshes.java @@ -1,7 +1,6 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.ObjectColor; import mage.abilities.Ability; @@ -17,7 +16,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; @@ -25,6 +23,8 @@ import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.mageobject.ColorPredicate; import mage.target.common.TargetCardInHand; +import java.util.UUID; + /** * * @author emerald000 @@ -49,7 +49,7 @@ public final class MartyrOfAshes extends CardImpl { // {2}, Reveal X red cards from your hand, Sacrifice Martyr of Ashes: Martyr of Ashes deals X damage to each creature without flying. Effect effect = new DamageAllEffect(RevealTargetFromHandCostCount.instance, filterCreature); - effect.setText("Martyr of Ashes deals X damage to each creature without flying."); + effect.setText("{this} deals X damage to each creature without flying."); Ability ability = new SimpleActivatedAbility(effect, new GenericManaCost(2)); ability.addCost(new RevealTargetFromHandCost(new TargetCardInHand(0, Integer.MAX_VALUE, filterHand))); ability.addCost(new SacrificeSourceCost()); diff --git a/Mage.Sets/src/mage/cards/m/MassiveRaid.java b/Mage.Sets/src/mage/cards/m/MassiveRaid.java index 6e164905436..5fdf78b1335 100644 --- a/Mage.Sets/src/mage/cards/m/MassiveRaid.java +++ b/Mage.Sets/src/mage/cards/m/MassiveRaid.java @@ -19,7 +19,7 @@ public final class MassiveRaid extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}{R}"); // Massive Raid deals damage to any target equal to the number of creatures you control. - this.getSpellAbility().addEffect(new DamageTargetEffect(CreaturesYouControlCount.instance).setText("{this} deals damage to any target equal to the number of creatures you control")); + this.getSpellAbility().addEffect(new DamageTargetEffect(CreaturesYouControlCount.PLURAL).setText("{this} deals damage to any target equal to the number of creatures you control")); this.getSpellAbility().addTarget(new TargetAnyTarget()); this.getSpellAbility().addHint(CreaturesYouControlHint.instance); } diff --git a/Mage.Sets/src/mage/cards/m/MatsuTribeSniper.java b/Mage.Sets/src/mage/cards/m/MatsuTribeSniper.java index e6347dc4070..9759315997e 100644 --- a/Mage.Sets/src/mage/cards/m/MatsuTribeSniper.java +++ b/Mage.Sets/src/mage/cards/m/MatsuTribeSniper.java @@ -1,7 +1,6 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsDamageToACreatureTriggeredAbility; @@ -10,31 +9,22 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; import mage.abilities.effects.common.TapTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class MatsuTribeSniper extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public MatsuTribeSniper(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.SNAKE); this.subtype.add(SubType.WARRIOR); this.subtype.add(SubType.ARCHER); @@ -44,9 +34,9 @@ public final class MatsuTribeSniper extends CardImpl { // {t}: Matsu-Tribe Sniper deals 1 damage to target creature with flying. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(1), new TapSourceCost()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); - + // Whenever Matsu-Tribe Sniper deals damage to a creature, tap that creature and it doesn't untap during its controller's next untap step. ability = new DealsDamageToACreatureTriggeredAbility(new TapTargetEffect("tap that creature"), false, false, true); ability.addEffect(new DontUntapInControllersNextUntapStepTargetEffect("and it")); diff --git a/Mage.Sets/src/mage/cards/m/MeanderingTowershell.java b/Mage.Sets/src/mage/cards/m/MeanderingTowershell.java index b9074aad0b8..f4b4e6efc4b 100644 --- a/Mage.Sets/src/mage/cards/m/MeanderingTowershell.java +++ b/Mage.Sets/src/mage/cards/m/MeanderingTowershell.java @@ -1,39 +1,40 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.IslandwalkAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** * As Meandering Towershell returns to the battlefield because of the delayed * triggered ability, you choose which opponent or opposing planeswalker it's * attacking. It doesn't have to attack the same opponent or opposing * planeswalker that it was when it was exiled. - * + * -- * If Meandering Towershell enters the battlefield attacking, it wasn't declared * as an attacking creature that turn. Abilities that trigger when a creature * attacks, including its own triggered ability, won't trigger. - * + * -- * On the turn Meandering Towershell attacks and is exiled, raid abilities will * see it as a creature that attacked. Conversely, on the turn Meandering * Towershell enters the battlefield attacking, raid abilities will not. - * + * -- * If you attack with a Meandering Towershell that you don't own, you'll control * it when it returns to the battlefield. * @@ -42,7 +43,7 @@ import mage.players.Player; public final class MeanderingTowershell extends CardImpl { public MeanderingTowershell(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); this.subtype.add(SubType.TURTLE); this.power = new MageInt(5); @@ -90,58 +91,13 @@ class MeanderingTowershellEffect extends OneShotEffect { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (controller != null && sourcePermanent != null) { controller.moveCardToExileWithInfo(sourcePermanent, null, "", source, game, Zone.BATTLEFIELD, true); - game.addDelayedTriggeredAbility(new AtBeginningNextDeclareAttackersStepNextTurnDelayedTriggeredAbility(), source); + game.addDelayedTriggeredAbility(new AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility(new MeanderingTowershellReturnEffect(), GameEvent.EventType.DECLARE_ATTACKERS_STEP_PRE), source); return true; } return false; } } -class AtBeginningNextDeclareAttackersStepNextTurnDelayedTriggeredAbility extends DelayedTriggeredAbility { - - private int startingTurn; - - public AtBeginningNextDeclareAttackersStepNextTurnDelayedTriggeredAbility() { - super(new MeanderingTowershellReturnEffect()); - } - - private AtBeginningNextDeclareAttackersStepNextTurnDelayedTriggeredAbility(final AtBeginningNextDeclareAttackersStepNextTurnDelayedTriggeredAbility ability) { - super(ability); - this.startingTurn = ability.startingTurn; - } - - @Override - public void init(Game game) { - startingTurn = game.getTurnNum(); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getPlayerId().equals(this.controllerId)) { - if (game.getTurnNum() != startingTurn) { - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Return it to the battlefield under your control tapped and attacking at the beginning of the next declare attackers step on your next turn."; - } - - @Override - public AtBeginningNextDeclareAttackersStepNextTurnDelayedTriggeredAbility copy() { - return new AtBeginningNextDeclareAttackersStepNextTurnDelayedTriggeredAbility(this); - } - -} - class MeanderingTowershellReturnEffect extends OneShotEffect { MeanderingTowershellReturnEffect() { diff --git a/Mage.Sets/src/mage/cards/m/MechanAssembler.java b/Mage.Sets/src/mage/cards/m/MechanAssembler.java new file mode 100644 index 00000000000..22694021905 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MechanAssembler.java @@ -0,0 +1,42 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.RobotToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MechanAssembler extends CardImpl { + + public MechanAssembler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Whenever another artifact you control enters, create a 2/2 colorless Robot artifact creature token. This ability triggers only once each turn. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new CreateTokenEffect(new RobotToken()), StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT + ).setTriggersLimitEachTurn(1)); + } + + private MechanAssembler(final MechanAssembler card) { + super(card); + } + + @Override + public MechanAssembler copy() { + return new MechanAssembler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MechanNavigator.java b/Mage.Sets/src/mage/cards/m/MechanNavigator.java new file mode 100644 index 00000000000..899291192fa --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MechanNavigator.java @@ -0,0 +1,38 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +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 MechanNavigator extends CardImpl { + + public MechanNavigator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.PILOT); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Whenever this creature becomes tapped, draw a card, then discard a card. + this.addAbility(new BecomesTappedSourceTriggeredAbility(new DrawDiscardControllerEffect(1, 1))); + } + + private MechanNavigator(final MechanNavigator card) { + super(card); + } + + @Override + public MechanNavigator copy() { + return new MechanNavigator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MechanShieldmate.java b/Mage.Sets/src/mage/cards/m/MechanShieldmate.java new file mode 100644 index 00000000000..077b37d1bd8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MechanShieldmate.java @@ -0,0 +1,55 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.ArtifactEnteredUnderYourControlCondition; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.common.combat.CanAttackAsThoughItDidntHaveDefenderSourceEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.keyword.DefenderAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.watchers.common.ArtifactEnteredControllerWatcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class MechanShieldmate extends CardImpl { + + public MechanShieldmate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + + // As long as an artifact entered the battlefield under your control this turn, this creature can attack as though it didn't have defender. + this.addAbility(new SimpleStaticAbility( + new ConditionalAsThoughEffect( + new CanAttackAsThoughItDidntHaveDefenderSourceEffect(Duration.WhileOnBattlefield), + ArtifactEnteredUnderYourControlCondition.instance + ).setText("As long as an artifact entered the battlefield under your control this turn," + + "this creature can attack as though it didn't have defender") + ).addHint(new ConditionHint(ArtifactEnteredUnderYourControlCondition.instance)), + new ArtifactEnteredControllerWatcher() + ); + } + + private MechanShieldmate(final MechanShieldmate card) { + super(card); + } + + @Override + public MechanShieldmate copy() { + return new MechanShieldmate(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/Mechanozoa.java b/Mage.Sets/src/mage/cards/m/Mechanozoa.java new file mode 100644 index 00000000000..cab8091e648 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/Mechanozoa.java @@ -0,0 +1,50 @@ +package mage.cards.m; + +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.abilities.keyword.WarpAbility; +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 Mechanozoa extends CardImpl { + + public Mechanozoa(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}{U}{U}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.JELLYFISH); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // When this creature enters, tap target artifact or 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 TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE)); + this.addAbility(ability); + + // Warp {2}{U} + this.addAbility(new WarpAbility(this, "{2}{U}")); + } + + private Mechanozoa(final Mechanozoa card) { + super(card); + } + + @Override + public Mechanozoa copy() { + return new Mechanozoa(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MeldedMoxite.java b/Mage.Sets/src/mage/cards/m/MeldedMoxite.java new file mode 100644 index 00000000000..51454e5f5c1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MeldedMoxite.java @@ -0,0 +1,48 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +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.game.permanent.token.RobotToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MeldedMoxite extends CardImpl { + + public MeldedMoxite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}"); + + // When this artifact enters, you may discard a card. If you do, draw two cards. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new DoIfCostPaid(new DrawCardSourceControllerEffect(2), new DiscardCardCost()) + )); + + // {3}, Sacrifice this artifact: Create a tapped 2/2 colorless Robot artifact creature token. + Ability ability = new SimpleActivatedAbility( + new CreateTokenEffect(new RobotToken(), 1, true), new GenericManaCost(3) + ); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private MeldedMoxite(final MeldedMoxite card) { + super(card); + } + + @Override + public MeldedMoxite copy() { + return new MeldedMoxite(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MeltstriderEulogist.java b/Mage.Sets/src/mage/cards/m/MeltstriderEulogist.java new file mode 100644 index 00000000000..9faefd3494d --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MeltstriderEulogist.java @@ -0,0 +1,50 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MeltstriderEulogist extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("a creature you control with a +1/+1 counter on it"); + + static { + filter.add(CounterType.P1P1.getPredicate()); + } + + public MeltstriderEulogist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever a creature you control with a +1/+1 counter on it dies, draw a card. + this.addAbility(new DiesCreatureTriggeredAbility( + new DrawCardSourceControllerEffect(1), false, filter + )); + } + + private MeltstriderEulogist(final MeltstriderEulogist card) { + super(card); + } + + @Override + public MeltstriderEulogist copy() { + return new MeltstriderEulogist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MeltstridersGear.java b/Mage.Sets/src/mage/cards/m/MeltstridersGear.java new file mode 100644 index 00000000000..99a33db2e26 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MeltstridersGear.java @@ -0,0 +1,50 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAttachToTarget; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.ReachAbility; +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 MeltstridersGear extends CardImpl { + + public MeltstridersGear(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{G}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When this Equipment enters, attach it to target creature you control. + this.addAbility(new EntersBattlefieldAttachToTarget()); + + // Equipped creature gets +2/+1 and has reach. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 1)); + ability.addEffect(new GainAbilityAttachedEffect( + ReachAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText("and has reach")); + this.addAbility(ability); + + // Equip {5} + this.addAbility(new EquipAbility(5)); + } + + private MeltstridersGear(final MeltstridersGear card) { + super(card); + } + + @Override + public MeltstridersGear copy() { + return new MeltstridersGear(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MeltstridersResolve.java b/Mage.Sets/src/mage/cards/m/MeltstridersResolve.java new file mode 100644 index 00000000000..88fbff5be93 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MeltstridersResolve.java @@ -0,0 +1,90 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.combat.CantBeBlockedByMoreThanOneAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +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.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MeltstridersResolve extends CardImpl { + + public MeltstridersResolve(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature you control + TargetPermanent auraTarget = new TargetControlledCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When this Aura enters, enchanted creature fights up to one target creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new MeltstridersResolveEffect()); + ability.addTarget(new TargetOpponentsCreaturePermanent(0, 1)); + this.addAbility(ability); + + // Enchanted creature gets +0/+2 and can't be blocked by more than one creature. + ability = new SimpleStaticAbility(new BoostEnchantedEffect(0, 2)); + ability.addEffect(new CantBeBlockedByMoreThanOneAttachedEffect(AttachmentType.AURA) + .setText("and can't be blocked by more than one creature")); + this.addAbility(ability); + } + + private MeltstridersResolve(final MeltstridersResolve card) { + super(card); + } + + @Override + public MeltstridersResolve copy() { + return new MeltstridersResolve(this); + } +} + +class MeltstridersResolveEffect extends OneShotEffect { + + MeltstridersResolveEffect() { + super(Outcome.Benefit); + staticText = "enchanted creature fights up to one target creature an opponent controls"; + } + + private MeltstridersResolveEffect(final MeltstridersResolveEffect effect) { + super(effect); + } + + @Override + public MeltstridersResolveEffect copy() { + return new MeltstridersResolveEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .map(game::getPermanent) + .orElse(null); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); + return permanent != null && creature != null && permanent.fight(creature, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MemorialTeamLeader.java b/Mage.Sets/src/mage/cards/m/MemorialTeamLeader.java new file mode 100644 index 00000000000..6e6c62e918f --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MemorialTeamLeader.java @@ -0,0 +1,48 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.keyword.WarpAbility; +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 MemorialTeamLeader extends CardImpl { + + public MemorialTeamLeader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.KAVU); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // During your turn, other creatures you control get +1/+0. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostControlledEffect(1, 0, Duration.WhileOnBattlefield, true), + MyTurnCondition.instance, "during your turn, other creatures you control get +1/+0" + ))); + + // Warp {1}{R} + this.addAbility(new WarpAbility(this, "{1}{R}")); + } + + private MemorialTeamLeader(final MemorialTeamLeader card) { + super(card); + } + + @Override + public MemorialTeamLeader copy() { + return new MemorialTeamLeader(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MemorialVault.java b/Mage.Sets/src/mage/cards/m/MemorialVault.java new file mode 100644 index 00000000000..4f7bcd98b4c --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MemorialVault.java @@ -0,0 +1,78 @@ +package mage.cards.m; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.Collection; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MemorialVault extends CardImpl { + + public MemorialVault(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{R}"); + + // {T}, Sacrifice another artifact: Exile the top X cards of your library, where X is one plus the mana value of the sacrificed artifact. You may play those cards this turn. + Ability ability = new SimpleActivatedAbility(new MemorialVaultEffect(), new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT)); + this.addAbility(ability); + } + + private MemorialVault(final MemorialVault card) { + super(card); + } + + @Override + public MemorialVault copy() { + return new MemorialVault(this); + } +} + +class MemorialVaultEffect extends OneShotEffect { + + MemorialVaultEffect() { + super(Outcome.Benefit); + staticText = "exile the top X cards of your library, where X is one plus " + + "the mana value of the sacrificed artifact. You may play those cards this turn"; + } + + private MemorialVaultEffect(final MemorialVaultEffect effect) { + super(effect); + } + + @Override + public MemorialVaultEffect copy() { + return new MemorialVaultEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int xValue = 1 + CardUtil + .castStream(source.getCosts(), SacrificeTargetCost.class) + .map(SacrificeTargetCost::getPermanents) + .flatMap(Collection::stream) + .mapToInt(MageObject::getManaValue) + .sum(); + return new ExileTopXMayPlayUntilEffect(xValue, Duration.EndOfTurn).apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MentalModulation.java b/Mage.Sets/src/mage/cards/m/MentalModulation.java new file mode 100644 index 00000000000..7b5249369e5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MentalModulation.java @@ -0,0 +1,49 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class MentalModulation extends CardImpl { + + public MentalModulation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{W}{B}"); + + // This spell costs {1} less to cast during your turn. + Ability ability = new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(1, MyTurnCondition.instance) + .setText("this spell costs {1} less to cast during your turn")); + ability.setRuleAtTheTop(true); + this.addAbility(ability); + + // Tap target artifact or creature. + this.getSpellAbility().addEffect(new TapTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); + + // Draw a card. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); + } + + private MentalModulation(final MentalModulation card) { + super(card); + } + + @Override + public MentalModulation copy() { + return new MentalModulation(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MidnightClock.java b/Mage.Sets/src/mage/cards/m/MidnightClock.java index e4b789f69e1..a80350d786c 100644 --- a/Mage.Sets/src/mage/cards/m/MidnightClock.java +++ b/Mage.Sets/src/mage/cards/m/MidnightClock.java @@ -1,29 +1,25 @@ package mage.cards.m; -import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.effects.common.ShuffleHandGraveyardIntoLibraryEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.mana.BlueManaAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.TargetController; import mage.constants.Zone; import mage.counters.CounterType; 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; /** @@ -64,7 +60,10 @@ public final class MidnightClock extends CardImpl { class MidnightClockTriggeredAbility extends TriggeredAbilityImpl { MidnightClockTriggeredAbility() { - super(Zone.ALL, new MidnightClockEffect(), false); + super(Zone.ALL, new ShuffleHandGraveyardIntoLibraryEffect(), false); + this.addEffect(new DrawCardSourceControllerEffect(7).concatBy(", then")); + this.addEffect(new ExileSourceEffect()); + this.setTriggerPhrase("When the twelfth hour counter is put on {this}, "); } private MidnightClockTriggeredAbility(final MidnightClockTriggeredAbility ability) { @@ -78,64 +77,24 @@ class MidnightClockTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(getSourceId()) - && event.getData().equals(CounterType.HOUR.getName())) { - int amountAdded = event.getAmount(); - int hourCounters = amountAdded; - Permanent sourcePermanent = game.getPermanent(getSourceId()); - if (sourcePermanent == null) { - sourcePermanent = game.getPermanentEntering(getSourceId()); - } - if (sourcePermanent != null) { - hourCounters = sourcePermanent.getCounters(game).getCount(CounterType.HOUR); - } - return hourCounters - amountAdded < 12 - && 12 <= hourCounters; + if (!event.getTargetId().equals(getSourceId()) || !event.getData().equals(CounterType.HOUR.getName())) { + return false; } - return false; + int amountAdded = event.getAmount(); + Permanent sourcePermanent = Optional + .ofNullable(game.getPermanent(getSourceId())) + .orElse(game.getPermanentEntering(getSourceId())); + int hourCounters; + if (sourcePermanent != null) { + hourCounters = sourcePermanent.getCounters(game).getCount(CounterType.HOUR); + } else { + hourCounters = amountAdded; + } + return hourCounters - amountAdded < 12 && 12 <= hourCounters; } @Override public MidnightClockTriggeredAbility copy() { return new MidnightClockTriggeredAbility(this); } - - @Override - public String getRule() { - return "When the twelfth hour counter is put on {this}, " + - "shuffle your hand and graveyard into your library, " + - "then draw seven cards. Exile {this}."; - } -} - -class MidnightClockEffect extends OneShotEffect { - - private static final Effect effect = new ExileSourceEffect(); - - MidnightClockEffect() { - super(Outcome.Benefit); - } - - private MidnightClockEffect(final MidnightClockEffect effect) { - super(effect); - } - - @Override - public MidnightClockEffect copy() { - return new MidnightClockEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - Cards cards = new CardsImpl(player.getHand()); - cards.addAll(player.getGraveyard()); - player.putCardsOnTopOfLibrary(cards, game, source, false); - player.shuffleLibrary(source, game); - player.drawCards(7, source, game); - return effect.apply(game, source); - } } diff --git a/Mage.Sets/src/mage/cards/m/MightformHarmonizer.java b/Mage.Sets/src/mage/cards/m/MightformHarmonizer.java new file mode 100644 index 00000000000..f10742f28f3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MightformHarmonizer.java @@ -0,0 +1,79 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MightformHarmonizer extends CardImpl { + + public MightformHarmonizer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Landfall -- Whenever a land you control enters, double the power of target creature you control until end of turn. + Ability ability = new LandfallAbility(new MightformHarmonizerEffect()); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + + // Warp {2}{G} + this.addAbility(new WarpAbility(this, "{2}{G}")); + } + + private MightformHarmonizer(final MightformHarmonizer card) { + super(card); + } + + @Override + public MightformHarmonizer copy() { + return new MightformHarmonizer(this); + } +} + +class MightformHarmonizerEffect extends OneShotEffect { + + MightformHarmonizerEffect() { + super(Outcome.Benefit); + staticText = "double the power of target creature you control until end of turn"; + } + + private MightformHarmonizerEffect(final MightformHarmonizerEffect effect) { + super(effect); + } + + @Override + public MightformHarmonizerEffect copy() { + return new MightformHarmonizerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null || permanent.getPower().getValue() == 0) { + return false; + } + game.addEffect(new BoostTargetEffect(permanent.getPower().getValue(), 0) + .setTargetPointer(new FixedTarget(permanent, game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MirrodinBesieged.java b/Mage.Sets/src/mage/cards/m/MirrodinBesieged.java index 459afeaa1d6..387586e2453 100644 --- a/Mage.Sets/src/mage/cards/m/MirrodinBesieged.java +++ b/Mage.Sets/src/mage/cards/m/MirrodinBesieged.java @@ -61,6 +61,7 @@ class MirrodinBesiegedEffect extends OneShotEffect { MirrodinBesiegedEffect() { super(Outcome.Benefit); + this.setText("draw a card, then discard a card. Then if there are fifteen or more artifact cards in your graveyard, target opponent loses the game."); } private MirrodinBesiegedEffect(final MirrodinBesiegedEffect effect) { diff --git a/Mage.Sets/src/mage/cards/m/MirrorGolem.java b/Mage.Sets/src/mage/cards/m/MirrorGolem.java index 731db559799..b0e3d070bae 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorGolem.java +++ b/Mage.Sets/src/mage/cards/m/MirrorGolem.java @@ -76,9 +76,13 @@ class MirrorGolemImprintEffect extends OneShotEffect { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); 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()); if (sourcePermanent != null) { + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), sourcePermanent.getZoneChangeCounter(game)); + String exileZoneName = sourcePermanent.getIdName(); + controller.moveCardsToExile(card, source, game, true, exileZoneId, exileZoneName); sourcePermanent.imprint(this.getTargetPointer().getFirst(game, source), game); + } else { + controller.moveCardsToExile(card, source, game, true, null, ""); } } return true; @@ -102,11 +106,14 @@ class MirrorGolemEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { Permanent sourceObject = game.getPermanent(source.getSourceId()); - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source.getSourceId())); - if (sourceObject == null || sourceObject.getImprinted() == null) { return false; } + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), sourceObject.getZoneChangeCounter(game)); + ExileZone exileZone = game.getExile().getExileZone(exileZoneId); + if (exileZone == null) { + return false; + } for (UUID imprinted : sourceObject.getImprinted()) { if (imprinted != null && exileZone.contains(imprinted)) { diff --git a/Mage.Sets/src/mage/cards/m/MishrasWarMachine.java b/Mage.Sets/src/mage/cards/m/MishrasWarMachine.java index 38814d489e2..63ecd3d24ee 100644 --- a/Mage.Sets/src/mage/cards/m/MishrasWarMachine.java +++ b/Mage.Sets/src/mage/cards/m/MishrasWarMachine.java @@ -2,10 +2,10 @@ package mage.cards.m; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.costs.common.DiscardCardCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.BandingAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -51,7 +51,7 @@ class MishrasWarMachineEffect extends OneShotEffect { MishrasWarMachineEffect() { super(Outcome.Sacrifice); - staticText = "{this} deals 3 damage to you unless you discard a card. If Mishra's War Machine deals damage to you this way, tap it"; + staticText = "{this} deals 3 damage to you unless you discard a card. If {this} deals damage to you this way, tap it"; } private MishrasWarMachineEffect(final MishrasWarMachineEffect effect) { diff --git a/Mage.Sets/src/mage/cards/m/MmmenonTheRightHand.java b/Mage.Sets/src/mage/cards/m/MmmenonTheRightHand.java new file mode 100644 index 00000000000..c2f6e2bf00b --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MmmenonTheRightHand.java @@ -0,0 +1,110 @@ +package mage.cards.m; + +import mage.ConditionalMana; +import mage.MageInt; +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.mana.ConditionalColoredManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterArtifactCard; +import mage.game.Game; +import mage.game.stack.Spell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MmmenonTheRightHand extends CardImpl { + + private static final FilterCard filter = new FilterArtifactCard("cast artifact spells"); + + public MmmenonTheRightHand(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.JELLYFISH); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // You may cast artifact spells from the top of your library. + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); + + // Artifacts you control have "{T}: Add {U}. Spend this mana only to cast a spell from anywhere other than your hand." + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + new ConditionalColoredManaAbility(Mana.BlueMana(1), new MmmenonTheRightHandManaBuilder()), + Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_ARTIFACTS + ))); + } + + private MmmenonTheRightHand(final MmmenonTheRightHand card) { + super(card); + } + + @Override + public MmmenonTheRightHand copy() { + return new MmmenonTheRightHand(this); + } +} + +class MmmenonTheRightHandManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new MmmenonTheRightHandConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to cast a spell from anywhere other than your hand"; + } +} + +class MmmenonTheRightHandConditionalMana extends ConditionalMana { + + public MmmenonTheRightHandConditionalMana(Mana mana) { + super(mana); + this.staticText = "Spend this mana only to cast a spell from anywhere other than your hand"; + addCondition(MmmenonTheRightHandManaCondition.instance); + } +} + +enum MmmenonTheRightHandManaCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + if (!(source instanceof SpellAbility)) { + return false; + } + MageObject object = game.getObject(source); + if (!source.isControlledBy(game.getOwnerId(object))) { + return false; + } + if (object instanceof Spell) { + return !Zone.HAND.match(((Spell) object).getFromZone()); + } + // checking mana without real cast + return game.inCheckPlayableState() && !Zone.HAND.match(game.getState().getZone(source.getSourceId())); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MmmenonUthrosExile.java b/Mage.Sets/src/mage/cards/m/MmmenonUthrosExile.java new file mode 100644 index 00000000000..bd9d14accb5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MmmenonUthrosExile.java @@ -0,0 +1,53 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MmmenonUthrosExile extends CardImpl { + + public MmmenonUthrosExile(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.JELLYFISH); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever an artifact you control enters, put a +1/+1 counter on target creature. + Ability ability = new EntersBattlefieldAllTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT + ); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private MmmenonUthrosExile(final MmmenonUthrosExile card) { + super(card); + } + + @Override + public MmmenonUthrosExile copy() { + return new MmmenonUthrosExile(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MobJustice.java b/Mage.Sets/src/mage/cards/m/MobJustice.java index 2ab61befce8..12d35f42b46 100644 --- a/Mage.Sets/src/mage/cards/m/MobJustice.java +++ b/Mage.Sets/src/mage/cards/m/MobJustice.java @@ -20,7 +20,7 @@ public final class MobJustice extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); // Mob Justice deals damage to target player equal to the number of creatures you control. - Effect effect = new DamageTargetEffect(CreaturesYouControlCount.instance); + Effect effect = new DamageTargetEffect(CreaturesYouControlCount.PLURAL); effect.setText("{this} deals damage to target player or planeswalker equal to the number of creatures you control"); this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetPlayerOrPlaneswalker()); diff --git a/Mage.Sets/src/mage/cards/m/MoggJailer.java b/Mage.Sets/src/mage/cards/m/MoggJailer.java index 455abd71cb0..f8a720ed30e 100644 --- a/Mage.Sets/src/mage/cards/m/MoggJailer.java +++ b/Mage.Sets/src/mage/cards/m/MoggJailer.java @@ -1,7 +1,6 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.Effect; @@ -9,14 +8,15 @@ import mage.abilities.effects.common.combat.CantAttackIfDefenderControlsPermanen import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.ComparisonType; -import mage.constants.Zone; +import mage.constants.SubType; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.PowerPredicate; import mage.filter.predicate.permanent.TappedPredicate; +import java.util.UUID; + /** * @author BursegSardaukar */ @@ -36,7 +36,7 @@ public final class MoggJailer extends CardImpl { // Mogg Jailer can't attack if defending player controls an untapped creature with power 2 or less. Effect effect = new CantAttackIfDefenderControlsPermanent(filter); - effect.setText("Mogg Jailer can't attack if defending player controls an untapped creature with power 2 or less."); + effect.setText("{this} can't attack if defending player controls an untapped creature with power 2 or less."); this.addAbility(new SimpleStaticAbility(effect)); } diff --git a/Mage.Sets/src/mage/cards/m/MoldShambler.java b/Mage.Sets/src/mage/cards/m/MoldShambler.java index a19a1f6c58b..d098c5bc7fa 100644 --- a/Mage.Sets/src/mage/cards/m/MoldShambler.java +++ b/Mage.Sets/src/mage/cards/m/MoldShambler.java @@ -1,6 +1,7 @@ package mage.cards.m; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.KickedCondition; import mage.abilities.effects.common.DestroyTargetEffect; @@ -9,26 +10,18 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; /** - * * @author North */ public final class MoldShambler extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public MoldShambler(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); this.subtype.add(SubType.FUNGUS); this.subtype.add(SubType.BEAST); @@ -39,9 +32,9 @@ public final class MoldShambler extends CardImpl { this.addAbility(new KickerAbility("{1}{G}")); // When Mold Shambler enters the battlefield, if it was kicked, destroy target noncreature permanent. - EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect(), false); - ability.addTarget(new TargetPermanent(filter)); - this.addAbility(ability.withInterveningIf(KickedCondition.ONCE)); + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()).withInterveningIf(KickedCondition.ONCE); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); + this.addAbility(ability); } private MoldShambler(final MoldShambler card) { diff --git a/Mage.Sets/src/mage/cards/m/MolecularModifier.java b/Mage.Sets/src/mage/cards/m/MolecularModifier.java new file mode 100644 index 00000000000..63b857288b5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MolecularModifier.java @@ -0,0 +1,51 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +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.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class MolecularModifier extends CardImpl { + + public MolecularModifier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.KAVU); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // At the beginning of combat on your turn, target creature you control gets +1/+0 and gains first strike until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility( + new BoostTargetEffect(1, 0) + .setText("target creature you control gets +1/+0") + ); + ability.addEffect(new GainAbilityTargetEffect( + FirstStrikeAbility.getInstance(), Duration.EndOfTurn + ).setText("and gains first strike until end of turn")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private MolecularModifier(final MolecularModifier card) { + super(card); + } + + @Override + public MolecularModifier copy() { + return new MolecularModifier(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoltenRain.java b/Mage.Sets/src/mage/cards/m/MoltenRain.java index 39b8e3b5a29..bcf60e55094 100644 --- a/Mage.Sets/src/mage/cards/m/MoltenRain.java +++ b/Mage.Sets/src/mage/cards/m/MoltenRain.java @@ -45,7 +45,7 @@ class MoltenRainEffect extends OneShotEffect { MoltenRainEffect() { super(Outcome.Damage); - this.staticText = "If that land was nonbasic, Molten Rain deals 2 damage to the land's controller"; + this.staticText = "If that land was nonbasic, {this} deals 2 damage to the land's controller"; } private MoltenRainEffect(final MoltenRainEffect effect) { diff --git a/Mage.Sets/src/mage/cards/m/MonoistCircuitFeeder.java b/Mage.Sets/src/mage/cards/m/MonoistCircuitFeeder.java new file mode 100644 index 00000000000..78b8799fa6d --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MonoistCircuitFeeder.java @@ -0,0 +1,60 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.common.ArtifactYouControlHint; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MonoistCircuitFeeder extends CardImpl { + + private static final DynamicValue xValue = new SignInversionDynamicValue(ArtifactYouControlCount.instance); + + public MonoistCircuitFeeder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add(SubType.NAUTILUS); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, until end of turn, target creature you control gets +X/+0 and target creature an opponent controls gets -0/-X, where X is the number of artifacts you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect( + ArtifactYouControlCount.instance, StaticValue.get(0) + ).setText("until end of turn, target creature you control gets +X/+0")); + ability.addTarget(new TargetControlledCreaturePermanent()); + ability.addEffect(new BoostTargetEffect(StaticValue.get(0), xValue) + .setText("and target creature an opponent controls gets -0/-X, where X is the number of artifacts you control") + .setTargetPointer(new SecondTargetPointer())); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability.addHint(ArtifactYouControlHint.instance)); + } + + private MonoistCircuitFeeder(final MonoistCircuitFeeder card) { + super(card); + } + + @Override + public MonoistCircuitFeeder copy() { + return new MonoistCircuitFeeder(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MonoistSentry.java b/Mage.Sets/src/mage/cards/m/MonoistSentry.java new file mode 100644 index 00000000000..7cd895c95e3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MonoistSentry.java @@ -0,0 +1,36 @@ +package mage.cards.m; + +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 Susucr + */ +public final class MonoistSentry extends CardImpl { + + public MonoistSentry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{B}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(4); + this.toughness = new MageInt(1); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + } + + private MonoistSentry(final MonoistSentry card) { + super(card); + } + + @Override + public MonoistSentry copy() { + return new MonoistSentry(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MonsterManual.java b/Mage.Sets/src/mage/cards/m/MonsterManual.java index a1a17bff4b1..783ccd5af5f 100644 --- a/Mage.Sets/src/mage/cards/m/MonsterManual.java +++ b/Mage.Sets/src/mage/cards/m/MonsterManual.java @@ -84,7 +84,7 @@ class ZoologicalStudyEffect extends OneShotEffect { break; default: TargetCard target = new TargetCard(Zone.ALL, StaticFilters.FILTER_CARD_CREATURE); - player.choose(outcome, cards, target, source, game); + player.choose(Outcome.PutCardInPlay, cards, target, source, game); card = cards.get(target.getFirstTarget(), game); } player.moveCards(card, Zone.HAND, source, game); diff --git a/Mage.Sets/src/mage/cards/m/MooglesValor.java b/Mage.Sets/src/mage/cards/m/MooglesValor.java index ed82d21e02b..a88288f175e 100644 --- a/Mage.Sets/src/mage/cards/m/MooglesValor.java +++ b/Mage.Sets/src/mage/cards/m/MooglesValor.java @@ -23,7 +23,7 @@ public final class MooglesValor extends CardImpl { // For each creature you control, create a 1/2 white Moogle creature token with lifelink. Then creatures you control gain indestructible until end of turn. this.getSpellAbility().addEffect(new CreateTokenEffect( - new MoogleToken(), CreaturesYouControlCount.instance + new MoogleToken(), CreaturesYouControlCount.PLURAL ).setText("for each creature you control, create a 1/2 white Moogle creature token with lifelink")); this.getSpellAbility().addEffect(new GainAbilityControlledEffect( IndestructibleAbility.getInstance(), Duration.EndOfTurn, diff --git a/Mage.Sets/src/mage/cards/m/MoonlitMeditation.java b/Mage.Sets/src/mage/cards/m/MoonlitMeditation.java new file mode 100644 index 00000000000..3685127c310 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoonlitMeditation.java @@ -0,0 +1,145 @@ +package mage.cards.m; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.CopyEffect; +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.StaticFilters; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.events.CreateTokenEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.Token; +import mage.target.TargetPermanent; +import mage.util.functions.CopyApplier; +import mage.util.functions.CopyTokenFunction; +import mage.util.functions.EmptyCopyApplier; +import mage.watchers.common.CreatedTokenWatcher; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MoonlitMeditation extends CardImpl { + + public MoonlitMeditation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); + + this.subtype.add(SubType.AURA); + + // Enchant artifact or creature you control + TargetPermanent auraTarget = new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // The first time you would create one or more tokens each turn, you may instead create that many tokens that are copies of enchanted permanent. + this.addAbility(new SimpleStaticAbility(new MoonlitMeditationEffect()), new CreatedTokenWatcher()); + } + + private MoonlitMeditation(final MoonlitMeditation card) { + super(card); + } + + @Override + public MoonlitMeditation copy() { + return new MoonlitMeditation(this); + } +} + +class MoonlitMeditationEffect extends ReplacementEffectImpl { + + MoonlitMeditationEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, false); + staticText = "the first time you would create one or more tokens each turn, " + + "you may instead create that many tokens that are copies of enchanted permanent"; + } + + private MoonlitMeditationEffect(MoonlitMeditationEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CREATE_TOKEN; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.isControlledBy(event.getPlayerId()) + && !CreatedTokenWatcher.checkPlayer(source.getControllerId(), game); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent permanent = Optional + .ofNullable(source.getSourcePermanentIfItStillExists(game)) + .map(Permanent::getAttachedTo) + .map(game::getPermanent) + .orElse(null); + if (permanent == null + || !Optional + .ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .filter(player -> player.chooseUse( + Outcome.Benefit, "Replace the tokens with copies of " + + permanent.getLogName() + '?', source, game + )) + .isPresent()) { + return false; + } + CreateTokenEvent tokenEvent = (CreateTokenEvent) event; + int amount = tokenEvent.getAmount(); + tokenEvent.getTokens().clear(); + tokenEvent.getTokens().put(copyPermanentToToken(permanent, game, source), amount); + return false; + } + + @Override + public MoonlitMeditationEffect copy() { + return new MoonlitMeditationEffect(this); + } + + private static Token copyPermanentToToken(Permanent permanent, Game game, Ability source) { + CopyApplier applier = new EmptyCopyApplier(); + // handle copies of copies + Permanent copyFromPermanent = permanent; + for (ContinuousEffect effect : game.getState().getContinuousEffects().getLayeredEffects(game)) { + if (!(effect instanceof CopyEffect)) { + continue; + } + CopyEffect copyEffect = (CopyEffect) effect; + // there is another copy effect that our targetPermanent copies stats from + if (!copyEffect.getSourceId().equals(permanent.getId())) { + continue; + } + MageObject object = ((CopyEffect) effect).getTarget(); + if (!(object instanceof Permanent)) { + continue; + } + copyFromPermanent = (Permanent) object; + if (copyEffect.getApplier() != null) { + applier = copyEffect.getApplier(); + } + } + + // create token and modify all attributes permanently (without game usage) + Token token = CopyTokenFunction.createTokenCopy(copyFromPermanent, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer) + applier.apply(game, token, source, permanent.getId()); + return token; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoonshakerCavalry.java b/Mage.Sets/src/mage/cards/m/MoonshakerCavalry.java index 6f0881e75fe..1c1c9715e54 100644 --- a/Mage.Sets/src/mage/cards/m/MoonshakerCavalry.java +++ b/Mage.Sets/src/mage/cards/m/MoonshakerCavalry.java @@ -39,7 +39,7 @@ public final class MoonshakerCavalry extends CardImpl { StaticFilters.FILTER_PERMANENT_CREATURES ).setText("creatures you control gain flying")); ability.addEffect(new BoostControlledEffect( - CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn + CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn ).setText("and get +X/+X until end of turn, where X is the number of creatures you control")); this.addAbility(ability.addHint(CreaturesYouControlHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/m/MorgueBurst.java b/Mage.Sets/src/mage/cards/m/MorgueBurst.java index 4a0d9e2870e..cfac441a154 100644 --- a/Mage.Sets/src/mage/cards/m/MorgueBurst.java +++ b/Mage.Sets/src/mage/cards/m/MorgueBurst.java @@ -45,7 +45,7 @@ class MorgueBurstEffect extends OneShotEffect { MorgueBurstEffect() { super(Outcome.ReturnToHand); - this.staticText = "Return target creature card from your graveyard to your hand. Morgue Burst deals damage to any target equal to the power of the card returned this way"; + this.staticText = "Return target creature card from your graveyard to your hand. {this} deals damage to any target equal to the power of the card returned this way"; } private MorgueBurstEffect(final MorgueBurstEffect effect) { diff --git a/Mage.Sets/src/mage/cards/m/MouthOfTheStorm.java b/Mage.Sets/src/mage/cards/m/MouthOfTheStorm.java new file mode 100644 index 00000000000..fc26e49093a --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MouthOfTheStorm.java @@ -0,0 +1,51 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BoostAllEffect; +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 mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MouthOfTheStorm extends CardImpl { + + public MouthOfTheStorm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{U}"); + + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // When this creature enters, creatures your opponents control get -3/-0 until your next turn. + this.addAbility(new EntersBattlefieldTriggeredAbility(new BoostAllEffect( + -3, 0, Duration.UntilYourNextTurn, + StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURES, false + ))); + } + + private MouthOfTheStorm(final MouthOfTheStorm card) { + super(card); + } + + @Override + public MouthOfTheStorm copy() { + return new MouthOfTheStorm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoxiteRefinery.java b/Mage.Sets/src/mage/cards/m/MoxiteRefinery.java new file mode 100644 index 00000000000..a2237f65bfc --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoxiteRefinery.java @@ -0,0 +1,58 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveVariableCountersTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TimingRule; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MoxiteRefinery extends CardImpl { + + public MoxiteRefinery(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + // {2}, {T}, Remove X counters from an artifact or creature you control: Choose one. Activate only as a sorcery. + // * Put X charge counters on target artifact. + Ability ability = new SimpleActivatedAbility(new AddCountersTargetEffect( + CounterType.CHARGE.createInstance(), GetXValue.instance + ), new GenericManaCost(2)).setTiming(TimingRule.SORCERY); + ability.addCost(new TapSourceCost()); + ability.addCost(new RemoveVariableCountersTargetCost( + StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE, + null, "X", 0, true, null + )); + ability.addTarget(new TargetArtifactPermanent()); + ability.getModes().setChooseText("choose one. Activate only as a sorcery."); + + // * Put X +1/+1 counters on target creature. + ability.addMode(new Mode(new AddCountersTargetEffect( + CounterType.P1P1.createInstance(), GetXValue.instance + )).addTarget(new TargetCreaturePermanent())); + this.addAbility(ability); + } + + private MoxiteRefinery(final MoxiteRefinery card) { + super(card); + } + + @Override + public MoxiteRefinery copy() { + return new MoxiteRefinery(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MulDayaChannelers.java b/Mage.Sets/src/mage/cards/m/MulDayaChannelers.java index 2ad815a2701..ae8ee997ce0 100644 --- a/Mage.Sets/src/mage/cards/m/MulDayaChannelers.java +++ b/Mage.Sets/src/mage/cards/m/MulDayaChannelers.java @@ -1,24 +1,25 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.TopLibraryCardTypeCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.mana.AddManaOfAnyColorEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect; +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.SubType; import mage.constants.Duration; +import mage.constants.SubType; import mage.constants.Zone; +import java.util.UUID; + /** * * @author jeffwadsworth @@ -48,7 +49,7 @@ public final class MulDayaChannelers extends CardImpl { SimpleManaAbility manaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, new AddManaOfAnyColorEffect(2), new TapSourceCost()); effect = new ConditionalContinuousEffect(new GainAbilitySourceEffect(manaAbility, Duration.WhileOnBattlefield), new TopLibraryCardTypeCondition(CardType.LAND), - "As long as the top card of your library is a land card, Mul Daya Channelers has \"{T}: Add two mana of any one color.\""); + "As long as the top card of your library is a land card, {this} has \"{T}: Add two mana of any one color.\""); this.addAbility(new SimpleStaticAbility(effect)); } diff --git a/Mage.Sets/src/mage/cards/m/MuseVessel.java b/Mage.Sets/src/mage/cards/m/MuseVessel.java index 04a1518e5e6..2d7be46fbbd 100644 --- a/Mage.Sets/src/mage/cards/m/MuseVessel.java +++ b/Mage.Sets/src/mage/cards/m/MuseVessel.java @@ -6,24 +6,26 @@ import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; +import mage.abilities.effects.OneShotNonTargetEffect; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.CardsImpl; -import mage.constants.*; -import mage.filter.FilterCard; -import mage.game.ExileZone; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; import mage.target.common.TargetCardInExile; import mage.target.common.TargetCardInHand; +import mage.target.targetadjustment.TargetAdjuster; import mage.util.CardUtil; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -40,10 +42,12 @@ public final class MuseVessel extends CardImpl { tapAbility.addCost(new ManaCostsImpl<>("{3}")); tapAbility.addTarget(new TargetPlayer()); this.addAbility(tapAbility); - // {1}: Choose a card exiled with Muse Vessel. You may play that card this turn. - SimpleActivatedAbility playAbility = new SimpleActivatedAbility(new MuseVesselMayPlayExiledEffect(), new ManaCostsImpl<>("{1}")); - playAbility.addTarget(new TargetCardInMuseVesselExile()); + SimpleActivatedAbility playAbility = new SimpleActivatedAbility(new OneShotNonTargetEffect( + new AddContinuousEffectToGame(new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, Duration.EndOfTurn)) + .setText("Choose a card exiled with {this}. You may play that card this turn."), + new TargetCardInExile(StaticFilters.FILTER_CARD), MuseVesselAdjuster.instance + ), new ManaCostsImpl<>("{1}")); this.addAbility(playAbility); } @@ -80,8 +84,8 @@ class MuseVesselExileEffect 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()); + && target.choose(Outcome.Exile, player.getId(), source, game)) { + UUID exileId = CardUtil.getExileZoneId(game, source); return player.moveCardsToExile(new CardsImpl(target.getTargets()).getCards(game), source, game, true, exileId, sourceObject.getIdName()); } return false; @@ -94,90 +98,14 @@ class MuseVesselExileEffect extends OneShotEffect { } -class MuseVesselMayPlayExiledEffect extends AsThoughEffectImpl { - - MuseVesselMayPlayExiledEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); - this.staticText = "Choose a card exiled with {this}. You may play that card this turn"; - } - - private MuseVesselMayPlayExiledEffect(final MuseVesselMayPlayExiledEffect effect) { - super(effect); - } +enum MuseVesselAdjuster implements TargetAdjuster { + instance; @Override - public MuseVesselMayPlayExiledEffect copy() { - return new MuseVesselMayPlayExiledEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - return affectedControllerId.equals(source.getControllerId()) - && getTargetPointer().getTargets(game, source).contains(objectId); - } - -} - -// TODO: cleanup. there should be no need for custom Target there. -class TargetCardInMuseVesselExile extends TargetCardInExile { - - public TargetCardInMuseVesselExile() { - super(new FilterCard("card exiled with Muse Vessel")); - } - - private TargetCardInMuseVesselExile(final TargetCardInMuseVesselExile target) { - super(target); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - Card sourceCard = game.getCard(source.getSourceId()); - if (sourceCard != null) { - UUID exileId = CardUtil.getCardExileZoneId(game, source.getSourceId()); - ExileZone exile = game.getExile().getExileZone(exileId); - if (exile != null && !exile.isEmpty()) { - possibleTargets.addAll(exile); - } - } - return possibleTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - Card sourceCard = game.getCard(source.getSourceId()); - if (sourceCard != null) { - UUID exileId = CardUtil.getCardExileZoneId(game, source.getSourceId()); - ExileZone exile = game.getExile().getExileZone(exileId); - return exile != null && !exile.isEmpty(); - } - 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) { - ExileZone exile = null; - Card sourceCard = game.getCard(source.getSourceId()); - if (sourceCard != null) { - UUID exileId = CardUtil.getCardExileZoneId(game, source); - exile = game.getExile().getExileZone(exileId); - } - if (exile != null && exile.contains(id)) { - return filter.match(card, source.getControllerId(), source, game); - } - } - return false; - } - - @Override - public TargetCardInMuseVesselExile copy() { - return new TargetCardInMuseVesselExile(this); + public void adjustTargets(Ability ability, Game game) { + ability.getTargets().clear(); + ability.addTarget( + new TargetCardInExile(StaticFilters.FILTER_CARD, CardUtil.getCardExileZoneId(game, ability.getSourceId())) + .withNotTarget(true)); } } diff --git a/Mage.Sets/src/mage/cards/m/MutinousMassacre.java b/Mage.Sets/src/mage/cards/m/MutinousMassacre.java new file mode 100644 index 00000000000..4ea83ce8cec --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MutinousMassacre.java @@ -0,0 +1,84 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainControlAllUntapGainHasteEffect; +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.mageobject.ManaValueParityPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MutinousMassacre extends CardImpl { + + public MutinousMassacre(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{B}{R}{R}"); + + // Choose odd or even. Destroy each creature with mana value of the chosen quality. Then gain control of all creatures until end of turn. Untap them. They gain haste until end of turn. + this.getSpellAbility().addEffect(new MutinousMassacreEffect()); + this.getSpellAbility().addEffect(new GainControlAllUntapGainHasteEffect(StaticFilters.FILTER_PERMANENT_CREATURES).concatBy("Then")); + } + + private MutinousMassacre(final MutinousMassacre card) { + super(card); + } + + @Override + public MutinousMassacre copy() { + return new MutinousMassacre(this); + } +} + +class MutinousMassacreEffect extends OneShotEffect { + + private static final FilterPermanent evenFilter = new FilterCreaturePermanent(); + private static final FilterPermanent oddFilter = new FilterCreaturePermanent(); + + static { + evenFilter.add(ManaValueParityPredicate.EVEN); + oddFilter.add(ManaValueParityPredicate.ODD); + } + + MutinousMassacreEffect() { + super(Outcome.Benefit); + staticText = "choose odd or even. Destroy each creature with mana value of the chosen quality"; + } + + private MutinousMassacreEffect(final MutinousMassacreEffect effect) { + super(effect); + } + + @Override + public MutinousMassacreEffect copy() { + return new MutinousMassacreEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + FilterPermanent filter = player.chooseUse( + outcome, "Odd or even?", null, + "Odd", "Even", source, game + ) ? oddFilter : evenFilter; + for (Permanent permanent : game.getBattlefield().getActivePermanents( + filter, source.getControllerId(), source, game + )) { + permanent.destroy(source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MycoidShepherd.java b/Mage.Sets/src/mage/cards/m/MycoidShepherd.java index 51f3d1f815b..b996d460869 100644 --- a/Mage.Sets/src/mage/cards/m/MycoidShepherd.java +++ b/Mage.Sets/src/mage/cards/m/MycoidShepherd.java @@ -1,40 +1,42 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; -import mage.MageObject; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DiesThisOrAnotherTriggeredAbility; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ComparisonType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; /** - * * @author jeffwadsworth */ public final class MycoidShepherd extends CardImpl { + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another creature you control with power 5 or greater"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 4)); + } public MycoidShepherd(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}{G}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{G}{W}"); this.subtype.add(SubType.FUNGUS); - - this.power = new MageInt(5); this.toughness = new MageInt(4); // Whenever Mycoid Shepherd or another creature you control with power 5 or greater dies, you may gain 5 life. - this.addAbility(new MycoidShepherdTriggeredAbility()); - + this.addAbility(new DiesThisOrAnotherTriggeredAbility(new GainLifeEffect(5), true, filter)); + } private MycoidShepherd(final MycoidShepherd card) { @@ -46,55 +48,3 @@ public final class MycoidShepherd extends CardImpl { return new MycoidShepherd(this); } } - -class MycoidShepherdTriggeredAbility extends TriggeredAbilityImpl { - - public MycoidShepherdTriggeredAbility() { - super(Zone.BATTLEFIELD, new GainLifeEffect(5), true); - setLeavesTheBattlefieldTrigger(true); - } - - private MycoidShepherdTriggeredAbility(final MycoidShepherdTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - MageObject lastKnown = game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD); - if (lastKnown == null) { - return false; - } - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - Permanent permanent = zEvent.getTarget(); - if (permanent == null) { - return false; - } - if (super.getSourceId().equals(event.getTargetId()) - || permanent.getPower().getValue() > 4 - && permanent.isControlledBy(controllerId)) { - Zone after = game.getState().getZone(event.getTargetId()); - return after != null && Zone.GRAVEYARD.match(after); - } - return false; - } - - @Override - public String getRule() { - return "Whenever Mycoid Shepherd or another creature you control with power 5 or greater dies, you may gain 5 life."; - } - - @Override - public MycoidShepherdTriggeredAbility copy() { - return new MycoidShepherdTriggeredAbility(this); - } - - @Override - public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/NanoformSentinel.java b/Mage.Sets/src/mage/cards/n/NanoformSentinel.java new file mode 100644 index 00000000000..b2fef7c5de0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NanoformSentinel.java @@ -0,0 +1,50 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class NanoformSentinel extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("another target permanent"); + + static { + filter.add(AnotherPredicate.instance); + } + + public NanoformSentinel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Whenever this creature becomes tapped, untap another target permanent. This ability triggers only once each turn. + TriggeredAbility ability = new BecomesTappedSourceTriggeredAbility(new UntapTargetEffect()); + ability.addTarget(new TargetPermanent(filter)); + ability.setTriggersLimitEachTurn(1); + this.addAbility(ability); + } + + private NanoformSentinel(final NanoformSentinel card) { + super(card); + } + + @Override + public NanoformSentinel copy() { + return new NanoformSentinel(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NayaHushblade.java b/Mage.Sets/src/mage/cards/n/NayaHushblade.java index e42a0764a67..1e16ae4e125 100644 --- a/Mage.Sets/src/mage/cards/n/NayaHushblade.java +++ b/Mage.Sets/src/mage/cards/n/NayaHushblade.java @@ -1,7 +1,6 @@ package mage.cards.n; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -13,12 +12,13 @@ import mage.abilities.keyword.ShroudAbility; 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 mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.mageobject.MulticoloredPredicate; import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.MulticoloredPredicate; + +import java.util.UUID; /** * @@ -46,9 +46,9 @@ public final class NayaHushblade extends CardImpl { // As long as you control another multicolored permanent, Naya Hushblade gets +1/+1 and has shroud. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( - new BoostSourceEffect(1,1, Duration.WhileOnBattlefield), + new BoostSourceEffect(1, 1, Duration.WhileOnBattlefield), new PermanentsOnTheBattlefieldCondition(filter), - "As long as you control another multicolored permanent, Naya Hushblade gets +1/+1")); + "As long as you control another multicolored permanent, {this} gets +1/+1")); ability.addEffect(new ConditionalContinuousEffect( new GainAbilitySourceEffect(ShroudAbility.getInstance()), new PermanentsOnTheBattlefieldCondition(filter), diff --git a/Mage.Sets/src/mage/cards/n/NebulaDragon.java b/Mage.Sets/src/mage/cards/n/NebulaDragon.java new file mode 100644 index 00000000000..6e298db6ab2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NebulaDragon.java @@ -0,0 +1,45 @@ +package mage.cards.n; + +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.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class NebulaDragon extends CardImpl { + + public NebulaDragon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, it deals 3 damage to any target. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(3, "it")); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + } + + private NebulaDragon(final NebulaDragon card) { + super(card); + } + + @Override + public NebulaDragon copy() { + return new NebulaDragon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NecromanticSelection.java b/Mage.Sets/src/mage/cards/n/NecromanticSelection.java index 5ae14f15495..288d6b06abc 100644 --- a/Mage.Sets/src/mage/cards/n/NecromanticSelection.java +++ b/Mage.Sets/src/mage/cards/n/NecromanticSelection.java @@ -30,7 +30,6 @@ import java.util.List; import java.util.UUID; /** - * * @author LevelX2 */ public final class NecromanticSelection extends CardImpl { @@ -73,40 +72,42 @@ class NecromanticSelectionEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = game.getObject(source); - if (sourceObject != null - && controller != null) { - Cards cards = new CardsImpl(); - for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, controller.getId(), source, game)) { - permanent.destroy(source, game, false); - game.checkStateAndTriggered(); // Meren of the Clan Nel Toth bug #8515 - if (game.getState().getZone(permanent.getId()) == Zone.GRAVEYARD) { - cards.add(permanent); - } - } - FilterCard filter = new FilterCreatureCard("creature card put into a graveyard with " + sourceObject.getLogName()); - List> cardIdPredicates = new ArrayList<>(); - for (UUID cardId : cards) { - cardIdPredicates.add(new CardIdPredicate(cardId)); - } - filter.add(Predicates.or(cardIdPredicates)); - Target target = new TargetCardInGraveyard(filter); - target.withNotTarget(true); - if (controller.chooseTarget(Outcome.Benefit, target, source, game)) { - Card card = game.getCard(target.getFirstTarget()); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game); - Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game); - if (permanent != null) { - ContinuousEffect effect = new AddCreatureTypeAdditionEffect(SubType.ZOMBIE, true); - effect.setText("It's a black Zombie in addition to its other colors and types"); - effect.setTargetPointer(new FixedTarget(permanent.getId(), game)); - game.addEffect(effect, source); - } - } - } - return true; + if (controller == null) { + return false; } - return false; + + Cards cards = new CardsImpl(); + for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, controller.getId(), source, game)) { + permanent.destroy(source, game, false); + if (game.getState().getZone(permanent.getId()) == Zone.GRAVEYARD) { + cards.add(permanent); + } + } + game.processAction(); + + MageObject sourceObject = game.getObject(source); + String sourceName = sourceObject != null ? sourceObject.getLogName() : ""; + FilterCard filter = new FilterCreatureCard("creature card put into a graveyard with " + sourceName); + List> cardIdPredicates = new ArrayList<>(); + for (UUID cardId : cards) { + cardIdPredicates.add(new CardIdPredicate(cardId)); + } + filter.add(Predicates.or(cardIdPredicates)); + Target target = new TargetCardInGraveyard(filter); + target.withNotTarget(true); + if (controller.chooseTarget(Outcome.PutCreatureInPlay, target, source, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + controller.moveCards(card, Zone.BATTLEFIELD, source, game); + Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game); + if (permanent != null) { + ContinuousEffect effect = new AddCreatureTypeAdditionEffect(SubType.ZOMBIE, true); + effect.setText("It's a black Zombie in addition to its other colors and types"); + effect.setTargetPointer(new FixedTarget(permanent.getId(), game)); + game.addEffect(effect, source); + } + } + } + return true; } } diff --git a/Mage.Sets/src/mage/cards/n/NeedleStorm.java b/Mage.Sets/src/mage/cards/n/NeedleStorm.java index a45a14b307d..34195a295eb 100644 --- a/Mage.Sets/src/mage/cards/n/NeedleStorm.java +++ b/Mage.Sets/src/mage/cards/n/NeedleStorm.java @@ -1,30 +1,23 @@ package mage.cards.n; -import java.util.UUID; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** * @author Loki */ public final class NeedleStorm extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public NeedleStorm(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); - this.getSpellAbility().addEffect(new DamageAllEffect(4, filter)); + this.getSpellAbility().addEffect(new DamageAllEffect(4, StaticFilters.FILTER_CREATURE_FLYING)); } private NeedleStorm(final NeedleStorm card) { diff --git a/Mage.Sets/src/mage/cards/n/NerivCracklingVanguard.java b/Mage.Sets/src/mage/cards/n/NerivCracklingVanguard.java index 75b317b9e1c..bfcaf621f89 100644 --- a/Mage.Sets/src/mage/cards/n/NerivCracklingVanguard.java +++ b/Mage.Sets/src/mage/cards/n/NerivCracklingVanguard.java @@ -1,45 +1,46 @@ package mage.cards.n; -import java.util.*; -import java.util.stream.Collectors; - import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.Condition; import mage.abilities.decorator.ConditionalAsThoughEffect; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; -import mage.cards.Card; -import mage.cards.Cards; -import mage.constants.*; -import mage.abilities.keyword.FlyingAbility; +import mage.abilities.hint.Hint; import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; 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.permanent.PermanentToken; import mage.game.permanent.token.GoblinToken; import mage.players.Player; import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; import mage.watchers.common.AttackedThisTurnWatcher; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + /** - * * @author Jmlundeen */ public final class NerivCracklingVanguard extends CardImpl { public NerivCracklingVanguard(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{W}{B}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.SPIRIT); this.subtype.add(SubType.DRAGON); @@ -56,7 +57,7 @@ public final class NerivCracklingVanguard extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new GoblinToken(false), 2))); // Whenever Neriv attacks, exile a number of cards from the top of your library equal to the number of differently named tokens you control. During any turn you attacked with a commander, you may play those cards. - this.addAbility(new AttacksTriggeredAbility(new NerivCracklingVanguardEffect())); + this.addAbility(new AttacksTriggeredAbility(new NerivCracklingVanguardEffect()).addHint(NerivCracklingVanguardEffect.getHint())); } private NerivCracklingVanguard(final NerivCracklingVanguard card) { @@ -71,6 +72,18 @@ public final class NerivCracklingVanguard extends CardImpl { class NerivCracklingVanguardEffect extends OneShotEffect { + private static final FilterPermanent filter = new FilterControlledPermanent("tokens you control"); + + static { + filter.add(TokenPredicate.TRUE); + } + + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(filter); + + static final Hint getHint() { + return xValue.getHint(); + } + NerivCracklingVanguardEffect() { super(Outcome.Benefit); staticText = "exile a number of cards from the top of your library equal to the number of " + @@ -89,7 +102,7 @@ class NerivCracklingVanguardEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - int tokenNameCount = NerivDynamicValue.instance.calculate(game, source, this); + int tokenNameCount = xValue.calculate(game, source, this); if (controller == null || tokenNameCount == 0) { return false; } @@ -111,30 +124,6 @@ class NerivCracklingVanguardEffect extends OneShotEffect { } } -enum NerivDynamicValue implements DynamicValue { - instance; - - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - return (int) game.getBattlefield().getAllActivePermanents(sourceAbility.getControllerId()).stream() - .filter(permanent -> permanent instanceof PermanentToken) - .map(MageObject::getName) - .distinct() - .count(); - } - - @Override - public DynamicValue copy() { - return instance; - } - - @Override - public String getMessage() { - return "X"; - } -} - class CommanderAttackedThisTurnCondition implements Condition { private Ability ability; @@ -177,4 +166,4 @@ class CommanderAttackedThisTurnCondition implements Condition { public String toString() { return "During that turn you attacked with a Commander"; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/n/NessianDemolok.java b/Mage.Sets/src/mage/cards/n/NessianDemolok.java index aa6f76928ff..16efb484b29 100644 --- a/Mage.Sets/src/mage/cards/n/NessianDemolok.java +++ b/Mage.Sets/src/mage/cards/n/NessianDemolok.java @@ -10,8 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -21,12 +20,6 @@ import java.util.UUID; */ public final class NessianDemolok extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public NessianDemolok(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); this.subtype.add(SubType.BEAST); @@ -40,7 +33,7 @@ public final class NessianDemolok extends CardImpl { // When Nessian Demolok enters the battlefield, if tribute wasn't paid, destroy target noncreature permanent. Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()) .withInterveningIf(TributeNotPaidCondition.instance); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/n/NetcasterSpider.java b/Mage.Sets/src/mage/cards/n/NetcasterSpider.java index 067d8e48f87..e025ab806b0 100644 --- a/Mage.Sets/src/mage/cards/n/NetcasterSpider.java +++ b/Mage.Sets/src/mage/cards/n/NetcasterSpider.java @@ -1,34 +1,26 @@ package mage.cards.n; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.BlocksCreatureTriggeredAbility; import mage.abilities.effects.common.continuous.BoostSourceEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class NetcasterSpider extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public NetcasterSpider(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.subtype.add(SubType.SPIDER); this.power = new MageInt(2); @@ -38,7 +30,7 @@ public final class NetcasterSpider extends CardImpl { this.addAbility(ReachAbility.getInstance()); // Whenever Netcaster Spider blocks a creature with flying, Netcaster Spider gets +2/+0 until end of turn. - this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn), filter, false)); + this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn), StaticFilters.FILTER_CREATURE_FLYING, false)); } private NetcasterSpider(final NetcasterSpider card) { @@ -49,4 +41,4 @@ public final class NetcasterSpider extends CardImpl { public NetcasterSpider copy() { return new NetcasterSpider(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/n/NibelheimAflame.java b/Mage.Sets/src/mage/cards/n/NibelheimAflame.java index 43362e5b1fc..996db588a9e 100644 --- a/Mage.Sets/src/mage/cards/n/NibelheimAflame.java +++ b/Mage.Sets/src/mage/cards/n/NibelheimAflame.java @@ -78,6 +78,9 @@ class NibelheimAflameEffect extends OneShotEffect { for (Permanent creature : game.getBattlefield().getActivePermanents( StaticFilters.FILTER_ANOTHER_CREATURE, source.getControllerId(), source, game )) { + if(creature.equals(permanent)){ + continue; + } creature.damage(power, permanent.getId(), source, game); } return true; diff --git a/Mage.Sets/src/mage/cards/n/NicolBolasPlaneswalker.java b/Mage.Sets/src/mage/cards/n/NicolBolasPlaneswalker.java index 83364730890..e5c8cb84425 100644 --- a/Mage.Sets/src/mage/cards/n/NicolBolasPlaneswalker.java +++ b/Mage.Sets/src/mage/cards/n/NicolBolasPlaneswalker.java @@ -1,7 +1,5 @@ - package mage.cards.n; -import java.util.UUID; import mage.abilities.LoyaltyAbility; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; @@ -11,27 +9,22 @@ 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.constants.Duration; +import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetPlayerOrPlaneswalker; +import java.util.UUID; + /** - * * @author North */ public final class NicolBolasPlaneswalker extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public NicolBolasPlaneswalker(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{4}{U}{B}{B}{R}"); this.supertype.add(SuperType.LEGENDARY); @@ -41,12 +34,14 @@ public final class NicolBolasPlaneswalker extends CardImpl { // +3: Destroy target noncreature permanent. LoyaltyAbility ability = new LoyaltyAbility(new DestroyTargetEffect(), 3); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); this.addAbility(ability); + // -2: Gain control of target creature. ability = new LoyaltyAbility(new GainControlTargetEffect(Duration.Custom), -2); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); + // -9: Nicol Bolas, Planeswalker deals 7 damage to target player. That player discards seven cards, then sacrifices seven permanents. ability = new LoyaltyAbility(new DamageTargetEffect(7), -9); ability.addTarget(new TargetPlayerOrPlaneswalker()); diff --git a/Mage.Sets/src/mage/cards/n/NobleQuarry.java b/Mage.Sets/src/mage/cards/n/NobleQuarry.java index b80487bf4bf..4e343e1934c 100644 --- a/Mage.Sets/src/mage/cards/n/NobleQuarry.java +++ b/Mage.Sets/src/mage/cards/n/NobleQuarry.java @@ -1,7 +1,6 @@ package mage.cards.n; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -14,9 +13,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.AttachmentType; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; +import mage.constants.SubType; + +import java.util.UUID; /** * @@ -35,7 +35,7 @@ public final class NobleQuarry extends CardImpl { this.addAbility(new BestowAbility(this, "{5}{G}")); // All creatures able to block Noble Quarry or enchanted creature do so. Effect effect = new MustBeBlockedByAllSourceEffect(Duration.WhileOnBattlefield); - effect.setText("All creatures able to block Noble Quarry"); + effect.setText("All creatures able to block {this}"); Ability ability = new SimpleStaticAbility(effect); effect = new MustBeBlockedByAllAttachedEffect(Duration.WhileOnBattlefield, AttachmentType.AURA); effect.setText("or enchanted creature do so"); diff --git a/Mage.Sets/src/mage/cards/n/NoctisPrinceOfLucis.java b/Mage.Sets/src/mage/cards/n/NoctisPrinceOfLucis.java index 14ad8788e14..e8af96f9c80 100644 --- a/Mage.Sets/src/mage/cards/n/NoctisPrinceOfLucis.java +++ b/Mage.Sets/src/mage/cards/n/NoctisPrinceOfLucis.java @@ -43,7 +43,8 @@ public final class NoctisPrinceOfLucis extends CardImpl { this.addAbility(LifelinkAbility.getInstance()); // You may cast artifact spells from your graveyard by paying 3 life in addition to paying their other costs. If you cast a spell this way, that artifact enters with a finality counter on it. - this.addAbility(new SimpleStaticAbility(new NoctisPrinceOfLucisEffect()), new NoctisPrinceOfLucisWatcher()); + this.addAbility(new SimpleStaticAbility(new NoctisPrinceOfLucisEffect()) + .setIdentifier(MageIdentifier.NoctisPrinceOfLucisAlternateCast), new NoctisPrinceOfLucisWatcher()); } private NoctisPrinceOfLucis(final NoctisPrinceOfLucis card) { diff --git a/Mage.Sets/src/mage/cards/n/NoosegrafMob.java b/Mage.Sets/src/mage/cards/n/NoosegrafMob.java index 319f669d11f..2bf724ef143 100644 --- a/Mage.Sets/src/mage/cards/n/NoosegrafMob.java +++ b/Mage.Sets/src/mage/cards/n/NoosegrafMob.java @@ -1,7 +1,6 @@ package mage.cards.n; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; @@ -13,14 +12,16 @@ 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.Outcome; +import mage.constants.SubType; import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.ZombieToken; import mage.players.Player; +import java.util.UUID; + /** * * @author fireshoes @@ -54,7 +55,7 @@ class NoosegrafMobEffect extends OneShotEffect { NoosegrafMobEffect() { super(Outcome.Benefit); - staticText = "remove a +1/+1 counter from Noosegraf Mob. If you do, create a 2/2 black Zombie creature token"; + staticText = "remove a +1/+1 counter from {this}. If you do, create a 2/2 black Zombie creature token"; } private NoosegrafMobEffect(final NoosegrafMobEffect effect) { diff --git a/Mage.Sets/src/mage/cards/n/NovaHellkite.java b/Mage.Sets/src/mage/cards/n/NovaHellkite.java new file mode 100644 index 00000000000..57478a13c6c --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NovaHellkite.java @@ -0,0 +1,53 @@ +package mage.cards.n; + +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.abilities.keyword.HasteAbility; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NovaHellkite extends CardImpl { + + public NovaHellkite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When this creature enters, it deals 1 damage to target creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1)); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + + // Warp {2}{R} + this.addAbility(new WarpAbility(this, "{2}{R}")); + } + + private NovaHellkite(final NovaHellkite card) { + super(card); + } + + @Override + public NovaHellkite copy() { + return new NovaHellkite(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NutrientBlock.java b/Mage.Sets/src/mage/cards/n/NutrientBlock.java new file mode 100644 index 00000000000..f05e888d550 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NutrientBlock.java @@ -0,0 +1,42 @@ +package mage.cards.n; + +import mage.abilities.common.PutIntoGraveFromBattlefieldSourceTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.token.FoodAbility; +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 NutrientBlock extends CardImpl { + + public NutrientBlock(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.FOOD); + + // Indestructible + this.addAbility(IndestructibleAbility.getInstance()); + + // {2}, {T}, Sacrifice this artifact: You gain 3 life. + this.addAbility(new FoodAbility()); + + // When this artifact is put into a graveyard from the battlefield, draw a card. + this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility(new DrawCardSourceControllerEffect(1))); + } + + private NutrientBlock(final NutrientBlock card) { + super(card); + } + + @Override + public NutrientBlock copy() { + return new NutrientBlock(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/ObsidianFireheart.java b/Mage.Sets/src/mage/cards/o/ObsidianFireheart.java index 84f8fc0cc08..ad07b6158f2 100644 --- a/Mage.Sets/src/mage/cards/o/ObsidianFireheart.java +++ b/Mage.Sets/src/mage/cards/o/ObsidianFireheart.java @@ -31,7 +31,7 @@ public final class ObsidianFireheart extends CardImpl { private static final String rule = "For as long as that land has a blaze counter " + "on it, it has \"At the beginning of your upkeep, this land deals 1 damage " - + "to you.\" (The land continues to burn after Obsidian Fireheart has left the battlefield.)"; + + "to you.\" (The land continues to burn after {this} has left the battlefield.)"; private static final FilterPermanent filter = new FilterLandPermanent("land without a blaze counter on it"); static { diff --git a/Mage.Sets/src/mage/cards/o/OranRiefRecluse.java b/Mage.Sets/src/mage/cards/o/OranRiefRecluse.java index 4a3bf001199..38eb6622d5b 100644 --- a/Mage.Sets/src/mage/cards/o/OranRiefRecluse.java +++ b/Mage.Sets/src/mage/cards/o/OranRiefRecluse.java @@ -5,16 +5,13 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.KickedCondition; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.KickerAbility; import mage.abilities.keyword.ReachAbility; 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.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -24,12 +21,6 @@ import java.util.UUID; */ public final class OranRiefRecluse extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public OranRiefRecluse(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.subtype.add(SubType.SPIDER); @@ -45,7 +36,7 @@ public final class OranRiefRecluse extends CardImpl { // When Oran-Rief Recluse enters the battlefield, if it was kicked, destroy target creature with flying. Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()).withInterveningIf(KickedCondition.ONCE); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java b/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java new file mode 100644 index 00000000000..94fa3a24ccd --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java @@ -0,0 +1,71 @@ +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; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.LanderToken; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class OrbitalPlunge extends CardImpl { + + public OrbitalPlunge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}"); + + // Orbital Plunge deals 6 damage to target creature. If excess damage was dealt to a permanent this way, create a Lander token. + this.getSpellAbility().addEffect(new OrbitalPlungeEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private OrbitalPlunge(final OrbitalPlunge card) { + super(card); + } + + @Override + public OrbitalPlunge copy() { + return new OrbitalPlunge(this); + } +} + +// Inspired by Bottle-Cap Blast +class OrbitalPlungeEffect extends OneShotEffect { + + OrbitalPlungeEffect() { + super(Outcome.Benefit); + staticText = "{this} deals 6 damage to target creature. " + + "If excess damage was dealt to a permanent this way, create a Lander token."; + } + + private OrbitalPlungeEffect(final OrbitalPlungeEffect effect) { + super(effect); + } + + @Override + public OrbitalPlungeEffect copy() { + return new OrbitalPlungeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + 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); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OreplatePangolin.java b/Mage.Sets/src/mage/cards/o/OreplatePangolin.java new file mode 100644 index 00000000000..f6e61e7da40 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OreplatePangolin.java @@ -0,0 +1,56 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class OreplatePangolin extends CardImpl { + + private static final FilterArtifactPermanent filter = new FilterArtifactPermanent("another artifact"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public OreplatePangolin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.PANGOLIN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever another artifact you control enters, you may pay {1}. If you do, put a +1/+1 counter on this creature. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + new DoIfCostPaid( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + new GenericManaCost(1) + ), filter) + ); + } + + private OreplatePangolin(final OreplatePangolin card) { + super(card); + } + + @Override + public OreplatePangolin copy() { + return new OreplatePangolin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OrimsTouch.java b/Mage.Sets/src/mage/cards/o/OrimsTouch.java index bdfcd3eb30d..7f93fa5780d 100644 --- a/Mage.Sets/src/mage/cards/o/OrimsTouch.java +++ b/Mage.Sets/src/mage/cards/o/OrimsTouch.java @@ -1,7 +1,6 @@ package mage.cards.o; -import java.util.UUID; import mage.abilities.condition.LockedInCondition; import mage.abilities.condition.common.KickedCondition; import mage.abilities.decorator.ConditionalReplacementEffect; @@ -14,6 +13,8 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.target.common.TargetAnyTarget; +import java.util.UUID; + /** * * @author fireshoes @@ -31,7 +32,7 @@ public final class OrimsTouch extends CardImpl { new PreventDamageToTargetEffect(Duration.EndOfTurn, 4), new LockedInCondition(KickedCondition.ONCE), new PreventDamageToTargetEffect(Duration.EndOfTurn, 2)); - effect.setText("Prevent the next 2 damage that would be dealt to any target this turn. If Orim's Touch was kicked, prevent the next 4 damage that would be dealt to that permanent or player this turn instead"); + effect.setText("Prevent the next 2 damage that would be dealt to any target this turn. If {this} was kicked, prevent the next 4 damage that would be dealt to that permanent or player this turn instead"); this.getSpellAbility().addTarget(new TargetAnyTarget()); this.getSpellAbility().addEffect(effect); } diff --git a/Mage.Sets/src/mage/cards/o/Ouroboroid.java b/Mage.Sets/src/mage/cards/o/Ouroboroid.java new file mode 100644 index 00000000000..91728108fcd --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/Ouroboroid.java @@ -0,0 +1,44 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Ouroboroid extends CardImpl { + + public Ouroboroid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.WURM); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // At the beginning of combat on your turn, put X +1/+1 counters on each creature you control, where X is this creature's power. + this.addAbility(new BeginningOfCombatTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), SourcePermanentPowerValue.NOT_NEGATIVE, + StaticFilters.FILTER_CONTROLLED_CREATURE + ).setText("put X +1/+1 counters on each creature you control, where X is {this}'s power"))); + } + + private Ouroboroid(final Ouroboroid card) { + super(card); + } + + @Override + public Ouroboroid copy() { + return new Ouroboroid(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OwlbearCub.java b/Mage.Sets/src/mage/cards/o/OwlbearCub.java index 10f8af835bd..c3f57105b5a 100644 --- a/Mage.Sets/src/mage/cards/o/OwlbearCub.java +++ b/Mage.Sets/src/mage/cards/o/OwlbearCub.java @@ -53,7 +53,7 @@ class OwlbearCubTriggeredAbility extends TriggeredAbilityImpl { OwlbearCubTriggeredAbility() { super(Zone.BATTLEFIELD, new OwlbearCubEffect()); this.withFlavorWord("Mama's Coming"); - setTriggerPhrase("Whenever Owlbear Cub attacks a player who controls eight or more lands, "); + setTriggerPhrase("Whenever {this} attacks a player who controls eight or more lands, "); } private OwlbearCubTriggeredAbility(final OwlbearCubTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/p/PainForAll.java b/Mage.Sets/src/mage/cards/p/PainForAll.java new file mode 100644 index 00000000000..b245e81c0a8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PainForAll.java @@ -0,0 +1,143 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.DealtDamageAttachedTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterPermanentOrPlayer; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetPermanentOrPlayer; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PainForAll extends CardImpl { + + private static final FilterPermanentOrPlayer filter = new FilterPermanentOrPlayer("any other target"); + + static { + filter.getPermanentFilter().add(PainForAllPredicate.instance); + } + + public PainForAll(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature you control + TargetPermanent auraTarget = new TargetControlledCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When this Aura enters, enchanted creature deals damage equal to its power to any other target. + Ability ability = new EntersBattlefieldTriggeredAbility(new PainForAllFirstEffect()); + ability.addTarget(new TargetPermanentOrPlayer(filter)); + this.addAbility(ability); + + // Whenever enchanted creature is dealt damage, it deals that much damage to each opponent. + this.addAbility(new DealtDamageAttachedTriggeredAbility( + Zone.BATTLEFIELD, new PainForAllSecondEffect(), false, SetTargetPointer.PERMANENT + )); + } + + private PainForAll(final PainForAll card) { + super(card); + } + + @Override + public PainForAll copy() { + return new PainForAll(this); + } +} + +enum PainForAllPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return Optional + .ofNullable(input) + .map(ObjectSourcePlayer::getSource) + .map(source -> source.getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .filter(permanentId -> !permanentId.equals(input.getObject().getId())) + .isPresent(); + } +} + +class PainForAllFirstEffect extends OneShotEffect { + + PainForAllFirstEffect() { + super(Outcome.Benefit); + staticText = "enchanted creature deals damage equal to its power to any other target"; + } + + private PainForAllFirstEffect(final PainForAllFirstEffect effect) { + super(effect); + } + + @Override + public PainForAllFirstEffect copy() { + return new PainForAllFirstEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .map(game::getPermanentOrLKIBattlefield) + .filter(permanent -> game.damagePlayerOrPermanent( + getTargetPointer().getFirst(game, source), + permanent.getPower().getValue(), permanent.getId(), + source, game, false, true + ) > 0) + .isPresent(); + } +} + +class PainForAllSecondEffect extends OneShotEffect { + + PainForAllSecondEffect() { + super(Outcome.Benefit); + staticText = "it deals that much damage to each opponent"; + } + + private PainForAllSecondEffect(final PainForAllSecondEffect effect) { + super(effect); + } + + @Override + public PainForAllSecondEffect copy() { + return new PainForAllSecondEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + UUID permanentId = this.getTargetPointer().getFirst(game, source); + int damage = (Integer) this.getValue("damage"); + if (permanentId == null || damage < 1) { + return false; + } + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Optional.ofNullable(opponentId) + .map(game::getPlayer) + .ifPresent(player -> player.damage(damage, permanentId, source, game)); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PalazzoArchers.java b/Mage.Sets/src/mage/cards/p/PalazzoArchers.java index 012c34f46b3..e910740ee79 100644 --- a/Mage.Sets/src/mage/cards/p/PalazzoArchers.java +++ b/Mage.Sets/src/mage/cards/p/PalazzoArchers.java @@ -39,7 +39,7 @@ public final class PalazzoArchers extends CardImpl { this.addAbility(ReachAbility.getInstance()); // Whenever a creature with flying attacks you or a planeswalker you control, Palazzo Archers deals damage equal to its power to that creature. - this.addAbility(new AttacksAllTriggeredAbility(new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE), + this.addAbility(new AttacksAllTriggeredAbility(new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE).withTargetDescription("that creature"), false, filter, SetTargetPointer.PERMANENT, true)); } diff --git a/Mage.Sets/src/mage/cards/p/ParasiticStrix.java b/Mage.Sets/src/mage/cards/p/ParasiticStrix.java index 10bae477a59..80bad60a6d6 100644 --- a/Mage.Sets/src/mage/cards/p/ParasiticStrix.java +++ b/Mage.Sets/src/mage/cards/p/ParasiticStrix.java @@ -1,10 +1,11 @@ package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.ObjectColor; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.YouControlPermanentCondition; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.keyword.FlyingAbility; @@ -12,21 +13,24 @@ 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.filter.predicate.mageobject.ColorPredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.target.TargetPlayer; +import java.util.UUID; + /** * @author mluds */ public final class ParasiticStrix extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent("black permanent"); + + static { + filter.add(new ColorPredicate(ObjectColor.BLACK)); + } public ParasiticStrix(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},"{2}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{U}"); this.subtype.add(SubType.BIRD); this.power = new MageInt(2); @@ -34,9 +38,13 @@ public final class ParasiticStrix extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - + // When Parasitic Strix enters the battlefield, if you control a black permanent, target player loses 2 life and you gain 2 life. - this.addAbility(new ParasiticStrixTriggeredAbility()); + TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new LoseLifeTargetEffect(2)); + ability.addEffect(new GainLifeEffect(2).concatBy("and")); + ability.withInterveningIf(new YouControlPermanentCondition(filter)); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); } private ParasiticStrix(final ParasiticStrix card) { @@ -48,46 +56,3 @@ public final class ParasiticStrix extends CardImpl { return new ParasiticStrix(this); } } - -class ParasiticStrixTriggeredAbility extends TriggeredAbilityImpl { - - public ParasiticStrixTriggeredAbility() { - super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(2)); - this.addEffect(new GainLifeEffect(2)); - this.addTarget(new TargetPlayer()); - } - - private ParasiticStrixTriggeredAbility(final ParasiticStrixTriggeredAbility ability) { - super(ability); - } - - @Override - public ParasiticStrixTriggeredAbility copy() { - return new ParasiticStrixTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getTargetId().equals(this.getSourceId()); - } - - @Override - public boolean checkInterveningIfClause(Game game) { - FilterPermanent filter = new FilterPermanent(); - filter.add(new ColorPredicate(ObjectColor.BLACK)); - if (game.getBattlefield().countAll(filter, this.controllerId, game) >= 1) { - return true; - } - return false; - } - - @Override - public String getRule() { - return "When Parasitic Strix enters the battlefield, if you control a black permanent, target player loses 2 life and you gain 2 life."; - } -} diff --git a/Mage.Sets/src/mage/cards/p/PatrollingPeacemaker.java b/Mage.Sets/src/mage/cards/p/PatrollingPeacemaker.java new file mode 100644 index 00000000000..d8fc9023f07 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PatrollingPeacemaker.java @@ -0,0 +1,85 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PatrollingPeacemaker extends CardImpl { + + public PatrollingPeacemaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // This creature 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" + )); + + // Whenever an opponent commits a crime, proliferate. + this.addAbility(new PatrollingPeacemakerTriggeredAbility()); + } + + private PatrollingPeacemaker(final PatrollingPeacemaker card) { + super(card); + } + + @Override + public PatrollingPeacemaker copy() { + return new PatrollingPeacemaker(this); + } +} + +class PatrollingPeacemakerTriggeredAbility extends TriggeredAbilityImpl { + + PatrollingPeacemakerTriggeredAbility() { + super(Zone.BATTLEFIELD, new ProliferateEffect()); + setTriggerPhrase("Whenever an opponent commits a crime, "); + } + + private PatrollingPeacemakerTriggeredAbility(final PatrollingPeacemakerTriggeredAbility ability) { + super(ability); + } + + @Override + public PatrollingPeacemakerTriggeredAbility copy() { + return new PatrollingPeacemakerTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + switch (event.getType()) { + case SPELL_CAST: + case ACTIVATED_ABILITY: + case TRIGGERED_ABILITY: + return true; + default: + return false; + } + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return game.getOpponents(getControllerId()).contains(CommittedCrimeTriggeredAbility.getCriminal(event, game)); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PawpatchFormation.java b/Mage.Sets/src/mage/cards/p/PawpatchFormation.java index 2588adc0179..0a4ee1ba923 100644 --- a/Mage.Sets/src/mage/cards/p/PawpatchFormation.java +++ b/Mage.Sets/src/mage/cards/p/PawpatchFormation.java @@ -4,13 +4,10 @@ import mage.abilities.Mode; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DestroyTargetEffect; 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.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.game.permanent.token.FoodToken; import mage.target.TargetPermanent; import mage.target.common.TargetEnchantmentPermanent; @@ -22,19 +19,13 @@ import java.util.UUID; */ public final class PawpatchFormation extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public PawpatchFormation(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Choose one -- // * Destroy target creature with flying. this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); // * Destroy target enchantment. this.getSpellAbility().addMode(new Mode(new DestroyTargetEffect()).addTarget(new TargetEnchantmentPermanent())); diff --git a/Mage.Sets/src/mage/cards/p/PerigeeBeckoner.java b/Mage.Sets/src/mage/cards/p/PerigeeBeckoner.java new file mode 100644 index 00000000000..73f702f719c --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PerigeeBeckoner.java @@ -0,0 +1,55 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.WarpAbility; +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.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PerigeeBeckoner extends CardImpl { + + public PerigeeBeckoner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.subtype.add(SubType.HORROR); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // When this creature enters, until end of turn, another target creature you control gets +2/+0 and gains "When this creature dies, return it to the battlefield tapped under its owner's control." + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(2, 0) + .setText("another target creature you control gets +2/+0")); + ability.addEffect(new GainAbilityTargetEffect(new DiesSourceTriggeredAbility( + new ReturnSourceFromGraveyardToBattlefieldEffect(true, true), false + ), Duration.EndOfTurn, "and gains \"When this creature dies, " + + "return it to the battlefield tapped under its owner's control.\"")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + + // Warp {1}{B} + this.addAbility(new WarpAbility(this, "{1}{B}")); + } + + private PerigeeBeckoner(final PerigeeBeckoner card) { + super(card); + } + + @Override + public PerigeeBeckoner copy() { + return new PerigeeBeckoner(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PestInfestation.java b/Mage.Sets/src/mage/cards/p/PestInfestation.java index e41a56e7b2a..ba74767a5a1 100644 --- a/Mage.Sets/src/mage/cards/p/PestInfestation.java +++ b/Mage.Sets/src/mage/cards/p/PestInfestation.java @@ -29,7 +29,7 @@ public final class PestInfestation extends CardImpl { this.getSpellAbility().addEffect(new DestroyTargetEffect() .setText("destroy up to X target artifacts and/or enchantments.")); this.getSpellAbility().addEffect(new CreateTokenEffect(new Pest11GainLifeToken(), xValue) - .setText("Create twice X 1/1 black and green Pest creature tokens with \"When this creature dies, you gain 1 life.\"")); + .setText("Create twice X 1/1 black and green Pest creature tokens with \"When this token dies, you gain 1 life.\"")); this.getSpellAbility().addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); this.getSpellAbility().setTargetAdjuster(new XTargetsCountAdjuster()); } diff --git a/Mage.Sets/src/mage/cards/p/PhantomSteed.java b/Mage.Sets/src/mage/cards/p/PhantomSteed.java index e52885b95d1..2dd56e0bdcf 100644 --- a/Mage.Sets/src/mage/cards/p/PhantomSteed.java +++ b/Mage.Sets/src/mage/cards/p/PhantomSteed.java @@ -18,17 +18,13 @@ import mage.constants.SubType; import mage.filter.StaticFilters; import mage.game.ExileZone; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.game.permanent.token.Token; import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; import mage.util.functions.CopyTokenFunction; -import java.util.List; -import java.util.Objects; import java.util.UUID; -import java.util.stream.Collectors; /** * @author TheElk801 @@ -92,15 +88,9 @@ class PhantomSteedEffect extends OneShotEffect { Token token = CopyTokenFunction.createTokenCopy(card, game); token.addSubType(SubType.ILLUSION); token.putOntoBattlefield(1, game, source, source.getControllerId(), true, true); - List permanents = token - .getLastAddedTokenIds() - .stream() - .map(game::getPermanent) - .filter(Objects::nonNull) - .collect(Collectors.toList()); game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility( new ExileTargetEffect("Sacrifice that token at end of combat") - .setTargetPointer(new FixedTargets(permanents, game)) + .setTargetPointer(new FixedTargets(token, game)) ), source); } return true; diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianAltar.java b/Mage.Sets/src/mage/cards/p/PhyrexianAltar.java index 0e4a9aaca85..8d03da7acb2 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianAltar.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianAltar.java @@ -8,7 +8,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.StaticFilters; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -22,7 +21,7 @@ public final class PhyrexianAltar extends CardImpl { // Sacrifice a creature: Add one mana of any color. this.addAbility(new AnyColorManaAbility( new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE), - CreaturesYouControlCount.instance, + CreaturesYouControlCount.PLURAL, false )); } diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianMetamorph.java b/Mage.Sets/src/mage/cards/p/PhyrexianMetamorph.java index 494f4deda07..c7d80d561cb 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianMetamorph.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianMetamorph.java @@ -3,15 +3,12 @@ package mage.cards.p; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.Effect; -import mage.abilities.effects.EntersBattlefieldEffect; +import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.effects.common.CopyPermanentEffect; 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.util.functions.CopyApplier; @@ -19,11 +16,23 @@ import mage.util.functions.CopyApplier; import java.util.UUID; /** - * * @author Loki */ public final class PhyrexianMetamorph extends CardImpl { + private static final CopyApplier applier = new CopyApplier() { + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID copyToObjectId) { + blueprint.addCardType(CardType.ARTIFACT); + return true; + } + + @Override + public String getText() { + return ", except it's an artifact in addition to its other types"; + } + }; + public PhyrexianMetamorph(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{U/P}"); this.subtype.add(SubType.PHYREXIAN); @@ -32,20 +41,11 @@ public final class PhyrexianMetamorph extends CardImpl { this.power = new MageInt(0); this.toughness = new MageInt(0); - CopyApplier phyrexianMetamorphCopyApplier = new CopyApplier() { - @Override - public boolean apply(Game game, MageObject blueprint, Ability source, UUID copyToObjectId) { - blueprint.addCardType(CardType.ARTIFACT); - return true; - } - }; - // {U/P} ( can be paid with either {U} or 2 life.) // You may have Phyrexian Metamorph enter the battlefield as a copy of any artifact or creature on the battlefield, except it's an artifact in addition to its other types. - Effect effect = new CopyPermanentEffect(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE, phyrexianMetamorphCopyApplier); - effect.setText("You may have {this} enter the battlefield as a copy of any artifact or creature on the battlefield, except it's an artifact in addition to its other types"); - Ability ability = new SimpleStaticAbility(Zone.ALL, new EntersBattlefieldEffect(effect, "", true)); - this.addAbility(ability); + this.addAbility(new EntersBattlefieldAbility(new CopyPermanentEffect( + StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE, applier + ), true)); } private PhyrexianMetamorph(final PhyrexianMetamorph card) { diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianScuta.java b/Mage.Sets/src/mage/cards/p/PhyrexianScuta.java index e1e3e824edb..5b12ac6300c 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianScuta.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianScuta.java @@ -1,7 +1,6 @@ package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.common.KickedCondition; @@ -14,6 +13,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; +import java.util.UUID; + /** * * @author LoneFox @@ -31,7 +32,7 @@ public final class PhyrexianScuta extends CardImpl { // Kicker-Pay 3 life. this.addAbility(new KickerAbility(new PayLifeCost(3))); // If Phyrexian Scuta was kicked, it enters with two +1/+1 counters on it. - this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), KickedCondition.ONCE, "If Phyrexian Scuta was kicked, it enters with two +1/+1 counters on it.", "")); + this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), KickedCondition.ONCE, "If {this} was kicked, it enters with two +1/+1 counters on it.", "")); } private PhyrexianScuta(final PhyrexianScuta card) { diff --git a/Mage.Sets/src/mage/cards/p/PiaNalaarConsulOfRevival.java b/Mage.Sets/src/mage/cards/p/PiaNalaarConsulOfRevival.java index d1d824a73c4..408f158493c 100644 --- a/Mage.Sets/src/mage/cards/p/PiaNalaarConsulOfRevival.java +++ b/Mage.Sets/src/mage/cards/p/PiaNalaarConsulOfRevival.java @@ -1,7 +1,7 @@ package mage.cards.p; import mage.MageInt; -import mage.abilities.common.PlayLandOrCastSpellFromExileTriggeredAbility; +import mage.abilities.common.PlayLandOrCastSpellTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; @@ -39,7 +39,9 @@ public final class PiaNalaarConsulOfRevival extends CardImpl { ))); // Whenever you play a land from exile or cast a spell from exile, create a 1/1 colorless Thopter artifact creature token with flying. - this.addAbility(new PlayLandOrCastSpellFromExileTriggeredAbility(new CreateTokenEffect(new ThopterColorlessToken()))); + this.addAbility(new PlayLandOrCastSpellTriggeredAbility( + new CreateTokenEffect(new ThopterColorlessToken()), true, false + )); } private PiaNalaarConsulOfRevival(final PiaNalaarConsulOfRevival card) { diff --git a/Mage.Sets/src/mage/cards/p/PickYourPoison.java b/Mage.Sets/src/mage/cards/p/PickYourPoison.java index fa624d5439e..6460fef2c44 100644 --- a/Mage.Sets/src/mage/cards/p/PickYourPoison.java +++ b/Mage.Sets/src/mage/cards/p/PickYourPoison.java @@ -2,14 +2,10 @@ package mage.cards.p; import mage.abilities.Mode; import mage.abilities.effects.common.SacrificeOpponentsEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; import java.util.UUID; @@ -18,12 +14,6 @@ import java.util.UUID; */ public final class PickYourPoison extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public PickYourPoison(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}"); @@ -35,7 +25,7 @@ public final class PickYourPoison extends CardImpl { this.getSpellAbility().addMode(new Mode(new SacrificeOpponentsEffect(StaticFilters.FILTER_PERMANENT_ENCHANTMENT))); // * Each opponent sacrifices a creature with flying. - this.getSpellAbility().addMode(new Mode(new SacrificeOpponentsEffect(filter) + this.getSpellAbility().addMode(new Mode(new SacrificeOpponentsEffect(StaticFilters.FILTER_CREATURE_FLYING) .setText("each opponent sacrifices a creature of their choice with flying"))); } diff --git a/Mage.Sets/src/mage/cards/p/PierceTheSky.java b/Mage.Sets/src/mage/cards/p/PierceTheSky.java index d2d3a4d0273..708beef88db 100644 --- a/Mage.Sets/src/mage/cards/p/PierceTheSky.java +++ b/Mage.Sets/src/mage/cards/p/PierceTheSky.java @@ -1,35 +1,26 @@ package mage.cards.p; -import java.util.UUID; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class PierceTheSky extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public PierceTheSky(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Pierce the Sky deals 7 damage to target creature with flying. this.getSpellAbility().addEffect(new DamageTargetEffect(7)); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private PierceTheSky(final PierceTheSky card) { diff --git a/Mage.Sets/src/mage/cards/p/PinionFeast.java b/Mage.Sets/src/mage/cards/p/PinionFeast.java index 42080cdd626..be8a98c2f07 100644 --- a/Mage.Sets/src/mage/cards/p/PinionFeast.java +++ b/Mage.Sets/src/mage/cards/p/PinionFeast.java @@ -1,36 +1,27 @@ package mage.cards.p; -import java.util.UUID; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.keyword.BolsterEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class PinionFeast extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public PinionFeast(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{4}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{G}"); // Destroy target creature with flying. Bolster 2. this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.getSpellAbility().addEffect(new BolsterEffect(2)); } diff --git a/Mage.Sets/src/mage/cards/p/PinnacleEmissary.java b/Mage.Sets/src/mage/cards/p/PinnacleEmissary.java new file mode 100644 index 00000000000..8c356c63b6d --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PinnacleEmissary.java @@ -0,0 +1,45 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.DroneToken2; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PinnacleEmissary extends CardImpl { + + public PinnacleEmissary(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}{R}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever you cast an artifact spell, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying." + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new DroneToken2()), StaticFilters.FILTER_SPELL_AN_ARTIFACT, false + )); + + // Warp {U/R} + this.addAbility(new WarpAbility(this, "{U/R}")); + } + + private PinnacleEmissary(final PinnacleEmissary card) { + super(card); + } + + @Override + public PinnacleEmissary copy() { + return new PinnacleEmissary(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PinnacleKillShip.java b/Mage.Sets/src/mage/cards/p/PinnacleKillShip.java new file mode 100644 index 00000000000..319a63286f8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PinnacleKillShip.java @@ -0,0 +1,51 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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 PinnacleKillShip extends CardImpl { + + public PinnacleKillShip(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{7}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, it deals 10 damage to up to one target creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(10, "it")); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // Station + this.addAbility(new StationAbility()); + + // STATION 7+ + // Flying + // 7/7 + this.addAbility(new StationLevelAbility(7) + .withLevelAbility(FlyingAbility.getInstance()) + .withPT(7, 7)); + } + + private PinnacleKillShip(final PinnacleKillShip card) { + super(card); + } + + @Override + public PinnacleKillShip copy() { + return new PinnacleKillShip(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PinnacleStarcage.java b/Mage.Sets/src/mage/cards/p/PinnacleStarcage.java new file mode 100644 index 00000000000..32f9326474f --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PinnacleStarcage.java @@ -0,0 +1,140 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.delayed.OnLeaveReturnExiledAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.permanent.token.RobotToken; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PinnacleStarcage extends CardImpl { + + public PinnacleStarcage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}{W}"); + + // When this artifact enters, exile all artifacts and creatures with mana value 2 or less until this artifact leaves the battlefield. + this.addAbility(new EntersBattlefieldTriggeredAbility(new PinnacleStarcageExileEffect())); + + // {6}{W}{W}: Put each card exiled with this artifact into its owner's graveyard, then create a 2/2 colorless Robot artifact token for each card put into a graveyard this way. Sacrifice this artifact. + Ability ability = new SimpleActivatedAbility(new PinnacleStarcageTokenEffect(), new ManaCostsImpl<>("{6}{W}{W}")); + ability.addEffect(new SacrificeSourceEffect()); + this.addAbility(ability); + } + + private PinnacleStarcage(final PinnacleStarcage card) { + super(card); + } + + @Override + public PinnacleStarcage copy() { + return new PinnacleStarcage(this); + } +} + +class PinnacleStarcageExileEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate() + )); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 3)); + } + + PinnacleStarcageExileEffect() { + super(Outcome.Benefit); + staticText = "exile all artifacts and creatures with mana value 2 or less until {this} leaves the battlefield"; + } + + private PinnacleStarcageExileEffect(final PinnacleStarcageExileEffect effect) { + super(effect); + } + + @Override + public PinnacleStarcageExileEffect copy() { + return new PinnacleStarcageExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || source.getSourcePermanentIfItStillExists(game) == null) { + return false; + } + Cards cards = new CardsImpl(game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)); + if (cards.isEmpty()) { + return false; + } + player.moveCardsToExile( + cards.getCards(game), source, game, true, + CardUtil.getExileZoneId(game, source), + CardUtil.getSourceName(game, source) + ); + game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source); + return true; + } +} + +class PinnacleStarcageTokenEffect extends OneShotEffect { + + PinnacleStarcageTokenEffect() { + super(Outcome.Benefit); + staticText = "put each card exiled with this artifact into its owner's graveyard, " + + "then create a 2/2 colorless Robot artifact token for each card put into a graveyard this way"; + } + + private PinnacleStarcageTokenEffect(final PinnacleStarcageTokenEffect effect) { + super(effect); + } + + @Override + public PinnacleStarcageTokenEffect copy() { + return new PinnacleStarcageTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(); + Optional.ofNullable(CardUtil.getExileZoneId(game, source)) + .map(game.getExile()::getExileZone) + .map(e -> e.getCards(game)) + .ifPresent(cards::addAllCards); + if (cards.isEmpty()) { + return false; + } + player.moveCards(cards, Zone.GRAVEYARD, source, game); + cards.retainZone(Zone.GRAVEYARD, game); + if (cards.size() > 0) { + new RobotToken().putOntoBattlefield(cards.size(), game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PiousKitsune.java b/Mage.Sets/src/mage/cards/p/PiousKitsune.java index a7b3bab6af2..9f3bf795872 100644 --- a/Mage.Sets/src/mage/cards/p/PiousKitsune.java +++ b/Mage.Sets/src/mage/cards/p/PiousKitsune.java @@ -1,22 +1,20 @@ package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; 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.SubType; import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.NamePredicate; @@ -24,6 +22,8 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** * * @author LevelX2 @@ -66,7 +66,7 @@ class PiousKitsuneEffect extends OneShotEffect { public PiousKitsuneEffect() { super(Outcome.Benefit); - this.staticText = "put a devotion counter on Pious Kitsune. Then if a creature named Eight-and-a-Half-Tails is on the battlefield, you gain 1 life for each devotion counter on Pious Kitsune"; + this.staticText = "put a devotion counter on {this}. Then if a creature named Eight-and-a-Half-Tails is on the battlefield, you gain 1 life for each devotion counter on {this}"; } private PiousKitsuneEffect(final PiousKitsuneEffect effect) { diff --git a/Mage.Sets/src/mage/cards/p/PippinsBravery.java b/Mage.Sets/src/mage/cards/p/PippinsBravery.java index e2354a84cb8..d3b61ba3178 100644 --- a/Mage.Sets/src/mage/cards/p/PippinsBravery.java +++ b/Mage.Sets/src/mage/cards/p/PippinsBravery.java @@ -25,7 +25,7 @@ public final class PippinsBravery extends CardImpl { // You may sacrifice a Food. If you do, target creature gets +4/+4 until end of turn. Otherwise, that creature gets +2/+2 until end of turn. this.getSpellAbility().addEffect(new DoIfCostPaid( new BoostTargetEffect(4, 4), - new BoostTargetEffect(2, 2), + new BoostTargetEffect(2, 2).withTargetDescription("that creature"), new SacrificeTargetCost(filter) )); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/p/PistusStrike.java b/Mage.Sets/src/mage/cards/p/PistusStrike.java index e446b0b2fc8..146577553f5 100644 --- a/Mage.Sets/src/mage/cards/p/PistusStrike.java +++ b/Mage.Sets/src/mage/cards/p/PistusStrike.java @@ -1,42 +1,33 @@ package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.counters.CounterType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author Viserion */ public final class PistusStrike extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public PistusStrike(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); this.getSpellAbility().addEffect(new DestroyTargetEffect()); this.getSpellAbility().addEffect(new PoisonControllerTargetCreatureEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private PistusStrike(final PistusStrike card) { diff --git a/Mage.Sets/src/mage/cards/p/PlaneswalkersFury.java b/Mage.Sets/src/mage/cards/p/PlaneswalkersFury.java index dca3c1b02c5..d10ae4380ec 100644 --- a/Mage.Sets/src/mage/cards/p/PlaneswalkersFury.java +++ b/Mage.Sets/src/mage/cards/p/PlaneswalkersFury.java @@ -42,7 +42,7 @@ class PlaneswalkersFuryEffect extends OneShotEffect { PlaneswalkersFuryEffect() { super(Outcome.Damage); - staticText = "Target opponent reveals a card at random from their hand. Planeswalker's Fury deals damage equal to that card's mana value to that player"; + staticText = "Target opponent reveals a card at random from their hand. {this} deals damage equal to that card's mana value to that player"; } private PlaneswalkersFuryEffect(final PlaneswalkersFuryEffect effect) { diff --git a/Mage.Sets/src/mage/cards/p/PlanetaryAnnihilation.java b/Mage.Sets/src/mage/cards/p/PlanetaryAnnihilation.java new file mode 100644 index 00000000000..20b327efd38 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PlanetaryAnnihilation.java @@ -0,0 +1,92 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class PlanetaryAnnihilation extends CardImpl { + + public PlanetaryAnnihilation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{R}"); + + // Each player chooses six lands they control, then sacrifices the rest. Planetary Annihilation deals 6 damage to each creature. + this.getSpellAbility().addEffect(new PlanetaryAnnihilationEffect()); + this.getSpellAbility().addEffect(new DamageAllEffect(6, StaticFilters.FILTER_PERMANENT_CREATURE)); + } + + private PlanetaryAnnihilation(final PlanetaryAnnihilation card) { + super(card); + } + + @Override + public PlanetaryAnnihilation copy() { + return new PlanetaryAnnihilation(this); + } +} + +class PlanetaryAnnihilationEffect extends OneShotEffect { + + PlanetaryAnnihilationEffect() { + super(Outcome.Benefit); + staticText = "each player chooses six lands they control, then sacrifices the rest"; + } + + private PlanetaryAnnihilationEffect(final PlanetaryAnnihilationEffect effect) { + super(effect); + } + + @Override + public PlanetaryAnnihilationEffect copy() { + return new PlanetaryAnnihilationEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set toSave = new HashSet<>(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null || game.getBattlefield().count( + StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS, playerId, source, game + ) <= 6) { + continue; + } + TargetPermanent target = new TargetPermanent( + 6, 6, StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS, true + ); + target.withChooseHint("you will sacrifice the rest"); + player.choose(outcome, target, source, game); + toSave.addAll(target + .getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet())); + } + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_LAND, source.getControllerId(), source, game + )) { + if (!toSave.contains(permanent)) { + permanent.sacrifice(source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PlasmaBolt.java b/Mage.Sets/src/mage/cards/p/PlasmaBolt.java new file mode 100644 index 00000000000..b6aa83fa397 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PlasmaBolt.java @@ -0,0 +1,43 @@ +package mage.cards.p; + +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.target.common.TargetAnyTarget; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PlasmaBolt extends CardImpl { + + public PlasmaBolt(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}"); + + // Plasma Bolt deals 2 damage to any target. + // Void -- Plasma Bolt deals 3 damage instead if a nonland permanent left the battlefield this turn or a spell was warped this turn. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new DamageTargetEffect(3), new DamageTargetEffect(2), VoidCondition.instance, + "{this} deals 2 damage to any target.
" + AbilityWord.VOID.formatWord() + + "{this} deals 3 damage instead if " + VoidCondition.instance + )); + this.getSpellAbility().addTarget(new TargetAnyTarget()); + this.getSpellAbility().addHint(VoidCondition.getHint()); + this.getSpellAbility().addWatcher(new VoidWatcher()); + } + + private PlasmaBolt(final PlasmaBolt card) { + super(card); + } + + @Override + public PlasmaBolt copy() { + return new PlasmaBolt(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/Plummet.java b/Mage.Sets/src/mage/cards/p/Plummet.java index 559acade3ba..a3acce9ba19 100644 --- a/Mage.Sets/src/mage/cards/p/Plummet.java +++ b/Mage.Sets/src/mage/cards/p/Plummet.java @@ -3,34 +3,25 @@ package mage.cards.p; -import java.util.UUID; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public final class Plummet extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Plummet(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private Plummet(final Plummet card) { diff --git a/Mage.Sets/src/mage/cards/p/PollenShieldHare.java b/Mage.Sets/src/mage/cards/p/PollenShieldHare.java index ca5d47e011e..93c887fcaba 100644 --- a/Mage.Sets/src/mage/cards/p/PollenShieldHare.java +++ b/Mage.Sets/src/mage/cards/p/PollenShieldHare.java @@ -43,7 +43,7 @@ public final class PollenShieldHare extends AdventureCard { .setText("target creature you control gains vigilance") ); this.getSpellCard().getSpellAbility().addEffect( - new BoostTargetEffect(CreaturesYouControlCount.instance, CreaturesYouControlCount.instance) + new BoostTargetEffect(CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL) .setText("and gets +X/+X until end of turn, where X is the number of creatures you control") ); this.getSpellCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); @@ -60,4 +60,4 @@ public final class PollenShieldHare extends AdventureCard { public PollenShieldHare copy() { return new PollenShieldHare(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/p/PossibilityTechnician.java b/Mage.Sets/src/mage/cards/p/PossibilityTechnician.java new file mode 100644 index 00000000000..f7fe54a376f --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PossibilityTechnician.java @@ -0,0 +1,93 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PossibilityTechnician extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.KAVU, "Kavu"); + + public PossibilityTechnician(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.KAVU); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever this creature or another Kavu you control enters, exile the top card of your library. For as long as that card remains exiled, you may play it if you control a Kavu. + this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility( + new PossibilityTechnicianEffect(), filter, false, true + )); + + // Warp {1}{R} + this.addAbility(new WarpAbility(this, "{1}{R}")); + } + + private PossibilityTechnician(final PossibilityTechnician card) { + super(card); + } + + @Override + public PossibilityTechnician copy() { + return new PossibilityTechnician(this); + } +} + +class PossibilityTechnicianEffect extends OneShotEffect { + + private static final Condition condition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.KAVU)); + + PossibilityTechnicianEffect() { + super(Outcome.Benefit); + staticText = "exile the top card of your library. " + + "For as long as that card remains exiled, you may play it if you control a Kavu"; + } + + private PossibilityTechnicianEffect(final PossibilityTechnicianEffect effect) { + super(effect); + } + + @Override + public PossibilityTechnicianEffect copy() { + return new PossibilityTechnicianEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = player.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + CardUtil.makeCardPlayable( + game, source, card, false, Duration.Custom, + false, source.getControllerId(), condition + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java b/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java index 55b363389a0..ff13f000dd5 100644 --- a/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java +++ b/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java @@ -28,17 +28,17 @@ public final class PowerOfPersuasion extends CardImpl { this.getSpellAbility().addTarget(new TargetOpponentsCreaturePermanent()); // 1-9 | Return it to its owner's hand. - effect.addTableEntry(1, 9, new ReturnToHandTargetEffect().setText("return it to its owner's hand")); + effect.addTableEntry(1, 9, new ReturnToHandTargetEffect().withTargetDescription("it")); // 10-19 | Its owner puts it on the top of bottom of their library. effect.addTableEntry(10, 19, new PutOnTopOrBottomLibraryTargetEffect(false).setText( - "its owner puts it on the top or bottom of their library" + "its owner puts it on their choice of the top or bottom of their library" )); // 20 | Gain control of it until the end of your next turn. effect.addTableEntry(20, 20, new GainControlTargetEffect( Duration.UntilEndOfYourNextTurn, true - ).setText("gain control of it until the end of your next turn")); + ).withTargetDescription("it")); } private PowerOfPersuasion(final PowerOfPersuasion card) { diff --git a/Mage.Sets/src/mage/cards/p/PredatorFlagship.java b/Mage.Sets/src/mage/cards/p/PredatorFlagship.java index e7a586da48a..97dc2e2cd83 100644 --- a/Mage.Sets/src/mage/cards/p/PredatorFlagship.java +++ b/Mage.Sets/src/mage/cards/p/PredatorFlagship.java @@ -1,7 +1,6 @@ package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -14,26 +13,19 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SuperType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class PredatorFlagship extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public PredatorFlagship(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{5}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); this.supertype.add(SuperType.LEGENDARY); // {2}: Target creature gains flying until end of turn. @@ -44,8 +36,8 @@ public final class PredatorFlagship extends CardImpl { // {5}, {T}: Destroy target creature with flying. ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new ManaCostsImpl<>("{5}")); ability.addCost(new TapSourceCost()); - ability.addTarget(new TargetPermanent(filter)); - this.addAbility(ability); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); + this.addAbility(ability); } private PredatorFlagship(final PredatorFlagship card) { diff --git a/Mage.Sets/src/mage/cards/p/PrimalAdversary.java b/Mage.Sets/src/mage/cards/p/PrimalAdversary.java index 659ecf61e14..714342e2f94 100644 --- a/Mage.Sets/src/mage/cards/p/PrimalAdversary.java +++ b/Mage.Sets/src/mage/cards/p/PrimalAdversary.java @@ -1,6 +1,5 @@ package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -11,19 +10,21 @@ import mage.abilities.effects.common.DoIfAnyNumberCostPaid; import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.HasteAbility; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.SubType; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.token.TokenImpl; import mage.target.TargetPermanent; +import java.util.UUID; + /** * * @author weirddan455 @@ -62,7 +63,7 @@ class PrimalAdversaryEffect extends OneShotEffect { PrimalAdversaryEffect() { super(Outcome.Benefit); - staticText = "put that many +1/+1 counters on Primal Adversary, " + + staticText = "put that many +1/+1 counters on {this}, " + "then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands"; } diff --git a/Mage.Sets/src/mage/cards/p/PrimalCommand.java b/Mage.Sets/src/mage/cards/p/PrimalCommand.java index 013315d94c2..5ec4c8c3620 100644 --- a/Mage.Sets/src/mage/cards/p/PrimalCommand.java +++ b/Mage.Sets/src/mage/cards/p/PrimalCommand.java @@ -1,62 +1,52 @@ package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeTargetEffect; import mage.abilities.effects.common.PutOnLibraryTargetEffect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; -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.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.predicate.Predicates; import mage.game.Game; import mage.players.Player; -import mage.target.Target; import mage.target.TargetPermanent; import mage.target.TargetPlayer; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class PrimalCommand extends CardImpl { - private static final FilterPermanent filterNonCreature = new FilterPermanent("noncreature permanent"); - - static { - filterNonCreature.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public PrimalCommand(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}{G}"); // Choose two - this.getSpellAbility().getModes().setMinModes(2); this.getSpellAbility().getModes().setMaxModes(2); + // Target player gains 7 life; this.getSpellAbility().addEffect(new GainLifeTargetEffect(7)); this.getSpellAbility().addTarget(new TargetPlayer()); - // or put target noncreature permanent on top of its owner's library; - Mode mode = new Mode(new PutOnLibraryTargetEffect(true)); - Target target = new TargetPermanent(filterNonCreature); - mode.addTarget(target); - this.getSpellAbility().getModes().addMode(mode); - // or target player shuffles their graveyard into their library; - mode = new Mode(new PrimalCommandShuffleGraveyardEffect()); - mode.addTarget(new TargetPlayer()); - this.getSpellAbility().getModes().addMode(mode); - // or search your library for a creature card, reveal it, put it into your hand, then shuffle your library. - mode = new Mode(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_CREATURE), true)); - this.getSpellAbility().getModes().addMode(mode); + // or put target noncreature permanent on top of its owner's library; + this.getSpellAbility().getModes().addMode(new Mode(new PutOnLibraryTargetEffect(true)) + .addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE))); + + // or target player shuffles their graveyard into their library; + this.getSpellAbility().getModes().addMode(new Mode(new PrimalCommandShuffleGraveyardEffect()) + .addTarget(new TargetPlayer())); + + // or search your library for a creature card, reveal it, put it into your hand, then shuffle your library. + this.getSpellAbility().getModes().addMode(new Mode(new SearchLibraryPutInHandEffect( + new TargetCardInLibrary(StaticFilters.FILTER_CARD_CREATURE), true + ))); } private PrimalCommand(final PrimalCommand card) { @@ -87,14 +77,8 @@ class PrimalCommandShuffleGraveyardEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getFirstTarget()); - if (player != null) { - for (Card card : player.getGraveyard().getCards(game)) { - player.moveCardToLibraryWithInfo(card, source, game, Zone.GRAVEYARD, true, true); - } - player.shuffleLibrary(source, game); - return true; - } - return false; + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + player.shuffleCardsToLibrary(player.getGraveyard(), game, source); + return true; } } diff --git a/Mage.Sets/src/mage/cards/p/Primalcrux.java b/Mage.Sets/src/mage/cards/p/Primalcrux.java index 0b5fcc35d53..fb209126e93 100644 --- a/Mage.Sets/src/mage/cards/p/Primalcrux.java +++ b/Mage.Sets/src/mage/cards/p/Primalcrux.java @@ -32,7 +32,7 @@ public final class Primalcrux extends CardImpl { // Chroma - Primalcrux's power and toughness are each equal to the number of green mana symbols in the mana costs of permanents you control. DynamicValue xValue = new ChromaCount(ManaType.GREEN); Effect effect = new SetBasePowerToughnessSourceEffect(xValue); - effect.setText("Chroma — Primalcrux's power and toughness are each equal to the number of green mana symbols in the mana costs of permanents you control."); + effect.setText("Chroma — {this}'s power and toughness are each equal to the number of green mana symbols in the mana costs of permanents you control."); this.addAbility(new SimpleStaticAbility(Zone.ALL, effect) .addHint(new ValueHint("Green mana symbols in your permanents", xValue)) ); diff --git a/Mage.Sets/src/mage/cards/p/PrimevalSpawn.java b/Mage.Sets/src/mage/cards/p/PrimevalSpawn.java index 5fead7a8e80..15c1a2a0146 100644 --- a/Mage.Sets/src/mage/cards/p/PrimevalSpawn.java +++ b/Mage.Sets/src/mage/cards/p/PrimevalSpawn.java @@ -132,6 +132,8 @@ class PrimevalSpawnSpellEffect extends OneShotEffect { } Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 10)); player.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); + cards.retainZone(Zone.EXILED, game); CardUtil.castMultipleWithAttributeForFree( player, source, game, cards, StaticFilters.FILTER_CARD, Integer.MAX_VALUE, new PrimevalSpawnTracker() diff --git a/Mage.Sets/src/mage/cards/p/ProfessorHojo.java b/Mage.Sets/src/mage/cards/p/ProfessorHojo.java index 92eef1471b2..3a199ba66f9 100644 --- a/Mage.Sets/src/mage/cards/p/ProfessorHojo.java +++ b/Mage.Sets/src/mage/cards/p/ProfessorHojo.java @@ -101,12 +101,19 @@ class ProfessorHojoEffect extends CostModificationEffectImpl { @Override public boolean applies(Ability abilityToModify, Ability source, Game game) { + + Set targets; + if (game.inCheckPlayableState()) { + targets = CardUtil.getAllPossibleTargets(abilityToModify, game); + } else { + targets = CardUtil.getAllSelectedTargets(abilityToModify, game); + } + return game.isActivePlayer(source.getControllerId()) && abilityToModify.isControlledBy(source.getControllerId()) + && abilityToModify.isActivatedAbility() && !ProfessorHojoWatcher.checkPlayer(game, source) - && CardUtil - .getAllSelectedTargets(abilityToModify, game) - .stream() + && targets.stream() .map(game::getPermanent) .filter(Objects::nonNull) .filter(permanent -> permanent.isCreature(game)) diff --git a/Mage.Sets/src/mage/cards/p/ProtectTheNegotiators.java b/Mage.Sets/src/mage/cards/p/ProtectTheNegotiators.java index 72a0ca87009..52bda7a64d2 100644 --- a/Mage.Sets/src/mage/cards/p/ProtectTheNegotiators.java +++ b/Mage.Sets/src/mage/cards/p/ProtectTheNegotiators.java @@ -34,7 +34,7 @@ public final class ProtectTheNegotiators extends CardImpl { )); // Counter target spell unless its controller pays {1} for each creature you control. - this.getSpellAbility().addEffect(new CounterUnlessPaysEffect(CreaturesYouControlCount.instance) + this.getSpellAbility().addEffect(new CounterUnlessPaysEffect(CreaturesYouControlCount.PLURAL) .setText("
Counter target spell unless its controller pays {1} for each creature you control.")); this.getSpellAbility().addTarget(new TargetSpell()); } diff --git a/Mage.Sets/src/mage/cards/p/PuPuUFO.java b/Mage.Sets/src/mage/cards/p/PuPuUFO.java index 9e1fac28305..6a272783a8d 100644 --- a/Mage.Sets/src/mage/cards/p/PuPuUFO.java +++ b/Mage.Sets/src/mage/cards/p/PuPuUFO.java @@ -1,14 +1,14 @@ package mage.cards.p; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.FlyingAbility; @@ -16,9 +16,12 @@ 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.common.FilterControlledPermanent; +import mage.game.Game; import java.util.UUID; @@ -27,10 +30,6 @@ import java.util.UUID; */ public final class PuPuUFO extends CardImpl { - private static final DynamicValue xValue - = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.TOWN)); - private static final Hint hint = new ValueHint("Towns you control", xValue); - public PuPuUFO(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); @@ -48,12 +47,7 @@ public final class PuPuUFO extends CardImpl { )); // {3}: Until end of turn, this creature's base power becomes equal to the number of Towns you control. - this.addAbility(new SimpleActivatedAbility( - new SetBasePowerToughnessSourceEffect( - xValue, StaticValue.get(0), Duration.EndOfTurn, "until end of turn, " + - "this creature's base power becomes equal to the number of Towns you control" - ), new GenericManaCost(3) - ).addHint(hint)); + this.addAbility(new SimpleActivatedAbility(new PuPuUFOEffect(), new GenericManaCost(3)).addHint(PuPuUFOEffect.getHint())); } private PuPuUFO(final PuPuUFO card) { @@ -65,3 +59,38 @@ public final class PuPuUFO extends CardImpl { return new PuPuUFO(this); } } + +class PuPuUFOEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.TOWN); + private static final Hint hint = new ValueHint("Towns you control", new PermanentsOnBattlefieldCount(filter)); + + public static Hint getHint() { + return hint; + } + + PuPuUFOEffect() { + super(Outcome.Benefit); + staticText = "until end of turn, {this}'s base power becomes equal to the number of Towns you control"; + } + + private PuPuUFOEffect(final PuPuUFOEffect effect) { + super(effect); + } + + @Override + public PuPuUFOEffect copy() { + return new PuPuUFOEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (source.getSourcePermanentIfItStillExists(game) == null) { + return false; + } + game.addEffect(new SetBasePowerSourceEffect( + game.getBattlefield().count(filter, source.getControllerId(), source, game), Duration.EndOfTurn + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PullThroughTheWeft.java b/Mage.Sets/src/mage/cards/p/PullThroughTheWeft.java new file mode 100644 index 00000000000..6f605e023bf --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PullThroughTheWeft.java @@ -0,0 +1,50 @@ +package mage.cards.p; + +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterNonlandCard; +import mage.filter.predicate.mageobject.PermanentPredicate; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PullThroughTheWeft extends CardImpl { + + private static final FilterCard filter = new FilterNonlandCard("nonland permanent cards from your graveyard"); + + static { + filter.add(PermanentPredicate.instance); + } + + public PullThroughTheWeft(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}{G}"); + + // Return up to two target nonland permanent cards from your graveyard to your hand, then return up to two target land cards from your graveyard to the battlefield tapped. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 2, filter)); + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect(true) + .setTargetPointer(new SecondTargetPointer()) + .setText(", then return up to two target land cards from your graveyard to the battlefield tapped")); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard( + 0, 2, StaticFilters.FILTER_CARD_LAND_FROM_YOUR_GRAVEYARD + )); + } + + private PullThroughTheWeft(final PullThroughTheWeft card) { + super(card); + } + + @Override + public PullThroughTheWeft copy() { + return new PullThroughTheWeft(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java b/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java new file mode 100644 index 00000000000..6c282803f31 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java @@ -0,0 +1,93 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.common.FilterBySubtypeCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PulsarSquadronAce extends CardImpl { + + public PulsarSquadronAce(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PILOT); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // When this creature enters, look at the top five cards of your library. You may reveal a Spacecraft card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. If you didn't put a card into your hand this way, put a +1/+1 counter on this creature. + this.addAbility(new EntersBattlefieldTriggeredAbility(new PulsarSquadronAceEffect())); + } + + private PulsarSquadronAce(final PulsarSquadronAce card) { + super(card); + } + + @Override + public PulsarSquadronAce copy() { + return new PulsarSquadronAce(this); + } +} + +class PulsarSquadronAceEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterBySubtypeCard(SubType.SPACECRAFT); + + PulsarSquadronAceEffect() { + super(Outcome.Benefit); + staticText = "look at the top five cards of your library. You may reveal a Spacecraft card from among them " + + "and put it into your hand. Put the rest on the bottom of your library in a random order. " + + "If you didn't put a card into your hand this way, put a +1/+1 counter on {this}"; + } + + private PulsarSquadronAceEffect(final PulsarSquadronAceEffect effect) { + super(effect); + } + + @Override + public PulsarSquadronAceEffect copy() { + return new PulsarSquadronAceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 5)); + TargetCard target = new TargetCardInLibrary(0, 1, filter); + player.choose(outcome, cards, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + player.putCardsOnBottomOfLibrary(cards, game, source, false); + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .filter(permanent -> permanent.addCounters(CounterType.P1P1.createInstance(), source, game)) + .isPresent(); + return true; + } + player.revealCards(source, new CardsImpl(card), game); + player.moveCards(card, Zone.HAND, source, game); + cards.retainZone(Zone.LIBRARY, game); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/q/QuantumRiddler.java b/Mage.Sets/src/mage/cards/q/QuantumRiddler.java new file mode 100644 index 00000000000..c97549fb1d5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/q/QuantumRiddler.java @@ -0,0 +1,108 @@ +package mage.cards.q; + +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.DrawCardSourceControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WarpAbility; +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.GameEvent; +import mage.players.Player; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class QuantumRiddler extends CardImpl { + + public QuantumRiddler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); + + this.subtype.add(SubType.SPHINX); + this.power = new MageInt(4); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1))); + + // As long as you have one or fewer cards in hand, if you would draw one or more cards, you draw that many cards plus one instead. + this.addAbility(new SimpleStaticAbility(new QuantumRiddlerReplacementEffect())); + + // Warp {1}{U} + this.addAbility(new WarpAbility(this, "{1}{U}")); + } + + private QuantumRiddler(final QuantumRiddler card) { + super(card); + } + + @Override + public QuantumRiddler copy() { + return new QuantumRiddler(this); + } +} + +class QuantumRiddlerReplacementEffect extends ReplacementEffectImpl { + + QuantumRiddlerReplacementEffect() { + super(Duration.WhileOnBattlefield, Outcome.Neutral); + staticText = "as long as you have one or fewer cards in hand, " + + "if you would draw one or more cards, you draw that many cards plus one instead"; + } + + private QuantumRiddlerReplacementEffect(final QuantumRiddlerReplacementEffect effect) { + super(effect); + } + + @Override + public QuantumRiddlerReplacementEffect copy() { + return new QuantumRiddlerReplacementEffect(this); + } + + // While this card technically replaces a multi-draw event, it's effectively the same as replacing a single draw + // as the first replacement will immediately negate the future replacements from occurring + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DRAW_CARD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!source.isControlledBy(event.getPlayerId())) { + return false; + } + return source.isControlledBy(event.getPlayerId()) + && Optional + .ofNullable(event) + .map(GameEvent::getPlayerId) + .map(game::getPlayer) + .map(Player::getHand) + .map(Set::size) + .filter(x -> x <= 1) + .isPresent(); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Optional.ofNullable(event) + .map(GameEvent::getPlayerId) + .map(game::getPlayer) + .ifPresent(player -> player.drawCards(2, source, game, event)); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RadiantStrike.java b/Mage.Sets/src/mage/cards/r/RadiantStrike.java new file mode 100644 index 00000000000..0ae04145108 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RadiantStrike.java @@ -0,0 +1,49 @@ +package mage.cards.r; + +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RadiantStrike extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact or tapped creature"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + Predicates.and( + TappedPredicate.TAPPED, + CardType.CREATURE.getPredicate() + ) + )); + } + + public RadiantStrike(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{W}"); + + // Destroy target artifact or tapped creature. You gain 3 life. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addEffect(new GainLifeEffect(3)); + } + + private RadiantStrike(final RadiantStrike card) { + super(card); + } + + @Override + public RadiantStrike copy() { + return new RadiantStrike(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RadiantSummit.java b/Mage.Sets/src/mage/cards/r/RadiantSummit.java new file mode 100644 index 00000000000..17f44f56d91 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RadiantSummit.java @@ -0,0 +1,53 @@ +package mage.cards.r; + +import mage.abilities.common.EntersBattlefieldTappedUnlessAbility; +import mage.abilities.condition.common.YouControlPermanentCondition; +import mage.abilities.mana.RedManaAbility; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RadiantSummit extends CardImpl { + + private static final FilterLandPermanent filter = new FilterLandPermanent("basic lands"); + + static { + filter.add(SuperType.BASIC.getPredicate()); + } + + private static final YouControlPermanentCondition condition = + new YouControlPermanentCondition(filter, ComparisonType.OR_GREATER, 2); + + public RadiantSummit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.MOUNTAIN); + this.subtype.add(SubType.PLAINS); + + // ({T}: Add {R} or {W}.) + this.addAbility(new RedManaAbility()); + this.addAbility(new WhiteManaAbility()); + + // This land enters tapped unless you control two or more basic lands. + this.addAbility(new EntersBattlefieldTappedUnlessAbility(condition).addHint(condition.getHint())); + } + + private RadiantSummit(final RadiantSummit card) { + super(card); + } + + @Override + public RadiantSummit copy() { + return new RadiantSummit(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java b/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java new file mode 100644 index 00000000000..74997b24a41 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java @@ -0,0 +1,73 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.YouGainedLifeCondition; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +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.token.FoodAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RagostDeftGastronaut extends CardImpl { + + private static final Condition condition = new YouGainedLifeCondition(); + + public RagostDeftGastronaut(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.LOBSTER); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + 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( + StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACTS, SubType.FOOD, null + )); + ability.addEffect(new GainAbilityAllEffect( + new FoodAbility(), Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACTS, + "and have \"{2}, {T}, Sacrifice this artifact: You gain 3 life.\"" + )); + this.addAbility(ability); + + // {1}, {T}, Sacrifice a Food: Ragost deals 3 damage to each opponent. + ability = new SimpleActivatedAbility( + new DamagePlayersEffect(3, TargetController.OPPONENT), new GenericManaCost(1) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_FOOD)); + this.addAbility(ability); + + // 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 + )); + } + + private RagostDeftGastronaut(final RagostDeftGastronaut card) { + super(card); + } + + @Override + public RagostDeftGastronaut copy() { + return new RagostDeftGastronaut(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RavagingBlaze.java b/Mage.Sets/src/mage/cards/r/RavagingBlaze.java index 40c11a6cc3f..aa0a718d3ee 100644 --- a/Mage.Sets/src/mage/cards/r/RavagingBlaze.java +++ b/Mage.Sets/src/mage/cards/r/RavagingBlaze.java @@ -1,7 +1,6 @@ package mage.cards.r; -import java.util.UUID; import mage.abilities.condition.common.SpellMasteryCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.dynamicvalue.common.GetXValue; @@ -12,6 +11,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author LevelX2 @@ -26,7 +27,7 @@ public final class RavagingBlaze extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, Ravaging Blaze also deals X damage to that creature's controller. this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new DamageTargetControllerEffect(GetXValue.instance), - SpellMasteryCondition.instance, "
Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, Ravaging Blaze also deals X damage to that creature's controller.")); + SpellMasteryCondition.instance, "
Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, {this} also deals X damage to that creature's controller.")); } private RavagingBlaze(final RavagingBlaze card) { diff --git a/Mage.Sets/src/mage/cards/r/RaybladeTrooper.java b/Mage.Sets/src/mage/cards/r/RaybladeTrooper.java new file mode 100644 index 00000000000..72299510ebc --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RaybladeTrooper.java @@ -0,0 +1,65 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.HumanSoldierToken; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RaybladeTrooper extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("nontoken creature you control with a +1/+1 counter on it"); + + static { + filter.add(TokenPredicate.FALSE); + filter.add(CounterType.P1P1.getPredicate()); + } + + public RaybladeTrooper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When this creature enters, put a +1/+1 counter on target creature you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + + // Whenever a nontoken creature you control with a +1/+1 counter on it dies, create a 1/1 white Human Soldier creature token. + this.addAbility(new DiesCreatureTriggeredAbility( + new CreateTokenEffect(new HumanSoldierToken()), false, filter + )); + + // Warp {1}{W} + this.addAbility(new WarpAbility(this, "{1}{W}")); + } + + private RaybladeTrooper(final RaybladeTrooper card) { + super(card); + } + + @Override + public RaybladeTrooper copy() { + return new RaybladeTrooper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RecklessAirStrike.java b/Mage.Sets/src/mage/cards/r/RecklessAirStrike.java index 911cd944f23..e9e496b9f17 100644 --- a/Mage.Sets/src/mage/cards/r/RecklessAirStrike.java +++ b/Mage.Sets/src/mage/cards/r/RecklessAirStrike.java @@ -3,13 +3,10 @@ package mage.cards.r; import mage.abilities.Mode; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetArtifactPermanent; @@ -20,19 +17,13 @@ import java.util.UUID; */ public final class RecklessAirStrike extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public RecklessAirStrike(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}"); // Choose one — // • Reckless Air Strike deals 3 damage to target creature with flying. this.getSpellAbility().addEffect(new DamageTargetEffect(3)); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); // • Destroy target artifact. Mode mode = new Mode(new DestroyTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/r/RedTigerMechan.java b/Mage.Sets/src/mage/cards/r/RedTigerMechan.java new file mode 100644 index 00000000000..f78b3b44594 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RedTigerMechan.java @@ -0,0 +1,41 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.WarpAbility; +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 RedTigerMechan extends CardImpl { + + public RedTigerMechan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.CAT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Warp {1}{R} + this.addAbility(new WarpAbility(this, "{1}{R}")); + } + + private RedTigerMechan(final RedTigerMechan card) { + super(card); + } + + @Override + public RedTigerMechan copy() { + return new RedTigerMechan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RefuseCooperate.java b/Mage.Sets/src/mage/cards/r/RefuseCooperate.java index b51df9a6eb7..23bac480f0e 100644 --- a/Mage.Sets/src/mage/cards/r/RefuseCooperate.java +++ b/Mage.Sets/src/mage/cards/r/RefuseCooperate.java @@ -52,7 +52,7 @@ class RefuseEffect extends OneShotEffect { RefuseEffect() { super(Outcome.Damage); - staticText = "Refuse deals damage to target spell's controller equal to that spell's mana value"; + staticText = "{this} deals damage to target spell's controller equal to that spell's mana value"; } private RefuseEffect(final RefuseEffect effect) { diff --git a/Mage.Sets/src/mage/cards/r/RemnantElemental.java b/Mage.Sets/src/mage/cards/r/RemnantElemental.java new file mode 100644 index 00000000000..b33574e0ada --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RemnantElemental.java @@ -0,0 +1,42 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.ReachAbility; +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 RemnantElemental extends CardImpl { + + public RemnantElemental(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Landfall -- Whenever a land you control enters, this creature gets +2/+0 until end of turn. + this.addAbility(new LandfallAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn))); + } + + private RemnantElemental(final RemnantElemental card) { + super(card); + } + + @Override + public RemnantElemental copy() { + return new RemnantElemental(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RequiemMonolith.java b/Mage.Sets/src/mage/cards/r/RequiemMonolith.java new file mode 100644 index 00000000000..25fed4a505e --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RequiemMonolith.java @@ -0,0 +1,86 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +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 RequiemMonolith extends CardImpl { + + public RequiemMonolith(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{B}"); + + // {T}: Until end of turn, target creature gains "Whenever this creature is dealt damage, you draw that many cards and lose that much life." That creature's controller may have this artifact deal 1 damage to it. Activate only as a sorcery. + Ability ability = new DealtDamageToSourceTriggeredAbility( + new DrawCardSourceControllerEffect(SavedDamageValue.MANY, true), false + ); + ability.addEffect(new LoseLifeSourceControllerEffect(SavedDamageValue.MUCH).setText("and lose that much life")); + ability = new ActivateAsSorceryActivatedAbility(new GainAbilityTargetEffect(ability) + .setText("Until end of turn, target creature gains \"Whenever this creature " + + "is dealt damage, you draw that many cards and lose that much life.\""), new TapSourceCost()); + ability.addEffect(new RequiemMonolithEffect()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private RequiemMonolith(final RequiemMonolith card) { + super(card); + } + + @Override + public RequiemMonolith copy() { + return new RequiemMonolith(this); + } +} + +class RequiemMonolithEffect extends OneShotEffect { + + RequiemMonolithEffect() { + super(Outcome.Benefit); + staticText = "That creature's controller may have {this} deal 1 damage to it"; + } + + private RequiemMonolithEffect(final RequiemMonolithEffect effect) { + super(effect); + } + + @Override + public RequiemMonolithEffect copy() { + return new RequiemMonolithEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + return permanent != null + && Optional + .ofNullable(permanent) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .filter(player -> player.chooseUse( + Outcome.Neutral, "Have this artifact deal 1 damage to " + + permanent.getLogName() + '?', source, game + )) + .filter(x -> permanent.damage(1, source, game) > 0) + .isPresent(); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RerouteSystems.java b/Mage.Sets/src/mage/cards/r/RerouteSystems.java new file mode 100644 index 00000000000..063df7a7a12 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RerouteSystems.java @@ -0,0 +1,49 @@ +package mage.cards.r; + +import mage.abilities.Mode; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RerouteSystems extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("tapped creature"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + public RerouteSystems(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Choose one -- + // * Target artifact or creature gains indestructible until end of turn. + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(IndestructibleAbility.getInstance())); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); + + // * Reroute Systems deals 2 damage to target tapped creature. + this.getSpellAbility().addMode(new Mode(new DamageTargetEffect(2)).addTarget(new TargetPermanent(filter))); + } + + private RerouteSystems(final RerouteSystems card) { + super(card); + } + + @Override + public RerouteSystems copy() { + return new RerouteSystems(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RescueSkiff.java b/Mage.Sets/src/mage/cards/r/RescueSkiff.java new file mode 100644 index 00000000000..9c65db0dc48 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RescueSkiff.java @@ -0,0 +1,62 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RescueSkiff extends CardImpl { + + private static final FilterCard filter = new FilterCard("creature or enchantment card from your graveyard"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + CardType.ENCHANTMENT.getPredicate() + )); + } + + public RescueSkiff(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}{W}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, return target creature or enchantment card from your graveyard to the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + // Station + this.addAbility(new StationAbility()); + + // STATION 10+ + // Flying + // 5/6 + this.addAbility(new StationLevelAbility(10) + .withLevelAbility(FlyingAbility.getInstance()) + .withPT(5, 6)); + } + + private RescueSkiff(final RescueSkiff card) { + super(card); + } + + @Override + public RescueSkiff copy() { + return new RescueSkiff(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/ResplendentMarshal.java b/Mage.Sets/src/mage/cards/r/ResplendentMarshal.java index 3421512625b..cd6f67ec705 100644 --- a/Mage.Sets/src/mage/cards/r/ResplendentMarshal.java +++ b/Mage.Sets/src/mage/cards/r/ResplendentMarshal.java @@ -1,6 +1,5 @@ package mage.cards.r; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -9,13 +8,13 @@ import mage.abilities.common.delayed.ReflexiveTriggeredAbility; import mage.abilities.costs.common.ExileFromGraveCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DoWhenCostPaid; -import mage.cards.Card; -import mage.constants.Outcome; -import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; @@ -25,6 +24,8 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** * * @author weirddan455 @@ -54,7 +55,7 @@ public final class ResplendentMarshal extends CardImpl { this.addAbility(new EntersBattlefieldOrDiesSourceTriggeredAbility( new DoWhenCostPaid( new ReflexiveTriggeredAbility(new ResplendentMarshalEffect(), false, - "put a +1/+1 counter on each creature you control other than Resplendent Marshal that shares a creature type with the exiled card"), + "put a +1/+1 counter on each creature you control other than {this} that shares a creature type with the exiled card"), new ExileFromGraveCost(new TargetCardInYourGraveyard(filter), true), "Exile another creature card from your graveyard?" ), false diff --git a/Mage.Sets/src/mage/cards/r/RevengeOfTheHunted.java b/Mage.Sets/src/mage/cards/r/RevengeOfTheHunted.java index e4e1fb8315d..1b3825155d6 100644 --- a/Mage.Sets/src/mage/cards/r/RevengeOfTheHunted.java +++ b/Mage.Sets/src/mage/cards/r/RevengeOfTheHunted.java @@ -25,7 +25,7 @@ public final class RevengeOfTheHunted extends CardImpl { // Until end of turn, target creature gets +6/+6 and gains trample, and all creatures able to block it this turn do so. this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addEffect(new BoostTargetEffect(6, 6, Duration.EndOfTurn)); - this.getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn).withTargetDescription("and")); Effect effect = new MustBeBlockedByAllTargetEffect(Duration.EndOfTurn); effect.setText("and all creatures able to block it this turn do so"); this.getSpellAbility().addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/r/RigForWar.java b/Mage.Sets/src/mage/cards/r/RigForWar.java new file mode 100644 index 00000000000..795b6fc653a --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RigForWar.java @@ -0,0 +1,41 @@ +package mage.cards.r; + +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.ReachAbility; +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 Susucr + */ +public final class RigForWar extends CardImpl { + + public RigForWar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Target creature gets +3/+0 and gains first strike and reach until end of turn. + getSpellAbility().addEffect(new BoostTargetEffect(3, 0, Duration.EndOfTurn) + .setText("target creature gets +3/+0")); + getSpellAbility().addEffect(new GainAbilityTargetEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn) + .setText("and gains first strike")); + getSpellAbility().addEffect(new GainAbilityTargetEffect(ReachAbility.getInstance(), Duration.EndOfTurn) + .setText("and first reach until end of turn")); + getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private RigForWar(final RigForWar card) { + super(card); + } + + @Override + public RigForWar copy() { + return new RigForWar(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RinoaHeartilly.java b/Mage.Sets/src/mage/cards/r/RinoaHeartilly.java index fe7bbf4609c..2c2e311a2ff 100644 --- a/Mage.Sets/src/mage/cards/r/RinoaHeartilly.java +++ b/Mage.Sets/src/mage/cards/r/RinoaHeartilly.java @@ -39,7 +39,7 @@ public final class RinoaHeartilly extends CardImpl { // Angelo Cannon -- Whenever Rinoa Heartilly attacks, another target creature you control gets +1/+1 until end of turn for each creature you control. Ability ability = new AttacksTriggeredAbility(new BoostTargetEffect( - CreaturesYouControlCount.instance, CreaturesYouControlCount.instance + CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL ).setText("another target creature you control gets +1/+1 until end of turn for each creature you control")); ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); this.addAbility(ability.withFlavorWord("Angelo Cannon").addHint(CreaturesYouControlHint.instance)); diff --git a/Mage.Sets/src/mage/cards/r/Rivalry.java b/Mage.Sets/src/mage/cards/r/Rivalry.java index a65b63d7836..901ad55322b 100644 --- a/Mage.Sets/src/mage/cards/r/Rivalry.java +++ b/Mage.Sets/src/mage/cards/r/Rivalry.java @@ -1,7 +1,6 @@ package mage.cards.r; -import java.util.UUID; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; @@ -12,9 +11,10 @@ import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author Plopman @@ -87,6 +87,6 @@ class RivalryTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "At the beginning of each player's upkeep, if that player controls more lands than each other player, Rivalry deals 2 damage to them."; + return "At the beginning of each player's upkeep, if that player controls more lands than each other player, {this} deals 2 damage to them."; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/r/RockcasterPlatoon.java b/Mage.Sets/src/mage/cards/r/RockcasterPlatoon.java index 69919e1f7e3..ec70a7ea6ec 100644 --- a/Mage.Sets/src/mage/cards/r/RockcasterPlatoon.java +++ b/Mage.Sets/src/mage/cards/r/RockcasterPlatoon.java @@ -6,13 +6,11 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.DamagePlayersEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import java.util.UUID; @@ -21,12 +19,6 @@ import java.util.UUID; */ public final class RockcasterPlatoon extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public RockcasterPlatoon(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}{W}"); this.subtype.add(SubType.RHINO); @@ -36,7 +28,7 @@ public final class RockcasterPlatoon extends CardImpl { this.toughness = new MageInt(7); // {4}{G}: Rockcaster Platoon deals 2 damage to each creature with flying and each player. - Ability ability = new SimpleActivatedAbility(new DamageAllEffect(2, filter), new ManaCostsImpl<>("{4}{G}")); + Ability ability = new SimpleActivatedAbility(new DamageAllEffect(2, StaticFilters.FILTER_CREATURE_FLYING), new ManaCostsImpl<>("{4}{G}")); ability.addEffect(new DamagePlayersEffect(2).setText("and each player")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/r/Rootgrapple.java b/Mage.Sets/src/mage/cards/r/Rootgrapple.java index cb783f0270b..48ad227104b 100644 --- a/Mage.Sets/src/mage/cards/r/Rootgrapple.java +++ b/Mage.Sets/src/mage/cards/r/Rootgrapple.java @@ -1,6 +1,6 @@ - package mage.cards.r; +import mage.abilities.condition.Condition; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.DestroyTargetEffect; @@ -9,38 +9,32 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; import mage.target.TargetPermanent; import java.util.UUID; /** - * * @author Wehk */ public final class Rootgrapple extends CardImpl { - - private static final FilterPermanent filterNoncreature = new FilterPermanent("noncreature permanent"); - private static final FilterPermanent filterTreefolk = new FilterPermanent("If you control a Treefolk,"); - static { - filterNoncreature.add(Predicates.not(CardType.CREATURE.getPredicate())); - filterTreefolk.add(SubType.TREEFOLK.getPredicate()); - } + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.TREEFOLK)); public Rootgrapple(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.KINDRED,CardType.INSTANT},"{4}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.KINDRED, CardType.INSTANT}, "{4}{G}"); this.subtype.add(SubType.TREEFOLK); // Destroy target noncreature permanent. - this.getSpellAbility().addTarget(new TargetPermanent(filterNoncreature)); this.getSpellAbility().addEffect(new DestroyTargetEffect()); - + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); + // If you control a Treefolk, draw a card. - this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(1), - new PermanentsOnTheBattlefieldCondition(filterTreefolk), - "If you control a Treefolk, draw a card")); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(1), condition, + "If you control a Treefolk, draw a card" + )); } private Rootgrapple(final Rootgrapple card) { diff --git a/Mage.Sets/src/mage/cards/r/RoughTumble.java b/Mage.Sets/src/mage/cards/r/RoughTumble.java index fd631dc43d1..50db48375ab 100644 --- a/Mage.Sets/src/mage/cards/r/RoughTumble.java +++ b/Mage.Sets/src/mage/cards/r/RoughTumble.java @@ -1,7 +1,6 @@ package mage.cards.r; -import java.util.UUID; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.keyword.FlyingAbility; @@ -9,21 +8,21 @@ import mage.cards.CardSetInfo; import mage.cards.SplitCard; import mage.constants.CardType; import mage.constants.SpellAbilityType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class RoughTumble extends SplitCard { - private static final FilterCreaturePermanent filterFlying = new FilterCreaturePermanent("creature with flying"); private static final FilterCreaturePermanent filterWithoutFlying = new FilterCreaturePermanent("creature without flying"); static { - filterFlying.add(new AbilityPredicate(FlyingAbility.class)); filterWithoutFlying.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); } @@ -33,13 +32,13 @@ public final class RoughTumble extends SplitCard { // Rough // Rough deals 2 damage to each creature without flying. Effect effect = new DamageAllEffect(2, filterWithoutFlying); - effect.setText("Rough deals 2 damage to each creature without flying"); + effect.setText("{this} deals 2 damage to each creature without flying"); getLeftHalfCard().getSpellAbility().addEffect(effect); // Tumble // Tumble deals 6 damage to each creature with flying. - effect = new DamageAllEffect(6, filterFlying); - effect.setText("Tumble deals 6 damage to each creature with flying"); + effect = new DamageAllEffect(6, StaticFilters.FILTER_CREATURE_FLYING); + effect.setText("{this} deals 6 damage to each creature with flying"); getRightHalfCard().getSpellAbility().addEffect(effect); } diff --git a/Mage.Sets/src/mage/cards/r/RovingActuator.java b/Mage.Sets/src/mage/cards/r/RovingActuator.java new file mode 100644 index 00000000000..abfd6e5e39f --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RovingActuator.java @@ -0,0 +1,57 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.effects.common.ExileTargetCardCopyAndCastEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterInstantOrSorceryCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RovingActuator extends CardImpl { + + private static final FilterCard filter + = new FilterInstantOrSorceryCard("instant or sorcery card with mana value 2 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 3)); + } + + public RovingActuator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Void - When this creature enters, if a nonland permanent left the battlefield this turn or a spell was warped this turn, exile up to one target instant or sorcery card with mana value 2 or less from your graveyard. Copy it. You may cast the copy without paying its mana cost. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetCardCopyAndCastEffect(true) + .setText("exile up to one target instant or sorcery card with mana value 2 or less from your graveyard. " + + "Copy it. You may cast the copy without paying its mana cost")).withInterveningIf(VoidCondition.instance); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter)); + this.addAbility(ability.setAbilityWord(AbilityWord.VOID).addHint(VoidCondition.getHint()), new VoidWatcher()); + } + + private RovingActuator(final RovingActuator card) { + super(card); + } + + @Override + public RovingActuator copy() { + return new RovingActuator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RoyalDecree.java b/Mage.Sets/src/mage/cards/r/RoyalDecree.java index 1c0b51e1a83..f6777e24c23 100644 --- a/Mage.Sets/src/mage/cards/r/RoyalDecree.java +++ b/Mage.Sets/src/mage/cards/r/RoyalDecree.java @@ -1,7 +1,6 @@ package mage.cards.r; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.costs.mana.ManaCostsImpl; @@ -22,6 +21,8 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author L_J @@ -94,6 +95,6 @@ class RoyalDecreeAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever a Swamp, Mountain, black permanent, or red permanent becomes tapped, Royal Decree deals 1 damage to that permanent's controller."; + return "Whenever a Swamp, Mountain, black permanent, or red permanent becomes tapped, {this} deals 1 damage to that permanent's controller."; } } diff --git a/Mage.Sets/src/mage/cards/r/RuinousRampage.java b/Mage.Sets/src/mage/cards/r/RuinousRampage.java new file mode 100644 index 00000000000..8cbb5c70c19 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RuinousRampage.java @@ -0,0 +1,47 @@ +package mage.cards.r; + +import mage.abilities.Mode; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.ExileAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.predicate.mageobject.ManaValuePredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RuinousRampage extends CardImpl { + + private static final FilterPermanent filter = new FilterArtifactPermanent("artifacts with mana value 3 or less"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); + } + + public RuinousRampage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}{R}"); + + // Choose one -- + // * Ruinous Rampage deals 3 damage to each opponent. + this.getSpellAbility().addEffect(new DamagePlayersEffect(3, TargetController.OPPONENT)); + + // * Exile all artifacts with mana value 3 or less. + this.getSpellAbility().addMode(new Mode(new ExileAllEffect(filter))); + } + + private RuinousRampage(final RuinousRampage card) { + super(card); + } + + @Override + public RuinousRampage copy() { + return new RuinousRampage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RunAfoul.java b/Mage.Sets/src/mage/cards/r/RunAfoul.java index e2d0d55f990..ef76c320114 100644 --- a/Mage.Sets/src/mage/cards/r/RunAfoul.java +++ b/Mage.Sets/src/mage/cards/r/RunAfoul.java @@ -1,32 +1,24 @@ package mage.cards.r; -import java.util.UUID; - import mage.abilities.effects.common.SacrificeEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * @author arcox */ public final class RunAfoul extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public RunAfoul(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); // Target opponent sacrifices a creature with flying. - this.getSpellAbility().addEffect(new SacrificeEffect(filter, 1, "Target opponent") + this.getSpellAbility().addEffect(new SacrificeEffect(StaticFilters.FILTER_CREATURE_FLYING, 1, "Target opponent") .setText("target opponent sacrifices a creature of their choice with flying")); this.getSpellAbility().addTarget(new TargetOpponent()); } diff --git a/Mage.Sets/src/mage/cards/r/Rupture.java b/Mage.Sets/src/mage/cards/r/Rupture.java index 3b495306b74..8e3dd87e62e 100644 --- a/Mage.Sets/src/mage/cards/r/Rupture.java +++ b/Mage.Sets/src/mage/cards/r/Rupture.java @@ -52,7 +52,7 @@ class RuptureEffect extends OneShotEffect { public RuptureEffect() { super(Outcome.Damage); - staticText = "Sacrifice a creature. Rupture deals damage equal to that creature's power to each creature without flying and each player"; + staticText = "Sacrifice a creature. {this} deals damage equal to that creature's power to each creature without flying and each player"; } private RuptureEffect(final RuptureEffect effect) { diff --git a/Mage.Sets/src/mage/cards/r/RustHarvester.java b/Mage.Sets/src/mage/cards/r/RustHarvester.java new file mode 100644 index 00000000000..9221b03adb1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RustHarvester.java @@ -0,0 +1,61 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.ExileFromGraveCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RustHarvester extends CardImpl { + + public RustHarvester(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{R}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Menace + this.addAbility(new MenaceAbility()); + + // {2}, {T}, Exile an artifact card from your graveyard: Put a +1/+1 counter on this creature, then it deals damage equal to its power to any target. + Ability ability = new SimpleActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new ExileFromGraveCost( + new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_ARTIFACT_FROM_YOUR_GRAVEYARD) + )); + ability.addEffect(new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE) + .setText(", then it deals damage equal to its power to any target")); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + } + + private RustHarvester(final RustHarvester card) { + super(card); + } + + @Override + public RustHarvester copy() { + return new RustHarvester(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RyanSinclair.java b/Mage.Sets/src/mage/cards/r/RyanSinclair.java index ee997f3e9dc..acb6109df07 100644 --- a/Mage.Sets/src/mage/cards/r/RyanSinclair.java +++ b/Mage.Sets/src/mage/cards/r/RyanSinclair.java @@ -77,7 +77,7 @@ class RyanSinclairEffect extends OneShotEffect { if (card != null) { FilterCard filter = new FilterCard(); filter.add(new ManaValuePredicate( - ComparisonType.FEWER_THAN, + ComparisonType.OR_LESS, Optional.ofNullable(source.getSourcePermanentOrLKI(game)) .map(MageObject::getPower) .map(MageInt::getValue) diff --git a/Mage.Sets/src/mage/cards/s/SagittarsVolley.java b/Mage.Sets/src/mage/cards/s/SagittarsVolley.java index 1a017f2a8ca..d04107f67b8 100644 --- a/Mage.Sets/src/mage/cards/s/SagittarsVolley.java +++ b/Mage.Sets/src/mage/cards/s/SagittarsVolley.java @@ -7,7 +7,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterOpponentsCreaturePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.target.TargetPermanent; @@ -20,13 +20,10 @@ import java.util.UUID; public final class SagittarsVolley extends CardImpl { private static final FilterPermanent filter - = new FilterCreaturePermanent("creature with flying"); - private static final FilterPermanent filter2 = new FilterOpponentsCreaturePermanent("creature with flying your opponents control"); static { filter.add(new AbilityPredicate(FlyingAbility.class)); - filter2.add(new AbilityPredicate(FlyingAbility.class)); } public SagittarsVolley(UUID ownerId, CardSetInfo setInfo) { @@ -34,8 +31,8 @@ public final class SagittarsVolley extends CardImpl { // Destroy target creature with flying. Sagittars' Volley deals 1 damage to each creature with flying your opponents control. this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); - this.getSpellAbility().addEffect(new DamageAllEffect(1, filter2)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); + this.getSpellAbility().addEffect(new DamageAllEffect(1, filter)); } private SagittarsVolley(final SagittarsVolley card) { diff --git a/Mage.Sets/src/mage/cards/s/SailIntoTheWest.java b/Mage.Sets/src/mage/cards/s/SailIntoTheWest.java index 341955abcc5..60cca26267c 100644 --- a/Mage.Sets/src/mage/cards/s/SailIntoTheWest.java +++ b/Mage.Sets/src/mage/cards/s/SailIntoTheWest.java @@ -56,7 +56,7 @@ class SailIntoTheWestEffect extends OneShotEffect { super(Outcome.Benefit); staticText = "starting with you, each player votes for return or embark. " + "If return gets more votes, each player returns up to two cards from their graveyard " + - "to their hand, then you exile Sail into the West. " + + "to their hand, then you exile {this}. " + "If embark gets more votes or the vote is tied, each player may discard their hand and draw seven cards."; } diff --git a/Mage.Sets/src/mage/cards/s/SaltRoadPackbeast.java b/Mage.Sets/src/mage/cards/s/SaltRoadPackbeast.java index 31c7d247038..4abb51be552 100644 --- a/Mage.Sets/src/mage/cards/s/SaltRoadPackbeast.java +++ b/Mage.Sets/src/mage/cards/s/SaltRoadPackbeast.java @@ -29,7 +29,7 @@ public final class SaltRoadPackbeast extends CardImpl { // This spell costs {1} less to cast for each creature you control. this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SpellCostReductionForEachSourceEffect(1, CreaturesYouControlCount.instance) + Zone.ALL, new SpellCostReductionForEachSourceEffect(1, CreaturesYouControlCount.SINGULAR) ).addHint(CreaturesYouControlHint.instance)); // When this creature enters, draw a card. diff --git a/Mage.Sets/src/mage/cards/s/SamiShipsEngineer.java b/Mage.Sets/src/mage/cards/s/SamiShipsEngineer.java index 18e9fda5562..0927546c35e 100644 --- a/Mage.Sets/src/mage/cards/s/SamiShipsEngineer.java +++ b/Mage.Sets/src/mage/cards/s/SamiShipsEngineer.java @@ -1,21 +1,14 @@ package mage.cards.s; import mage.MageInt; -import mage.abilities.condition.Condition; -import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; -import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.condition.common.TwoTappedCreaturesCondition; import mage.abilities.effects.common.CreateTokenEffect; -import mage.abilities.hint.Hint; -import mage.abilities.hint.ValueHint; 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.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.permanent.TappedPredicate; import mage.game.permanent.token.RobotToken; import java.util.UUID; @@ -25,18 +18,6 @@ import java.util.UUID; */ public final class SamiShipsEngineer extends CardImpl { - private static final FilterPermanent filter - = new FilterControlledCreaturePermanent("you control two or more tapped creatures"); - - static { - filter.add(TappedPredicate.TAPPED); - } - - private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); - private static final Hint hint = new ValueHint( - "Tapped creatures you control", new PermanentsOnBattlefieldCount(filter) - ); - public SamiShipsEngineer(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{W}"); @@ -49,7 +30,7 @@ public final class SamiShipsEngineer extends CardImpl { // At the beginning of your end step, if you control two or more tapped creatures, create a tapped 2/2 colorless Robot artifact creature token. this.addAbility(new BeginningOfEndStepTriggeredAbility( new CreateTokenEffect(new RobotToken(), 1, true) - ).withInterveningIf(condition).addHint(hint)); + ).withInterveningIf(TwoTappedCreaturesCondition.instance).addHint(TwoTappedCreaturesCondition.getHint())); } private SamiShipsEngineer(final SamiShipsEngineer card) { diff --git a/Mage.Sets/src/mage/cards/s/SamiWildcatCaptain.java b/Mage.Sets/src/mage/cards/s/SamiWildcatCaptain.java new file mode 100644 index 00000000000..5aa30b4900a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SamiWildcatCaptain.java @@ -0,0 +1,55 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledSpellsEffect; +import mage.abilities.keyword.AffinityForArtifactsAbility; +import mage.abilities.keyword.DoubleStrikeAbility; +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.common.FilterNonlandCard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SamiWildcatCaptain extends CardImpl { + + private static final FilterNonlandCard filter = new FilterNonlandCard("spells you cast"); + + public SamiWildcatCaptain(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Spells you cast have affinity for artifacts. + this.addAbility(new SimpleStaticAbility( + new GainAbilityControlledSpellsEffect(new AffinityForArtifactsAbility(), filter) + )); + } + + private SamiWildcatCaptain(final SamiWildcatCaptain card) { + super(card); + } + + @Override + public SamiWildcatCaptain copy() { + return new SamiWildcatCaptain(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SamisCuriosity.java b/Mage.Sets/src/mage/cards/s/SamisCuriosity.java new file mode 100644 index 00000000000..fadc1d95d51 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SamisCuriosity.java @@ -0,0 +1,33 @@ +package mage.cards.s; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SamisCuriosity extends CardImpl { + + public SamisCuriosity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}"); + + // You gain 2 life. Create a Lander token. + this.getSpellAbility().addEffect(new GainLifeEffect(2)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new LanderToken())); + } + + private SamisCuriosity(final SamisCuriosity card) { + super(card); + } + + @Override + public SamisCuriosity copy() { + return new SamisCuriosity(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SanctumOfShatteredHeights.java b/Mage.Sets/src/mage/cards/s/SanctumOfShatteredHeights.java index e12069ab73c..cfaafbf6305 100644 --- a/Mage.Sets/src/mage/cards/s/SanctumOfShatteredHeights.java +++ b/Mage.Sets/src/mage/cards/s/SanctumOfShatteredHeights.java @@ -12,7 +12,6 @@ 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.FilterPermanent; import mage.filter.common.FilterControlledPermanent; @@ -45,7 +44,7 @@ public final class SanctumOfShatteredHeights extends CardImpl { // {1}, Discard a land card or Shrine card: Sanctum of Shattered Heights deals X damage to target creature or planeswalker, where X is the number of Shrines you control. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(xValue) - .setText("Sanctum of Shattered Heights deals X damage to target creature or planeswalker, where X is the number of Shrines you control"), + .setText("{this} deals X damage to target creature or planeswalker, where X is the number of Shrines you control"), new ManaCostsImpl<>("{1}")) .addHint(new ValueHint("Shrines you control", xValue)); ability.addCost(new DiscardTargetCost(new TargetCardInHand(filter))); diff --git a/Mage.Sets/src/mage/cards/s/SandsteppeWarRiders.java b/Mage.Sets/src/mage/cards/s/SandsteppeWarRiders.java index 8f18224eb1c..b7ea5a082bd 100644 --- a/Mage.Sets/src/mage/cards/s/SandsteppeWarRiders.java +++ b/Mage.Sets/src/mage/cards/s/SandsteppeWarRiders.java @@ -1,15 +1,10 @@ package mage.cards.s; import mage.MageInt; -import mage.MageObject; -import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; import mage.abilities.effects.keyword.BolsterEffect; -import mage.abilities.hint.Hint; -import mage.abilities.hint.ValueHint; import mage.abilities.keyword.TrampleAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -17,9 +12,7 @@ import mage.constants.SubType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledArtifactPermanent; import mage.filter.predicate.permanent.TokenPredicate; -import mage.game.Game; -import java.util.Objects; import java.util.UUID; /** @@ -27,6 +20,14 @@ import java.util.UUID; */ public final class SandsteppeWarRiders extends CardImpl { + private static final FilterPermanent filter = new FilterControlledArtifactPermanent("artifact tokens you control"); + + static { + filter.add(TokenPredicate.TRUE); + } + + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(filter); + public SandsteppeWarRiders(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); @@ -39,7 +40,7 @@ public final class SandsteppeWarRiders extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // At the beginning of combat on your turn, bolster X, where X is the number of differently named artifact tokens you control. - this.addAbility(new BeginningOfCombatTriggeredAbility(new BolsterEffect(SandsteppeWarRidersValue.instance))); + this.addAbility(new BeginningOfCombatTriggeredAbility(new BolsterEffect(xValue)).addHint(xValue.getHint())); } private SandsteppeWarRiders(final SandsteppeWarRiders card) { @@ -51,43 +52,3 @@ public final class SandsteppeWarRiders extends CardImpl { return new SandsteppeWarRiders(this); } } - -enum SandsteppeWarRidersValue implements DynamicValue { - instance; - private static final FilterPermanent filter = new FilterControlledArtifactPermanent(); - - static { - filter.add(TokenPredicate.TRUE); - } - - private static final Hint hint = new ValueHint("Different artifact token names you control", instance); - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - return game - .getBattlefield() - .getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility, game) - .stream() - .map(MageObject::getName) - .filter(Objects::nonNull) - .filter(s -> !s.isEmpty()) - .distinct() - .mapToInt(x -> 1) - .sum(); - } - - @Override - public SandsteppeWarRidersValue copy() { - return this; - } - - @Override - public String getMessage() { - return "differently named artifact tokens you control"; - } - - @Override - public String toString() { - return "X"; - } -} diff --git a/Mage.Sets/src/mage/cards/s/SarkhansResolve.java b/Mage.Sets/src/mage/cards/s/SarkhansResolve.java index 6447331e66d..b88484e4dc7 100644 --- a/Mage.Sets/src/mage/cards/s/SarkhansResolve.java +++ b/Mage.Sets/src/mage/cards/s/SarkhansResolve.java @@ -3,13 +3,10 @@ package mage.cards.s; import mage.abilities.Mode; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -20,12 +17,6 @@ import java.util.UUID; */ public final class SarkhansResolve extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public SarkhansResolve(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); @@ -35,7 +26,7 @@ public final class SarkhansResolve extends CardImpl { this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // * Destroy target creature with flying. - this.getSpellAbility().addMode(new Mode(new DestroyTargetEffect()).addTarget(new TargetPermanent(filter))); + this.getSpellAbility().addMode(new Mode(new DestroyTargetEffect()).addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING))); } private SarkhansResolve(final SarkhansResolve card) { diff --git a/Mage.Sets/src/mage/cards/s/SawtuskDemolisher.java b/Mage.Sets/src/mage/cards/s/SawtuskDemolisher.java index 563da83624b..65205699278 100644 --- a/Mage.Sets/src/mage/cards/s/SawtuskDemolisher.java +++ b/Mage.Sets/src/mage/cards/s/SawtuskDemolisher.java @@ -3,26 +3,17 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.MutatesSourceTriggeredAbility; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenControllerTargetEffect; -import mage.abilities.effects.common.CreateTokenTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.MutateAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.filter.StaticFilters; import mage.game.permanent.token.BeastToken; -import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -31,12 +22,6 @@ import java.util.UUID; */ public final class SawtuskDemolisher extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public SawtuskDemolisher(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); @@ -53,7 +38,7 @@ public final class SawtuskDemolisher extends CardImpl { // Whenever this creature mutates, destroy target noncreature permanent. Its controller creates a 3/3 green Beast creature token. Ability ability = new MutatesSourceTriggeredAbility(new DestroyTargetEffect()); ability.addEffect(new CreateTokenControllerTargetEffect(new BeastToken())); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); this.addAbility(ability); } @@ -66,36 +51,3 @@ public final class SawtuskDemolisher extends CardImpl { return new SawtuskDemolisher(this); } } - -class SawtuskDemolisherEffect extends OneShotEffect { - - SawtuskDemolisherEffect() { - super(Outcome.Benefit); - staticText = "destroy target noncreature permanent. Its controller creates a 3/3 green Beast creature token."; - } - - private SawtuskDemolisherEffect(final SawtuskDemolisherEffect effect) { - super(effect); - } - - @Override - public SawtuskDemolisherEffect copy() { - return new SawtuskDemolisherEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent == null) { - return false; - } - Player player = game.getPlayer(permanent.getControllerId()); - permanent.destroy(source, game, false); - if (player == null) { - return false; - } - Effect effect = new CreateTokenTargetEffect(new BeastToken()); - effect.setTargetPointer(new FixedTarget(player.getId(), game)); - return effect.apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/s/ScaldingSalamander.java b/Mage.Sets/src/mage/cards/s/ScaldingSalamander.java index 3fca24508e9..029937ba992 100644 --- a/Mage.Sets/src/mage/cards/s/ScaldingSalamander.java +++ b/Mage.Sets/src/mage/cards/s/ScaldingSalamander.java @@ -1,21 +1,21 @@ package mage.cards.s; -import java.util.UUID; - import mage.MageInt; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.keyword.FlyingAbility; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate; +import java.util.UUID; + /** * @author TheElk801 */ @@ -37,10 +37,7 @@ public final class ScaldingSalamander extends CardImpl { this.toughness = new MageInt(1); // Whenever Scalding Salamander attacks, you may have it deal 1 damage to each creature without flying defending player controls. - this.addAbility(new AttacksTriggeredAbility( - new DamageAllEffect(1, filter), true, - "Whenever Scalding Salamander attacks, you may have it deal 1 damage to each creature without flying defending player controls." - )); + this.addAbility(new AttacksTriggeredAbility(new DamageAllEffect(1, filter), true)); } private ScaldingSalamander(final ScaldingSalamander card) { diff --git a/Mage.Sets/src/mage/cards/s/ScattershotArcher.java b/Mage.Sets/src/mage/cards/s/ScattershotArcher.java index 47b70bb743f..6372cdad809 100644 --- a/Mage.Sets/src/mage/cards/s/ScattershotArcher.java +++ b/Mage.Sets/src/mage/cards/s/ScattershotArcher.java @@ -1,34 +1,25 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author North */ public final class ScattershotArcher extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public ScattershotArcher(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); this.subtype.add(SubType.ELF); this.subtype.add(SubType.ARCHER); @@ -36,7 +27,7 @@ public final class ScattershotArcher extends CardImpl { this.toughness = new MageInt(2); // {tap}: Scattershot Archer deals 1 damage to each creature with flying. - this.addAbility(new SimpleActivatedAbility(new DamageAllEffect(1, filter), new TapSourceCost())); + this.addAbility(new SimpleActivatedAbility(new DamageAllEffect(1, StaticFilters.FILTER_CREATURE_FLYING), new TapSourceCost())); } private ScattershotArcher(final ScattershotArcher card) { diff --git a/Mage.Sets/src/mage/cards/s/ScepterOfEmpires.java b/Mage.Sets/src/mage/cards/s/ScepterOfEmpires.java index e28cb424043..b1811c63e75 100644 --- a/Mage.Sets/src/mage/cards/s/ScepterOfEmpires.java +++ b/Mage.Sets/src/mage/cards/s/ScepterOfEmpires.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -10,11 +9,12 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetPlayerOrPlaneswalker; +import java.util.UUID; + /** * @author nantuko */ @@ -43,7 +43,7 @@ class ScepterOfEmpiresEffect extends OneShotEffect { ScepterOfEmpiresEffect() { super(Outcome.PutCreatureInPlay); - staticText = "Scepter of Empires deals 1 damage to target player or planeswalker. It deals 3 damage instead if you control artifacts named Crown of Empires and Throne of Empires"; + staticText = "{this} deals 1 damage to target player or planeswalker. It deals 3 damage instead if you control artifacts named Crown of Empires and Throne of Empires"; } private ScepterOfEmpiresEffect(final ScepterOfEmpiresEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/ScionOfStygia.java b/Mage.Sets/src/mage/cards/s/ScionOfStygia.java index 2ef8ef1f9a9..9a00e769c15 100644 --- a/Mage.Sets/src/mage/cards/s/ScionOfStygia.java +++ b/Mage.Sets/src/mage/cards/s/ScionOfStygia.java @@ -40,13 +40,14 @@ public final class ScionOfStygia extends CardImpl { this.addAbility(ability.withFlavorWord("Cone of Cold")); // 1-9 | Tap that creature. - effect.addTableEntry(1, 9, new TapTargetEffect("tap that creature")); + effect.addTableEntry(1, 9, new TapTargetEffect()); // 10-20 | Tap that creature. It doesn't untap during its controller's next untap step. effect.addTableEntry( - 10, 20, new TapTargetEffect("tap that creature"), + 10, 20, new TapTargetEffect(), new DontUntapInControllersNextUntapStepTargetEffect("it") ); + effect.withTargetDescription("that creature"); } private ScionOfStygia(final ScionOfStygia card) { diff --git a/Mage.Sets/src/mage/cards/s/ScourForScrap.java b/Mage.Sets/src/mage/cards/s/ScourForScrap.java new file mode 100644 index 00000000000..d38e5f381cc --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScourForScrap.java @@ -0,0 +1,46 @@ +package mage.cards.s; + +import mage.abilities.Mode; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ScourForScrap extends CardImpl { + + public ScourForScrap(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); + + // Choose one or both — + this.getSpellAbility().getModes().setMinModes(1); + this.getSpellAbility().getModes().setMaxModes(2); + + // • Search your library for an artifact card, reveal it, put it into your hand, then shuffle. + this.getSpellAbility().addEffect(new SearchLibraryPutInHandEffect( + new TargetCardInLibrary(StaticFilters.FILTER_CARD_ARTIFACT_AN), true + )); + + // • Return target artifact card from your graveyard to your hand. + Mode mode = new Mode(new ReturnFromGraveyardToHandTargetEffect()); + mode.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_ARTIFACT_FROM_YOUR_GRAVEYARD)); + this.getSpellAbility().addMode(mode); + } + + private ScourForScrap(final ScourForScrap card) { + super(card); + } + + @Override + public ScourForScrap copy() { + return new ScourForScrap(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScouringSwarm.java b/Mage.Sets/src/mage/cards/s/ScouringSwarm.java new file mode 100644 index 00000000000..ec82e724870 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScouringSwarm.java @@ -0,0 +1,61 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.CreateTokenCopySourceEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.XiraBlackInsectToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ScouringSwarm extends CardImpl { + + private static final Condition condition = new CardsInControllerGraveyardCondition(7, StaticFilters.FILTER_CARD_LAND); + private static final Hint hint = new ValueHint( + "Lands in your graveyard", new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_LAND) + ); + + public ScouringSwarm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{G}"); + + this.subtype.add(SubType.INSECT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you sacrifice a land, create a tapped token that's a copy of this creature if seven or more land cards are in your graveyard. Otherwise, create a tapped 1/1 black Insect creature token with flying. + this.addAbility(new SacrificePermanentTriggeredAbility(new ConditionalOneShotEffect( + new CreateTokenCopySourceEffect(1, true), + new CreateTokenEffect(new XiraBlackInsectToken(), 1, true), + condition, "create a tapped token that's a copy of this creature " + + "if seven or more land cards are in your graveyard. Otherwise, create a " + + "tapped 1/1 black Insect creature token with flying" + ), StaticFilters.FILTER_LAND).addHint(hint)); + } + + private ScouringSwarm(final ScouringSwarm card) { + super(card); + } + + @Override + public ScouringSwarm copy() { + return new ScouringSwarm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java b/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java new file mode 100644 index 00000000000..44072938fec --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java @@ -0,0 +1,127 @@ +package mage.cards.s; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ScoutForSurvivors extends CardImpl { + + public ScoutForSurvivors(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}"); + + // Return up to three target creature cards with total mana value 3 or less from your graveyard to the battlefield. Put a +1/+1 counter on each of them. + this.getSpellAbility().addEffect(new ScoutForSurvivorsEffect()); + this.getSpellAbility().addTarget(new ScoutForSurvivorsTarget()); + } + + private ScoutForSurvivors(final ScoutForSurvivors card) { + super(card); + } + + @Override + public ScoutForSurvivors copy() { + return new ScoutForSurvivors(this); + } +} + +class ScoutForSurvivorsEffect extends OneShotEffect { + + ScoutForSurvivorsEffect() { + super(Outcome.Benefit); + staticText = "return up to three target creature cards with total mana value 3 or less " + + "from your graveyard to the battlefield. Put a +1/+1 counter on each of them"; + } + + private ScoutForSurvivorsEffect(final ScoutForSurvivorsEffect effect) { + super(effect); + } + + @Override + public ScoutForSurvivorsEffect copy() { + return new ScoutForSurvivorsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Cards cards = new CardsImpl(getTargetPointer().getTargets(game, source)); + cards.retainZone(Zone.GRAVEYARD, game); + if (player == null || cards.isEmpty()) { + return false; + } + player.moveCards(cards, Zone.BATTLEFIELD, source, game); + for (Card card : cards.getCards(game)) { + Optional.ofNullable(CardUtil.getPermanentFromCardPutToBattlefield(card, game)) + .ifPresent(permanent -> permanent.addCounters(CounterType.P1P1.createInstance(), source, game)); + } + return true; + } +} + +class ScoutForSurvivorsTarget extends TargetCardInYourGraveyard { + + private static final FilterCard filterStatic + = new FilterCreatureCard("creature cards with total mana value 3 or less from your graveyard"); + + ScoutForSurvivorsTarget() { + super(0, 3, filterStatic, false); + } + + private ScoutForSurvivorsTarget(final ScoutForSurvivorsTarget target) { + super(target); + } + + @Override + public ScoutForSurvivorsTarget copy() { + return new ScoutForSurvivorsTarget(this); + } + + @Override + public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + return super.canTarget(controllerId, id, source, game) + && CardUtil.checkCanTargetTotalValueLimit( + this.getTargets(), id, MageObject::getManaValue, 3, game + ); + } + + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + return CardUtil.checkPossibleTargetsTotalValueLimit( + this.getTargets(), + super.possibleTargets(sourceControllerId, source, game), + MageObject::getManaValue, 3, 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/s/ScroungeForEternity.java b/Mage.Sets/src/mage/cards/s/ScroungeForEternity.java new file mode 100644 index 00000000000..d75d5e8abad --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScroungeForEternity.java @@ -0,0 +1,55 @@ +package mage.cards.s; + +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.permanent.token.LanderToken; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ScroungeForEternity extends CardImpl { + + private static final FilterCard filter = new FilterCard("creature or Spacecraft card with mana value 5 or less from your graveyard"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.SPACECRAFT.getPredicate() + )); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 6)); + } + + public ScroungeForEternity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // As an additional cost to cast this spell, sacrifice an artifact or creature. + this.getSpellAbility().addCost(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); + + // Return target creature or Spacecraft card with mana value 5 or less from your graveyard to the battlefield. Then create a Lander token. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new LanderToken()).concatBy("Then")); + } + + private ScroungeForEternity(final ScroungeForEternity card) { + super(card); + } + + @Override + public ScroungeForEternity copy() { + return new ScroungeForEternity(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScurryOfGremlins.java b/Mage.Sets/src/mage/cards/s/ScurryOfGremlins.java index 638410f2b4a..b7a4764159a 100644 --- a/Mage.Sets/src/mage/cards/s/ScurryOfGremlins.java +++ b/Mage.Sets/src/mage/cards/s/ScurryOfGremlins.java @@ -17,7 +17,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.filter.StaticFilters; import mage.game.permanent.token.Gremlin11Token; -import mage.game.permanent.token.GremlinToken; import java.util.UUID; @@ -31,7 +30,7 @@ public final class ScurryOfGremlins extends CardImpl { // When Scurry of Gremlins enters the battlefield, create two 1/1 red Gremlin creature tokens. Then you get an amount of {E} equal to the number of creatures you control. Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new Gremlin11Token(), 2)); - ability.addEffect(new GetEnergyCountersControllerEffect(CreaturesYouControlCount.instance) + ability.addEffect(new GetEnergyCountersControllerEffect(CreaturesYouControlCount.PLURAL) .setText("Then you get an amount of {E} equal to the number of creatures you control")); this.addAbility(ability.addHint(CreaturesYouControlHint.instance)); diff --git a/Mage.Sets/src/mage/cards/s/SeamRip.java b/Mage.Sets/src/mage/cards/s/SeamRip.java new file mode 100644 index 00000000000..133622e4ca6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeamRip.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SeamRip extends CardImpl { + + private static final FilterPermanent filter + = new FilterNonlandPermanent("nonland permanent an opponent controls with mana value 2 or less"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 3)); + } + + public SeamRip(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}"); + + // When this enchantment enters, exile target nonland permanent an opponent controls with mana value 2 or less until this enchantment leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private SeamRip(final SeamRip card) { + super(card); + } + + @Override + public SeamRip copy() { + return new SeamRip(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SecludedStarforge.java b/Mage.Sets/src/mage/cards/s/SecludedStarforge.java new file mode 100644 index 00000000000..74c8ac33672 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SecludedStarforge.java @@ -0,0 +1,65 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.common.TapVariableTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.permanent.token.RobotToken; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SecludedStarforge extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledArtifactPermanent("untapped artifacts you control"); + + static { + filter.add(TappedPredicate.UNTAPPED); + } + + public SecludedStarforge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {2}, {T}, Tap X untapped artifacts you control: Target creature gets +X/+0 until end of turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new BoostTargetEffect(GetXValue.instance, StaticValue.get(0)), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new TapVariableTargetCost(filter)); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + + // {5}, {T}: Create a 2/2 colorless Robot artifact creature token. + ability = new SimpleActivatedAbility(new CreateTokenEffect(new RobotToken()), new GenericManaCost(5)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private SecludedStarforge(final SecludedStarforge card) { + super(card); + } + + @Override + public SecludedStarforge copy() { + return new SecludedStarforge(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeedshipAgrarian.java b/Mage.Sets/src/mage/cards/s/SeedshipAgrarian.java new file mode 100644 index 00000000000..97bfe6a1e55 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeedshipAgrarian.java @@ -0,0 +1,45 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.permanent.token.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SeedshipAgrarian extends CardImpl { + + public SeedshipAgrarian(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.SCIENTIST); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever this creature becomes tapped, create a Lander token. + this.addAbility(new BecomesTappedSourceTriggeredAbility(new CreateTokenEffect(new LanderToken()))); + + // Landfall -- Whenever a land you control enters, put a +1/+1 counter on this creature. + this.addAbility(new LandfallAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); + } + + private SeedshipAgrarian(final SeedshipAgrarian card) { + super(card); + } + + @Override + public SeedshipAgrarian copy() { + return new SeedshipAgrarian(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeedshipBroodtender.java b/Mage.Sets/src/mage/cards/s/SeedshipBroodtender.java new file mode 100644 index 00000000000..439e9758f57 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeedshipBroodtender.java @@ -0,0 +1,63 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SeedshipBroodtender extends CardImpl { + + private static final FilterCard filter = new FilterCard("creature or Spacecraft card from your graveyard"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.SPACECRAFT.getPredicate() + )); + } + + public SeedshipBroodtender(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When this creature enters, mill three cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(3))); + + // {3}{B}{G}, Sacrifice this creature: Return target creature or Spacecraft card from your graveyard to the battlefield. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl<>("{3}{B}{G}") + ); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + } + + private SeedshipBroodtender(final SeedshipBroodtender card) { + super(card); + } + + @Override + public SeedshipBroodtender copy() { + return new SeedshipBroodtender(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeedshipImpact.java b/Mage.Sets/src/mage/cards/s/SeedshipImpact.java new file mode 100644 index 00000000000..50c8c165166 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeedshipImpact.java @@ -0,0 +1,53 @@ +package mage.cards.s; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceTargetsPermanentCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +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.ComparisonType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.permanent.token.LanderToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SeedshipImpact extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 2)); + } + + private static final Condition condition = new SourceTargetsPermanentCondition(filter); + + public SeedshipImpact(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // Destroy target artifact or enchantment. If its mana value was 2 or less, create a Lander token. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new CreateTokenEffect(new LanderToken()), condition, + "If its mana value was 2 or less, create a Lander token. " + LanderToken.getReminderText() + )); + } + + private SeedshipImpact(final SeedshipImpact card) { + super(card); + } + + @Override + public SeedshipImpact copy() { + return new SeedshipImpact(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SelfcraftMechan.java b/Mage.Sets/src/mage/cards/s/SelfcraftMechan.java new file mode 100644 index 00000000000..57841a66bcd --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SelfcraftMechan.java @@ -0,0 +1,51 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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.filter.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SelfcraftMechan extends CardImpl { + + public SelfcraftMechan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When this creature enters, you may sacrifice an artifact. When you do, put a +1/+1 counter on target creature and draw a card. + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false); + ability.addEffect(new DrawCardSourceControllerEffect(1)); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoWhenCostPaid( + ability, new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_AN), + "Sacrifice an artifact?" + ))); + } + + private SelfcraftMechan(final SelfcraftMechan card) { + super(card); + } + + @Override + public SelfcraftMechan copy() { + return new SelfcraftMechan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeraphOfTheMasses.java b/Mage.Sets/src/mage/cards/s/SeraphOfTheMasses.java index 81f9226baa2..2aa045d9bf6 100644 --- a/Mage.Sets/src/mage/cards/s/SeraphOfTheMasses.java +++ b/Mage.Sets/src/mage/cards/s/SeraphOfTheMasses.java @@ -32,7 +32,7 @@ public final class SeraphOfTheMasses extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // Seraph of the Masses's power and toughness are each equal to the number of creatures you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance)) + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL)) .addHint(CreaturesYouControlHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/s/SerumCoreChimera.java b/Mage.Sets/src/mage/cards/s/SerumCoreChimera.java index d96b62c13d5..fcf29c2f18b 100644 --- a/Mage.Sets/src/mage/cards/s/SerumCoreChimera.java +++ b/Mage.Sets/src/mage/cards/s/SerumCoreChimera.java @@ -7,12 +7,10 @@ import mage.abilities.common.delayed.ReflexiveTriggeredAbility; import mage.abilities.costs.common.DiscardCardCost; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.DoWhenCostPaid; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.FlyingAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -50,7 +48,7 @@ public class SerumCoreChimera extends CardImpl { reflexiveTriggeredAbility.addTarget(new TargetCreatureOrPlaneswalker()); activateAsSorceryActivatedAbility.addEffect(new DoWhenCostPaid(reflexiveTriggeredAbility, new DiscardCardCost(StaticFilters.FILTER_CARD_A_NON_LAND), "Discard nonland card?") - .setText("Then you may discard a nonland card. When you discard a card this way, Serum-Core Chimera " + + .setText("Then you may discard a nonland card. When you discard a card this way, {this} " + "deals 3 damage to target creature or planeswalker.")); this.addAbility(activateAsSorceryActivatedAbility); } diff --git a/Mage.Sets/src/mage/cards/s/SharpshooterElf.java b/Mage.Sets/src/mage/cards/s/SharpshooterElf.java index b82888fff6c..17f50ae0431 100644 --- a/Mage.Sets/src/mage/cards/s/SharpshooterElf.java +++ b/Mage.Sets/src/mage/cards/s/SharpshooterElf.java @@ -47,7 +47,7 @@ public final class SharpshooterElf extends CardImpl { // Sharpshooter Elf's power is equal to the number of creatures you control. this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SetBasePowerSourceEffect(CreaturesYouControlCount.instance) + Zone.ALL, new SetBasePowerSourceEffect(CreaturesYouControlCount.PLURAL) )); // When Sharpshooter Elf enters the battlefield, it deals damage equal to its power to target creature with flying an opponent controls. diff --git a/Mage.Sets/src/mage/cards/s/ShatteredWings.java b/Mage.Sets/src/mage/cards/s/ShatteredWings.java new file mode 100644 index 00000000000..f0a7826dad4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShatteredWings.java @@ -0,0 +1,52 @@ +package mage.cards.s; + +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShatteredWings extends CardImpl { + + private static final FilterPermanent filter + = new FilterPermanent("artifact, enchantment, or creature with flying"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.ENCHANTMENT.getPredicate(), + Predicates.and( + CardType.CREATURE.getPredicate(), + new AbilityPredicate(FlyingAbility.class) + ) + )); + } + + public ShatteredWings(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); + + // Destroy target artifact, enchantment, or creature with flying. Surveil 1. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addEffect(new SurveilEffect(1)); + } + + private ShatteredWings(final ShatteredWings card) { + super(card); + } + + @Override + public ShatteredWings copy() { + return new ShatteredWings(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShieldOfTheAvatar.java b/Mage.Sets/src/mage/cards/s/ShieldOfTheAvatar.java index 457a96ffd5c..3930bb4f6ec 100644 --- a/Mage.Sets/src/mage/cards/s/ShieldOfTheAvatar.java +++ b/Mage.Sets/src/mage/cards/s/ShieldOfTheAvatar.java @@ -68,7 +68,7 @@ class ShieldOfTheAvatarPreventionEffect extends PreventionEffectImpl { boolean result = false; Permanent equipment = game.getPermanent(source.getSourceId()); if (equipment != null && equipment.getAttachedTo() != null) { - int numberOfCreaturesControlled = CreaturesYouControlCount.instance.calculate(game, source, this); + int numberOfCreaturesControlled = CreaturesYouControlCount.PLURAL.calculate(game, source, this); int toPrevent = Math.min(numberOfCreaturesControlled, event.getAmount()); GameEvent preventEvent = new PreventDamageEvent(event.getTargetId(), source.getSourceId(), source, source.getControllerId(), toPrevent, ((DamageEvent) event).isCombatDamage()); if (!game.replaceEvent(preventEvent)) { diff --git a/Mage.Sets/src/mage/cards/s/ShreddedSails.java b/Mage.Sets/src/mage/cards/s/ShreddedSails.java index d77da51831e..ee0ee45d7fe 100644 --- a/Mage.Sets/src/mage/cards/s/ShreddedSails.java +++ b/Mage.Sets/src/mage/cards/s/ShreddedSails.java @@ -5,13 +5,10 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.CyclingAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetArtifactPermanent; @@ -22,12 +19,6 @@ import java.util.UUID; */ public final class ShreddedSails extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public ShreddedSails(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); @@ -38,7 +29,7 @@ public final class ShreddedSails extends CardImpl { // • Shredded Sails deals 4 damage to target creature with flying. Mode mode = new Mode(new DamageTargetEffect(4)); - mode.addTarget(new TargetPermanent(filter)); + mode.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.getSpellAbility().addMode(mode); // Cycling {2} diff --git a/Mage.Sets/src/mage/cards/s/ShreddingWinds.java b/Mage.Sets/src/mage/cards/s/ShreddingWinds.java index 90d61a2adb1..56ffb337d0f 100644 --- a/Mage.Sets/src/mage/cards/s/ShreddingWinds.java +++ b/Mage.Sets/src/mage/cards/s/ShreddingWinds.java @@ -1,37 +1,26 @@ package mage.cards.s; -import java.util.UUID; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.target.Target; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class ShreddingWinds extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public ShreddingWinds(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); // Shredding Winds deals 7 damage to target creature with flying. this.getSpellAbility().addEffect(new DamageTargetEffect(7)); - Target target = new TargetPermanent(filter); - this.getSpellAbility().addTarget(target); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private ShreddingWinds(final ShreddingWinds card) { diff --git a/Mage.Sets/src/mage/cards/s/SilklashSpider.java b/Mage.Sets/src/mage/cards/s/SilklashSpider.java index b660e93cc82..f3a74050a02 100644 --- a/Mage.Sets/src/mage/cards/s/SilklashSpider.java +++ b/Mage.Sets/src/mage/cards/s/SilklashSpider.java @@ -1,36 +1,27 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author North */ public final class SilklashSpider extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public SilklashSpider(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); this.subtype.add(SubType.SPIDER); this.power = new MageInt(2); @@ -40,7 +31,7 @@ public final class SilklashSpider extends CardImpl { this.addAbility(ReachAbility.getInstance()); // {X}{G}{G}: Silklash Spider deals X damage to each creature with flying. this.addAbility(new SimpleActivatedAbility( - new DamageAllEffect(GetXValue.instance, filter), + new DamageAllEffect(GetXValue.instance, StaticFilters.FILTER_CREATURE_FLYING), new ManaCostsImpl<>("{X}{G}{G}"))); } diff --git a/Mage.Sets/src/mage/cards/s/SilverwingSquadron.java b/Mage.Sets/src/mage/cards/s/SilverwingSquadron.java index 7d3a445300f..3f1fba75177 100644 --- a/Mage.Sets/src/mage/cards/s/SilverwingSquadron.java +++ b/Mage.Sets/src/mage/cards/s/SilverwingSquadron.java @@ -40,7 +40,7 @@ public final class SilverwingSquadron extends CardImpl { // Silverwing Squadron's power and toughness are each equal to the number of creatures you control. this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance) + Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL) ).addHint(CreaturesYouControlHint.instance)); // Whenever Silverwing Squadron attacks, create a number of 2/2 white Knight creature tokens with vigilance equal to the number of opponents you have. diff --git a/Mage.Sets/src/mage/cards/s/SimianSling.java b/Mage.Sets/src/mage/cards/s/SimianSling.java index a3bcb8ac777..a9e77100def 100644 --- a/Mage.Sets/src/mage/cards/s/SimianSling.java +++ b/Mage.Sets/src/mage/cards/s/SimianSling.java @@ -89,7 +89,7 @@ class SimianSlingTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever Simian Sling or equipped creature becomes blocked, it deals 1 damage to defending player."; + return "Whenever {this} or equipped creature becomes blocked, it deals 1 damage to defending player."; } } diff --git a/Mage.Sets/src/mage/cards/s/SingularityRupture.java b/Mage.Sets/src/mage/cards/s/SingularityRupture.java new file mode 100644 index 00000000000..f978497f046 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SingularityRupture.java @@ -0,0 +1,36 @@ +package mage.cards.s; + +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.MillHalfLibraryTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SingularityRupture extends CardImpl { + + public SingularityRupture(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}{B}{B}"); + + // Destroy all creatures, then any number of target players each mill half their library, rounded down. + this.getSpellAbility().addEffect(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES)); + this.getSpellAbility().addEffect(new MillHalfLibraryTargetEffect(false) + .setText(", then any number of target players each mill half their library, rounded down")); + this.getSpellAbility().addTarget(new TargetPlayer(0, Integer.MAX_VALUE, false)); + } + + private SingularityRupture(final SingularityRupture card) { + super(card); + } + + @Override + public SingularityRupture copy() { + return new SingularityRupture(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SinisterCryologist.java b/Mage.Sets/src/mage/cards/s/SinisterCryologist.java new file mode 100644 index 00000000000..5bc7be6b24e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SinisterCryologist.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SinisterCryologist extends CardImpl { + + public SinisterCryologist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.JELLYFISH); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When this creature enters, target creature an opponent controls gets -3/-0 until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(-3, 0, Duration.EndOfTurn)); + ability.addTarget(new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.addAbility(ability); + + // Warp {U} + this.addAbility(new WarpAbility(this, "{U}")); + } + + private SinisterCryologist(final SinisterCryologist card) { + super(card); + } + + @Override + public SinisterCryologist copy() { + return new SinisterCryologist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SkullStorm.java b/Mage.Sets/src/mage/cards/s/SkullStorm.java index d7934a3e4e6..17b7da11a2f 100644 --- a/Mage.Sets/src/mage/cards/s/SkullStorm.java +++ b/Mage.Sets/src/mage/cards/s/SkullStorm.java @@ -1,6 +1,5 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; @@ -18,6 +17,8 @@ import mage.game.Game; import mage.players.Player; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author TheElk801 @@ -28,7 +29,7 @@ public final class SkullStorm extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{7}{B}{B}"); // When you cast this spell, copy it for each time you've cast your commander from the command zone this game. - this.addAbility(new CommanderStormAbility()); + this.addAbility(new CommanderStormAbility(false)); // Each opponent sacrifices a creature. Each opponent who can't loses half their life, rounded up. this.getSpellAbility().addEffect(new SkullStormEffect()); @@ -48,7 +49,7 @@ class SkullStormEffect extends OneShotEffect { SkullStormEffect() { super(Outcome.Benefit); - this.staticText = "Each opponent sacrifices a creature. " + this.staticText = "Each opponent sacrifices a creature of their choice. " + "Each opponent who can't loses half their life, rounded up."; } diff --git a/Mage.Sets/src/mage/cards/s/Skyreaping.java b/Mage.Sets/src/mage/cards/s/Skyreaping.java index b094e1c29dc..8ef559e9ac8 100644 --- a/Mage.Sets/src/mage/cards/s/Skyreaping.java +++ b/Mage.Sets/src/mage/cards/s/Skyreaping.java @@ -3,12 +3,10 @@ package mage.cards.s; import mage.abilities.dynamicvalue.common.DevotionCount; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import java.util.UUID; @@ -17,17 +15,11 @@ import java.util.UUID; */ public final class Skyreaping extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Skyreaping(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); // Skyreaping deals damage to each creature with flying equal to your devotion to green. - Effect effect = new DamageAllEffect(DevotionCount.G, filter); + Effect effect = new DamageAllEffect(DevotionCount.G, StaticFilters.FILTER_CREATURE_FLYING); effect.setText("{this} deals damage to each creature with flying equal to your devotion to green. " + DevotionCount.G.getReminder()); this.getSpellAbility().addEffect(effect); this.getSpellAbility().addHint(DevotionCount.G.getHint()); diff --git a/Mage.Sets/src/mage/cards/s/SkyshroudArcher.java b/Mage.Sets/src/mage/cards/s/SkyshroudArcher.java index 3a953a4d172..6d09a512a9f 100644 --- a/Mage.Sets/src/mage/cards/s/SkyshroudArcher.java +++ b/Mage.Sets/src/mage/cards/s/SkyshroudArcher.java @@ -1,38 +1,28 @@ package mage.cards.s; -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.common.continuous.BoostTargetEffect; -import mage.abilities.keyword.FlyingAbility; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author LoneFox */ public final class SkyshroudArcher extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public SkyshroudArcher(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); this.subtype.add(SubType.ELF); this.subtype.add(SubType.ARCHER); this.power = new MageInt(1); @@ -40,7 +30,7 @@ public final class SkyshroudArcher extends CardImpl { // {tap}: Target creature with flying gets -1/-1 until end of turn. Ability ability = new SimpleActivatedAbility(new BoostTargetEffect(-1, -1, Duration.EndOfTurn), new TapSourceCost()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/Skystinger.java b/Mage.Sets/src/mage/cards/s/Skystinger.java new file mode 100644 index 00000000000..3e1d61ac96c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Skystinger.java @@ -0,0 +1,47 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.BlocksCreatureTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.ReachAbility; +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 Skystinger extends CardImpl { + + public Skystinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever this creature blocks a creature with flying, this creature gets +5/+0 until end of turn. + this.addAbility(new BlocksCreatureTriggeredAbility( + new BoostSourceEffect(5, 0, Duration.EndOfTurn), + StaticFilters.FILTER_CREATURE_FLYING, false + )); + } + + private Skystinger(final Skystinger card) { + super(card); + } + + @Override + public Skystinger copy() { + return new Skystinger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SkywayRobber.java b/Mage.Sets/src/mage/cards/s/SkywayRobber.java index 0c1833339c0..25e90be8490 100644 --- a/Mage.Sets/src/mage/cards/s/SkywayRobber.java +++ b/Mage.Sets/src/mage/cards/s/SkywayRobber.java @@ -19,7 +19,6 @@ import mage.filter.predicate.Predicates; 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; @@ -73,7 +72,7 @@ class SkywayRobberCastForFreeEffect extends OneShotEffect { public SkywayRobberCastForFreeEffect() { super(Outcome.PlayForFree); - this.staticText = "you may cast an artifact, instant, or sorcery spell from among cards exiled with Skyway Robber without paying its mana cost"; + this.staticText = "you may cast an artifact, instant, or sorcery spell from among cards exiled with {this} without paying its mana cost"; } private SkywayRobberCastForFreeEffect(final SkywayRobberCastForFreeEffect effect) { @@ -124,4 +123,4 @@ class SkywayRobberCastForFreeEffect extends OneShotEffect { public SkywayRobberCastForFreeEffect copy() { return new SkywayRobberCastForFreeEffect(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/s/SkywaySniper.java b/Mage.Sets/src/mage/cards/s/SkywaySniper.java index b41d481aad9..034499db1c6 100644 --- a/Mage.Sets/src/mage/cards/s/SkywaySniper.java +++ b/Mage.Sets/src/mage/cards/s/SkywaySniper.java @@ -5,15 +5,12 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; 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.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -23,12 +20,6 @@ import java.util.UUID; */ public final class SkywaySniper extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public SkywaySniper(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); @@ -42,7 +33,7 @@ public final class SkywaySniper extends CardImpl { // {2}{G}: Skyway Sniper deals 1 damage to target creature with flying. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(1), new ManaCostsImpl<>("{2}{G}")); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SlagdrillScrapper.java b/Mage.Sets/src/mage/cards/s/SlagdrillScrapper.java new file mode 100644 index 00000000000..0f599097279 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SlagdrillScrapper.java @@ -0,0 +1,60 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SlagdrillScrapper extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("another artifact or land"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.LAND.getPredicate() + )); + } + + public SlagdrillScrapper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{R}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // {2}, {T}, Sacrifice another artifact or land: Draw a card. + Ability ability = new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(1), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(filter)); + this.addAbility(ability); + } + + private SlagdrillScrapper(final SlagdrillScrapper card) { + super(card); + } + + @Override + public SlagdrillScrapper copy() { + return new SlagdrillScrapper(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SledgeClassSeedship.java b/Mage.Sets/src/mage/cards/s/SledgeClassSeedship.java new file mode 100644 index 00000000000..dfbf4928bfb --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SledgeClassSeedship.java @@ -0,0 +1,49 @@ +package mage.cards.s; + +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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 SledgeClassSeedship extends CardImpl { + + public SledgeClassSeedship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{G}"); + + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 7+ + // Flying + // Whenever this Spacecraft attacks, you may put a creature from your hand onto the battlefield. + // 4/5 + this.addAbility(new StationLevelAbility(7) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(new AttacksTriggeredAbility( + new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_CREATURE_A) + )) + .withPT(4, 5)); + } + + private SledgeClassSeedship(final SledgeClassSeedship card) { + super(card); + } + + @Override + public SledgeClassSeedship copy() { + return new SledgeClassSeedship(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SmashToSmithereens.java b/Mage.Sets/src/mage/cards/s/SmashToSmithereens.java index 02829adafe0..883cac3e083 100644 --- a/Mage.Sets/src/mage/cards/s/SmashToSmithereens.java +++ b/Mage.Sets/src/mage/cards/s/SmashToSmithereens.java @@ -1,6 +1,5 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -13,6 +12,8 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; +import java.util.UUID; + /** * * @author jonubuu @@ -42,7 +43,7 @@ class SmashToSmithereensEffect extends OneShotEffect { SmashToSmithereensEffect() { super(Outcome.Detriment); - staticText = "Destroy target artifact. Smash to Smithereens deals 3 damage to that artifact's controller"; + staticText = "Destroy target artifact. {this} deals 3 damage to that artifact's controller"; } private SmashToSmithereensEffect(final SmashToSmithereensEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/Smokestack.java b/Mage.Sets/src/mage/cards/s/Smokestack.java index 889eef8da55..eb3855151a7 100644 --- a/Mage.Sets/src/mage/cards/s/Smokestack.java +++ b/Mage.Sets/src/mage/cards/s/Smokestack.java @@ -2,9 +2,9 @@ package mage.cards.s; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -48,7 +48,7 @@ class SmokestackEffect extends OneShotEffect { SmokestackEffect() { super(Outcome.Sacrifice); - this.staticText = "that player sacrifices a permanent for each soot counter on Smokestack"; + this.staticText = "that player sacrifices a permanent of their choice for each soot counter on {this}"; } private SmokestackEffect(final SmokestackEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/Snarespinner.java b/Mage.Sets/src/mage/cards/s/Snarespinner.java index 0715e6da8e2..42ee05e4e01 100644 --- a/Mage.Sets/src/mage/cards/s/Snarespinner.java +++ b/Mage.Sets/src/mage/cards/s/Snarespinner.java @@ -3,15 +3,13 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.common.BlocksCreatureTriggeredAbility; import mage.abilities.effects.common.continuous.BoostSourceEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import java.util.UUID; @@ -20,12 +18,6 @@ import java.util.UUID; */ public final class Snarespinner extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Snarespinner(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); @@ -37,7 +29,7 @@ public final class Snarespinner extends CardImpl { this.addAbility(ReachAbility.getInstance()); // Whenever Snarespinner blocks a creature with flying, Snarespinner gets +2/+0 until end of turn. - this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn), filter, false)); + this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn), StaticFilters.FILTER_CREATURE_FLYING, false)); } private Snarespinner(final Snarespinner card) { @@ -48,4 +40,4 @@ public final class Snarespinner extends CardImpl { public Snarespinner copy() { return new Snarespinner(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/s/SnowVilliers.java b/Mage.Sets/src/mage/cards/s/SnowVilliers.java index 8e966163dfe..cc2d6dca596 100644 --- a/Mage.Sets/src/mage/cards/s/SnowVilliers.java +++ b/Mage.Sets/src/mage/cards/s/SnowVilliers.java @@ -32,7 +32,7 @@ public final class SnowVilliers extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // Snow Villiers's power is equal to the number of creatures you control. - this.addAbility(new SimpleStaticAbility(new SetBasePowerSourceEffect(CreaturesYouControlCount.instance))); + this.addAbility(new SimpleStaticAbility(new SetBasePowerSourceEffect(CreaturesYouControlCount.PLURAL))); } private SnowVilliers(final SnowVilliers card) { diff --git a/Mage.Sets/src/mage/cards/s/SolarArray.java b/Mage.Sets/src/mage/cards/s/SolarArray.java new file mode 100644 index 00000000000..7d974288bbd --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SolarArray.java @@ -0,0 +1,95 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.CopyNextSpellDelayedTriggeredAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.keyword.SunburstAbility; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; + +import java.util.UUID; + +/** + * @author TheElk801, PurpleCrowbar + */ +public final class SolarArray extends CardImpl { + + public SolarArray(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // {T}: Add one mana of any color. When you next cast an artifact spell this turn, that spell gains sunburst. + AnyColorManaAbility ability = new AnyColorManaAbility(); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new CopyNextSpellDelayedTriggeredAbility( + StaticFilters.FILTER_SPELL_AN_ARTIFACT, new SolarArrayEffect(), + "When you next cast an artifact spell this turn, that spell gains sunburst." + ))); + ability.setUndoPossible(false); + this.addAbility(ability); + } + + private SolarArray(final SolarArray card) { + super(card); + } + + @Override + public SolarArray copy() { + return new SolarArray(this); + } +} + +class SolarArrayEffect extends ContinuousEffectImpl { + + private UUID permanentId; + private int zoneChangeCounter; + + SolarArrayEffect() { + super(Duration.OneUse, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + staticText = "that spell gains sunburst"; + } + + private SolarArrayEffect(final SolarArrayEffect effect) { + super(effect); + this.permanentId = effect.permanentId; + this.zoneChangeCounter = effect.zoneChangeCounter; + } + + @Override + public SolarArrayEffect copy() { + return new SolarArrayEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + Spell object = game.getSpell(getTargetPointer().getFirst(game, source)); + if (object != null) { + permanentId = object.getSourceId(); + zoneChangeCounter = game.getState().getZoneChangeCounter(object.getSourceId()) + 1; + } + } + + @Override + public boolean apply(Game game, Ability source) { + if (game.getState().getZoneChangeCounter(permanentId) >= zoneChangeCounter) { + discard(); + return false; + } + Permanent permanent = game.getPermanent(permanentId); + if (permanent != null && permanent.getZoneChangeCounter(game) <= zoneChangeCounter) { + permanent.addAbility(new SunburstAbility(permanent), source.getSourceId(), game); + return true; + } + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); + if (spell != null) { + game.getState().addOtherAbility(spell.getCard(), new SunburstAbility(spell), true); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SonicTheHedgehog.java b/Mage.Sets/src/mage/cards/s/SonicTheHedgehog.java index aa5ee9e48c8..6007ee92063 100644 --- a/Mage.Sets/src/mage/cards/s/SonicTheHedgehog.java +++ b/Mage.Sets/src/mage/cards/s/SonicTheHedgehog.java @@ -1,8 +1,8 @@ package mage.cards.s; import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.DealtDamageAnyTriggeredAbility; -import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.counter.AddCountersAllEffect; import mage.abilities.keyword.FlashAbility; @@ -50,7 +50,7 @@ public final class SonicTheHedgehog extends CardImpl { this.addAbility(HasteAbility.getInstance()); // Gotta Go Fast -- Whenever Sonic the Hedgehog attacks, put a +1/+1 counter on each creature you control with flash or haste. - this.addAbility(new EntersBattlefieldTriggeredAbility( + this.addAbility(new AttacksTriggeredAbility( new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter) ).withFlavorWord("Gotta Go Fast")); diff --git a/Mage.Sets/src/mage/cards/s/SoulSeizer.java b/Mage.Sets/src/mage/cards/s/SoulSeizer.java index f7e7b786183..5dc4a1fe428 100644 --- a/Mage.Sets/src/mage/cards/s/SoulSeizer.java +++ b/Mage.Sets/src/mage/cards/s/SoulSeizer.java @@ -2,7 +2,8 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TransformAbility; @@ -11,16 +12,11 @@ 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.filter.StaticFilters; import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; -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.ThatPlayerControlsTargetAdjuster; import java.util.UUID; @@ -42,7 +38,11 @@ public final class SoulSeizer extends CardImpl { // When Soul Seizer deals combat damage to a player, you may transform it. If you do, attach it to target creature that player controls. this.addAbility(new TransformAbility()); - this.addAbility(new SoulSeizerTriggeredAbility()); + TriggeredAbility ability = new DealsCombatDamageToAPlayerTriggeredAbility(new SoulSeizerEffect(), true, true); + ability.setTriggerPhrase("When {this} deals combat damage to a player, "); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE)); + ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); + this.addAbility(ability); } private SoulSeizer(final SoulSeizer card) { @@ -55,53 +55,11 @@ public final class SoulSeizer extends CardImpl { } } -class SoulSeizerTriggeredAbility extends TriggeredAbilityImpl { - - public SoulSeizerTriggeredAbility() { - super(Zone.BATTLEFIELD, new SoulSeizerEffect(), true); - } - - private SoulSeizerTriggeredAbility(final SoulSeizerTriggeredAbility ability) { - super(ability); - } - - @Override - public SoulSeizerTriggeredAbility copy() { - return new SoulSeizerTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event; - if (damageEvent.isCombatDamage() && event.getSourceId().equals(this.getSourceId())) { - Player opponent = game.getPlayer(event.getPlayerId()); - if (opponent != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("creature " + opponent.getLogName() + " controls"); - filter.add(new ControllerIdPredicate(opponent.getId())); - - this.getTargets().clear(); - this.addTarget(new TargetPermanent(filter)); - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "When {this} deals combat damage to a player, you may transform it. If you do, attach it to target creature that player controls"; - } -} - class SoulSeizerEffect extends OneShotEffect { SoulSeizerEffect() { super(Outcome.GainControl); + this.staticText = "you may transform it. If you do, attach it to target creature that player controls"; } private SoulSeizerEffect(final SoulSeizerEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/Soulblast.java b/Mage.Sets/src/mage/cards/s/Soulblast.java index 176acb76271..017e3f05fce 100644 --- a/Mage.Sets/src/mage/cards/s/Soulblast.java +++ b/Mage.Sets/src/mage/cards/s/Soulblast.java @@ -46,7 +46,7 @@ class SoulblastEffect extends OneShotEffect { SoulblastEffect() { super(Outcome.Benefit); - this.staticText = "Soulblast deals damage to any target equal to the total power of the sacrificed creatures"; + this.staticText = "{this} deals damage to any target equal to the total power of the sacrificed creatures"; } private SoulblastEffect(final SoulblastEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/SoulsurgeElemental.java b/Mage.Sets/src/mage/cards/s/SoulsurgeElemental.java index 788f4f69fe9..e7982a0004b 100644 --- a/Mage.Sets/src/mage/cards/s/SoulsurgeElemental.java +++ b/Mage.Sets/src/mage/cards/s/SoulsurgeElemental.java @@ -31,7 +31,7 @@ public final class SoulsurgeElemental extends CardImpl { this.addAbility(FirstStrikeAbility.getInstance()); // Soulsurge Elemental's power is equal to the number of creatures you control. - Effect effect = new SetBasePowerSourceEffect(CreaturesYouControlCount.instance); + Effect effect = new SetBasePowerSourceEffect(CreaturesYouControlCount.PLURAL); this.addAbility(new SimpleStaticAbility(Zone.ALL, effect) .addHint(CreaturesYouControlHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/s/SpaceTimeAnomaly.java b/Mage.Sets/src/mage/cards/s/SpaceTimeAnomaly.java new file mode 100644 index 00000000000..04573e267a4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpaceTimeAnomaly.java @@ -0,0 +1,34 @@ +package mage.cards.s; + +import mage.abilities.dynamicvalue.common.ControllerLifeCount; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpaceTimeAnomaly extends CardImpl { + + public SpaceTimeAnomaly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}{U}"); + + // Target player mills cards equal to your life total. + this.getSpellAbility().addEffect(new MillCardsTargetEffect(ControllerLifeCount.instance) + .setText("target player mills cards equal to your life total")); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + private SpaceTimeAnomaly(final SpaceTimeAnomaly card) { + super(card); + } + + @Override + public SpaceTimeAnomaly copy() { + return new SpaceTimeAnomaly(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SparkFiend.java b/Mage.Sets/src/mage/cards/s/SparkFiend.java index 6d72318f696..0f66d2bf910 100644 --- a/Mage.Sets/src/mage/cards/s/SparkFiend.java +++ b/Mage.Sets/src/mage/cards/s/SparkFiend.java @@ -50,7 +50,7 @@ class SparkFiendEffect extends OneShotEffect { SparkFiendEffect() { super(Outcome.Sacrifice); - this.staticText = "roll two six-sided dice. If you rolled 2, 3, or 12, sacrifice Spark Fiend. If you rolled 7 or 11, don't roll dice for Spark Fiend during any of your following upkeeps. If you rolled any other total, note that total"; + this.staticText = "roll two six-sided dice. If you rolled 2, 3, or 12, sacrifice {this}. If you rolled 7 or 11, don't roll dice for {this} during any of your following upkeeps. If you rolled any other total, note that total"; } private SparkFiendEffect(final SparkFiendEffect effect) { @@ -93,7 +93,7 @@ class SparkFiendUpkeepEffect extends OneShotEffect { SparkFiendUpkeepEffect() { super(Outcome.Sacrifice); - this.staticText = "roll two six-sided dice. If you rolled 7, sacrifice Spark Fiend. If you roll the noted total, don't roll dice for Spark Fiend during any of your following upkeeps. Otherwise, do nothing"; + this.staticText = "roll two six-sided dice. If you rolled 7, sacrifice {this}. If you roll the noted total, don't roll dice for {this} during any of your following upkeeps. Otherwise, do nothing"; } private SparkFiendUpkeepEffect(final SparkFiendUpkeepEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/SpecimenFreighter.java b/Mage.Sets/src/mage/cards/s/SpecimenFreighter.java new file mode 100644 index 00000000000..0ff4500a538 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpecimenFreighter.java @@ -0,0 +1,69 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpecimenFreighter extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("non-Spacecraft creatures"); + + static { + filter.add(Predicates.not(SubType.SPACECRAFT.getPredicate())); + } + + public SpecimenFreighter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}{U}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, return up to two target non-Spacecraft creatures to their owners' hands. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect()); + ability.addTarget(new TargetPermanent(0, 2, filter)); + this.addAbility(ability); + + // Station + this.addAbility(new StationAbility()); + + // STATION 9+ + // Flying + // Whenever this Spacecraft attacks, defending player mills four cards. + // 4/7 + this.addAbility(new StationLevelAbility(9) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(new AttacksTriggeredAbility( + new MillCardsTargetEffect(4) + .setText("defending player mills four cards"), + false, null, SetTargetPointer.PLAYER + )) + .withPT(4, 7)); + } + + private SpecimenFreighter(final SpecimenFreighter card) { + super(card); + } + + @Override + public SpecimenFreighter copy() { + return new SpecimenFreighter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpellboundDragon.java b/Mage.Sets/src/mage/cards/s/SpellboundDragon.java index 6e7b4347b4e..b23d4187dfe 100644 --- a/Mage.Sets/src/mage/cards/s/SpellboundDragon.java +++ b/Mage.Sets/src/mage/cards/s/SpellboundDragon.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; @@ -12,14 +11,16 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Outcome; +import mage.constants.SubType; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetDiscard; +import java.util.UUID; + /** * * @author jeffwadsworth @@ -54,7 +55,7 @@ class SpellboundDragonEffect extends OneShotEffect { SpellboundDragonEffect() { super(Outcome.BoostCreature); - staticText = "draw a card, then discard a card. Spellbound Dragon gets +X/+0 until end of turn, where X is the discarded card's mana value"; + staticText = "draw a card, then discard a card. {this} gets +X/+0 until end of turn, where X is the discarded card's mana value"; } private SpellboundDragonEffect(final SpellboundDragonEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/SphinxBoneWand.java b/Mage.Sets/src/mage/cards/s/SphinxBoneWand.java index 7fd45681f59..5e51d11c7de 100644 --- a/Mage.Sets/src/mage/cards/s/SphinxBoneWand.java +++ b/Mage.Sets/src/mage/cards/s/SphinxBoneWand.java @@ -53,7 +53,7 @@ class SphinxBoneWandEffect extends OneShotEffect { SphinxBoneWandEffect() { super(Outcome.Damage); - this.staticText = "put a charge counter on Sphinx-Bone Wand. If you do, Sphinx-Bone Wand deals damage equal to the number of charge counters on it to any target"; + this.staticText = "put a charge counter on {this}. If you do, {this} deals damage equal to the number of charge counters on it to any target"; } private SphinxBoneWandEffect(final SphinxBoneWandEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/SpittingSpider.java b/Mage.Sets/src/mage/cards/s/SpittingSpider.java index cf98843aa42..df1af4b3881 100644 --- a/Mage.Sets/src/mage/cards/s/SpittingSpider.java +++ b/Mage.Sets/src/mage/cards/s/SpittingSpider.java @@ -1,35 +1,26 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; /** - * * @author Plopman */ public final class SpittingSpider extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - static{ - filter.add(new AbilityPredicate(FlyingAbility.class)); - } public SpittingSpider(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); this.subtype.add(SubType.SPIDER); this.power = new MageInt(3); @@ -37,8 +28,9 @@ public final class SpittingSpider extends CardImpl { // Reach this.addAbility(ReachAbility.getInstance()); + // Sacrifice a land: Spitting Spider deals 1 damage to each creature with flying. - this.addAbility(new SimpleActivatedAbility(new DamageAllEffect(1, filter), new SacrificeTargetCost(StaticFilters.FILTER_LAND))); + this.addAbility(new SimpleActivatedAbility(new DamageAllEffect(1, StaticFilters.FILTER_CREATURE_FLYING), new SacrificeTargetCost(StaticFilters.FILTER_LAND))); } private SpittingSpider(final SpittingSpider card) { diff --git a/Mage.Sets/src/mage/cards/s/Squall.java b/Mage.Sets/src/mage/cards/s/Squall.java index e12e5e639b0..10665864247 100644 --- a/Mage.Sets/src/mage/cards/s/Squall.java +++ b/Mage.Sets/src/mage/cards/s/Squall.java @@ -1,33 +1,24 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LoneFox - */ public final class Squall extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Squall(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); // Squall deals 2 damage to each creature with flying. - this.getSpellAbility().addEffect(new DamageAllEffect(2, filter)); + this.getSpellAbility().addEffect(new DamageAllEffect(2, StaticFilters.FILTER_CREATURE_FLYING)); } private Squall(final Squall card) { diff --git a/Mage.Sets/src/mage/cards/s/SquallLine.java b/Mage.Sets/src/mage/cards/s/SquallLine.java index cc3faa1929d..328faf84b5b 100644 --- a/Mage.Sets/src/mage/cards/s/SquallLine.java +++ b/Mage.Sets/src/mage/cards/s/SquallLine.java @@ -1,33 +1,26 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageEverythingEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LoneFox */ public final class SquallLine extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public SquallLine(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{X}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{G}{G}"); // Squall Line deals X damage to each creature with flying and each player. - this.getSpellAbility().addEffect(new DamageEverythingEffect(GetXValue.instance, filter)); } + this.getSpellAbility().addEffect(new DamageEverythingEffect(GetXValue.instance, StaticFilters.FILTER_CREATURE_FLYING)); + } private SquallLine(final SquallLine card) { super(card); diff --git a/Mage.Sets/src/mage/cards/s/Squallmonger.java b/Mage.Sets/src/mage/cards/s/Squallmonger.java index e23adb6b275..a1f21a30e67 100644 --- a/Mage.Sets/src/mage/cards/s/Squallmonger.java +++ b/Mage.Sets/src/mage/cards/s/Squallmonger.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -9,36 +8,28 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.effects.common.InfoEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.TargetController; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author fireshoes */ public final class Squallmonger extends CardImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } public Squallmonger(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); this.subtype.add(SubType.MONGER); this.power = new MageInt(3); this.toughness = new MageInt(3); // {2}: Squallmonger deals 1 damage to each creature with flying and each player. Any player may activate this ability. - SimpleActivatedAbility ability = new SimpleActivatedAbility(new DamageAllEffect(1, filter), new ManaCostsImpl<>("{2}")); + SimpleActivatedAbility ability = new SimpleActivatedAbility(new DamageAllEffect(1, StaticFilters.FILTER_CREATURE_FLYING), new ManaCostsImpl<>("{2}")); Effect effect = new DamagePlayersEffect(1); effect.setText("and each player"); ability.addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/s/SquiresLightblade.java b/Mage.Sets/src/mage/cards/s/SquiresLightblade.java new file mode 100644 index 00000000000..e684763a980 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SquiresLightblade.java @@ -0,0 +1,52 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAttachToTarget; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.FirstStrikeAbility; +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 SquiresLightblade extends CardImpl { + + public SquiresLightblade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When this Equipment enters, attach it to target creature you control. That creature gains first strike until end of turn. + Ability ability = new EntersBattlefieldAttachToTarget(); + ability.addEffect(new GainAbilityTargetEffect(FirstStrikeAbility.getInstance()) + .setText("That creature gains first strike until end of turn")); + this.addAbility(ability); + + // Equipped creature gets +1/+0. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(1, 0))); + + // Equip 3 + this.addAbility(new EquipAbility(3)); + } + + private SquiresLightblade(final SquiresLightblade card) { + super(card); + } + + @Override + public SquiresLightblade copy() { + return new SquiresLightblade(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StadiumHeadliner.java b/Mage.Sets/src/mage/cards/s/StadiumHeadliner.java index 9cf0ad36514..3ce44dbd3fc 100644 --- a/Mage.Sets/src/mage/cards/s/StadiumHeadliner.java +++ b/Mage.Sets/src/mage/cards/s/StadiumHeadliner.java @@ -6,7 +6,6 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; -import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.MobilizeAbility; @@ -35,7 +34,7 @@ public final class StadiumHeadliner extends CardImpl { this.addAbility(new MobilizeAbility(1)); // {1}{R}, Sacrifice this creature: It deals damage equal to the number of creatures you control to target creature. - Effect effect = new DamageTargetEffect(CreaturesYouControlCount.instance); + Effect effect = new DamageTargetEffect(CreaturesYouControlCount.PLURAL); effect.setText("It deals damage equal to the number of creatures you control to target creature"); Ability ability = new SimpleActivatedAbility(effect, new ManaCostsImpl<>("{1}{R}")); ability.addCost(new SacrificeSourceCost()); diff --git a/Mage.Sets/src/mage/cards/s/StarbreachWhale.java b/Mage.Sets/src/mage/cards/s/StarbreachWhale.java new file mode 100644 index 00000000000..ccc70cc47e2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StarbreachWhale.java @@ -0,0 +1,45 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WarpAbility; +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 StarbreachWhale extends CardImpl { + + public StarbreachWhale(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.WHALE); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, surveil 2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SurveilEffect(2))); + + // Warp {1}{U} + this.addAbility(new WarpAbility(this, "{1}{U}")); + } + + private StarbreachWhale(final StarbreachWhale card) { + super(card); + } + + @Override + public StarbreachWhale copy() { + return new StarbreachWhale(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StarfieldShepherd.java b/Mage.Sets/src/mage/cards/s/StarfieldShepherd.java new file mode 100644 index 00000000000..ae1dbb445fb --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StarfieldShepherd.java @@ -0,0 +1,68 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StarfieldShepherd extends CardImpl { + + private static final FilterCard filter = new FilterCard("a basic Plains card or a creature card with mana value 1 or less"); + + static { + filter.add(Predicates.or( + Predicates.and( + SuperType.BASIC.getPredicate(), + SubType.PLAINS.getPredicate() + ), + Predicates.and( + CardType.CREATURE.getPredicate(), + new ManaValuePredicate(ComparisonType.FEWER_THAN, 2) + ) + )); + } + + public StarfieldShepherd(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, search your library for a basic Plains card or a creature card with mana value 1 or less, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + )); + + // Warp {1}{W} + this.addAbility(new WarpAbility(this, "{1}{W}")); + } + + private StarfieldShepherd(final StarfieldShepherd card) { + super(card); + } + + @Override + public StarfieldShepherd copy() { + return new StarfieldShepherd(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StarfieldVocalist.java b/Mage.Sets/src/mage/cards/s/StarfieldVocalist.java new file mode 100644 index 00000000000..274a8fd3644 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StarfieldVocalist.java @@ -0,0 +1,92 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.keyword.WarpAbility; +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.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.events.NumberOfTriggersEvent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StarfieldVocalist extends CardImpl { + + public StarfieldVocalist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.BARD); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. + this.addAbility(new SimpleStaticAbility(new StarfieldVocalistEffect())); + + // Warp {1}{U} + this.addAbility(new WarpAbility(this, "{1}{U}")); + } + + private StarfieldVocalist(final StarfieldVocalist card) { + super(card); + } + + @Override + public StarfieldVocalist copy() { + return new StarfieldVocalist(this); + } +} + +class StarfieldVocalistEffect extends ReplacementEffectImpl { + + StarfieldVocalistEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "if a permanent entering the battlefield causes a triggered ability of " + + "a permanent you control to trigger, that ability triggers an additional time"; + } + + private StarfieldVocalistEffect(final StarfieldVocalistEffect effect) { + super(effect); + } + + @Override + public StarfieldVocalistEffect copy() { + return new StarfieldVocalistEffect(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) { + return source.isControlledBy(event.getPlayerId()) + && Optional + .ofNullable(event) + .filter(NumberOfTriggersEvent.class::isInstance) + .map(NumberOfTriggersEvent.class::cast) + .map(NumberOfTriggersEvent::getSourceEvent) + .filter(EntersTheBattlefieldEvent.class::isInstance) + .isPresent() + && game.getPermanent(event.getSourceId()) != null; + } + + @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/s/StarfighterPilot.java b/Mage.Sets/src/mage/cards/s/StarfighterPilot.java new file mode 100644 index 00000000000..60133be6b99 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StarfighterPilot.java @@ -0,0 +1,38 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StarfighterPilot extends CardImpl { + + public StarfighterPilot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PILOT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever this creature becomes tapped, surveil 1. + this.addAbility(new BecomesTappedSourceTriggeredAbility(new SurveilEffect(1))); + } + + private StarfighterPilot(final StarfighterPilot card) { + super(card); + } + + @Override + public StarfighterPilot copy() { + return new StarfighterPilot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StarportSecurity.java b/Mage.Sets/src/mage/cards/s/StarportSecurity.java new file mode 100644 index 00000000000..1d23c61d4df --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StarportSecurity.java @@ -0,0 +1,82 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StarportSecurity extends CardImpl { + + public StarportSecurity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // {3}{W}, {T}: Tap another target creature. This ability costs {2} less to activate if you control a creature with a +1/+1 counter on it. + Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new ManaCostsImpl<>("{3}{W}")); + ability.addCost(new TapSourceCost()); + ability.addEffect(new InfoEffect("This ability costs {2} less to activate if you control a creature with a +1/+1 counter on it")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE)); + this.addAbility(ability.setCostAdjuster(StarportSecurityAdjuster.instance).addHint(StarportSecurityAdjuster.getHint())); + } + + private StarportSecurity(final StarportSecurity card) { + super(card); + } + + @Override + public StarportSecurity copy() { + return new StarportSecurity(this); + } +} + +enum StarportSecurityAdjuster implements CostAdjuster { + instance; + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(CounterType.P1P1.getPredicate()); + } + + private static final Hint hint = new ConditionHint( + new PermanentsOnTheBattlefieldCondition(filter), + "You control a creature with a +1/+1 counter on it" + ); + + public static Hint getHint() { + return hint; + } + + @Override + public void reduceCost(Ability ability, Game game) { + if (game.getBattlefield().contains(filter, ability, game, 1)) { + CardUtil.reduceCost(ability, 2); + } + } +} diff --git a/Mage.Sets/src/mage/cards/s/Starwinder.java b/Mage.Sets/src/mage/cards/s/Starwinder.java new file mode 100644 index 00000000000..f964819a60b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Starwinder.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Starwinder extends CardImpl { + + public Starwinder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); + + this.subtype.add(SubType.LEVIATHAN); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Whenever a creature you control deals combat damage to a player, you may draw that many cards. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new DrawCardSourceControllerEffect(SavedDamageValue.MANY), + StaticFilters.FILTER_CONTROLLED_CREATURE, + true, SetTargetPointer.NONE, true + )); + + // Warp {2}{U}{U} + this.addAbility(new WarpAbility(this, "{2}{U}{U}")); + } + + private Starwinder(final Starwinder card) { + super(card); + } + + @Override + public Starwinder copy() { + return new Starwinder(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StaticOrb.java b/Mage.Sets/src/mage/cards/s/StaticOrb.java index f5027681dd5..c9072dd1181 100644 --- a/Mage.Sets/src/mage/cards/s/StaticOrb.java +++ b/Mage.Sets/src/mage/cards/s/StaticOrb.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.RestrictionUntapNotMoreThanEffect; @@ -9,12 +8,13 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** * * @author LevelX2 @@ -45,7 +45,7 @@ class StaticOrbEffect extends RestrictionUntapNotMoreThanEffect { public StaticOrbEffect() { super(Duration.WhileOnBattlefield, 2, filter); - staticText = "As long as Static Orb is untapped, players can't untap more than two permanents during their untap steps"; + staticText = "As long as {this} is untapped, players can't untap more than two permanents during their untap steps"; } private StaticOrbEffect(final StaticOrbEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/StationMonitor.java b/Mage.Sets/src/mage/cards/s/StationMonitor.java new file mode 100644 index 00000000000..d82f0d5f1ee --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StationMonitor.java @@ -0,0 +1,39 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +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.DroneToken2; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StationMonitor extends CardImpl { + + public StationMonitor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}"); + + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you cast your second spell each turn, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying." + this.addAbility(new CastSecondSpellTriggeredAbility(new CreateTokenEffect(new DroneToken2()))); + } + + private StationMonitor(final StationMonitor card) { + super(card); + } + + @Override + public StationMonitor copy() { + return new StationMonitor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SteelswarmOperator.java b/Mage.Sets/src/mage/cards/s/SteelswarmOperator.java new file mode 100644 index 00000000000..5e859e7c9a2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SteelswarmOperator.java @@ -0,0 +1,133 @@ +package mage.cards.s; + +import mage.ConditionalMana; +import mage.MageInt; +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.Cost; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.mana.ConditionalColoredManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.abilities.mana.conditional.ManaCondition; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SteelswarmOperator extends CardImpl { + + public SteelswarmOperator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // {T}: Add {U}. Spend this mana only to cast an artifact spell. + this.addAbility(new ConditionalColoredManaAbility(Mana.BlueMana(1), new SteelswarmOperatorSpellManaBuilder())); + + // {T}: Add {U}{U}. Spend this mana only to activate abilities of artifact sources. + this.addAbility(new ConditionalColoredManaAbility(Mana.BlueMana(2), new SteelswarmOperatorAbilitiesManaBuilder())); + } + + private SteelswarmOperator(final SteelswarmOperator card) { + super(card); + } + + @Override + public SteelswarmOperator copy() { + return new SteelswarmOperator(this); + } +} + +class SteelswarmOperatorSpellManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new SteelswarmOperatorSpellConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to cast an artifact spell"; + } +} + +class SteelswarmOperatorSpellConditionalMana extends ConditionalMana { + + public SteelswarmOperatorSpellConditionalMana(Mana mana) { + super(mana); + staticText = "Spend this mana only to cast an artifact spell"; + addCondition(new SteelswarmOperatorSpellManaCondition()); + } +} + +class SteelswarmOperatorSpellManaCondition extends ManaCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = game.getObject(source); + return source instanceof SpellAbility + && !source.isActivated() + && sourceObject != null + && sourceObject.isArtifact(game); + } + + @Override + public boolean apply(Game game, Ability source, UUID originalId, Cost costToPay) { + return apply(game, source); + } +} + +class SteelswarmOperatorAbilitiesManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new SteelswarmOperatorAbilitiesConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to activate abilities of artifact sources"; + } +} + +class SteelswarmOperatorAbilitiesConditionalMana extends ConditionalMana { + + public SteelswarmOperatorAbilitiesConditionalMana(Mana mana) { + super(mana); + staticText = "Spend this mana only to activate abilities of artifact sources"; + addCondition(new SteelswarmOperatorAbilitiesManaCondition()); + } +} + +class SteelswarmOperatorAbilitiesManaCondition extends ManaCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = game.getObject(source); + return source != null + && !source.isActivated() + && source.isActivatedAbility() + && sourceObject != null + && sourceObject.isArtifact(game); + } + + @Override + public boolean apply(Game game, Ability source, UUID originalId, Cost costToPay) { + return apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StingerflingSpider.java b/Mage.Sets/src/mage/cards/s/StingerflingSpider.java index 2e38bd05cba..48499eabcd7 100644 --- a/Mage.Sets/src/mage/cards/s/StingerflingSpider.java +++ b/Mage.Sets/src/mage/cards/s/StingerflingSpider.java @@ -2,42 +2,34 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** * @author Loki */ public final class StingerflingSpider extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public StingerflingSpider(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); this.subtype.add(SubType.SPIDER); this.power = new MageInt(2); this.toughness = new MageInt(5); this.addAbility(ReachAbility.getInstance()); Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect(), true); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/StingingShot.java b/Mage.Sets/src/mage/cards/s/StingingShot.java index b3fb6f6d770..24e00163a23 100644 --- a/Mage.Sets/src/mage/cards/s/StingingShot.java +++ b/Mage.Sets/src/mage/cards/s/StingingShot.java @@ -1,38 +1,29 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.keyword.CyclingAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.counters.CounterType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author stravant */ public final class StingingShot extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public StingingShot(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); // Put three -1/-1 counters on target creature with flying. - getSpellAbility().addTarget(new TargetPermanent(filter)); getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.M1M1.createInstance(3))); + getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); // Cycling {2} this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}"))); diff --git a/Mage.Sets/src/mage/cards/s/StolenUniform.java b/Mage.Sets/src/mage/cards/s/StolenUniform.java index 7eb0c03ed8a..3983bea0a08 100644 --- a/Mage.Sets/src/mage/cards/s/StolenUniform.java +++ b/Mage.Sets/src/mage/cards/s/StolenUniform.java @@ -80,7 +80,7 @@ class StolenUniformAttachEffect extends OneShotEffect { .map(game::getPermanent) .filter(Objects::nonNull) .collect(Collectors.toList()); - return permanents.size() >= 2 && permanents.get(1).addAttachment(permanents.get(0).getId(), source, game); + return permanents.size() >= 2 && permanents.get(0).addAttachment(permanents.get(1).getId(), source, game); } } @@ -116,16 +116,19 @@ class StolenUniformTriggerEffect extends OneShotEffect { class StolenUniformTriggeredAbility extends DelayedTriggeredAbility { private final MageObjectReference mor; + private final int turnNum; StolenUniformTriggeredAbility(Permanent permanent, Game game) { - super(new StolenUniformUnattachEffect(permanent, game), Duration.EndOfTurn, true, false); + super(new StolenUniformUnattachEffect(permanent, game), Duration.Custom, false, false); setTriggerPhrase("When you lose control of that Equipment this turn, "); this.mor = new MageObjectReference(permanent, game); + this.turnNum = game.getTurnNum(); } private StolenUniformTriggeredAbility(final StolenUniformTriggeredAbility ability) { super(ability); this.mor = ability.mor; + this.turnNum = ability.turnNum; } @Override @@ -140,6 +143,11 @@ class StolenUniformTriggeredAbility extends DelayedTriggeredAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { + // We can't use end of turn duration as losing control happens after the trigger has gone away, so we need this workaround + if (game.getTurnNum() > this.turnNum) { + this.setDuration(Duration.EndOfTurn); + return false; + } return isControlledBy(event.getPlayerId()) && mor.zoneCounterIsCurrent(game) && event.getTargetId().equals(mor.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/s/StormElemental.java b/Mage.Sets/src/mage/cards/s/StormElemental.java index 3f793bcb1d8..47d2b6053c8 100644 --- a/Mage.Sets/src/mage/cards/s/StormElemental.java +++ b/Mage.Sets/src/mage/cards/s/StormElemental.java @@ -14,13 +14,11 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterLandCard; -import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.Game; import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -29,12 +27,6 @@ import java.util.UUID; */ public final class StormElemental extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public StormElemental(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}"); this.subtype.add(SubType.ELEMENTAL); @@ -47,7 +39,7 @@ public final class StormElemental extends CardImpl { // {U}, Exile the top card of your library: Tap target creature with flying. Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new ManaCostsImpl<>("{U}")); ability.addCost(new ExileTopCardLibraryCost()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); // {U}, Exile the top card of your library: If the exiled card is a snow land, Storm Elemental gets +1/+1 until end of turn. diff --git a/Mage.Sets/src/mage/cards/s/StormFront.java b/Mage.Sets/src/mage/cards/s/StormFront.java index 6ab00a62498..bcf3cc11526 100644 --- a/Mage.Sets/src/mage/cards/s/StormFront.java +++ b/Mage.Sets/src/mage/cards/s/StormFront.java @@ -1,39 +1,29 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.TapTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author KholdFuzion */ public final class StormFront extends CardImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } public StormFront(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}"); // {G}{G}: Tap target creature with flying. Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new ManaCostsImpl<>("{G}{G}")); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/StormchaserChimera.java b/Mage.Sets/src/mage/cards/s/StormchaserChimera.java index a8f25dec02d..fcd3024d338 100644 --- a/Mage.Sets/src/mage/cards/s/StormchaserChimera.java +++ b/Mage.Sets/src/mage/cards/s/StormchaserChimera.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -10,20 +9,17 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.keyword.FlyingAbility; -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.Duration; 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.UUID; + /** * * @author LevelX2 @@ -59,7 +55,7 @@ class StormchaserChimeraEffect extends OneShotEffect { StormchaserChimeraEffect() { super(Outcome.Benefit); - this.staticText = ", then reveal the top card of your library. Stormchaser Chimera gets +X/+0 until end of turn, where X is that card's mana value"; + this.staticText = ", then reveal the top card of your library. {this} gets +X/+0 until end of turn, where X is that card's mana value"; } private StormchaserChimeraEffect(final StormchaserChimeraEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/StragoAndRelm.java b/Mage.Sets/src/mage/cards/s/StragoAndRelm.java index ef49bd3a27c..d7f26bdefe0 100644 --- a/Mage.Sets/src/mage/cards/s/StragoAndRelm.java +++ b/Mage.Sets/src/mage/cards/s/StragoAndRelm.java @@ -115,7 +115,7 @@ class StragoAndRelmEffect extends OneShotEffect { .map(Controllable::getControllerId) .map(game::getPlayer) .ifPresent(player -> CardUtil.castSpellWithAttributesForFree( - opponent, source, game, new CardsImpl(card), + player, source, game, new CardsImpl(card), StaticFilters.FILTER_CARD, StragoAndRelmTracker.instance )); return true; diff --git a/Mage.Sets/src/mage/cards/s/StruggleSurvive.java b/Mage.Sets/src/mage/cards/s/StruggleSurvive.java index 8359745a101..0ae2fefafb6 100644 --- a/Mage.Sets/src/mage/cards/s/StruggleSurvive.java +++ b/Mage.Sets/src/mage/cards/s/StruggleSurvive.java @@ -1,6 +1,5 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.Effect; @@ -17,6 +16,8 @@ import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author LevelX2 @@ -29,7 +30,7 @@ public final class StruggleSurvive extends SplitCard { // Struggle // Struggle deals damage to target creature equal to the number of lands you control. Effect effect = new DamageTargetEffect(new PermanentsOnBattlefieldCount(new FilterControlledLandPermanent("the number of lands you control"))); - effect.setText("Struggle deals damage to target creature equal to the number of lands you control"); + effect.setText("{this} deals damage to target creature equal to the number of lands you control"); getLeftHalfCard().getSpellAbility().addEffect(effect); getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/s/SulfurousBlast.java b/Mage.Sets/src/mage/cards/s/SulfurousBlast.java index c45e6aa7ae5..188707472d1 100644 --- a/Mage.Sets/src/mage/cards/s/SulfurousBlast.java +++ b/Mage.Sets/src/mage/cards/s/SulfurousBlast.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.abilities.condition.common.AddendumCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.DamageEverythingEffect; @@ -9,6 +8,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import java.util.UUID; + /** * * @author fireshoes @@ -23,7 +24,7 @@ public final class SulfurousBlast extends CardImpl { new DamageEverythingEffect(3), new DamageEverythingEffect(2), AddendumCondition.instance, - "Sulfurous Blast deals 2 damage to each creature and each player. If you cast this spell during your main phase, Sulfurous Blast deals 3 damage to each creature and each player instead")); + "{this} deals 2 damage to each creature and each player. If you cast this spell during your main phase, {this} deals 3 damage to each creature and each player instead")); } private SulfurousBlast(final SulfurousBlast card) { diff --git a/Mage.Sets/src/mage/cards/s/Sunforger.java b/Mage.Sets/src/mage/cards/s/Sunforger.java index 44d9e4d2c21..17f7c9b95a6 100644 --- a/Mage.Sets/src/mage/cards/s/Sunforger.java +++ b/Mage.Sets/src/mage/cards/s/Sunforger.java @@ -1,6 +1,5 @@ package mage.cards.s; -import java.util.UUID; import mage.ApprovingObject; import mage.ObjectColor; import mage.abilities.Ability; @@ -17,12 +16,7 @@ import mage.abilities.keyword.EquipAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterCard; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; @@ -33,6 +27,8 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** * * @author jeffwadsworth @@ -52,7 +48,7 @@ public final class Sunforger extends CardImpl { // without paying its mana cost. Then shuffle your library. Ability ability = new SimpleActivatedAbility( new SunforgerEffect(), new ManaCostsImpl<>("{R}{W}")); - ability.addCost(new SunforgerUnattachCost(this.getName())); + ability.addCost(new SunforgerUnattachCost()); this.addAbility(ability); // Equip {3} @@ -127,8 +123,8 @@ class SunforgerEffect extends OneShotEffect { class SunforgerUnattachCost extends CostImpl { - public SunforgerUnattachCost(String name) { - this.text = "Unattach " + name; + public SunforgerUnattachCost() { + this.text = "Unattach {this}"; } private SunforgerUnattachCost(final SunforgerUnattachCost cost) { diff --git a/Mage.Sets/src/mage/cards/s/SunscapeBattlemage.java b/Mage.Sets/src/mage/cards/s/SunscapeBattlemage.java index 17180abd35d..307e0417057 100644 --- a/Mage.Sets/src/mage/cards/s/SunscapeBattlemage.java +++ b/Mage.Sets/src/mage/cards/s/SunscapeBattlemage.java @@ -7,14 +7,12 @@ import mage.abilities.condition.Condition; import mage.abilities.condition.common.KickedCostCondition; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.KickerAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -24,12 +22,6 @@ import java.util.UUID; */ public final class SunscapeBattlemage extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - private static final Condition condition = new KickedCostCondition("{1}{G}"); private static final Condition condition2 = new KickedCostCondition("{2}{U}"); @@ -47,7 +39,7 @@ public final class SunscapeBattlemage extends CardImpl { // When {this} enters, if it was kicked with its {1}{G} kicker, destroy target creature with flying. Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()).withInterveningIf(condition); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); // When {this} enters, if it was kicked with its {2}{U} kicker, draw two cards. diff --git a/Mage.Sets/src/mage/cards/s/SunsetSaboteur.java b/Mage.Sets/src/mage/cards/s/SunsetSaboteur.java new file mode 100644 index 00000000000..1aa86dc5258 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunsetSaboteur.java @@ -0,0 +1,52 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SunsetSaboteur extends CardImpl { + + public SunsetSaboteur(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(1); + + // Menace + this.addAbility(new MenaceAbility()); + + // Ward--Discard a card. + this.addAbility(new WardAbility(new DiscardCardCost())); + + // Whenever this creature attacks, put a +1/+1 counter on target creature an opponent controls. + Ability ability = new AttacksTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + } + + private SunsetSaboteur(final SunsetSaboteur card) { + super(card); + } + + @Override + public SunsetSaboteur copy() { + return new SunsetSaboteur(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SunsetStrikemaster.java b/Mage.Sets/src/mage/cards/s/SunsetStrikemaster.java index e1c674adb5e..a10c87da0fb 100644 --- a/Mage.Sets/src/mage/cards/s/SunsetStrikemaster.java +++ b/Mage.Sets/src/mage/cards/s/SunsetStrikemaster.java @@ -7,15 +7,12 @@ import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.mana.RedManaAbility; 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.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -25,12 +22,6 @@ import java.util.UUID; */ public final class SunsetStrikemaster extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public SunsetStrikemaster(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); @@ -48,7 +39,7 @@ public final class SunsetStrikemaster extends CardImpl { ); ability.addCost(new TapSourceCost()); ability.addCost(new SacrificeSourceCost()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SunstarChaplain.java b/Mage.Sets/src/mage/cards/s/SunstarChaplain.java new file mode 100644 index 00000000000..8af764494f6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunstarChaplain.java @@ -0,0 +1,58 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.TwoTappedCreaturesCondition; +import mage.abilities.costs.common.RemoveCounterCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SunstarChaplain extends CardImpl { + + public SunstarChaplain(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on target creature you control. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()) + ).withInterveningIf(TwoTappedCreaturesCondition.instance); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability.addHint(TwoTappedCreaturesCondition.getHint())); + + // {2}, Remove a +1/+1 counter from a creature you control: Tap target artifact or creature. + ability = new SimpleActivatedAbility(new TapTargetEffect(), new GenericManaCost(2)); + ability.addCost(new RemoveCounterCost(new TargetControlledCreaturePermanent(), CounterType.P1P1)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); + this.addAbility(ability); + } + + private SunstarChaplain(final SunstarChaplain card) { + super(card); + } + + @Override + public SunstarChaplain copy() { + return new SunstarChaplain(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SunstarExpansionist.java b/Mage.Sets/src/mage/cards/s/SunstarExpansionist.java new file mode 100644 index 00000000000..a0056cb9320 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunstarExpansionist.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LandfallAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.OpponentControlsMoreCondition; +import mage.abilities.effects.common.CreateTokenEffect; +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 mage.game.permanent.token.LanderToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SunstarExpansionist extends CardImpl { + + private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS); + + public SunstarExpansionist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When this creature enters, if an opponent controls more lands than you, create a Lander token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new LanderToken())).withInterveningIf(condition)); + + // Landfall -- Whenever a land you control enters, this creature gets +1/+0 until end of turn. + this.addAbility(new LandfallAbility(new BoostSourceEffect(1, 0, Duration.EndOfTurn))); + } + + private SunstarExpansionist(final SunstarExpansionist card) { + super(card); + } + + @Override + public SunstarExpansionist copy() { + return new SunstarExpansionist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SunstarLightsmith.java b/Mage.Sets/src/mage/cards/s/SunstarLightsmith.java new file mode 100644 index 00000000000..7866797ab6c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunstarLightsmith.java @@ -0,0 +1,43 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.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 SunstarLightsmith extends CardImpl { + + public SunstarLightsmith(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever you cast your second spell each turn, put a +1/+1 counter on this creature and draw a card. + Ability ability = new CastSecondSpellTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private SunstarLightsmith(final SunstarLightsmith card) { + super(card); + } + + @Override + public SunstarLightsmith copy() { + return new SunstarLightsmith(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SurgeConductor.java b/Mage.Sets/src/mage/cards/s/SurgeConductor.java new file mode 100644 index 00000000000..be0d385f7ea --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SurgeConductor.java @@ -0,0 +1,49 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.TokenPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SurgeConductor extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledArtifactPermanent("another nontoken artifact you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TokenPredicate.TRUE); + } + + public SurgeConductor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Whenever another nontoken artifact you control enters, proliferate. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(new ProliferateEffect(), filter)); + } + + private SurgeConductor(final SurgeConductor card) { + super(card); + } + + @Override + public SurgeConductor copy() { + return new SurgeConductor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SurveyMechan.java b/Mage.Sets/src/mage/cards/s/SurveyMechan.java new file mode 100644 index 00000000000..8d8ec09ff60 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SurveyMechan.java @@ -0,0 +1,88 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.GainLifeTargetEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.TargetPlayer; +import mage.target.common.TargetAnyTarget; +import mage.target.targetpointer.SecondTargetPointer; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SurveyMechan extends CardImpl { + + public SurveyMechan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Hexproof + this.addAbility(HexproofAbility.getInstance()); + + // {10}, Sacrifice this creature: It deals 3 damage to any target. Target player draws three cards and gains 3 life. This ability costs {X} less to activate, where X is the number of differently named lands you control. + Ability ability = new SimpleActivatedAbility( + new DamageTargetEffect(3, "it"), new GenericManaCost(10) + ); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetAnyTarget().withChooseHint("to deal damage to")); + ability.addEffect(new DrawCardTargetEffect(3).setTargetPointer(new SecondTargetPointer())); + ability.addEffect(new GainLifeTargetEffect(3).setTargetPointer(new SecondTargetPointer()).setText("and gains 3 life")); + ability.addTarget(new TargetPlayer().withChooseHint("to draw three cards and gain 3 life")); + ability.addEffect(new InfoEffect("This ability costs {X} less to activate, " + + "where X is the number of differently named lands you control")); + this.addAbility(ability.setCostAdjuster(SurveyMechanAdjuster.instance).addHint(SurveyMechanAdjuster.getHint())); + } + + private SurveyMechan(final SurveyMechan card) { + super(card); + } + + @Override + public SurveyMechan copy() { + return new SurveyMechan(this); + } +} + +enum SurveyMechanAdjuster implements CostAdjuster { + instance; + private static final DifferentlyNamedPermanentCount xValue + = new DifferentlyNamedPermanentCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS); + + static Hint getHint() { + return xValue.getHint(); + } + + @Override + public void reduceCost(Ability ability, Game game) { + int amount = xValue.calculate(game, ability, null); + if (amount > 0) { + CardUtil.reduceCost(ability, amount); + } + } +} diff --git a/Mage.Sets/src/mage/cards/s/SusurSecundiVoidAltar.java b/Mage.Sets/src/mage/cards/s/SusurSecundiVoidAltar.java new file mode 100644 index 00000000000..dbecd33dd67 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SusurSecundiVoidAltar.java @@ -0,0 +1,63 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.SacrificeCostCreaturesPower; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.abilities.mana.BlackManaAbility; +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 SusurSecundiVoidAltar extends CardImpl { + + public SusurSecundiVoidAltar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.PLANET); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {B}. + this.addAbility(new BlackManaAbility()); + + // Station + this.addAbility(new StationAbility()); + + // STATION 12+ + // {1}{B}, {T}, Pay 2 life, Sacrifice a creature: Draw cards equal to the sacrificed creature's power. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new DrawCardSourceControllerEffect(SacrificeCostCreaturesPower.instance) + .setText("draw cards equal to the sacrificed creature's power"), + new ManaCostsImpl<>("{1}{B}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new PayLifeCost(2)); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE)); + this.addAbility(new StationLevelAbility(12).withLevelAbility(ability)); + } + + private SusurSecundiVoidAltar(final SusurSecundiVoidAltar card) { + super(card); + } + + @Override + public SusurSecundiVoidAltar copy() { + return new SusurSecundiVoidAltar(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SusurianDirgecraft.java b/Mage.Sets/src/mage/cards/s/SusurianDirgecraft.java new file mode 100644 index 00000000000..7d08c220eb3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SusurianDirgecraft.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.SacrificeOpponentsEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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 SusurianDirgecraft extends CardImpl { + + public SusurianDirgecraft(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{B}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, each opponent sacrifices a nontoken creature. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SacrificeOpponentsEffect(StaticFilters.FILTER_CREATURE_NON_TOKEN) + )); + + // Station + this.addAbility(new StationAbility()); + + // STATION 7+ + // Flying + // 4/3 + this.addAbility(new StationLevelAbility(7) + .withLevelAbility(FlyingAbility.getInstance()) + .withPT(4, 3)); + } + + private SusurianDirgecraft(final SusurianDirgecraft card) { + super(card); + } + + @Override + public SusurianDirgecraft copy() { + return new SusurianDirgecraft(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SusurianVoidborn.java b/Mage.Sets/src/mage/cards/s/SusurianVoidborn.java new file mode 100644 index 00000000000..445070ade92 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SusurianVoidborn.java @@ -0,0 +1,53 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesThisOrAnotherTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SusurianVoidborn extends CardImpl { + + public SusurianVoidborn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever this creature or another creature or artifact you control dies, target opponent loses 1 life and you gain 1 life. + Ability ability = new DiesThisOrAnotherTriggeredAbility( + new LoseLifeTargetEffect(1), false, + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT + ); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // Warp {B} + this.addAbility(new WarpAbility(this, "{B}")); + + } + + private SusurianVoidborn(final SusurianVoidborn card) { + super(card); + } + + @Override + public SusurianVoidborn copy() { + return new SusurianVoidborn(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SwarmCuller.java b/Mage.Sets/src/mage/cards/s/SwarmCuller.java new file mode 100644 index 00000000000..8e063f6c058 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SwarmCuller.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +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.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SwarmCuller extends CardImpl { + + public SwarmCuller(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever this creature becomes tapped, you may sacrifice another creature or artifact. If you do, draw a card. + this.addAbility(new BecomesTappedSourceTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), + new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT) + ))); + } + + private SwarmCuller(final SwarmCuller card) { + super(card); + } + + @Override + public SwarmCuller copy() { + return new SwarmCuller(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SylvanPrimordial.java b/Mage.Sets/src/mage/cards/s/SylvanPrimordial.java index 91b61dff522..193f4db285f 100644 --- a/Mage.Sets/src/mage/cards/s/SylvanPrimordial.java +++ b/Mage.Sets/src/mage/cards/s/SylvanPrimordial.java @@ -12,9 +12,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterLandCard; -import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.Target; @@ -29,11 +28,6 @@ import java.util.UUID; */ public final class SylvanPrimordial extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } public SylvanPrimordial(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}"); this.subtype.add(SubType.AVATAR); @@ -46,7 +40,7 @@ public final class SylvanPrimordial extends CardImpl { // When Sylvan Primordial enters the battlefield, for each opponent, destroy target noncreature permanent that player controls. For each permanent destroyed this way, search your library for a Forest card and put that card onto the battlefield tapped. Then shuffle your library. Ability ability = new EntersBattlefieldTriggeredAbility(new SylvanPrimordialEffect(), false); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); ability.setTargetAdjuster(new ForEachPlayerTargetsAdjuster(false, true)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SynthesizerLabship.java b/Mage.Sets/src/mage/cards/s/SynthesizerLabship.java new file mode 100644 index 00000000000..aefdf5a527e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SynthesizerLabship.java @@ -0,0 +1,65 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.common.continuous.AddCardTypeTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.abilities.keyword.VigilanceAbility; +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.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SynthesizerLabship extends CardImpl { + + public SynthesizerLabship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{U}"); + + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 2+ + // At the beginning of combat on your turn, up to one other target artifact you control becomes an artifact creature with base power and toughness 2/2 and gains flying until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility(new AddCardTypeTargetEffect( + Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE + ).setText("up to one other target artifact you control becomes an artifact creature")); + ability.addEffect(new SetBasePowerToughnessTargetEffect(2, 2, Duration.EndOfTurn) + .setText("with base power and toughness 2/2")); + ability.addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance()) + .setText("and gains flying until end of turn")); + ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT)); + this.addAbility(new StationLevelAbility(2).withLevelAbility(ability)); + + // STATION 9+ + // Flying + // Vigilance + // 4/4 + this.addAbility(new StationLevelAbility(9) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(VigilanceAbility.getInstance()) + .withPT(4, 4)); + } + + private SynthesizerLabship(final SynthesizerLabship card) { + super(card); + } + + @Override + public SynthesizerLabship copy() { + return new SynthesizerLabship(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SyrVondamSunstarExemplar.java b/Mage.Sets/src/mage/cards/s/SyrVondamSunstarExemplar.java new file mode 100644 index 00000000000..7f8662a830d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SyrVondamSunstarExemplar.java @@ -0,0 +1,146 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageItem; +import mage.MageObject; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.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.target.common.TargetNonlandPermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SyrVondamSunstarExemplar extends CardImpl { + + public SyrVondamSunstarExemplar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever another creature you control dies or is put into exile, put a +1/+1 counter on Syr Vondam and you gain 1 life. + this.addAbility(new SyrVondamSunstarExemplarFirstTriggeredAbility()); + + // When Syr Vondam dies or is put into exile while its power is 4 or greater, destroy up to one target nonland permanent. + this.addAbility(new SyrVondamSunstarExemplarSecondTriggeredAbility()); + } + + private SyrVondamSunstarExemplar(final SyrVondamSunstarExemplar card) { + super(card); + } + + @Override + public SyrVondamSunstarExemplar copy() { + return new SyrVondamSunstarExemplar(this); + } +} + +class SyrVondamSunstarExemplarFirstTriggeredAbility extends TriggeredAbilityImpl { + + SyrVondamSunstarExemplarFirstTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + this.addEffect(new GainLifeEffect(1).concatBy("and")); + this.setTriggerPhrase("Whenever another creature you control dies or is put into exile, "); + this.setLeavesTheBattlefieldTrigger(true); + } + + private SyrVondamSunstarExemplarFirstTriggeredAbility(final SyrVondamSunstarExemplarFirstTriggeredAbility ability) { + super(ability); + } + + @Override + public SyrVondamSunstarExemplarFirstTriggeredAbility copy() { + return new SyrVondamSunstarExemplarFirstTriggeredAbility(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 Zone.BATTLEFIELD.match(zEvent.getFromZone()) + && (Zone.GRAVEYARD.match(zEvent.getToZone()) || Zone.EXILED.match(zEvent.getToZone())) + && StaticFilters + .FILTER_ANOTHER_CREATURE_YOU_CONTROL + .match(zEvent.getTarget(), getControllerId(), this, game); + } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } +} + + +class SyrVondamSunstarExemplarSecondTriggeredAbility extends TriggeredAbilityImpl { + + SyrVondamSunstarExemplarSecondTriggeredAbility() { + super(Zone.BATTLEFIELD, new DestroyTargetEffect()); + this.addTarget(new TargetNonlandPermanent(0, 1)); + this.setTriggerPhrase("When {this} dies or is put into exile while its power is 4 or greater, "); + this.setLeavesTheBattlefieldTrigger(true); + } + + private SyrVondamSunstarExemplarSecondTriggeredAbility(final SyrVondamSunstarExemplarSecondTriggeredAbility ability) { + super(ability); + } + + @Override + public SyrVondamSunstarExemplarSecondTriggeredAbility copy() { + return new SyrVondamSunstarExemplarSecondTriggeredAbility(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 Zone.BATTLEFIELD.match(zEvent.getFromZone()) + && (Zone.GRAVEYARD.match(zEvent.getToZone()) || Zone.EXILED.match(zEvent.getToZone())) + && Optional + .ofNullable(zEvent) + .map(ZoneChangeEvent::getTarget) + .filter(permanent -> permanent.getPower().getValue() >= 4) + .map(MageItem::getId) + .filter(getSourceId()::equals) + .isPresent(); + } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SyrVondamTheLucent.java b/Mage.Sets/src/mage/cards/s/SyrVondamTheLucent.java new file mode 100644 index 00000000000..d4ec1502b4a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SyrVondamTheLucent.java @@ -0,0 +1,60 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.DeathtouchAbility; +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.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SyrVondamTheLucent extends CardImpl { + + public SyrVondamTheLucent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever Syr Vondam enters or attacks, other creatures you control get +1/+0 and gain deathtouch until end of turn. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new BoostControlledEffect( + 1, 0, Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE, true + ).setText("other creatures you control get +1/+0")); + ability.addEffect(new GainAbilityControlledEffect( + DeathtouchAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE, true + ).setText("and gain deathtouch until end of turn")); + this.addAbility(ability); + } + + private SyrVondamTheLucent(final SyrVondamTheLucent card) { + super(card); + } + + @Override + public SyrVondamTheLucent copy() { + return new SyrVondamTheLucent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SystemsOverride.java b/Mage.Sets/src/mage/cards/s/SystemsOverride.java new file mode 100644 index 00000000000..d7744470dec --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SystemsOverride.java @@ -0,0 +1,84 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.effects.common.counter.RemoveCounterTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SystemsOverride extends CardImpl { + + public SystemsOverride(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); + + // Gain control of target artifact or creature until end of turn. Untap that permanent. It gains haste until end of turn. If it's a Spacecraft, put ten charge counters on it. If you do, remove ten charge counters from it at the beginning of the next end step. + this.getSpellAbility().addEffect(new GainControlTargetEffect(Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new UntapTargetEffect().setText("Untap that permanent")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect( + HasteAbility.getInstance(), Duration.EndOfTurn + ).setText("It gains haste until end of turn.")); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); + this.getSpellAbility().addEffect(new SystemsOverrideEffect()); + } + + private SystemsOverride(final SystemsOverride card) { + super(card); + } + + @Override + public SystemsOverride copy() { + return new SystemsOverride(this); + } +} + +class SystemsOverrideEffect extends OneShotEffect { + + SystemsOverrideEffect() { + super(Outcome.Benefit); + staticText = "If it's a Spacecraft, put ten charge counters on it. If you do, " + + "remove ten charge counters from it at the beginning of the next end step."; + } + + private SystemsOverrideEffect(final SystemsOverrideEffect effect) { + super(effect); + } + + @Override + public SystemsOverrideEffect copy() { + return new SystemsOverrideEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null || !permanent.hasSubtype(SubType.SPACECRAFT, game) + || !permanent.addCounters(CounterType.CHARGE.createInstance(10), source, game)) { + return false; + } + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new RemoveCounterTargetEffect(CounterType.CHARGE.createInstance(10)) + .setTargetPointer(new FixedTarget(permanent, game)) + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TajuruArcher.java b/Mage.Sets/src/mage/cards/t/TajuruArcher.java index 18fc688065e..4434e054859 100644 --- a/Mage.Sets/src/mage/cards/t/TajuruArcher.java +++ b/Mage.Sets/src/mage/cards/t/TajuruArcher.java @@ -1,48 +1,45 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AllyEntersBattlefieldTriggeredAbility; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; 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.TargetController; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author North */ public final class TajuruArcher extends CardImpl { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Allies you control"); - private static final FilterCreaturePermanent filterTarget = new FilterCreaturePermanent("creature with flying"); static { filter.add(SubType.ALLY.getPredicate()); filter.add(TargetController.YOU.getControllerPredicate()); - filterTarget.add(new AbilityPredicate(FlyingAbility.class)); } public TajuruArcher(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.subtype.add(SubType.ELF); this.subtype.add(SubType.ARCHER); this.subtype.add(SubType.ALLY); this.power = new MageInt(1); this.toughness = new MageInt(2); + Ability ability = new AllyEntersBattlefieldTriggeredAbility(new DamageTargetEffect(new PermanentsOnBattlefieldCount(filter)), true); - ability.addTarget(new TargetPermanent(filterTarget)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability.setAbilityWord(null)); } diff --git a/Mage.Sets/src/mage/cards/t/TakeDown.java b/Mage.Sets/src/mage/cards/t/TakeDown.java index 14989740706..528b353861b 100644 --- a/Mage.Sets/src/mage/cards/t/TakeDown.java +++ b/Mage.Sets/src/mage/cards/t/TakeDown.java @@ -1,41 +1,32 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.Mode; import mage.abilities.effects.common.DamageAllEffect; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class TakeDown extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public TakeDown(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}"); // Choose one — // • Take Down deals 4 damage to target creature with flying. this.getSpellAbility().addEffect(new DamageTargetEffect(4)); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); // • Take Down deals 1 damage to each creature with flying - Mode mode = new Mode(new DamageAllEffect(1, filter)); + Mode mode = new Mode(new DamageAllEffect(1, StaticFilters.FILTER_CREATURE_FLYING)); this.getSpellAbility().addMode(mode); } diff --git a/Mage.Sets/src/mage/cards/t/TangleWire.java b/Mage.Sets/src/mage/cards/t/TangleWire.java index 39d89ac7b8c..0b77c52e5c4 100644 --- a/Mage.Sets/src/mage/cards/t/TangleWire.java +++ b/Mage.Sets/src/mage/cards/t/TangleWire.java @@ -1,11 +1,10 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FadingAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -21,6 +20,8 @@ import mage.players.Player; import mage.target.Target; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** * * @author Plopman @@ -58,7 +59,7 @@ class TangleWireEffect extends OneShotEffect { TangleWireEffect() { super(Outcome.Sacrifice); - staticText = "that player taps an untapped artifact, creature, or land they control for each fade counter on Tangle Wire"; + staticText = "that player taps an untapped artifact, creature, or land they control for each fade counter on {this}"; } private TangleWireEffect(final TangleWireEffect effect) { diff --git a/Mage.Sets/src/mage/cards/t/Tangletrap.java b/Mage.Sets/src/mage/cards/t/Tangletrap.java index b053c3a75c6..7c155603368 100644 --- a/Mage.Sets/src/mage/cards/t/Tangletrap.java +++ b/Mage.Sets/src/mage/cards/t/Tangletrap.java @@ -3,13 +3,10 @@ package mage.cards.t; import mage.abilities.Mode; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import mage.target.common.TargetArtifactPermanent; @@ -20,19 +17,13 @@ import java.util.UUID; */ public final class Tangletrap extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Tangletrap(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); // Choose one — // • Tangletrap deals 5 damage to target creature with flying. this.getSpellAbility().addEffect(new DamageTargetEffect(5)); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); // • Destroy target artifact. Mode mode = new Mode(new DestroyTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/t/Tanglewalker.java b/Mage.Sets/src/mage/cards/t/Tanglewalker.java index 7bf18e717e5..b06c6977439 100644 --- a/Mage.Sets/src/mage/cards/t/Tanglewalker.java +++ b/Mage.Sets/src/mage/cards/t/Tanglewalker.java @@ -3,19 +3,19 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.DefendingPlayerControlsSourceAttackingCondition; -import mage.abilities.decorator.ConditionalRestrictionEffect; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.combat.CantBeBlockedAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterPermanent; -import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; import java.util.UUID; @@ -24,10 +24,10 @@ import java.util.UUID; */ public final class Tanglewalker extends CardImpl { - private static final FilterPermanent filter = new FilterLandPermanent(); + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); static { - filter.add(CardType.ARTIFACT.getPredicate()); + filter.add(TanglewalkerPredicate.instance); } public Tanglewalker(UUID ownerId, CardSetInfo setInfo) { @@ -38,11 +38,8 @@ public final class Tanglewalker extends CardImpl { this.toughness = new MageInt(2); // Each creature you control can't be blocked as long as defending player controls an artifact land. - Effect effect = new ConditionalRestrictionEffect( - new CantBeBlockedAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURES, Duration.WhileOnBattlefield), - new DefendingPlayerControlsSourceAttackingCondition(filter)); - effect.setText("Each creature you control can't be blocked as long as defending player controls an artifact land"); - this.addAbility(new SimpleStaticAbility(effect)); + this.addAbility(new SimpleStaticAbility(new CantBeBlockedAllEffect(filter, Duration.WhileOnBattlefield) + .setText("Each creature you control can't be blocked as long as defending player controls an artifact land"))); } private Tanglewalker(final Tanglewalker card) { @@ -54,3 +51,24 @@ public final class Tanglewalker extends CardImpl { return new Tanglewalker(this); } } + +enum TanglewalkerPredicate implements ObjectSourcePlayerPredicate { + instance; // Each creature must independently evaluate if the player it's attacking has an artifact land + + private static final FilterPermanent filter = new FilterLandPermanent(); + + static { + filter.add(CardType.ARTIFACT.getPredicate()); + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + UUID defendingPlayer = game.getCombat().getDefendingPlayerId(input.getObject().getId(), game); + return defendingPlayer != null && game.getBattlefield().countAll(filter, defendingPlayer, game) > 0; + } + + @Override + public String toString() { + return ""; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TannukSteadfastSecond.java b/Mage.Sets/src/mage/cards/t/TannukSteadfastSecond.java new file mode 100644 index 00000000000..b20d5b01557 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TannukSteadfastSecond.java @@ -0,0 +1,97 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.WarpAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TannukSteadfastSecond extends CardImpl { + + public TannukSteadfastSecond(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.KAVU); + this.subtype.add(SubType.PILOT); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Other creatures you control have haste. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_CREATURES, true + ))); + + // Artifact cards and red creature cards in your hand have warp {2}{R}. + this.addAbility(new SimpleStaticAbility(new TannukSteadfastSecondEffect())); + } + + private TannukSteadfastSecond(final TannukSteadfastSecond card) { + super(card); + } + + @Override + public TannukSteadfastSecond copy() { + return new TannukSteadfastSecond(this); + } +} + +class TannukSteadfastSecondEffect extends ContinuousEffectImpl { + + private static final FilterCard filter = new FilterCard(); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + Predicates.and( + new ColorPredicate(ObjectColor.RED), + CardType.CREATURE.getPredicate() + ) + )); + } + + TannukSteadfastSecondEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.staticText = "artifact cards and red creature cards in your hand have warp {2}{R}"; + } + + private TannukSteadfastSecondEffect(final TannukSteadfastSecondEffect effect) { + super(effect); + } + + @Override + public TannukSteadfastSecondEffect copy() { + return new TannukSteadfastSecondEffect(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)) { + game.getState().addOtherAbility(card, new WarpAbility(card, "{2}{R}")); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TapestryWarden.java b/Mage.Sets/src/mage/cards/t/TapestryWarden.java new file mode 100644 index 00000000000..dbb9948dabd --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TapestryWarden.java @@ -0,0 +1,109 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.ruleModifying.CombatDamageByToughnessControlledEffect; +import mage.abilities.keyword.VigilanceAbility; +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.mageobject.ToughnessGreaterThanPowerPredicate; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.events.GameEvent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TapestryWarden extends CardImpl { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("creature you control with toughness greater than its power"); + + static { + filter.add(ToughnessGreaterThanPowerPredicate.instance); + } + + public TapestryWarden(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Each creature you control with toughness greater than its power assigns combat damage equal to its toughness rather than its power. + this.addAbility(new SimpleStaticAbility(new CombatDamageByToughnessControlledEffect(filter))); + + // Each creature you control with toughness greater than its power stations permanents using its toughness rather than its power. + this.addAbility(new SimpleStaticAbility(new TapestryWardenEffect())); + } + + private TapestryWarden(final TapestryWarden card) { + super(card); + } + + @Override + public TapestryWarden copy() { + return new TapestryWarden(this); + } +} + +class TapestryWardenEffect extends ReplacementEffectImpl { + + TapestryWardenEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "each creature you control with toughness greater than its power " + + "stations permanents using its toughness rather than its power"; + } + + private TapestryWardenEffect(final TapestryWardenEffect effect) { + super(effect); + } + + @Override + public TapestryWardenEffect copy() { + return new TapestryWardenEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.STATION_PERMANENT; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Optional.ofNullable(event) + .map(GameEvent::getTargetId) + .map(game::getPermanent) + .map(MageObject::getToughness) + .map(MageInt::getValue) + .ifPresent(event::setAmount); + return false; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return Optional + .ofNullable(event) + .map(GameEvent::getTargetId) + .map(game::getPermanent) + .filter(permanent -> permanent.getPower().getValue() < permanent.getToughness().getValue()) + .map(Controllable::getControllerId) + .filter(source::isControlledBy) + .isPresent(); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TearsOfValakut.java b/Mage.Sets/src/mage/cards/t/TearsOfValakut.java index 5eeb74dc09a..aeb2c43591e 100644 --- a/Mage.Sets/src/mage/cards/t/TearsOfValakut.java +++ b/Mage.Sets/src/mage/cards/t/TearsOfValakut.java @@ -1,39 +1,30 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.common.CantBeCounteredSourceAbility; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author fireshoes */ public final class TearsOfValakut extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public TearsOfValakut(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); // This spell can't be countered. this.addAbility(new CantBeCounteredSourceAbility().setRuleAtTheTop(true)); // Tears of Valakut deals 5 damage to target creature with flying. this.getSpellAbility().addEffect(new DamageTargetEffect(5)); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private TearsOfValakut(final TearsOfValakut card) { diff --git a/Mage.Sets/src/mage/cards/t/Teleport.java b/Mage.Sets/src/mage/cards/t/Teleport.java index c032191a5c3..9836ee79f45 100644 --- a/Mage.Sets/src/mage/cards/t/Teleport.java +++ b/Mage.Sets/src/mage/cards/t/Teleport.java @@ -1,7 +1,6 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; import mage.cards.CardImpl; @@ -10,6 +9,8 @@ import mage.constants.CardType; import mage.constants.PhaseStep; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author L_J @@ -20,7 +21,7 @@ public final class Teleport extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{U}{U}"); // Cast Teleport only during the declare attackers step. - this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, PhaseStep.DECLARE_ATTACKERS, null, "Cast Teleport only during the declare attackers step")); + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, PhaseStep.DECLARE_ATTACKERS, null, "Cast {this} only during the declare attackers step")); // Target creature can't be blocked this turn. this.getSpellAbility().addEffect(new CantBeBlockedTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/t/TemporalIntervention.java b/Mage.Sets/src/mage/cards/t/TemporalIntervention.java new file mode 100644 index 00000000000..9dd39b7c3fb --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TemporalIntervention.java @@ -0,0 +1,46 @@ +package mage.cards.t; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.effects.common.discard.DiscardCardYouChooseTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.target.common.TargetOpponent; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TemporalIntervention extends CardImpl { + + public TemporalIntervention(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // Void -- This spell costs {2} less to cast if a nonland permanent left the battlefield this turn or a spell was warped this turn. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(2, VoidCondition.instance) + .setText("this spell costs {2} less to cast if a nonland permanent " + + "left the battlefield this turn or a spell was warped this turn") + ).setRuleAtTheTop(true).setAbilityWord(AbilityWord.VOID).addHint(VoidCondition.getHint()), new VoidWatcher()); + + // Target opponent reveals their hand. You choose a nonland card from it. That player discards that card. + this.getSpellAbility().addEffect(new DiscardCardYouChooseTargetEffect(StaticFilters.FILTER_CARD_NON_LAND)); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + private TemporalIntervention(final TemporalIntervention card) { + super(card); + } + + @Override + public TemporalIntervention copy() { + return new TemporalIntervention(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/Terastodon.java b/Mage.Sets/src/mage/cards/t/Terastodon.java index af558f1bc72..3df0e40bfad 100644 --- a/Mage.Sets/src/mage/cards/t/Terastodon.java +++ b/Mage.Sets/src/mage/cards/t/Terastodon.java @@ -1,4 +1,3 @@ - package mage.cards.t; import mage.MageInt; @@ -8,11 +7,10 @@ import mage.abilities.effects.OneShotEffect; 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.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.ElephantToken; @@ -24,17 +22,10 @@ import java.util.Map.Entry; import java.util.UUID; /** - * * @author jeffwadsworth */ public final class Terastodon extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public Terastodon(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{G}{G}"); this.subtype.add(SubType.ELEPHANT); @@ -44,7 +35,7 @@ public final class Terastodon extends CardImpl { // When Terastodon enters the battlefield, you may destroy up to three target noncreature permanents. For each permanent put into a graveyard this way, its controller creates a 3/3 green Elephant creature token. Ability ability = new EntersBattlefieldTriggeredAbility(new TerastodonEffect(), true); - ability.addTarget(new TargetPermanent(0, 3, filter, false)); + ability.addTarget(new TargetPermanent(0, 3, StaticFilters.FILTER_PERMANENT_NON_CREATURE, false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/TerminalVelocity.java b/Mage.Sets/src/mage/cards/t/TerminalVelocity.java new file mode 100644 index 00000000000..835904c4eba --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TerminalVelocity.java @@ -0,0 +1,130 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +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.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TerminalVelocity extends CardImpl { + + public TerminalVelocity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{R}{R}"); + + // You may put an artifact or creature card from your hand onto the battlefield. That permanent gains haste, "When this permanent leaves the battlefield, it deals damage equal to its mana value to each creature," and "At the beginning of your end step, sacrifice this permanent." + this.getSpellAbility().addEffect(new TerminalVelocityEffect()); + } + + private TerminalVelocity(final TerminalVelocity card) { + super(card); + } + + @Override + public TerminalVelocity copy() { + return new TerminalVelocity(this); + } +} + +class TerminalVelocityEffect extends OneShotEffect { + + TerminalVelocityEffect() { + super(Outcome.Benefit); + staticText = "you may put an artifact or creature card from your hand onto the battlefield. " + + "That permanent gains haste, \"When this permanent leaves the battlefield, " + + "it deals damage equal to its mana value to each creature,\" and " + + "\"At the beginning of your end step, sacrifice this permanent.\""; + } + + private TerminalVelocityEffect(final TerminalVelocityEffect effect) { + super(effect); + } + + @Override + public TerminalVelocityEffect copy() { + return new TerminalVelocityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_ARTIFACT_OR_CREATURE); + player.choose(Outcome.PutCardInPlay, player.getHand(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + player.moveCards(card, Zone.BATTLEFIELD, source, game); + Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game); + if (permanent == null) { + return true; + } + game.addEffect(new GainAbilityTargetEffect( + HasteAbility.getInstance(), Duration.Custom + ).setTargetPointer(new FixedTarget(permanent, game)), source); + game.addEffect(new GainAbilityTargetEffect( + new LeavesBattlefieldTriggeredAbility(new TerminalVelocityDamageEffect()) + .setTriggerPhrase("When this permanent leaves the battlefield, "), Duration.Custom + ).setTargetPointer(new FixedTarget(permanent, game)), source); + game.addEffect(new GainAbilityTargetEffect( + new BeginningOfEndStepTriggeredAbility(new SacrificeSourceEffect() + .setText("sacrifice this permanent")), Duration.Custom + ).setTargetPointer(new FixedTarget(permanent, game)), source); + return true; + } +} + +class TerminalVelocityDamageEffect extends OneShotEffect { + + TerminalVelocityDamageEffect() { + super(Outcome.Benefit); + staticText = "it deals damage equal to its mana value to each creature"; + } + + private TerminalVelocityDamageEffect(final TerminalVelocityDamageEffect effect) { + super(effect); + } + + @Override + public TerminalVelocityDamageEffect copy() { + return new TerminalVelocityDamageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (permanent == null || permanent.getManaValue() < 1) { + return false; + } + for (Permanent creature : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source, game + )) { + creature.damage(permanent.getManaValue(), permanent.getId(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TerrapactIntimidator.java b/Mage.Sets/src/mage/cards/t/TerrapactIntimidator.java new file mode 100644 index 00000000000..4d4f582b529 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TerrapactIntimidator.java @@ -0,0 +1,90 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.token.LanderToken; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TerrapactIntimidator extends CardImpl { + + public TerrapactIntimidator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.KAVU); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // When this creature enters, target opponent may have you create two Lander tokens. If they don't, put two +1/+1 counters on this creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new TerrapactIntimidatorEffect()); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private TerrapactIntimidator(final TerrapactIntimidator card) { + super(card); + } + + @Override + public TerrapactIntimidator copy() { + return new TerrapactIntimidator(this); + } +} + +class TerrapactIntimidatorEffect extends OneShotEffect { + + TerrapactIntimidatorEffect() { + super(Outcome.Benefit); + staticText = "target opponent may have you create two Lander tokens. " + + "If they don't, put two +1/+1 counters on this creature"; + } + + private TerrapactIntimidatorEffect(final TerrapactIntimidatorEffect effect) { + super(effect); + } + + @Override + public TerrapactIntimidatorEffect copy() { + return new TerrapactIntimidatorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + if (player.chooseUse( + Outcome.Detriment, "Have " + Optional + .ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .map(Player::getLogName) + .orElse("them") + + " create two Lander tokens?", source, game + )) { + return new LanderToken().putOntoBattlefield(2, game, source); + } + return Optional + .ofNullable(source.getSourcePermanentIfItStillExists(game)) + .filter(permanent -> permanent.addCounters(CounterType.P1P1.createInstance(2), source, game)) + .isPresent(); + } +} diff --git a/Mage.Sets/src/mage/cards/t/Terrasymbiosis.java b/Mage.Sets/src/mage/cards/t/Terrasymbiosis.java new file mode 100644 index 00000000000..447a2ac2b93 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/Terrasymbiosis.java @@ -0,0 +1,40 @@ +package mage.cards.t; + +import mage.abilities.common.PutCounterOnCreatureTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.EffectKeyValue; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Terrasymbiosis extends CardImpl { + + private static final DynamicValue xValue = new EffectKeyValue("countersAdded", "that many"); + + public Terrasymbiosis(UUID ownerId, CardSetInfo setInfo) { + 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( + new DrawCardSourceControllerEffect(xValue), + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ).setDoOnlyOnceEachTurn(true)); + } + + private Terrasymbiosis(final Terrasymbiosis card) { + super(card); + } + + @Override + public Terrasymbiosis copy() { + return new Terrasymbiosis(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TerritorialBruntar.java b/Mage.Sets/src/mage/cards/t/TerritorialBruntar.java new file mode 100644 index 00000000000..8e91cad3df3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TerritorialBruntar.java @@ -0,0 +1,118 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TerritorialBruntar extends CardImpl { + + public TerritorialBruntar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Landfall — Whenever a land you control enters, exile cards from the top of your library until you exile a nonland card. You may cast that card this turn. + this.addAbility(new LandfallAbility(new TerritorialBruntarEffect())); + } + + private TerritorialBruntar(final TerritorialBruntar card) { + super(card); + } + + @Override + public TerritorialBruntar copy() { + return new TerritorialBruntar(this); + } +} + +class TerritorialBruntarEffect extends OneShotEffect { + + TerritorialBruntarEffect() { + super(Outcome.Benefit); + staticText = "exile cards from the top of your library until you exile a nonland card. You may cast that card this turn"; + } + + private TerritorialBruntarEffect(final TerritorialBruntarEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + boolean didSomething = false; + for (Card card : controller.getLibrary().getCards(game)) { + didSomething |= controller.moveCards(card, Zone.EXILED, source, game); + if (game.getState().getZone(card.getId()) == Zone.EXILED && !card.isLand(game)) { + ContinuousEffect effect = new TerritorialBruntarAsThoughEffect(); + effect.setTargetPointer(new FixedTarget(card, game)); + game.addEffect(effect, source); + return true; + } + } + return didSomething; + } + + @Override + public TerritorialBruntarEffect copy() { + return new TerritorialBruntarEffect(this); + } +} + +class TerritorialBruntarAsThoughEffect extends AsThoughEffectImpl { + + TerritorialBruntarAsThoughEffect() { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); + staticText = "you may cast that card this turn"; + } + + private TerritorialBruntarAsThoughEffect(final TerritorialBruntarAsThoughEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public TerritorialBruntarAsThoughEffect copy() { + return new TerritorialBruntarAsThoughEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID targetId = getTargetPointer().getFirst(game, source); + if (targetId == null) { + // cleanup if the target is no longer in the exile + discard(); + return false; + } + return targetId.equals(objectId) + && source.isControlledBy(affectedControllerId) + && Zone.EXILED == game.getState().getZone(objectId); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThatWhichWasTaken.java b/Mage.Sets/src/mage/cards/t/ThatWhichWasTaken.java index 88a616a5cac..b0e872417dd 100644 --- a/Mage.Sets/src/mage/cards/t/ThatWhichWasTaken.java +++ b/Mage.Sets/src/mage/cards/t/ThatWhichWasTaken.java @@ -1,7 +1,6 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; @@ -16,12 +15,13 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SuperType; -import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.TargetPermanent; +import java.util.UUID; + /** @@ -29,7 +29,7 @@ import mage.target.TargetPermanent; */ public final class ThatWhichWasTaken extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("permanent other than That Which Was Taken"); + private static final FilterPermanent filter = new FilterPermanent("permanent other than {this}"); private static final FilterPermanent filterIndestructible = new FilterPermanent("Each permanent with a divinity counter on it"); diff --git a/Mage.Sets/src/mage/cards/t/ThaumatonTorpedo.java b/Mage.Sets/src/mage/cards/t/ThaumatonTorpedo.java new file mode 100644 index 00000000000..dd3189512d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThaumatonTorpedo.java @@ -0,0 +1,121 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetNonlandPermanent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThaumatonTorpedo extends CardImpl { + + public ThaumatonTorpedo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + // {6}, {T}, Sacrifice this artifact: Destroy target nonland permanent. This ability costs {3} less to activate if you attacked with a Spacecraft this turn. + Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new GenericManaCost(6)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addEffect(new InfoEffect("This ability costs {3} less to activate if you attacked with a Spacecraft this turn")); + ability.addTarget(new TargetNonlandPermanent()); + this.addAbility(ability + .setCostAdjuster(ThaumatonTorpedoAdjuster.instance) + .addHint(ThaumatonTorpedoCondition.getHint()), new ThaumatonTorpedoWatcher()); + } + + private ThaumatonTorpedo(final ThaumatonTorpedo card) { + super(card); + } + + @Override + public ThaumatonTorpedo copy() { + return new ThaumatonTorpedo(this); + } +} + +enum ThaumatonTorpedoCondition 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 ThaumatonTorpedoWatcher.checkPlayer(game, source); + } + + @Override + public String toString() { + return "you attacked with a Spacecraft this turn"; + } +} + +enum ThaumatonTorpedoAdjuster implements CostAdjuster { + instance; + + @Override + public void reduceCost(Ability ability, Game game) { + if (ThaumatonTorpedoCondition.instance.apply(game, ability)) { + CardUtil.reduceCost(ability, 3); + } + } +} + +class ThaumatonTorpedoWatcher extends Watcher { + + private final Set set = new HashSet<>(); + + ThaumatonTorpedoWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ATTACKER_DECLARED) { + return; + } + Permanent permanent = game.getPermanent(event.getSourceId()); + if (permanent != null && permanent.hasSubtype(SubType.SPACECRAFT, game)) { + set.add(permanent.getControllerId()); + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkPlayer(Game game, Ability source) { + return game + .getState() + .getWatcher(ThaumatonTorpedoWatcher.class) + .set + .contains(source.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/t/Thawbringer.java b/Mage.Sets/src/mage/cards/t/Thawbringer.java new file mode 100644 index 00000000000..15c3715e9bf --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/Thawbringer.java @@ -0,0 +1,38 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldOrDiesSourceTriggeredAbility; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Thawbringer extends CardImpl { + + public Thawbringer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.INSECT); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // When this creature enters or dies, surveil 1. + this.addAbility(new EntersBattlefieldOrDiesSourceTriggeredAbility(new SurveilEffect(1), false)); + } + + private Thawbringer(final Thawbringer card) { + super(card); + } + + @Override + public Thawbringer copy() { + return new Thawbringer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheAesirEscapeValhalla.java b/Mage.Sets/src/mage/cards/t/TheAesirEscapeValhalla.java index 0095d30b9ae..59e83c71b2d 100644 --- a/Mage.Sets/src/mage/cards/t/TheAesirEscapeValhalla.java +++ b/Mage.Sets/src/mage/cards/t/TheAesirEscapeValhalla.java @@ -1,15 +1,13 @@ package mage.cards.t; -import java.util.UUID; - import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SagaAbility; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.ExileZone; @@ -20,6 +18,8 @@ import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetControlledCreaturePermanent; import mage.util.CardUtil; +import java.util.UUID; + /** * * @author Grath @@ -140,7 +140,7 @@ class TheAesirEscapeValhallaThreeEffect extends OneShotEffect { TheAesirEscapeValhallaThreeEffect() { super(Outcome.Neutral); - staticText = "Return The Aesir Escape Valhalla and the exiled card to their owner's hand."; + staticText = "Return {this} and the exiled card to their owner's hand."; } private TheAesirEscapeValhallaThreeEffect(final TheAesirEscapeValhallaThreeEffect effect) { diff --git a/Mage.Sets/src/mage/cards/t/TheBattleOfBywater.java b/Mage.Sets/src/mage/cards/t/TheBattleOfBywater.java index 9142977996f..de1bbc09401 100644 --- a/Mage.Sets/src/mage/cards/t/TheBattleOfBywater.java +++ b/Mage.Sets/src/mage/cards/t/TheBattleOfBywater.java @@ -31,7 +31,7 @@ public final class TheBattleOfBywater extends CardImpl { // Destroy all creatures with power 3 or greater. Then create a Food token for each creature you control. this.getSpellAbility().addEffect(new DestroyAllEffect(filter)); this.getSpellAbility().addEffect(new CreateTokenEffect( - new FoodToken(), CreaturesYouControlCount.instance + new FoodToken(), CreaturesYouControlCount.PLURAL ).setText("Then create a Food token for each creature you control")); } diff --git a/Mage.Sets/src/mage/cards/t/TheDominionBracelet.java b/Mage.Sets/src/mage/cards/t/TheDominionBracelet.java new file mode 100644 index 00000000000..0f758040fbe --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheDominionBracelet.java @@ -0,0 +1,83 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.common.ExileAttachmentCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effects; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityWithAttachmentEffect; +import mage.abilities.effects.common.turn.ControlTargetPlayerNextTurnEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TimingRule; +import mage.game.Game; +import mage.target.Targets; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheDominionBracelet extends CardImpl { + + public TheDominionBracelet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +1/+1 and has "{15}, Exile The Dominion Bracelet: You control target opponent during their next turn. This ability costs {X} less to activate, where X is this creature's power. Activate only as a sorcery." + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 1)); + ability.addEffect(new GainAbilityWithAttachmentEffect( + "and has \"{15}, Exile {this}: You control target opponent during their next turn. This ability " + + "costs {X} less to activate, where X is this creature's power. Activate only as a sorcery.\"", + new Effects( + new ControlTargetPlayerNextTurnEffect(), new InfoEffect("This ability costs {X} " + + "less to activate, where X is this creature's power. Activate only as a sorcery") + ), new Targets(new TargetOpponent()), new ExileAttachmentCost(), + activatedAbility -> { + activatedAbility.setCostAdjuster(TheDominionBraceletAdjuster.instance); + activatedAbility.setTiming(TimingRule.SORCERY); + }, + new GenericManaCost(15) + )); + this.addAbility(ability); + + // Equip {1} + this.addAbility(new EquipAbility(1)); + } + + private TheDominionBracelet(final TheDominionBracelet card) { + super(card); + } + + @Override + public TheDominionBracelet copy() { + return new TheDominionBracelet(this); + } +} + +enum TheDominionBraceletAdjuster implements CostAdjuster { + instance; + + @Override + public void reduceCost(Ability ability, Game game) { + Optional.ofNullable(ability.getSourcePermanentIfItStillExists(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .filter(x -> x > 0) + .ifPresent(x -> CardUtil.reduceCost(ability, x)); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheEndstone.java b/Mage.Sets/src/mage/cards/t/TheEndstone.java new file mode 100644 index 00000000000..c38de5d4f99 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheEndstone.java @@ -0,0 +1,70 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.PlayLandOrCastSpellTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.game.Controllable; +import mage.game.Game; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheEndstone extends CardImpl { + + public TheEndstone(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{7}"); + + this.supertype.add(SuperType.LEGENDARY); + + // Whenever you play a land or cast a spell, draw a card. + this.addAbility(new PlayLandOrCastSpellTriggeredAbility(new DrawCardSourceControllerEffect(1))); + + // At the beginning of your end step, your life total becomes half your starting life total, rounded up. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new TheEndstoneEffect())); + } + + private TheEndstone(final TheEndstone card) { + super(card); + } + + @Override + public TheEndstone copy() { + return new TheEndstone(this); + } +} + +class TheEndstoneEffect extends OneShotEffect { + + TheEndstoneEffect() { + super(Outcome.Benefit); + staticText = "your life total becomes half your starting life total, rounded up"; + } + + private TheEndstoneEffect(final TheEndstoneEffect effect) { + super(effect); + } + + @Override + public TheEndstoneEffect copy() { + return new TheEndstoneEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.setLife(game.getStartingLife() / 2 + game.getStartingLife() % 2, game, source)); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheEternalWanderer.java b/Mage.Sets/src/mage/cards/t/TheEternalWanderer.java index ef6212bfd3f..3b48821ca0b 100644 --- a/Mage.Sets/src/mage/cards/t/TheEternalWanderer.java +++ b/Mage.Sets/src/mage/cards/t/TheEternalWanderer.java @@ -1,38 +1,38 @@ package mage.cards.t; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import mage.constants.SuperType; -import mage.constants.Zone; -import mage.filter.FilterPermanent; -import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.AtTheBeginOfPlayersNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.RestrictionEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SuperType; +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.abilities.effects.Effect; -import mage.abilities.effects.common.CreateTokenEffect; -import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; -import mage.target.TargetPermanent; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.common.delayed.AtTheBeginOfPlayersNextEndStepDelayedTriggeredAbility; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.RestrictionEffect; -import mage.constants.Outcome; import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.permanent.Permanent; import mage.game.permanent.token.DoublestrikeSamuraiToken; import mage.players.Player; +import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTarget; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** * @@ -181,8 +181,7 @@ class TheEternalWandererAttackRestrictionEffect extends RestrictionEffect { TheEternalWandererAttackRestrictionEffect() { super(Duration.WhileOnBattlefield); - staticText = "No more than one creature can attack" + - " The Eternal Wanderer each combat"; + staticText = "No more than one creature can attack {this} each combat"; } private TheEternalWandererAttackRestrictionEffect(final TheEternalWandererAttackRestrictionEffect effect) { diff --git a/Mage.Sets/src/mage/cards/t/TheEternityElevator.java b/Mage.Sets/src/mage/cards/t/TheEternityElevator.java new file mode 100644 index 00000000000..f47b0486666 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheEternityElevator.java @@ -0,0 +1,56 @@ +package mage.cards.t; + +import mage.Mana; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.abilities.mana.DynamicManaAbility; +import mage.abilities.mana.SimpleManaAbility; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheEternityElevator extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(CounterType.CHARGE); + + public TheEternityElevator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPACECRAFT); + + // {T}: Add {C}{C}{C} + this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, Mana.ColorlessMana(3), new TapSourceCost())); + + // Station + this.addAbility(new StationAbility()); + + // STATION 20+ + // {T}: Add X mana of any one color, where X is the number of charge counters on The Eternity Elevator. + this.addAbility(new StationLevelAbility(20).withLevelAbility(new DynamicManaAbility( + Mana.AnyMana(1), xValue, new TapSourceCost(), "add X mana of any one color, " + + "where X is the number of charge counters on {this}", true + ))); + } + + private TheEternityElevator(final TheEternityElevator card) { + super(card); + } + + @Override + public TheEternityElevator copy() { + return new TheEternityElevator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheNecrobloom.java b/Mage.Sets/src/mage/cards/t/TheNecrobloom.java index d281200b1a6..6fc32e607fd 100644 --- a/Mage.Sets/src/mage/cards/t/TheNecrobloom.java +++ b/Mage.Sets/src/mage/cards/t/TheNecrobloom.java @@ -6,8 +6,10 @@ import mage.abilities.common.LandfallAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.Hint; import mage.abilities.keyword.DredgeAbility; import mage.cards.Card; import mage.cards.CardImpl; @@ -42,7 +44,7 @@ public final class TheNecrobloom extends CardImpl { new CreateTokenEffect(new PlantToken()), TheNecrobloomCondition.instance, "create a 0/1 green Plant creature token. If you control " + "seven or more lands with different names, create a 2/2 black Zombie creature token instead" - ))); + )).addHint(TheNecrobloomCondition.getHint())); // Land cards in your graveyard have dredge 2. this.addAbility(new SimpleStaticAbility(new TheNecrobloomDredgeEffect())); @@ -61,20 +63,20 @@ public final class TheNecrobloom extends CardImpl { enum TheNecrobloomCondition implements Condition { instance; + private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS); + + static Hint getHint() { + return xValue.getHint(); + } + @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - return game - .getBattlefield() - .getAllActivePermanents(StaticFilters.FILTER_LAND, source.getControllerId(), game) - .stream() - .map(permanent -> permanent.getName()) - .filter(s -> s.length() > 0) - .distinct() - .count() > 6; + return xValue.calculate(game, source, null) >= 7; + } + + @Override + public String toString() { + return "you control seven or more lands with different names"; } } diff --git a/Mage.Sets/src/mage/cards/t/TheSeriema.java b/Mage.Sets/src/mage/cards/t/TheSeriema.java new file mode 100644 index 00000000000..de7229e4af5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheSeriema.java @@ -0,0 +1,74 @@ +package mage.cards.t; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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.FilterPermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheSeriema extends CardImpl { + + private static final FilterCard filter = new FilterCreatureCard("legendary creature card"); + private static final FilterPermanent filter2 = new FilterCreaturePermanent("tapped legendary creatures"); + + static { + filter.add(SuperType.LEGENDARY.getPredicate()); + filter2.add(TappedPredicate.TAPPED); + filter2.add(SuperType.LEGENDARY.getPredicate()); + } + + public TheSeriema(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPACECRAFT); + + // When The Seriema enters, search your library for a legendary creature card, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + )); + + // Station + this.addAbility(new StationAbility()); + + // STATION 7+ + // Flying + // Other tapped legendary creatures you control have indestructible. + // 5/5 + this.addAbility(new StationLevelAbility(7) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, filter2, true + ))) + .withPT(5, 5)); + } + + private TheSeriema(final TheSeriema card) { + super(card); + } + + @Override + public TheSeriema copy() { + return new TheSeriema(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheWindCrystal.java b/Mage.Sets/src/mage/cards/t/TheWindCrystal.java index 0dcfa19b7ed..ca548ae2063 100644 --- a/Mage.Sets/src/mage/cards/t/TheWindCrystal.java +++ b/Mage.Sets/src/mage/cards/t/TheWindCrystal.java @@ -10,6 +10,7 @@ import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; import mage.abilities.effects.common.replacement.GainDoubleLifeReplacementEffect; import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -50,7 +51,7 @@ public final class TheWindCrystal extends CardImpl { ).setText("creatures you control gain flying"), new ManaCostsImpl<>("{4}{W}{W}")); ability.addCost(new TapSourceCost()); ability.addEffect(new GainAbilityControlledEffect( - FlyingAbility.getInstance(), Duration.EndOfTurn, + LifelinkAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE ).setText("and lifelink until end of turn")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/t/Thermopod.java b/Mage.Sets/src/mage/cards/t/Thermopod.java index b76fef80577..c6081963e73 100644 --- a/Mage.Sets/src/mage/cards/t/Thermopod.java +++ b/Mage.Sets/src/mage/cards/t/Thermopod.java @@ -20,7 +20,6 @@ import mage.constants.Duration; import mage.constants.SuperType; import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.target.common.TargetControlledCreaturePermanent; /** * @@ -39,7 +38,7 @@ public final class Thermopod extends CardImpl { this.addAbility(new SimpleActivatedAbility(new GainAbilitySourceEffect( HasteAbility.getInstance(), Duration.EndOfTurn), new ManaCostsImpl<>("{S}"))); // Sacrifice a creature: Add {R}. - this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new BasicManaEffect(Mana.RedMana(1), CreaturesYouControlCount.instance), + this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new BasicManaEffect(Mana.RedMana(1), CreaturesYouControlCount.PLURAL), new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE))); } diff --git a/Mage.Sets/src/mage/cards/t/Thornado.java b/Mage.Sets/src/mage/cards/t/Thornado.java index e730a192c86..c50df4343c0 100644 --- a/Mage.Sets/src/mage/cards/t/Thornado.java +++ b/Mage.Sets/src/mage/cards/t/Thornado.java @@ -3,13 +3,10 @@ package mage.cards.t; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.CyclingAbility; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; import java.util.UUID; @@ -19,18 +16,12 @@ import java.util.UUID; */ public final class Thornado extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Thornado(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); // Destroy target creature with flying. this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); // Cycling {1}{G} this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{1}{G}"))); diff --git a/Mage.Sets/src/mage/cards/t/ThrabenCharm.java b/Mage.Sets/src/mage/cards/t/ThrabenCharm.java index cf42b1c9525..b1b63f894cc 100644 --- a/Mage.Sets/src/mage/cards/t/ThrabenCharm.java +++ b/Mage.Sets/src/mage/cards/t/ThrabenCharm.java @@ -30,7 +30,7 @@ import java.util.UUID; */ public final class ThrabenCharm extends CardImpl { - private static final DynamicValue xValue = new MultipliedValue(CreaturesYouControlCount.instance, 2); + private static final DynamicValue xValue = new MultipliedValue(CreaturesYouControlCount.PLURAL, 2); public ThrabenCharm(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); diff --git a/Mage.Sets/src/mage/cards/t/ThranWeaponry.java b/Mage.Sets/src/mage/cards/t/ThranWeaponry.java index cafc735c331..2b6a48cb803 100644 --- a/Mage.Sets/src/mage/cards/t/ThranWeaponry.java +++ b/Mage.Sets/src/mage/cards/t/ThranWeaponry.java @@ -1,7 +1,6 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SkipUntapOptionalAbility; @@ -13,10 +12,11 @@ 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.permanent.Permanent; +import java.util.UUID; + /** * * @author Plopman @@ -52,7 +52,7 @@ class ThranWeaponryEffect extends BoostAllEffect{ public ThranWeaponryEffect() { super(2, 2, Duration.WhileOnBattlefield); - staticText = "All creatures get +2/+2 for as long as Thran Weaponry remains tapped"; + staticText = "All creatures get +2/+2 for as long as {this} remains tapped"; } private ThranWeaponryEffect(final ThranWeaponryEffect effect) { diff --git a/Mage.Sets/src/mage/cards/t/ThrummingHivepool.java b/Mage.Sets/src/mage/cards/t/ThrummingHivepool.java new file mode 100644 index 00000000000..30b6f50133e --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThrummingHivepool.java @@ -0,0 +1,57 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.AffinityAbility; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AffinityType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.SliverToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThrummingHivepool extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SLIVER, "Slivers"); + + public ThrummingHivepool(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}"); + + // Affinity for Slivers + this.addAbility(new AffinityAbility(AffinityType.SLIVERS)); + + // Slivers you control have double strike and haste. + Ability ability = new SimpleStaticAbility(new GainAbilityControlledEffect( + DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter + )); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter + ).setText("and haste")); + this.addAbility(ability); + + // At the beginning of your upkeep, create two 1/1 colorless Sliver creature tokens. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new CreateTokenEffect(new SliverToken(), 2))); + } + + private ThrummingHivepool(final ThrummingHivepool card) { + super(card); + } + + @Override + public ThrummingHivepool copy() { + return new ThrummingHivepool(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/Thunderbolt.java b/Mage.Sets/src/mage/cards/t/Thunderbolt.java index 453a29b60c2..296a4c67701 100644 --- a/Mage.Sets/src/mage/cards/t/Thunderbolt.java +++ b/Mage.Sets/src/mage/cards/t/Thunderbolt.java @@ -1,31 +1,22 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.Mode; 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.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetPlayerOrPlaneswalker; +import java.util.UUID; + /** - * * @author North */ public final class Thunderbolt extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Thunderbolt(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); @@ -33,7 +24,7 @@ public final class Thunderbolt extends CardImpl { this.getSpellAbility().addEffect(new DamageTargetEffect(3)); this.getSpellAbility().addTarget(new TargetPlayerOrPlaneswalker()); Mode mode = new Mode(new DamageTargetEffect(4)); - mode.addTarget(new TargetPermanent(filter)); + mode.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.getSpellAbility().addMode(mode); } diff --git a/Mage.Sets/src/mage/cards/t/TimeVault.java b/Mage.Sets/src/mage/cards/t/TimeVault.java index 9c30a86b26d..64c1fa116cb 100644 --- a/Mage.Sets/src/mage/cards/t/TimeVault.java +++ b/Mage.Sets/src/mage/cards/t/TimeVault.java @@ -1,7 +1,6 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTappedAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -15,13 +14,13 @@ 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.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** * * @author emerald000 @@ -59,7 +58,7 @@ class TimeVaultReplacementEffect extends ReplacementEffectImpl { TimeVaultReplacementEffect() { super(Duration.WhileOnBattlefield, Outcome.Untap); - staticText = "If you would begin your turn while {this} is tapped, you may skip that turn instead. If you do, untap Time Vault."; + staticText = "If you would begin your turn while {this} is tapped, you may skip that turn instead. If you do, untap {this}."; } private TimeVaultReplacementEffect(final TimeVaultReplacementEffect effect) { diff --git a/Mage.Sets/src/mage/cards/t/TimelineCuller.java b/Mage.Sets/src/mage/cards/t/TimelineCuller.java new file mode 100644 index 00000000000..2d268cd518e --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TimelineCuller.java @@ -0,0 +1,86 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TimelineCuller extends CardImpl { + + public TimelineCuller(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{B}"); + + this.subtype.add(SubType.DRIX); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // You may cast this card from your graveyard using its warp ability. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new TimelineCullerEffect())); + + // Warp--{B}, Pay 2 life. + Ability ability = new WarpAbility(this, "{B}", true); + ability.addCost(new PayLifeCost(2)); + this.addAbility(ability); + } + + private TimelineCuller(final TimelineCuller card) { + super(card); + } + + @Override + public TimelineCuller copy() { + return new TimelineCuller(this); + } +} + +class TimelineCullerEffect extends AsThoughEffectImpl { + + TimelineCullerEffect() { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.PutCreatureInPlay); + staticText = "you may cast this card from your graveyard using its warp ability"; + } + + private TimelineCullerEffect(final TimelineCullerEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public TimelineCullerEffect copy() { + return new TimelineCullerEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + return objectId.equals(source.getSourceId()) + && source.isControlledBy(playerId) + && affectedAbility instanceof WarpAbility + && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD + && game.getCard(source.getSourceId()) != null; + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TitaniasChosen.java b/Mage.Sets/src/mage/cards/t/TitaniasChosen.java index 99a42b09c06..2e3ce3ff52a 100644 --- a/Mage.Sets/src/mage/cards/t/TitaniasChosen.java +++ b/Mage.Sets/src/mage/cards/t/TitaniasChosen.java @@ -2,37 +2,41 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.ObjectColor; +import mage.abilities.common.SpellCastAllTriggeredAbility; 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.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.stack.Spell; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ColorPredicate; + +import java.util.UUID; /** - * * @author Backfir3 */ public final class TitaniasChosen extends CardImpl { + private static final FilterSpell filter = new FilterSpell("green spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.GREEN)); + } + public TitaniasChosen(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.subtype.add(SubType.ELF); this.subtype.add(SubType.ARCHER); this.power = new MageInt(1); this.toughness = new MageInt(1); - + // Whenever a player casts a green spell, put a +1/+1 counter on Titania's Chosen. - this.addAbility(new TitaniasChosenAbility()); + this.addAbility(new SpellCastAllTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter, false)); } private TitaniasChosen(final TitaniasChosen card) { @@ -45,36 +49,3 @@ public final class TitaniasChosen extends CardImpl { } } - -class TitaniasChosenAbility extends TriggeredAbilityImpl { - - public TitaniasChosenAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); - } - - private TitaniasChosenAbility(final TitaniasChosenAbility ability) { - super(ability); - } - - @Override - public TitaniasChosenAbility copy() { - return new TitaniasChosenAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.SPELL_CAST; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - return spell != null && spell.getColor(game).isGreen(); - } - - @Override - public String getRule() { - return "Whenever a player casts a green spell, put a +1/+1 counter on Titania's Chosen."; - } - -} diff --git a/Mage.Sets/src/mage/cards/t/ToilTrouble.java b/Mage.Sets/src/mage/cards/t/ToilTrouble.java index c1c2a83838a..e904bbcffe7 100644 --- a/Mage.Sets/src/mage/cards/t/ToilTrouble.java +++ b/Mage.Sets/src/mage/cards/t/ToilTrouble.java @@ -1,6 +1,5 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.dynamicvalue.common.CardsInTargetHandCount; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; @@ -12,6 +11,8 @@ import mage.constants.CardType; import mage.constants.SpellAbilityType; import mage.target.TargetPlayer; +import java.util.UUID; + public final class ToilTrouble extends SplitCard { public ToilTrouble(UUID ownerId, CardSetInfo setInfo) { @@ -26,7 +27,7 @@ public final class ToilTrouble extends SplitCard { // Trouble // Trouble deals damage to target player equal to the number of cards in that player's hand. Effect effect = new DamageTargetEffect(CardsInTargetHandCount.instance); - effect.setText("Trouble deals damage to target player equal to the number of cards in that player's hand"); + effect.setText("{this} deals damage to target player equal to the number of cards in that player's hand"); getRightHalfCard().getSpellAbility().addEffect(effect); getRightHalfCard().getSpellAbility().addTarget(new TargetPlayer().withChooseHint("to deal damage to")); diff --git a/Mage.Sets/src/mage/cards/t/TolarianAcademy.java b/Mage.Sets/src/mage/cards/t/TolarianAcademy.java index a6ed23bf1ac..235e539bdcd 100644 --- a/Mage.Sets/src/mage/cards/t/TolarianAcademy.java +++ b/Mage.Sets/src/mage/cards/t/TolarianAcademy.java @@ -1,36 +1,27 @@ - - package mage.cards.t; -import java.util.UUID; import mage.Mana; -import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.hint.common.ArtifactYouControlHint; import mage.abilities.mana.DynamicManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SuperType; -import mage.filter.common.FilterControlledPermanent; + +import java.util.UUID; /** - * * @author Backfir3 */ public final class TolarianAcademy extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("artifact you control"); - - static { - filter.add(CardType.ARTIFACT.getPredicate()); - } - public TolarianAcademy(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); this.supertype.add(SuperType.LEGENDARY); - DynamicManaAbility ability = new DynamicManaAbility(Mana.BlueMana(1), new PermanentsOnBattlefieldCount(filter)); - this.addAbility(ability); + this.addAbility(new DynamicManaAbility(Mana.BlueMana(1), ArtifactYouControlCount.instance).addHint(ArtifactYouControlHint.instance)); } private TolarianAcademy(final TolarianAcademy card) { diff --git a/Mage.Sets/src/mage/cards/t/TractorBeam.java b/Mage.Sets/src/mage/cards/t/TractorBeam.java new file mode 100644 index 00000000000..3c1c71622ca --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TractorBeam.java @@ -0,0 +1,64 @@ +package mage.cards.t; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DontUntapInControllersUntapStepEnchantedEffect; +import mage.abilities.effects.common.TapEnchantedEffect; +import mage.abilities.effects.common.continuous.ControlEnchantedEffect; +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.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TractorBeam extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("creature or Spacecraft"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.SPACECRAFT.getPredicate() + )); + } + + public TractorBeam(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{U}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature or Spacecraft + TargetPermanent auraTarget = new TargetPermanent(filter); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When this Aura enters, tap enchanted permanent. + this.addAbility(new EntersBattlefieldTriggeredAbility(new TapEnchantedEffect("permanent"))); + + // You control enchanted permanent. + this.addAbility(new SimpleStaticAbility(new ControlEnchantedEffect("permanent"))); + + // Enchanted permanent doesn't untap during its controller's untap step. + this.addAbility(new SimpleStaticAbility(new DontUntapInControllersUntapStepEnchantedEffect("permanent"))); + } + + private TractorBeam(final TractorBeam card) { + super(card); + } + + @Override + public TractorBeam copy() { + return new TractorBeam(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TragicTrajectory.java b/Mage.Sets/src/mage/cards/t/TragicTrajectory.java new file mode 100644 index 00000000000..ba467847b25 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TragicTrajectory.java @@ -0,0 +1,46 @@ +package mage.cards.t; + +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; +import mage.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TragicTrajectory extends CardImpl { + + public TragicTrajectory(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}"); + + // Target creature gets -2/-2 until end of turn. + // Void -- That creature gets -10/-10 until end of turn instead if a nonland permanent left the battlefield this turn or a spell was warped this turn. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new AddContinuousEffectToGame(new BoostTargetEffect(-10, -10)), + new AddContinuousEffectToGame(new BoostTargetEffect(-2, -2)), + VoidCondition.instance, "Target creature gets -2/-2 until end of turn.
" + + AbilityWord.VOID.formatWord() + "That creature gets -10/-10 until end of turn instead " + + "if a nonland permanent left the battlefield this turn or a spell was warped this turn" + )); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(VoidCondition.getHint()); + this.getSpellAbility().addWatcher(new VoidWatcher()); + } + + private TragicTrajectory(final TragicTrajectory card) { + super(card); + } + + @Override + public TragicTrajectory copy() { + return new TragicTrajectory(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TreeguardDuo.java b/Mage.Sets/src/mage/cards/t/TreeguardDuo.java index 3b00fa0f264..2db5fb18ec7 100644 --- a/Mage.Sets/src/mage/cards/t/TreeguardDuo.java +++ b/Mage.Sets/src/mage/cards/t/TreeguardDuo.java @@ -35,7 +35,7 @@ public final class TreeguardDuo extends CardImpl { .setText("until end of turn, target creature you control gains vigilance") ); ability.addEffect(new BoostTargetEffect( - CreaturesYouControlCount.instance, CreaturesYouControlCount.instance + CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL ).setText("and gets +X/+X, where X is the number of creatures you control")); ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability.addHint(CreaturesYouControlHint.instance)); diff --git a/Mage.Sets/src/mage/cards/t/TrophyHunter.java b/Mage.Sets/src/mage/cards/t/TrophyHunter.java index 260be60922e..fb818f763f8 100644 --- a/Mage.Sets/src/mage/cards/t/TrophyHunter.java +++ b/Mage.Sets/src/mage/cards/t/TrophyHunter.java @@ -1,7 +1,6 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealtDamageAndDiedTriggeredAbility; @@ -9,29 +8,21 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; -import mage.abilities.keyword.FlyingAbility; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class TrophyHunter extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public TrophyHunter(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); @@ -42,11 +33,11 @@ public final class TrophyHunter extends CardImpl { // {1}{G}: Trophy Hunter deals 1 damage to target creature with flying. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(1), new ManaCostsImpl<>("{1}{G}")); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); // Whenever a creature with flying dealt damage by Trophy Hunter this turn dies, put a +1/+1 counter on Trophy Hunter. - this.addAbility(new DealtDamageAndDiedTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false, filter)); + this.addAbility(new DealtDamageAndDiedTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false, StaticFilters.FILTER_CREATURE_FLYING)); } private TrophyHunter(final TrophyHunter card) { diff --git a/Mage.Sets/src/mage/cards/t/TropicalStorm.java b/Mage.Sets/src/mage/cards/t/TropicalStorm.java index d1b8392b927..83576fd861a 100644 --- a/Mage.Sets/src/mage/cards/t/TropicalStorm.java +++ b/Mage.Sets/src/mage/cards/t/TropicalStorm.java @@ -1,29 +1,26 @@ package mage.cards.t; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.mageobject.ColorPredicate; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class TropicalStorm extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("blue creature"); static { - filter.add(new AbilityPredicate(FlyingAbility.class)); filter2.add(new ColorPredicate(ObjectColor.BLUE)); } @@ -31,7 +28,7 @@ public final class TropicalStorm extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{G}"); // Tropical Storm deals X damage to each creature with flying and 1 additional damage to each blue creature. - this.getSpellAbility().addEffect(new DamageAllEffect(GetXValue.instance, filter)); + this.getSpellAbility().addEffect(new DamageAllEffect(GetXValue.instance, StaticFilters.FILTER_CREATURE_FLYING)); this.getSpellAbility().addEffect(new DamageAllEffect(1, filter2).setText("and 1 additional damage to each blue creature")); } diff --git a/Mage.Sets/src/mage/cards/t/TurnAgainst.java b/Mage.Sets/src/mage/cards/t/TurnAgainst.java index d51dd252f7c..2002a072f30 100644 --- a/Mage.Sets/src/mage/cards/t/TurnAgainst.java +++ b/Mage.Sets/src/mage/cards/t/TurnAgainst.java @@ -1,7 +1,6 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.effects.common.UntapTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; @@ -13,6 +12,8 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author LevelX2 @@ -28,8 +29,8 @@ public final class TurnAgainst extends CardImpl { // Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn. this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addEffect(new GainControlTargetEffect(Duration.EndOfTurn)); - this.getSpellAbility().addEffect(new UntapTargetEffect()); - this.getSpellAbility().addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new UntapTargetEffect().withTargetDescription("that creature")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn).withTargetDescription("It")); } private TurnAgainst(final TurnAgainst card) { diff --git a/Mage.Sets/src/mage/cards/t/TurnBurn.java b/Mage.Sets/src/mage/cards/t/TurnBurn.java index 9c96b0ff772..fcd1e9e2c4a 100644 --- a/Mage.Sets/src/mage/cards/t/TurnBurn.java +++ b/Mage.Sets/src/mage/cards/t/TurnBurn.java @@ -1,7 +1,6 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; @@ -9,13 +8,15 @@ import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; import mage.cards.CardSetInfo; import mage.cards.SplitCard; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; import mage.constants.SpellAbilityType; +import mage.constants.SubType; import mage.game.permanent.token.TokenImpl; import mage.target.common.TargetAnyTarget; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author LevelX2 @@ -34,9 +35,7 @@ public final class TurnBurn extends SplitCard { // Burn // Burn deals 2 damage to any target. - effect = new DamageTargetEffect(2); - effect.setText("Burn deals 2 damage to any target"); - getRightHalfCard().getSpellAbility().addEffect(effect); + getRightHalfCard().getSpellAbility().addEffect(new DamageTargetEffect(2)); getRightHalfCard().getSpellAbility().addTarget(new TargetAnyTarget().withChooseHint("2 damage")); } diff --git a/Mage.Sets/src/mage/cards/u/UglukOfTheWhiteHand.java b/Mage.Sets/src/mage/cards/u/UglukOfTheWhiteHand.java index 8baa25d6ecc..fe7b1234e62 100644 --- a/Mage.Sets/src/mage/cards/u/UglukOfTheWhiteHand.java +++ b/Mage.Sets/src/mage/cards/u/UglukOfTheWhiteHand.java @@ -70,8 +70,8 @@ class UglukOfTheWhiteHandEffect extends OneShotEffect { public UglukOfTheWhiteHandEffect() { super(Outcome.Benefit); - staticText = "put a +1/+1 counter on Ugluk of the White Hand. " + - "If that creature was a Goblin or Orc, put two +1/+1 counters on Ugluk instead."; + staticText = "put a +1/+1 counter on {this}. " + + "If that creature was a Goblin or Orc, put two +1/+1 counters on {this} instead."; } private UglukOfTheWhiteHandEffect(final UglukOfTheWhiteHandEffect effect) { diff --git a/Mage.Sets/src/mage/cards/u/UmbraStalker.java b/Mage.Sets/src/mage/cards/u/UmbraStalker.java index e55e90ad441..0995007199b 100644 --- a/Mage.Sets/src/mage/cards/u/UmbraStalker.java +++ b/Mage.Sets/src/mage/cards/u/UmbraStalker.java @@ -33,7 +33,7 @@ public final class UmbraStalker extends CardImpl { // Chroma - Umbra Stalker's power and toughness are each equal to the number of black mana symbols in the mana costs of cards in your graveyard. DynamicValue xValue = new ChromaUmbraStalkerCount(); Effect effect = new SetBasePowerToughnessSourceEffect(xValue); - effect.setText("Chroma — Umbra Stalker's power and toughness are each equal to the number of black mana symbols in the mana costs of cards in your graveyard."); + effect.setText("Chroma — {this}'s power and toughness are each equal to the number of black mana symbols in the mana costs of cards in your graveyard."); this.addAbility(new SimpleStaticAbility(Zone.ALL, effect) .addHint(new ValueHint("Black mana symbols in your graveyard's permanents", xValue)) ); diff --git a/Mage.Sets/src/mage/cards/u/UmbralCollarZealot.java b/Mage.Sets/src/mage/cards/u/UmbralCollarZealot.java new file mode 100644 index 00000000000..97019c56704 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UmbralCollarZealot.java @@ -0,0 +1,43 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.keyword.SurveilEffect; +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 UmbralCollarZealot extends CardImpl { + + public UmbralCollarZealot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Sacrifice another creature or artifact: Surveil 1. + this.addAbility(new SimpleActivatedAbility( + new SurveilEffect(1), + new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT) + )); + } + + private UmbralCollarZealot(final UmbralCollarZealot card) { + super(card); + } + + @Override + public UmbralCollarZealot copy() { + return new UmbralCollarZealot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnlivingPsychopath.java b/Mage.Sets/src/mage/cards/u/UnlivingPsychopath.java index 66b30b179d0..bec4d4e3a79 100644 --- a/Mage.Sets/src/mage/cards/u/UnlivingPsychopath.java +++ b/Mage.Sets/src/mage/cards/u/UnlivingPsychopath.java @@ -1,7 +1,6 @@ package mage.cards.u; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -9,20 +8,20 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ColoredManaCost; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ColoredManaSymbol; import mage.constants.Duration; -import mage.constants.Zone; +import mage.constants.SubType; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** * @@ -30,7 +29,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class UnlivingPsychopath extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power less than Unliving Psychopath's power"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power less than {this}'s power"); static { filter.add(new UnlivingPsychopathPowerLessThanSourcePredicate()); @@ -74,6 +73,6 @@ class UnlivingPsychopathPowerLessThanSourcePredicate implements ObjectSourcePlay @Override public String toString() { - return "power less than Unliving Psychopath's power"; + return "power less than {this}'s power"; } } diff --git a/Mage.Sets/src/mage/cards/u/Unravel.java b/Mage.Sets/src/mage/cards/u/Unravel.java new file mode 100644 index 00000000000..169faf1c777 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/Unravel.java @@ -0,0 +1,77 @@ +package mage.cards.u; + +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.stack.Spell; +import mage.target.TargetSpell; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Unravel extends CardImpl { + + public Unravel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}{U}"); + + // Counter target spell. If the amount of mana spent to cast that spell was less than its mana value, you draw a card. + this.getSpellAbility().addEffect(new UnravelEffect()); + this.getSpellAbility().addTarget(new TargetSpell()); + } + + private Unravel(final Unravel card) { + super(card); + } + + @Override + public Unravel copy() { + return new Unravel(this); + } +} + +class UnravelEffect extends OneShotEffect { + + UnravelEffect() { + super(Outcome.Benefit); + staticText = "counter target spell. If the amount of mana spent to cast that spell was less than its mana value, you draw a card"; + } + + private UnravelEffect(final UnravelEffect effect) { + super(effect); + } + + @Override + public UnravelEffect copy() { + return new UnravelEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); + if (spell == null) { + return false; + } + boolean flag = spell + .getStackAbility() + .getManaCostsToPay() + .getUsedManaToPay() + .count() < spell.getManaValue(); + game.getStack().counter(spell.getId(), source, game); + if (!flag) { + return true; + } + 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/u/UntamedKavu.java b/Mage.Sets/src/mage/cards/u/UntamedKavu.java index 10d381cfaf1..cd04b95d359 100644 --- a/Mage.Sets/src/mage/cards/u/UntamedKavu.java +++ b/Mage.Sets/src/mage/cards/u/UntamedKavu.java @@ -1,7 +1,6 @@ package mage.cards.u; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; @@ -16,6 +15,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; +import java.util.UUID; + /** * * @author tcontis @@ -39,7 +40,7 @@ public final class UntamedKavu extends CardImpl { // If Untamed Kavu was kicked, it enters with three +1/+1 counters on it. Ability ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(3)), KickedCondition.ONCE, - "If Untamed Kavu was kicked, it enters with three +1/+1 counters on it.", ""); + "If {this} was kicked, it enters with three +1/+1 counters on it.", ""); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java b/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java index a72e0d4c91d..df9fcd03c86 100644 --- a/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java +++ b/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java @@ -3,7 +3,7 @@ package mage.cards.u; import mage.MageIdentifier; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.PlayLandOrCastSpellFromExileTriggeredAbility; +import mage.abilities.common.PlayLandOrCastSpellTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.AsThoughEffectImpl; @@ -38,7 +38,7 @@ public final class UriangerAugurelt extends CardImpl { this.toughness = new MageInt(3); // Whenever you play a land from exile or cast a spell from exile, you gain 2 life. - this.addAbility(new PlayLandOrCastSpellFromExileTriggeredAbility(new GainLifeEffect(2))); + this.addAbility(new PlayLandOrCastSpellTriggeredAbility(new GainLifeEffect(2), true, false)); // Draw Arcanum -- {T}: Look at the top card of your library. You may exile it face down. this.addAbility(new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/u/UthrosPsionicist.java b/Mage.Sets/src/mage/cards/u/UthrosPsionicist.java new file mode 100644 index 00000000000..1b17c460343 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UthrosPsionicist.java @@ -0,0 +1,44 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.YouCastExactOneSpellThisTurnCondition; +import mage.abilities.decorator.ConditionalCostModificationEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.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 UthrosPsionicist extends CardImpl { + + public UthrosPsionicist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.JELLYFISH); + this.subtype.add(SubType.SCIENTIST); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // The second spell you cast each turn costs {2} less to cast. + this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( + new SpellsCostReductionControllerEffect(StaticFilters.FILTER_CARD, 2), + YouCastExactOneSpellThisTurnCondition.instance, "the second spell you cast each turn costs {2} less to cast" + ))); + } + + private UthrosPsionicist(final UthrosPsionicist card) { + super(card); + } + + @Override + public UthrosPsionicist copy() { + return new UthrosPsionicist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UthrosResearchCraft.java b/Mage.Sets/src/mage/cards/u/UthrosResearchCraft.java new file mode 100644 index 00000000000..6ec09ed190d --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UthrosResearchCraft.java @@ -0,0 +1,66 @@ +package mage.cards.u; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UthrosResearchCraft extends CardImpl { + + public UthrosResearchCraft(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + this.subtype.add(SubType.SPACECRAFT); + + // Station + this.addAbility(new StationAbility()); + + // STATION 3+ + // Whenever you cast an artifact spell, draw a card. Put a charge counter on this Spacecraft. + Ability ability = new SpellCastControllerTriggeredAbility( + new DrawCardSourceControllerEffect(1), + StaticFilters.FILTER_SPELL_AN_ARTIFACT, false + ); + ability.addEffect(new AddCountersSourceEffect(CounterType.CHARGE.createInstance())); + this.addAbility(new StationLevelAbility(3).withLevelAbility(ability)); + + // STATION 12+ + // Flying + // This Spacecraft gets +1/+0 for each artifact you control. + // 0/8 + this.addAbility(new StationLevelAbility(12) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(new SimpleStaticAbility(new BoostSourceEffect( + ArtifactYouControlCount.instance, StaticValue.get(0), Duration.WhileOnBattlefield + ))) + .withPT(0, 8)); + } + + private UthrosResearchCraft(final UthrosResearchCraft card) { + super(card); + } + + @Override + public UthrosResearchCraft copy() { + return new UthrosResearchCraft(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UthrosScanship.java b/Mage.Sets/src/mage/cards/u/UthrosScanship.java new file mode 100644 index 00000000000..be7a0cee04e --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UthrosScanship.java @@ -0,0 +1,49 @@ +package mage.cards.u; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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 UthrosScanship extends CardImpl { + + public UthrosScanship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{U}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, draw two cards, then discard a card. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new DrawDiscardControllerEffect(2, 1) + )); + + // Station + this.addAbility(new StationAbility()); + + // STATION 8+ + // Flying + // 4/4 + this.addAbility(new StationLevelAbility(8) + .withLevelAbility(FlyingAbility.getInstance()) + .withPT(4, 4)); + } + + private UthrosScanship(final UthrosScanship card) { + super(card); + } + + @Override + public UthrosScanship copy() { + return new UthrosScanship(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UthrosTitanicGodcore.java b/Mage.Sets/src/mage/cards/u/UthrosTitanicGodcore.java new file mode 100644 index 00000000000..d1228201515 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UthrosTitanicGodcore.java @@ -0,0 +1,57 @@ +package mage.cards.u; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.hint.common.ArtifactYouControlHint; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.abilities.mana.BlueManaAbility; +import mage.abilities.mana.DynamicManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UthrosTitanicGodcore extends CardImpl { + + public UthrosTitanicGodcore(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.PLANET); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {U}. + this.addAbility(new BlueManaAbility()); + + // Station + this.addAbility(new StationAbility()); + + // STATION 12+ + // {U}, {T}: Add {U} for each artifact you control. + Ability ability = new DynamicManaAbility( + Mana.BlueMana(1), ArtifactYouControlCount.instance, new ManaCostsImpl<>("{U}") + ); + ability.addCost(new TapSourceCost()); + this.addAbility(new StationLevelAbility(12).withLevelAbility(ability).addHint(ArtifactYouControlHint.instance)); + } + + private UthrosTitanicGodcore(final UthrosTitanicGodcore card) { + super(card); + } + + @Override + public UthrosTitanicGodcore copy() { + return new UthrosTitanicGodcore(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java index 421af9c0888..667cebb3c60 100644 --- a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java +++ b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java @@ -97,7 +97,7 @@ class ValkiGodOfLiesRevealExileEffect extends OneShotEffect { ValkiGodOfLiesRevealExileEffect() { super(Outcome.Benefit); - staticText = "each opponent reveals their hand. For each opponent, exile a creature card they revealed this way until Valki leaves the battlefield."; + staticText = "each opponent reveals their hand. For each opponent, exile a creature card they revealed this way until {this} leaves the battlefield."; } private ValkiGodOfLiesRevealExileEffect(final ValkiGodOfLiesRevealExileEffect effect) { @@ -147,7 +147,7 @@ class ValkiGodOfLiesCopyExiledEffect extends OneShotEffect { ValkiGodOfLiesCopyExiledEffect() { super(Outcome.Benefit); - this.staticText = "Choose a creature card exiled with Valki with mana value X. Valki becomes a copy of that card."; + this.staticText = "Choose a creature card exiled with {this} with mana value X. {this} becomes a copy of that card."; } private ValkiGodOfLiesCopyExiledEffect(final ValkiGodOfLiesCopyExiledEffect effect) { diff --git a/Mage.Sets/src/mage/cards/v/VaultguardTrooper.java b/Mage.Sets/src/mage/cards/v/VaultguardTrooper.java new file mode 100644 index 00000000000..322ff113c58 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VaultguardTrooper.java @@ -0,0 +1,45 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.common.TwoTappedCreaturesCondition; +import mage.abilities.costs.common.DiscardHandCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class VaultguardTrooper extends CardImpl { + + public VaultguardTrooper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.KAVU); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // At the beginning of your end step, if you control two or more tapped creatures, you may discard your hand. If you do, draw two cards. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new DoIfCostPaid(new DrawCardSourceControllerEffect(2), new DiscardHandCost()) + ).withInterveningIf(TwoTappedCreaturesCondition.instance); + this.addAbility(ability.addHint(TwoTappedCreaturesCondition.getHint())); + } + + private VaultguardTrooper(final VaultguardTrooper card) { + super(card); + } + + @Override + public VaultguardTrooper copy() { + return new VaultguardTrooper(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/v/VernalFen.java b/Mage.Sets/src/mage/cards/v/VernalFen.java new file mode 100644 index 00000000000..8f249cf9fbf --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VernalFen.java @@ -0,0 +1,53 @@ +package mage.cards.v; + +import mage.abilities.common.EntersBattlefieldTappedUnlessAbility; +import mage.abilities.condition.common.YouControlPermanentCondition; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.GreenManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VernalFen extends CardImpl { + + private static final FilterLandPermanent filter = new FilterLandPermanent("basic lands"); + + static { + filter.add(SuperType.BASIC.getPredicate()); + } + + private static final YouControlPermanentCondition condition = + new YouControlPermanentCondition(filter, ComparisonType.OR_GREATER, 2); + + public VernalFen(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.SWAMP); + this.subtype.add(SubType.FOREST); + + // ({T}: Add {B} or {G}.) + this.addAbility(new BlackManaAbility()); + this.addAbility(new GreenManaAbility()); + + // This land enters tapped unless you control two or more basic lands. + this.addAbility(new EntersBattlefieldTappedUnlessAbility(condition).addHint(condition.getHint())); + } + + private VernalFen(final VernalFen card) { + super(card); + } + + @Override + public VernalFen copy() { + return new VernalFen(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VertexPaladin.java b/Mage.Sets/src/mage/cards/v/VertexPaladin.java index cc5504fcf2c..179114e6001 100644 --- a/Mage.Sets/src/mage/cards/v/VertexPaladin.java +++ b/Mage.Sets/src/mage/cards/v/VertexPaladin.java @@ -33,7 +33,7 @@ public final class VertexPaladin extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Vertex Paladin's power and toughness are each equal to the number of creatures you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL))); } private VertexPaladin(final VertexPaladin card) { diff --git a/Mage.Sets/src/mage/cards/v/Vertigo.java b/Mage.Sets/src/mage/cards/v/Vertigo.java index 4e51ad30f95..819f70d54da 100644 --- a/Mage.Sets/src/mage/cards/v/Vertigo.java +++ b/Mage.Sets/src/mage/cards/v/Vertigo.java @@ -1,7 +1,6 @@ package mage.cards.v; -import java.util.UUID; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.LoseAbilityTargetEffect; import mage.abilities.keyword.FlyingAbility; @@ -9,32 +8,24 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author Quercitron */ public final class Vertigo extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Vertigo(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{R}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); // Vertigo deals 2 damage to target creature with flying. That creature loses flying until end of turn. this.getSpellAbility().addEffect(new DamageTargetEffect(2)); this.getSpellAbility().addEffect(new LoseAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn) .setText("That creature loses flying until end of turn")); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private Vertigo(final Vertigo card) { diff --git a/Mage.Sets/src/mage/cards/v/VeteranWarleader.java b/Mage.Sets/src/mage/cards/v/VeteranWarleader.java index 0a37c7a8ea8..2c65121d0f0 100644 --- a/Mage.Sets/src/mage/cards/v/VeteranWarleader.java +++ b/Mage.Sets/src/mage/cards/v/VeteranWarleader.java @@ -46,7 +46,7 @@ public final class VeteranWarleader extends CardImpl { // Veteran Warleader's power and toughness are each equal to the number of creatures you control. this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect( - CreaturesYouControlCount.instance)) + CreaturesYouControlCount.PLURAL)) .addHint(CreaturesYouControlHint.instance)); // Tap another untapped Ally you control: Veteran Warleader gains your choice of first strike, vigilance, or trample until end of turn. diff --git a/Mage.Sets/src/mage/cards/v/ViashinoHeretic.java b/Mage.Sets/src/mage/cards/v/ViashinoHeretic.java index bc04c348b9d..271ab8d64dd 100644 --- a/Mage.Sets/src/mage/cards/v/ViashinoHeretic.java +++ b/Mage.Sets/src/mage/cards/v/ViashinoHeretic.java @@ -11,7 +11,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; import mage.players.Player; @@ -53,7 +52,7 @@ class ViashinoHereticEffect extends OneShotEffect { public ViashinoHereticEffect() { super(Outcome.DestroyPermanent); - this.staticText = "Destroy target artifact. Viashino Heretic deals damage to that artifact's controller equal to the artifact's mana value"; + this.staticText = "Destroy target artifact. {this} deals damage to that artifact's controller equal to the artifact's mana value"; } private ViashinoHereticEffect(final ViashinoHereticEffect effect) { diff --git a/Mage.Sets/src/mage/cards/v/VileManifestation.java b/Mage.Sets/src/mage/cards/v/VileManifestation.java index e00a5399975..ffae65fb4eb 100644 --- a/Mage.Sets/src/mage/cards/v/VileManifestation.java +++ b/Mage.Sets/src/mage/cards/v/VileManifestation.java @@ -16,7 +16,6 @@ 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.mageobject.AbilityPredicate; @@ -46,7 +45,7 @@ public final class VileManifestation extends CardImpl { // Vile Manifestation gets +1/+0 for each card with cycling in your graveyard. DynamicValue amount = new CardsInControllerGraveyardCount(filter); Effect effect = new BoostSourceEffect(amount, StaticValue.get(0), Duration.WhileOnBattlefield); - effect.setText("Vile Manifestation gets +1/+0 for each card with cycling in your graveyard."); + effect.setText("{this} gets +1/+0 for each card with cycling in your graveyard."); Ability ability = new SimpleStaticAbility(effect); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/v/VillainousWealth.java b/Mage.Sets/src/mage/cards/v/VillainousWealth.java index 15bbf0c8b63..a9da3ab3fb2 100644 --- a/Mage.Sets/src/mage/cards/v/VillainousWealth.java +++ b/Mage.Sets/src/mage/cards/v/VillainousWealth.java @@ -71,6 +71,8 @@ class VillainousWealthEffect extends OneShotEffect { } Cards cards = new CardsImpl(opponent.getLibrary().getTopCards(game, xValue)); opponent.moveCards(cards, Zone.EXILED, source, game); + game.processAction(); + cards.retainZone(Zone.EXILED, game); FilterCard filter = new FilterCard(); filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, CardUtil.getSourceCostsTag(game, source, "X", 0) + 1)); CardUtil.castMultipleWithAttributeForFree(controller, source, game, cards, filter); diff --git a/Mage.Sets/src/mage/cards/v/ViridianScout.java b/Mage.Sets/src/mage/cards/v/ViridianScout.java index 3cbf66865ef..5cf039985b4 100644 --- a/Mage.Sets/src/mage/cards/v/ViridianScout.java +++ b/Mage.Sets/src/mage/cards/v/ViridianScout.java @@ -1,36 +1,28 @@ package mage.cards.v; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.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.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author Plopman */ public final class ViridianScout extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - static{ - filter.add(new AbilityPredicate(FlyingAbility.class)); - } public ViridianScout(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); this.subtype.add(SubType.ELF); this.subtype.add(SubType.WARRIOR); this.subtype.add(SubType.SCOUT); @@ -41,7 +33,7 @@ public final class ViridianScout extends CardImpl { // {2}{G}, Sacrifice Viridian Scout: Viridian Scout deals 2 damage to target creature with flying. Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(2, "it"), new ManaCostsImpl<>("{2}{G}")); ability.addCost(new SacrificeSourceCost()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/v/VirulentSilencer.java b/Mage.Sets/src/mage/cards/v/VirulentSilencer.java new file mode 100644 index 00000000000..2186ec9df40 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VirulentSilencer.java @@ -0,0 +1,53 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.effects.common.counter.AddPoisonCounterTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TokenPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VirulentSilencer extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("a nontoken artifact creature you control"); + + static { + filter.add(TokenPredicate.FALSE); + filter.add(CardType.ARTIFACT.getPredicate()); + filter.add(CardType.CREATURE.getPredicate()); + } + + public VirulentSilencer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever a nontoken artifact creature you control deals combat damage to a player, that player gets two poison counters. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new AddPoisonCounterTargetEffect(2), filter, + false, SetTargetPointer.PLAYER, true + )); + } + + private VirulentSilencer(final VirulentSilencer card) { + super(card); + } + + @Override + public VirulentSilencer copy() { + return new VirulentSilencer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VisionsOfGlory.java b/Mage.Sets/src/mage/cards/v/VisionsOfGlory.java index e21ee13a8dd..d9907ed42ca 100644 --- a/Mage.Sets/src/mage/cards/v/VisionsOfGlory.java +++ b/Mage.Sets/src/mage/cards/v/VisionsOfGlory.java @@ -23,7 +23,7 @@ public final class VisionsOfGlory extends CardImpl { // Create a 1/1 white Human creature token for each creature you control. this.getSpellAbility().addEffect(new CreateTokenEffect( - new HumanToken(), CreaturesYouControlCount.instance + new HumanToken(), CreaturesYouControlCount.PLURAL ).setText("create a 1/1 white Human creature token for each creature you control")); // Flashback {8}{W}{W}. This spell costs {X} less to cast this way, where X is the greatest mana value of a commander you own on the battlefield or in the command zone. diff --git a/Mage.Sets/src/mage/cards/v/VoidforgedTitan.java b/Mage.Sets/src/mage/cards/v/VoidforgedTitan.java new file mode 100644 index 00000000000..1183f7c3a64 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VoidforgedTitan.java @@ -0,0 +1,47 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.common.VoidCondition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +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.watchers.common.VoidWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VoidforgedTitan extends CardImpl { + + public VoidforgedTitan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}{B}"); + + this.subtype.add(SubType.ROBOT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, you draw a card and lose 1 life. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new DrawCardSourceControllerEffect(1, true) + ).withInterveningIf(VoidCondition.instance); + ability.addEffect(new LoseLifeSourceControllerEffect(1).setText("and lose 1 life")); + this.addAbility(ability.setAbilityWord(AbilityWord.VOID).addHint(VoidCondition.getHint()), new VoidWatcher()); + } + + private VoidforgedTitan(final VoidforgedTitan card) { + super(card); + } + + @Override + public VoidforgedTitan copy() { + return new VoidforgedTitan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VoteOut.java b/Mage.Sets/src/mage/cards/v/VoteOut.java new file mode 100644 index 00000000000..47a480669e2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VoteOut.java @@ -0,0 +1,36 @@ +package mage.cards.v; + +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.ConvokeAbility; +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 VoteOut extends CardImpl { + + public VoteOut(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}"); + + // Convoke + this.addAbility(new ConvokeAbility()); + + // Destroy target creature. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private VoteOut(final VoteOut card) { + super(card); + } + + @Override + public VoteOut copy() { + return new VoteOut(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WarBarge.java b/Mage.Sets/src/mage/cards/w/WarBarge.java index 007cfdb25e2..943ceb4a26c 100644 --- a/Mage.Sets/src/mage/cards/w/WarBarge.java +++ b/Mage.Sets/src/mage/cards/w/WarBarge.java @@ -1,7 +1,6 @@ package mage.cards.w; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -22,6 +21,8 @@ import mage.game.events.ZoneChangeEvent; import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author L_J @@ -83,6 +84,6 @@ class WarBargeDelayedTriggeredAbility extends DelayedTriggeredAbility { @Override public String getRule() { - return "When War Barge leaves the battlefield this turn, destroy that creature. A creature destroyed this way can't be regenerated."; + return "When {this} leaves the battlefield this turn, destroy that creature. A creature destroyed this way can't be regenerated."; } } diff --git a/Mage.Sets/src/mage/cards/w/WardenOfTheWall.java b/Mage.Sets/src/mage/cards/w/WardenOfTheWall.java index 80682bee3e0..63549eed066 100644 --- a/Mage.Sets/src/mage/cards/w/WardenOfTheWall.java +++ b/Mage.Sets/src/mage/cards/w/WardenOfTheWall.java @@ -14,7 +14,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; import mage.game.permanent.token.TokenImpl; import java.util.UUID; @@ -37,7 +36,7 @@ public final class WardenOfTheWall extends CardImpl { this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BecomesCreatureSourceEffect(new GargoyleToken(), CardType.ARTIFACT, Duration.WhileOnBattlefield), NotMyTurnCondition.instance, - "During turns other than yours, Warden of the Wall is a 2/3 Gargoyle artifact creature with flying")) + "During turns other than yours, {this} is a 2/3 Gargoyle artifact creature with flying")) .addHint(NotMyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/w/WarmakerGunship.java b/Mage.Sets/src/mage/cards/w/WarmakerGunship.java new file mode 100644 index 00000000000..1d67685cdf9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WarmakerGunship.java @@ -0,0 +1,53 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WarmakerGunship extends CardImpl { + + public WarmakerGunship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, it deals damage equal to the number of artifacts you control to target creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(ArtifactYouControlCount.instance) + .setText("it deals damage equal to the number of artifacts you control to target creature an opponent controls")); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + + // Station + this.addAbility(new StationAbility()); + + // STATION 6+ + // Flying + // 4/3 + this.addAbility(new StationLevelAbility(6) + .withLevelAbility(FlyingAbility.getInstance()) + .withPT(4, 3)); + } + + private WarmakerGunship(final WarmakerGunship card) { + super(card); + } + + @Override + public WarmakerGunship copy() { + return new WarmakerGunship(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WarrenPilferers.java b/Mage.Sets/src/mage/cards/w/WarrenPilferers.java index f5bab468061..743ae3ff9bf 100644 --- a/Mage.Sets/src/mage/cards/w/WarrenPilferers.java +++ b/Mage.Sets/src/mage/cards/w/WarrenPilferers.java @@ -1,6 +1,5 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -16,6 +15,8 @@ import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** * * @author LevelX2 @@ -50,7 +51,7 @@ class WarrenPilferersReturnEffect extends OneShotEffect { WarrenPilferersReturnEffect() { super(Outcome.ReturnToHand); - staticText = "return target creature card from your graveyard to your hand. If that card is a Goblin card, Warren Pilferers gains haste until end of turn"; + staticText = "return target creature card from your graveyard to your hand. If that card is a Goblin card, {this} gains haste until end of turn"; } private WarrenPilferersReturnEffect(final WarrenPilferersReturnEffect effect) { diff --git a/Mage.Sets/src/mage/cards/w/WayfaringTemple.java b/Mage.Sets/src/mage/cards/w/WayfaringTemple.java index 8a277c5d05e..9f719c07f97 100644 --- a/Mage.Sets/src/mage/cards/w/WayfaringTemple.java +++ b/Mage.Sets/src/mage/cards/w/WayfaringTemple.java @@ -28,7 +28,7 @@ public final class WayfaringTemple extends CardImpl { this.toughness = new MageInt(0); // Wayfaring Temple's power and toughness are each equal to the number of creatures you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance)) + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL)) .addHint(CreaturesYouControlHint.instance)); // Whenever Wayfaring Temple deals combat damage to a player, populate. (Create a token that's a copy of a creature token you control.) diff --git a/Mage.Sets/src/mage/cards/w/WeaponsManufacturing.java b/Mage.Sets/src/mage/cards/w/WeaponsManufacturing.java new file mode 100644 index 00000000000..ff7bfee496d --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WeaponsManufacturing.java @@ -0,0 +1,41 @@ +package mage.cards.w; + +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.MunitionsToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WeaponsManufacturing extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledArtifactPermanent("nontoken artifact you control"); + + static { + filter.add(TokenPredicate.FALSE); + } + + public WeaponsManufacturing(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); + + // Whenever a nontoken artifact you control enters, create a colorless artifact token named Munitions with "When this token leaves the battlefield, it deals 2 damage to any target." + this.addAbility(new EntersBattlefieldAllTriggeredAbility(new CreateTokenEffect(new MunitionsToken()), filter)); + } + + private WeaponsManufacturing(final WeaponsManufacturing card) { + super(card); + } + + @Override + public WeaponsManufacturing copy() { + return new WeaponsManufacturing(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WedgelightRammer.java b/Mage.Sets/src/mage/cards/w/WedgelightRammer.java new file mode 100644 index 00000000000..8eec34c492b --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WedgelightRammer.java @@ -0,0 +1,51 @@ +package mage.cards.w; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.RobotToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WedgelightRammer extends CardImpl { + + public WedgelightRammer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{W}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, create a 2/2 colorless Robot artifact creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new RobotToken()))); + + // Station + this.addAbility(new StationAbility()); + + // STATION 9+ + // Flying + // First strike + // 3/4 + this.addAbility(new StationLevelAbility(9) + .withLevelAbility(FlyingAbility.getInstance()) + .withLevelAbility(FirstStrikeAbility.getInstance()) + .withPT(3, 4)); + } + + private WedgelightRammer(final WedgelightRammer card) { + super(card); + } + + @Override + public WedgelightRammer copy() { + return new WedgelightRammer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WeftbladeEnhancer.java b/Mage.Sets/src/mage/cards/w/WeftbladeEnhancer.java new file mode 100644 index 00000000000..49a3ab6c4e1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WeftbladeEnhancer.java @@ -0,0 +1,47 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.WarpAbility; +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 WeftbladeEnhancer extends CardImpl { + + public WeftbladeEnhancer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); + + this.subtype.add(SubType.DRIX); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When this creature 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); + + // Warp {2}{W} + this.addAbility(new WarpAbility(this, "{2}{W}")); + } + + private WeftbladeEnhancer(final WeftbladeEnhancer card) { + super(card); + } + + @Override + public WeftbladeEnhancer copy() { + return new WeftbladeEnhancer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WeftstalkerArdent.java b/Mage.Sets/src/mage/cards/w/WeftstalkerArdent.java new file mode 100644 index 00000000000..c59cbadb227 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WeftstalkerArdent.java @@ -0,0 +1,47 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.keyword.WarpAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WeftstalkerArdent extends CardImpl { + + public WeftstalkerArdent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.DRIX); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever another creature or artifact you control enters, this creature deals 1 damage to each opponent. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new DamagePlayersEffect(1, TargetController.OPPONENT), + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT + )); + + // Warp {R} + this.addAbility(new WarpAbility(this, "{R}")); + } + + private WeftstalkerArdent(final WeftstalkerArdent card) { + super(card); + } + + @Override + public WeftstalkerArdent copy() { + return new WeftstalkerArdent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/Weftwalking.java b/Mage.Sets/src/mage/cards/w/Weftwalking.java new file mode 100644 index 00000000000..bb0277c953b --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/Weftwalking.java @@ -0,0 +1,95 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CastFromEverywhereSourceCondition; +import mage.abilities.costs.AlternativeCostSourceAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ShuffleHandGraveyardIntoLibraryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Weftwalking extends CardImpl { + + public Weftwalking(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{U}{U}"); + + // When this enchantment enters, if you cast it, shuffle your hand and graveyard into your library, then draw seven cards. + Ability ability = new EntersBattlefieldTriggeredAbility(new ShuffleHandGraveyardIntoLibraryEffect()) + .withInterveningIf(CastFromEverywhereSourceCondition.instance); + ability.addEffect(new DrawCardSourceControllerEffect(7).concatBy(", then")); + this.addAbility(ability); + + // The first spell each player casts during each of their turns may be cast without paying its mana cost. + this.addAbility(new SimpleStaticAbility(new WeftwalkingEffect())); + } + + private Weftwalking(final Weftwalking card) { + super(card); + } + + @Override + public Weftwalking copy() { + return new Weftwalking(this); + } +} + +class WeftwalkingEffect extends ContinuousEffectImpl { + + private enum WeftwalkingCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getState() + .getWatcher(SpellsCastWatcher.class) + .getSpellsCastThisTurn(game.getActivePlayerId()) + .isEmpty(); + } + } + + private final AlternativeCostSourceAbility alternativeCastingCostAbility; + + WeftwalkingEffect() { + super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.PlayForFree); + this.alternativeCastingCostAbility = new AlternativeCostSourceAbility( + null, WeftwalkingCondition.instance, null, StaticFilters.FILTER_CARD_NON_LAND, true + ); + staticText = "the first spell each player casts during each of their turns may be cast without paying its mana cost"; + } + + private WeftwalkingEffect(final WeftwalkingEffect effect) { + super(effect); + this.alternativeCastingCostAbility = effect.alternativeCastingCostAbility; + } + + @Override + public WeftwalkingEffect copy() { + return new WeftwalkingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(game.getActivePlayerId()); + if (player == null) { + return false; + } + alternativeCastingCostAbility.setSourceId(source.getSourceId()); + player.getAlternativeSourceCosts().add(alternativeCastingCostAbility); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/w/WestvaleCultLeader.java b/Mage.Sets/src/mage/cards/w/WestvaleCultLeader.java index 48a394838ec..2cdb7950a39 100644 --- a/Mage.Sets/src/mage/cards/w/WestvaleCultLeader.java +++ b/Mage.Sets/src/mage/cards/w/WestvaleCultLeader.java @@ -31,7 +31,7 @@ public final class WestvaleCultLeader extends CardImpl { this.nightCard = true; // Westvale Cult Leader's power and toughness are each equal to the number of creatures you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance)) + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL)) .addHint(CreaturesYouControlHint.instance)); // At the beginning of your end step, create a 1/1 white and black Human Cleric creature token. diff --git a/Mage.Sets/src/mage/cards/w/WhirlingCatapult.java b/Mage.Sets/src/mage/cards/w/WhirlingCatapult.java index 902b9b8eb09..895d5a1344b 100644 --- a/Mage.Sets/src/mage/cards/w/WhirlingCatapult.java +++ b/Mage.Sets/src/mage/cards/w/WhirlingCatapult.java @@ -1,36 +1,28 @@ package mage.cards.w; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.ExileFromTopOfLibraryCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageEverythingEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author L_J */ public final class WhirlingCatapult extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public WhirlingCatapult(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{4}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); // {2}, Exile the top two cards of your library: Whirling Catapult deals 1 damage to each creature with flying and each player. - Ability ability = new SimpleActivatedAbility(new DamageEverythingEffect(1, filter), new ManaCostsImpl<>("{2}")); + Ability ability = new SimpleActivatedAbility(new DamageEverythingEffect(1, StaticFilters.FILTER_CREATURE_FLYING), new ManaCostsImpl<>("{2}")); ability.addCost(new ExileFromTopOfLibraryCost(2)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/w/WhispergearSneak.java b/Mage.Sets/src/mage/cards/w/WhispergearSneak.java index 2c4a5d11fe9..c6df85e9149 100644 --- a/Mage.Sets/src/mage/cards/w/WhispergearSneak.java +++ b/Mage.Sets/src/mage/cards/w/WhispergearSneak.java @@ -1,14 +1,15 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.InfoEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.InfoEffect; + +import java.util.UUID; /** * @@ -26,10 +27,10 @@ public final class WhispergearSneak extends CardImpl { // TODO: Draft specific abilities not implemented // Draft Whispergear Sneak face up. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Draft Whispergear Sneak face up - not implemented."))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Draft {this} face up - not implemented."))); // During the draft, you may turn Whispergear Sneak face down. If you do, look at any unopened booster pack in the draft or any booster pack not being looked at by another player. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("During the draft, you may turn Whispergear Sneak face down. If you do, " + this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("During the draft, you may turn {this} face down. If you do, " + "look at any unopened booster pack in the draft or any booster pack not being looked at by another player - not implemented."))); } diff --git a/Mage.Sets/src/mage/cards/w/WickedAkuba.java b/Mage.Sets/src/mage/cards/w/WickedAkuba.java index 32f48de14fa..a4cfc10ff45 100644 --- a/Mage.Sets/src/mage/cards/w/WickedAkuba.java +++ b/Mage.Sets/src/mage/cards/w/WickedAkuba.java @@ -1,7 +1,6 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -10,9 +9,8 @@ import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.ColoredManaSymbol; -import mage.constants.Zone; +import mage.constants.SubType; import mage.filter.FilterPlayer; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; @@ -21,13 +19,15 @@ import mage.players.Player; import mage.target.TargetPlayer; import mage.watchers.common.PlayerDamagedBySourceWatcher; +import java.util.UUID; + /** * * @author North */ public final class WickedAkuba extends CardImpl { - private static final FilterPlayer filter = new FilterPlayer("player dealt damage by Wicked Akuba this turn"); + private static final FilterPlayer filter = new FilterPlayer("player dealt damage by {this} this turn"); static { filter.add(new WickedAkubaPredicate()); diff --git a/Mage.Sets/src/mage/cards/w/WillOfTheAbzan.java b/Mage.Sets/src/mage/cards/w/WillOfTheAbzan.java index 3bbcf532696..02f94c7229b 100644 --- a/Mage.Sets/src/mage/cards/w/WillOfTheAbzan.java +++ b/Mage.Sets/src/mage/cards/w/WillOfTheAbzan.java @@ -43,7 +43,7 @@ public final class WillOfTheAbzan extends CardImpl { .setText("any number of target opponents each sacrifice a creature " + "with the greatest power among creatures that player controls")); this.getSpellAbility().addEffect(new LoseLifeTargetControllerEffect(3).setText("and lose 3 life")); - this.getSpellAbility().addTarget(new TargetOpponent(0, 1, false)); + this.getSpellAbility().addTarget(new TargetOpponent(0, Integer.MAX_VALUE, false)); // * Return target creature card from your graveyard to the battlefield. this.getSpellAbility().addMode(new Mode(new ReturnFromGraveyardToBattlefieldTargetEffect()) diff --git a/Mage.Sets/src/mage/cards/w/WillOfTheMardu.java b/Mage.Sets/src/mage/cards/w/WillOfTheMardu.java index b3ce72955dd..4eefbf1ec73 100644 --- a/Mage.Sets/src/mage/cards/w/WillOfTheMardu.java +++ b/Mage.Sets/src/mage/cards/w/WillOfTheMardu.java @@ -43,7 +43,7 @@ public final class WillOfTheMardu extends CardImpl { this.getSpellAbility().addTarget(new TargetPlayer()); // * Will of the Mardu deals damage to target creature equal to the number of creatures you control. - this.getSpellAbility().addMode(new Mode(new DamageTargetEffect(CreaturesYouControlCount.instance) + this.getSpellAbility().addMode(new Mode(new DamageTargetEffect(CreaturesYouControlCount.PLURAL) .setText("{this} deals damage to target creature equal to the number of creatures you control")).addTarget(new TargetCreaturePermanent())); this.getSpellAbility().addHint(WillOfTheMarduHint.instance); } diff --git a/Mage.Sets/src/mage/cards/w/Windstorm.java b/Mage.Sets/src/mage/cards/w/Windstorm.java index bd29496642c..e88c2055c77 100644 --- a/Mage.Sets/src/mage/cards/w/Windstorm.java +++ b/Mage.Sets/src/mage/cards/w/Windstorm.java @@ -1,33 +1,24 @@ package mage.cards.w; -import java.util.UUID; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author North */ public final class Windstorm extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public Windstorm(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{X}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{G}"); - - this.getSpellAbility().addEffect(new DamageAllEffect(GetXValue.instance, filter)); + this.getSpellAbility().addEffect(new DamageAllEffect(GetXValue.instance, StaticFilters.FILTER_CREATURE_FLYING)); } private Windstorm(final Windstorm card) { diff --git a/Mage.Sets/src/mage/cards/w/WingPuncture.java b/Mage.Sets/src/mage/cards/w/WingPuncture.java index 33c8f39bdcd..618e51e6b80 100644 --- a/Mage.Sets/src/mage/cards/w/WingPuncture.java +++ b/Mage.Sets/src/mage/cards/w/WingPuncture.java @@ -1,42 +1,33 @@ package mage.cards.w; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author North */ public final class WingPuncture extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public WingPuncture(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); // Target creature you control deals damage equal to its power to target creature with flying. - this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); this.getSpellAbility().addEffect(new WingPunctureEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private WingPuncture(final WingPuncture card) { @@ -54,7 +45,7 @@ class WingPunctureEffect extends OneShotEffect { WingPunctureEffect() { super(Outcome.Damage); staticText = "Target creature you control deals damage equal to its power to target creature with flying"; - } + } private WingPunctureEffect(final WingPunctureEffect effect) { super(effect); diff --git a/Mage.Sets/src/mage/cards/w/WingSnare.java b/Mage.Sets/src/mage/cards/w/WingSnare.java index f041ce1f5ea..fc4974d233b 100644 --- a/Mage.Sets/src/mage/cards/w/WingSnare.java +++ b/Mage.Sets/src/mage/cards/w/WingSnare.java @@ -1,36 +1,26 @@ package mage.cards.w; -import java.util.UUID; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author Plopman */ public final class WingSnare extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public WingSnare(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); // Destroy target creature with flying. this.getSpellAbility().addEffect(new DestroyTargetEffect()); - this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING)); } private WingSnare(final WingSnare card) { diff --git a/Mage.Sets/src/mage/cards/w/WithengarUnbound.java b/Mage.Sets/src/mage/cards/w/WithengarUnbound.java index dfc2c30b8de..032eae161b0 100644 --- a/Mage.Sets/src/mage/cards/w/WithengarUnbound.java +++ b/Mage.Sets/src/mage/cards/w/WithengarUnbound.java @@ -1,7 +1,6 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -18,6 +17,8 @@ import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; +import java.util.UUID; + /** * * @author BetaSteward @@ -81,6 +82,6 @@ class WithengarUnboundTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever a player loses the game, put thirteen +1/+1 counters on Withengar Unbound."; + return "Whenever a player loses the game, put thirteen +1/+1 counters on {this}."; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/w/WoodfallPrimus.java b/Mage.Sets/src/mage/cards/w/WoodfallPrimus.java index 1414a8a1cff..2fa50b7691f 100644 --- a/Mage.Sets/src/mage/cards/w/WoodfallPrimus.java +++ b/Mage.Sets/src/mage/cards/w/WoodfallPrimus.java @@ -1,7 +1,5 @@ - package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -12,23 +10,18 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class WoodfallPrimus extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); - static { - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - } - public WoodfallPrimus(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{G}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}{G}"); this.subtype.add(SubType.TREEFOLK); this.subtype.add(SubType.SHAMAN); @@ -37,10 +30,12 @@ public final class WoodfallPrimus extends CardImpl { // Trample this.addAbility(TrampleAbility.getInstance()); + // When Woodfall Primus enters the battlefield, destroy target noncreature permanent. - Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect(), false); - ability.addTarget(new TargetPermanent(filter)); + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE)); this.addAbility(ability); + // Persist this.addAbility(new PersistAbility()); } diff --git a/Mage.Sets/src/mage/cards/w/WoollySpider.java b/Mage.Sets/src/mage/cards/w/WoollySpider.java index c0674ac5a21..54f02d7284a 100644 --- a/Mage.Sets/src/mage/cards/w/WoollySpider.java +++ b/Mage.Sets/src/mage/cards/w/WoollySpider.java @@ -1,34 +1,25 @@ - package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.BlocksCreatureTriggeredAbility; import mage.abilities.effects.common.continuous.BoostSourceEffect; -import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author tcontis */ public final class WoollySpider extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying"); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - } - public WoollySpider(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{G}"); this.subtype.add(SubType.SPIDER); this.power = new MageInt(2); @@ -38,7 +29,7 @@ public final class WoollySpider extends CardImpl { this.addAbility(ReachAbility.getInstance()); // Whenever Woolly Spider blocks a creature with flying, Woolly Spider gets +0/+2 until end of turn. - this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(0, 2, Duration.EndOfTurn), filter, false)); + this.addAbility(new BlocksCreatureTriggeredAbility(new BoostSourceEffect(0, 2, Duration.EndOfTurn), StaticFilters.FILTER_CREATURE_FLYING, false)); } private WoollySpider(final WoollySpider card) { @@ -49,4 +40,4 @@ public final class WoollySpider extends CardImpl { public WoollySpider copy() { return new WoollySpider(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/w/WurmwallSweeper.java b/Mage.Sets/src/mage/cards/w/WurmwallSweeper.java new file mode 100644 index 00000000000..ebcce0554cf --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WurmwallSweeper.java @@ -0,0 +1,47 @@ +package mage.cards.w; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.StationAbility; +import mage.abilities.keyword.StationLevelAbility; +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 WurmwallSweeper extends CardImpl { + + public WurmwallSweeper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.SPACECRAFT); + + // When this Spacecraft enters, surveil 2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SurveilEffect(2))); + + // Station + this.addAbility(new StationAbility()); + + // STATION 4+ + // Flying + // 2/2 + this.addAbility(new StationLevelAbility(4) + .withLevelAbility(FlyingAbility.getInstance()) + .withPT(2, 2)); + } + + private WurmwallSweeper(final WurmwallSweeper card) { + super(card); + } + + @Override + public WurmwallSweeper copy() { + return new WurmwallSweeper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/x/XuIfitOsteoharmonist.java b/Mage.Sets/src/mage/cards/x/XuIfitOsteoharmonist.java new file mode 100644 index 00000000000..fa26080b401 --- /dev/null +++ b/Mage.Sets/src/mage/cards/x/XuIfitOsteoharmonist.java @@ -0,0 +1,100 @@ +package mage.cards.x; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class XuIfitOsteoharmonist extends CardImpl { + + public XuIfitOsteoharmonist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // {T}: Return target creature card from your graveyard to the battlefield. It's a Skeleton in addition to its other types and has no abilities. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new ReturnFromGraveyardToBattlefieldTargetEffect(), new TapSourceCost() + ); + ability.addEffect(new XuIfitOsteoharmonistEffect()); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.addAbility(ability); + } + + private XuIfitOsteoharmonist(final XuIfitOsteoharmonist card) { + super(card); + } + + @Override + public XuIfitOsteoharmonist copy() { + return new XuIfitOsteoharmonist(this); + } +} + +class XuIfitOsteoharmonistEffect extends ContinuousEffectImpl { + + XuIfitOsteoharmonistEffect() { + super(Duration.Custom, Outcome.LoseAbility); + staticText = "It's a Skeleton in addition to its other types and has no abilities"; + } + + private XuIfitOsteoharmonistEffect(final XuIfitOsteoharmonistEffect effect) { + super(effect); + } + + @Override + public XuIfitOsteoharmonistEffect copy() { + return new XuIfitOsteoharmonistEffect(this); + } + + @Override + public boolean hasLayer(Layer layer) { + switch (layer) { + case TypeChangingEffects_4: + case AbilityAddingRemovingEffects_6: + return true; + default: + return false; + } + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + discard(); + return false; + } + switch (layer) { + case TypeChangingEffects_4: + permanent.addSubType(game, SubType.SKELETON); + break; + case AbilityAddingRemovingEffects_6: + permanent.removeAllAbilities(source.getSourceId(), game); + } + return true; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/y/YasovaDragonclaw.java b/Mage.Sets/src/mage/cards/y/YasovaDragonclaw.java index 6e6c1848776..9b7e5f83e20 100644 --- a/Mage.Sets/src/mage/cards/y/YasovaDragonclaw.java +++ b/Mage.Sets/src/mage/cards/y/YasovaDragonclaw.java @@ -1,10 +1,8 @@ package mage.cards.y; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DoIfCostPaid; @@ -13,20 +11,18 @@ import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.abilities.keyword.HasteAbility; import mage.abilities.keyword.TrampleAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.SuperType; -import mage.constants.TargetController; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** * @@ -34,7 +30,7 @@ import mage.target.common.TargetCreaturePermanent; */ public final class YasovaDragonclaw extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature an opponent controls with power less than Yasova Dragonclaw's power"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature an opponent controls with power less than {this}'s power"); static { filter.add(new YasovaDragonclawPowerLessThanSourcePredicate()); @@ -82,6 +78,6 @@ class YasovaDragonclawPowerLessThanSourcePredicate implements ObjectSourcePlayer @Override public String toString() { - return "power less than Yasova Dragonclaw's power"; + return "power less than {this}'s power"; } } diff --git a/Mage.Sets/src/mage/cards/y/YomijiWhoBarsTheWay.java b/Mage.Sets/src/mage/cards/y/YomijiWhoBarsTheWay.java index b53b439c8ed..46872a1216f 100644 --- a/Mage.Sets/src/mage/cards/y/YomijiWhoBarsTheWay.java +++ b/Mage.Sets/src/mage/cards/y/YomijiWhoBarsTheWay.java @@ -1,7 +1,6 @@ package mage.cards.y; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.PutIntoGraveFromBattlefieldAllTriggeredAbility; import mage.abilities.effects.Effect; @@ -14,14 +13,21 @@ import mage.constants.SuperType; import mage.filter.FilterPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class YomijiWhoBarsTheWay extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent("a legendary permanent other than {this}"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(SuperType.LEGENDARY.getPredicate()); + } public YomijiWhoBarsTheWay(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}{W}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.SPIRIT); @@ -29,11 +35,7 @@ public final class YomijiWhoBarsTheWay extends CardImpl { this.toughness = new MageInt(4); // Whenever a legendary permanent other than Yomiji, Who Bars the Way is put into a graveyard from the battlefield, return that card to its owner's hand. - FilterPermanent filter = new FilterPermanent("a legendary permanent other than " + getName()); - filter.add(AnotherPredicate.instance); - filter.add(SuperType.LEGENDARY.getPredicate()); - Effect effect = new ReturnToHandTargetEffect(); - effect.setText("return that card to its owner's hand"); + Effect effect = new ReturnToHandTargetEffect().setText("return that card to its owner's hand"); this.addAbility(new PutIntoGraveFromBattlefieldAllTriggeredAbility(effect, false, filter, true)); } diff --git a/Mage.Sets/src/mage/cards/y/YunaHopeOfSpira.java b/Mage.Sets/src/mage/cards/y/YunaHopeOfSpira.java index fcc138cc772..0916b1e5dc5 100644 --- a/Mage.Sets/src/mage/cards/y/YunaHopeOfSpira.java +++ b/Mage.Sets/src/mage/cards/y/YunaHopeOfSpira.java @@ -54,14 +54,14 @@ public final class YunaHopeOfSpira extends CardImpl { // During your turn, Yuna and enchantment creatures you control have trample, lifelink, and ward {2}. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( - new GainAbilityAllEffect(TrampleAbility.getInstance(), Duration.WhileControlled, filter2), + new GainAbilityAllEffect(TrampleAbility.getInstance(), Duration.WhileOnBattlefield, filter2), MyTurnCondition.instance, "during your turn, {this} and enchantment creatures you control have trample" )); ability.addEffect(new ConditionalContinuousEffect(new GainAbilityAllEffect( - LifelinkAbility.getInstance(), Duration.WhileControlled, filter2 + LifelinkAbility.getInstance(), Duration.WhileOnBattlefield, filter2 ), MyTurnCondition.instance, ", lifelink")); ability.addEffect(new ConditionalContinuousEffect(new GainAbilityAllEffect( - new WardAbility(new GenericManaCost(2)), Duration.WhileControlled, filter2 + new WardAbility(new GenericManaCost(2)), Duration.WhileOnBattlefield, filter2 ), MyTurnCondition.instance, ", and ward {2}")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/z/ZealousDisplay.java b/Mage.Sets/src/mage/cards/z/ZealousDisplay.java new file mode 100644 index 00000000000..c8b7d2cee94 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZealousDisplay.java @@ -0,0 +1,39 @@ +package mage.cards.z; + +import mage.abilities.condition.common.NotMyTurnCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.UntapAllEffect; +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 ZealousDisplay extends CardImpl { + + public ZealousDisplay(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}"); + + // Creatures you control get +2/+0 until end of turn. If it's not your turn, untap those creatures. + this.getSpellAbility().addEffect(new BoostControlledEffect(2, 0, Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new UntapAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE), + NotMyTurnCondition.instance, "If it's not your turn, untap those creatures" + )); + } + + private ZealousDisplay(final ZealousDisplay card) { + super(card); + } + + @Override + public ZealousDisplay copy() { + return new ZealousDisplay(this); + } +} diff --git a/Mage.Sets/src/mage/cards/z/ZeroPointBallad.java b/Mage.Sets/src/mage/cards/z/ZeroPointBallad.java new file mode 100644 index 00000000000..f29accdb4a2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZeroPointBallad.java @@ -0,0 +1,124 @@ +package mage.cards.z; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardIdPredicate; +import mage.filter.predicate.mageobject.ToughnessPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCardInGraveyard; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ZeroPointBallad extends CardImpl { + + public ZeroPointBallad(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{B}"); + + // Destroy all creatures with toughness X or less. You lose X life. If X is 6 or more, return a creature card put into a graveyard this way to the battlefield under your control. + this.getSpellAbility().addEffect(new ZeroPointBalladEffect()); + } + + private ZeroPointBallad(final ZeroPointBallad card) { + super(card); + } + + @Override + public ZeroPointBallad copy() { + return new ZeroPointBallad(this); + } +} + +class ZeroPointBalladEffect extends OneShotEffect { + + ZeroPointBalladEffect() { + super(Outcome.DestroyPermanent); + this.staticText = "Destroy all creatures with toughness X or less. " + + "You lose X life. " + + "If X is 6 or more, return a creature card put into a graveyard " + + "this way to the battlefield under your control."; + } + + private ZeroPointBalladEffect(final ZeroPointBalladEffect effect) { + super(effect); + } + + @Override + public ZeroPointBalladEffect copy() { + return new ZeroPointBalladEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + int xValue = GetXValue.instance.calculate(game, source, this); + + Cards putIntoGraveyardThisWay = new CardsImpl(); + + // Destroy all creatures with toughness X or less. + FilterPermanent filterToDestroy = new FilterCreaturePermanent(); + filterToDestroy.add(new ToughnessPredicate(ComparisonType.OR_LESS, xValue)); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filterToDestroy, controller.getId(), source, game)) { + permanent.destroy(source, game, false); + if (game.getState().getZone(permanent.getId()) == Zone.GRAVEYARD) { + putIntoGraveyardThisWay.add(permanent); + } + } + + // You lose X life. + game.processAction(); + new LoseLifeSourceControllerEffect(xValue).apply(game, source); + + if (xValue < 6) { + return true; + } + + // If X is 6 or more, return a creature card put into a graveyard + // this way to the battlefield under your control. + game.processAction(); + MageObject sourceObject = game.getObject(source); + String sourceName = sourceObject != null ? sourceObject.getLogName() : ""; + FilterCard filter = new FilterCreatureCard("creature card put into a graveyard with " + sourceName); + List> cardIdPredicates = new ArrayList<>(); + for (UUID cardId : putIntoGraveyardThisWay) { + cardIdPredicates.add(new CardIdPredicate(cardId)); + } + filter.add(Predicates.or(cardIdPredicates)); + Target target = new TargetCardInGraveyard(filter); + target.withNotTarget(true); + if (!controller.chooseTarget(Outcome.PutCreatureInPlay, target, source, game)) { + return true; + } + Card cardToReturn = game.getCard(target.getFirstTarget()); + if (cardToReturn == null) { + return true; + } + controller.moveCards(cardToReturn, Zone.BATTLEFIELD, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/z/ZoZuThePunisher.java b/Mage.Sets/src/mage/cards/z/ZoZuThePunisher.java index bd57de814ad..1e66dfdc67e 100644 --- a/Mage.Sets/src/mage/cards/z/ZoZuThePunisher.java +++ b/Mage.Sets/src/mage/cards/z/ZoZuThePunisher.java @@ -1,7 +1,6 @@ package mage.cards.z; -import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -18,6 +17,8 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author LevelX @@ -82,6 +83,6 @@ class ZoZuThePunisherAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever a land enters the battlefield, Zo-Zu the Punisher deals 2 damage to that land's controller."; + return "Whenever a land enters the battlefield, {this} deals 2 damage to that land's controller."; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/z/ZookeeperMechan.java b/Mage.Sets/src/mage/cards/z/ZookeeperMechan.java new file mode 100644 index 00000000000..6b9781ec3d2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZookeeperMechan.java @@ -0,0 +1,50 @@ +package mage.cards.z; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ZookeeperMechan extends CardImpl { + + public ZookeeperMechan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // {T}: Add {R}. + this.addAbility(new RedManaAbility()); + + // {6}{R}: Target creature you control gets +4/+0 until end of turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new BoostTargetEffect(4, 0, Duration.EndOfTurn), + new ManaCostsImpl<>("{6}{R}") + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private ZookeeperMechan(final ZookeeperMechan card) { + super(card); + } + + @Override + public ZookeeperMechan copy() { + return new ZookeeperMechan(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/z/ZurgosVanguard.java b/Mage.Sets/src/mage/cards/z/ZurgosVanguard.java index e9eef44b566..9c4f091ee88 100644 --- a/Mage.Sets/src/mage/cards/z/ZurgosVanguard.java +++ b/Mage.Sets/src/mage/cards/z/ZurgosVanguard.java @@ -30,7 +30,7 @@ public final class ZurgosVanguard extends CardImpl { this.addAbility(new MobilizeAbility(1)); // This creature's power is equal to the number of creatures you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(CreaturesYouControlCount.instance))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(CreaturesYouControlCount.PLURAL))); } private ZurgosVanguard(final ZurgosVanguard card) { diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 18abe25f7d0..481c8062516 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -142,6 +142,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Get Out", 60, Rarity.UNCOMMON, mage.cards.g.GetOut.class)); cards.add(new SetCardInfo("Ghost Vacuum", 248, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ghost Vacuum", 326, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghostly Dancers", 13, Rarity.RARE, mage.cards.g.GhostlyDancers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghostly Dancers", 302, Rarity.RARE, mage.cards.g.GhostlyDancers.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Give In to Violence", 101, Rarity.COMMON, mage.cards.g.GiveInToViolence.class)); cards.add(new SetCardInfo("Glimmer Seeker", 14, Rarity.UNCOMMON, mage.cards.g.GlimmerSeeker.class)); cards.add(new SetCardInfo("Glimmerburst", 62, Rarity.COMMON, mage.cards.g.Glimmerburst.class)); diff --git a/Mage.Sets/src/mage/sets/EdgeOfEternities.java b/Mage.Sets/src/mage/sets/EdgeOfEternities.java index b8d4b5138d4..59cb657ee78 100644 --- a/Mage.Sets/src/mage/sets/EdgeOfEternities.java +++ b/Mage.Sets/src/mage/sets/EdgeOfEternities.java @@ -18,32 +18,409 @@ public final class EdgeOfEternities extends ExpansionSet { private EdgeOfEternities() { super("Edge of Eternities", "EOE", ExpansionSet.buildDate(2025, 8, 1), SetType.EXPANSION); this.blockName = "Edge of Eternities"; // for sorting in GUI + this.hasBasicLands = true; + + this.enablePlayBooster(Integer.MAX_VALUE); this.rotationSet = true; + cards.add(new SetCardInfo("Adagia, Windswept Bastion", 250, Rarity.MYTHIC, mage.cards.a.AdagiaWindsweptBastion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Adagia, Windswept Bastion", 277, Rarity.MYTHIC, mage.cards.a.AdagiaWindsweptBastion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Adagia, Windswept Bastion", 372, Rarity.MYTHIC, mage.cards.a.AdagiaWindsweptBastion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("All-Fates Scroll", 234, Rarity.UNCOMMON, mage.cards.a.AllFatesScroll.class)); + cards.add(new SetCardInfo("All-Fates Stalker", 3, Rarity.UNCOMMON, mage.cards.a.AllFatesStalker.class)); cards.add(new SetCardInfo("Alpharael, Dreaming Acolyte", 212, Rarity.UNCOMMON, mage.cards.a.AlpharaelDreamingAcolyte.class)); - cards.add(new SetCardInfo("Breeding Pool", 251, Rarity.RARE, mage.cards.b.BreedingPool.class)); + cards.add(new SetCardInfo("Alpharael, Stonechosen", 292, Rarity.MYTHIC, mage.cards.a.AlpharaelStonechosen.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Alpharael, Stonechosen", 87, Rarity.MYTHIC, mage.cards.a.AlpharaelStonechosen.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Annul", 394, Rarity.UNCOMMON, mage.cards.a.Annul.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Annul", 46, Rarity.UNCOMMON, mage.cards.a.Annul.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anticausal Vestige", 1, Rarity.RARE, mage.cards.a.AnticausalVestige.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anticausal Vestige", 317, Rarity.RARE, mage.cards.a.AnticausalVestige.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anticausal Vestige", 357, Rarity.MYTHIC, mage.cards.a.AnticausalVestige.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anticausal Vestige", 383, Rarity.MYTHIC, mage.cards.a.AnticausalVestige.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archenemy's Charm", 307, Rarity.RARE, mage.cards.a.ArchenemysCharm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archenemy's Charm", 88, Rarity.RARE, mage.cards.a.ArchenemysCharm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Astelli Reclaimer", 288, Rarity.RARE, mage.cards.a.AstelliReclaimer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Astelli Reclaimer", 4, Rarity.RARE, mage.cards.a.AstelliReclaimer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Atmospheric Greenhouse", 171, Rarity.UNCOMMON, mage.cards.a.AtmosphericGreenhouse.class)); + cards.add(new SetCardInfo("Atomic Microsizer", 47, Rarity.UNCOMMON, mage.cards.a.AtomicMicrosizer.class)); + cards.add(new SetCardInfo("Auxiliary Boosters", 5, Rarity.COMMON, mage.cards.a.AuxiliaryBoosters.class)); + cards.add(new SetCardInfo("Banishing Light", 6, Rarity.COMMON, mage.cards.b.BanishingLight.class)); + cards.add(new SetCardInfo("Beamsaw Prospector", 89, Rarity.COMMON, mage.cards.b.BeamsawProspector.class)); + cards.add(new SetCardInfo("Beyond the Quiet", 303, Rarity.RARE, mage.cards.b.BeyondTheQuiet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Beyond the Quiet", 7, Rarity.RARE, mage.cards.b.BeyondTheQuiet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bioengineered Future", 172, Rarity.RARE, mage.cards.b.BioengineeredFuture.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bioengineered Future", 340, Rarity.RARE, mage.cards.b.BioengineeredFuture.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biomechan Engineer", 213, Rarity.UNCOMMON, mage.cards.b.BiomechanEngineer.class)); + cards.add(new SetCardInfo("Biosynthic Burst", 173, Rarity.COMMON, mage.cards.b.BiosynthicBurst.class)); + cards.add(new SetCardInfo("Biotech Specialist", 214, Rarity.RARE, mage.cards.b.BiotechSpecialist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biotech Specialist", 347, Rarity.RARE, mage.cards.b.BiotechSpecialist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blade of the Swarm", 90, Rarity.UNCOMMON, mage.cards.b.BladeOfTheSwarm.class)); + cards.add(new SetCardInfo("Blooming Stinger", 174, Rarity.COMMON, mage.cards.b.BloomingStinger.class)); + cards.add(new SetCardInfo("Bombard", 129, Rarity.COMMON, mage.cards.b.Bombard.class)); + cards.add(new SetCardInfo("Breeding Pool", 251, Rarity.RARE, mage.cards.b.BreedingPool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Breeding Pool", 278, Rarity.RARE, mage.cards.b.BreedingPool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Breeding Pool", 373, Rarity.RARE, mage.cards.b.BreedingPool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Brightspear Zealot", 8, Rarity.COMMON, mage.cards.b.BrightspearZealot.class)); + cards.add(new SetCardInfo("Broodguard Elite", 175, Rarity.UNCOMMON, mage.cards.b.BroodguardElite.class)); + cards.add(new SetCardInfo("Bygone Colossus", 235, Rarity.UNCOMMON, mage.cards.b.BygoneColossus.class)); + cards.add(new SetCardInfo("Cerebral Download", 48, Rarity.UNCOMMON, mage.cards.c.CerebralDownload.class)); + cards.add(new SetCardInfo("Chorale of the Void", 331, Rarity.RARE, mage.cards.c.ChoraleOfTheVoid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chorale of the Void", 91, Rarity.RARE, mage.cards.c.ChoraleOfTheVoid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chrome Companion", 236, Rarity.COMMON, mage.cards.c.ChromeCompanion.class)); + cards.add(new SetCardInfo("Close Encounter", 176, Rarity.UNCOMMON, mage.cards.c.CloseEncounter.class)); + cards.add(new SetCardInfo("Cloudsculpt Technician", 49, Rarity.COMMON, mage.cards.c.CloudsculptTechnician.class)); + cards.add(new SetCardInfo("Codecracker Hound", 50, Rarity.UNCOMMON, mage.cards.c.CodecrackerHound.class)); + cards.add(new SetCardInfo("Comet Crawler", 92, Rarity.COMMON, mage.cards.c.CometCrawler.class)); + cards.add(new SetCardInfo("Command Bridge", 252, Rarity.COMMON, mage.cards.c.CommandBridge.class)); + cards.add(new SetCardInfo("Consult the Star Charts", 325, Rarity.RARE, mage.cards.c.ConsultTheStarCharts.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Consult the Star Charts", 51, Rarity.RARE, mage.cards.c.ConsultTheStarCharts.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cosmogoyf", 215, Rarity.RARE, mage.cards.c.Cosmogoyf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cosmogoyf", 313, Rarity.RARE, mage.cards.c.Cosmogoyf.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cosmogrand Zenith", 304, Rarity.MYTHIC, mage.cards.c.CosmograndZenith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cosmogrand Zenith", 9, Rarity.MYTHIC, mage.cards.c.CosmograndZenith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryogen Relic", 52, Rarity.COMMON, mage.cards.c.CryogenRelic.class)); + cards.add(new SetCardInfo("Cryoshatter", 53, Rarity.COMMON, mage.cards.c.Cryoshatter.class)); + cards.add(new SetCardInfo("Cut Propulsion", 130, Rarity.UNCOMMON, mage.cards.c.CutPropulsion.class)); + cards.add(new SetCardInfo("Dark Endurance", 93, Rarity.COMMON, mage.cards.d.DarkEndurance.class)); + cards.add(new SetCardInfo("Dauntless Scrapbot", 237, Rarity.UNCOMMON, mage.cards.d.DauntlessScrapbot.class)); + cards.add(new SetCardInfo("Dawnsire, Sunstar Dreadnought", 238, Rarity.MYTHIC, mage.cards.d.DawnsireSunstarDreadnought.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dawnsire, Sunstar Dreadnought", 351, Rarity.MYTHIC, mage.cards.d.DawnsireSunstarDreadnought.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dawnstrike Vanguard", 10, Rarity.UNCOMMON, mage.cards.d.DawnstrikeVanguard.class)); + cards.add(new SetCardInfo("Debris Field Crusher", 131, Rarity.UNCOMMON, mage.cards.d.DebrisFieldCrusher.class)); + cards.add(new SetCardInfo("Decode Transmissions", 94, Rarity.COMMON, mage.cards.d.DecodeTransmissions.class)); + cards.add(new SetCardInfo("Depressurize", 95, Rarity.COMMON, mage.cards.d.Depressurize.class)); + cards.add(new SetCardInfo("Desculpting Blast", 54, Rarity.UNCOMMON, mage.cards.d.DesculptingBlast.class)); + cards.add(new SetCardInfo("Devastating Onslaught", 132, Rarity.MYTHIC, mage.cards.d.DevastatingOnslaught.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Devastating Onslaught", 308, Rarity.MYTHIC, mage.cards.d.DevastatingOnslaught.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Devastating Onslaught", 361, Rarity.MYTHIC, mage.cards.d.DevastatingOnslaught.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Devastating Onslaught", 387, Rarity.MYTHIC, mage.cards.d.DevastatingOnslaught.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Diplomatic Relations", 177, Rarity.COMMON, mage.cards.d.DiplomaticRelations.class)); + cards.add(new SetCardInfo("Divert Disaster", 55, Rarity.COMMON, mage.cards.d.DivertDisaster.class)); + cards.add(new SetCardInfo("Dockworker Drone", 11, Rarity.COMMON, mage.cards.d.DockworkerDrone.class)); + cards.add(new SetCardInfo("Drill Too Deep", 133, Rarity.COMMON, mage.cards.d.DrillTooDeep.class)); + cards.add(new SetCardInfo("Drix Fatemaker", 178, Rarity.COMMON, mage.cards.d.DrixFatemaker.class)); + cards.add(new SetCardInfo("Dual-Sun Adepts", 12, Rarity.UNCOMMON, mage.cards.d.DualSunAdepts.class)); + cards.add(new SetCardInfo("Dual-Sun Technique", 13, Rarity.UNCOMMON, mage.cards.d.DualSunTechnique.class)); + cards.add(new SetCardInfo("Dubious Delicacy", 96, Rarity.UNCOMMON, mage.cards.d.DubiousDelicacy.class)); + cards.add(new SetCardInfo("Dyadrine, Synthesis Amalgam", 216, Rarity.RARE, mage.cards.d.DyadrineSynthesisAmalgam.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dyadrine, Synthesis Amalgam", 298, Rarity.RARE, mage.cards.d.DyadrineSynthesisAmalgam.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Edge Rover", 179, Rarity.UNCOMMON, mage.cards.e.EdgeRover.class)); + cards.add(new SetCardInfo("Elegy Acolyte", 293, Rarity.RARE, mage.cards.e.ElegyAcolyte.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Elegy Acolyte", 97, Rarity.RARE, mage.cards.e.ElegyAcolyte.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Embrace Oblivion", 98, Rarity.COMMON, mage.cards.e.EmbraceOblivion.class)); + cards.add(new SetCardInfo("Emergency Eject", 14, Rarity.UNCOMMON, mage.cards.e.EmergencyEject.class)); + cards.add(new SetCardInfo("Emissary Escort", 326, Rarity.RARE, mage.cards.e.EmissaryEscort.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Emissary Escort", 399, Rarity.RARE, mage.cards.e.EmissaryEscort.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Emissary Escort", 56, Rarity.RARE, mage.cards.e.EmissaryEscort.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Entropic Battlecruiser", 332, Rarity.RARE, mage.cards.e.EntropicBattlecruiser.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Entropic Battlecruiser", 99, Rarity.RARE, mage.cards.e.EntropicBattlecruiser.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eumidian Terrabotanist", 180, Rarity.UNCOMMON, mage.cards.e.EumidianTerrabotanist.class)); + cards.add(new SetCardInfo("Eusocial Engineering", 181, Rarity.UNCOMMON, mage.cards.e.EusocialEngineering.class)); + cards.add(new SetCardInfo("Evendo, Waking Haven", 253, Rarity.MYTHIC, mage.cards.e.EvendoWakingHaven.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Evendo, Waking Haven", 279, Rarity.MYTHIC, mage.cards.e.EvendoWakingHaven.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Evendo, Waking Haven", 374, Rarity.MYTHIC, mage.cards.e.EvendoWakingHaven.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exalted Sunborn", 15, Rarity.MYTHIC, mage.cards.e.ExaltedSunborn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exalted Sunborn", 318, Rarity.MYTHIC, mage.cards.e.ExaltedSunborn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exalted Sunborn", 358, Rarity.MYTHIC, mage.cards.e.ExaltedSunborn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exalted Sunborn", 384, Rarity.MYTHIC, mage.cards.e.ExaltedSunborn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exosuit Savior", 16, Rarity.COMMON, mage.cards.e.ExosuitSavior.class)); + cards.add(new SetCardInfo("Extinguisher Battleship", 242, Rarity.RARE, mage.cards.e.ExtinguisherBattleship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Extinguisher Battleship", 355, Rarity.RARE, mage.cards.e.ExtinguisherBattleship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Faller's Faithful", 100, Rarity.UNCOMMON, mage.cards.f.FallersFaithful.class)); + cards.add(new SetCardInfo("Famished Worldsire", 182, Rarity.MYTHIC, mage.cards.f.FamishedWorldsire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Famished Worldsire", 341, Rarity.MYTHIC, mage.cards.f.FamishedWorldsire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fell Gravship", 101, Rarity.UNCOMMON, mage.cards.f.FellGravship.class)); + cards.add(new SetCardInfo("Flight-Deck Coordinator", 17, Rarity.COMMON, mage.cards.f.FlightDeckCoordinator.class)); + cards.add(new SetCardInfo("Focus Fire", 18, Rarity.COMMON, mage.cards.f.FocusFire.class)); cards.add(new SetCardInfo("Forest", 266, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Forest", 275, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 371, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); - cards.add(new SetCardInfo("Godless Shrine", 254, Rarity.RARE, mage.cards.g.GodlessShrine.class)); + cards.add(new SetCardInfo("Frenzied Baloth", 183, Rarity.RARE, mage.cards.f.FrenziedBaloth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Frenzied Baloth", 342, Rarity.RARE, mage.cards.f.FrenziedBaloth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Frontline War-Rager", 134, Rarity.COMMON, mage.cards.f.FrontlineWarRager.class)); + cards.add(new SetCardInfo("Full Bore", 135, Rarity.UNCOMMON, mage.cards.f.FullBore.class)); + cards.add(new SetCardInfo("Fungal Colossus", 184, Rarity.COMMON, mage.cards.f.FungalColossus.class)); + cards.add(new SetCardInfo("Galactic Wayfarer", 185, Rarity.COMMON, mage.cards.g.GalacticWayfarer.class)); + cards.add(new SetCardInfo("Galvanizing Sawship", 136, Rarity.UNCOMMON, mage.cards.g.GalvanizingSawship.class)); + cards.add(new SetCardInfo("Gene Pollinator", 186, Rarity.COMMON, mage.cards.g.GenePollinator.class)); + cards.add(new SetCardInfo("Genemorph Imago", 217, Rarity.RARE, mage.cards.g.GenemorphImago.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Genemorph Imago", 299, Rarity.RARE, mage.cards.g.GenemorphImago.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Germinating Wurm", 187, Rarity.COMMON, mage.cards.g.GerminatingWurm.class)); + cards.add(new SetCardInfo("Gigastorm Titan", 57, Rarity.UNCOMMON, mage.cards.g.GigastormTitan.class)); + cards.add(new SetCardInfo("Glacier Godmaw", 188, Rarity.UNCOMMON, mage.cards.g.GlacierGodmaw.class)); + cards.add(new SetCardInfo("Godless Shrine", 254, Rarity.RARE, mage.cards.g.GodlessShrine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Godless Shrine", 280, Rarity.RARE, mage.cards.g.GodlessShrine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Godless Shrine", 375, Rarity.RARE, mage.cards.g.GodlessShrine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gravblade Heavy", 102, Rarity.COMMON, mage.cards.g.GravbladeHeavy.class)); + cards.add(new SetCardInfo("Gravkill", 103, Rarity.COMMON, mage.cards.g.Gravkill.class)); + cards.add(new SetCardInfo("Gravpack Monoist", 104, Rarity.COMMON, mage.cards.g.GravpackMonoist.class)); + cards.add(new SetCardInfo("Haliya, Ascendant Cadet", 218, Rarity.UNCOMMON, mage.cards.h.HaliyaAscendantCadet.class)); + cards.add(new SetCardInfo("Haliya, Guided by Light", 19, Rarity.RARE, mage.cards.h.HaliyaGuidedByLight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Haliya, Guided by Light", 289, Rarity.RARE, mage.cards.h.HaliyaGuidedByLight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hardlight Containment", 20, Rarity.RARE, mage.cards.h.HardlightContainment.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hardlight Containment", 319, Rarity.RARE, mage.cards.h.HardlightContainment.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Harmonious Grovestrider", 189, Rarity.UNCOMMON, mage.cards.h.HarmoniousGrovestrider.class)); + cards.add(new SetCardInfo("Hemosymbic Mite", 190, Rarity.UNCOMMON, mage.cards.h.HemosymbicMite.class)); + cards.add(new SetCardInfo("Honor", 21, Rarity.UNCOMMON, mage.cards.h.Honor.class)); + cards.add(new SetCardInfo("Honored Knight-Captain", 22, Rarity.UNCOMMON, mage.cards.h.HonoredKnightCaptain.class)); + cards.add(new SetCardInfo("Hullcarver", 105, Rarity.COMMON, mage.cards.h.Hullcarver.class)); + cards.add(new SetCardInfo("Hylderblade", 106, Rarity.UNCOMMON, mage.cards.h.Hylderblade.class)); + cards.add(new SetCardInfo("Hymn of the Faller", 107, Rarity.UNCOMMON, mage.cards.h.HymnOfTheFaller.class)); + cards.add(new SetCardInfo("Icecave Crasher", 191, Rarity.COMMON, mage.cards.i.IcecaveCrasher.class)); + cards.add(new SetCardInfo("Icetill Explorer", 192, Rarity.RARE, mage.cards.i.IcetillExplorer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Icetill Explorer", 343, Rarity.RARE, mage.cards.i.IcetillExplorer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Icetill Explorer", 362, Rarity.MYTHIC, mage.cards.i.IcetillExplorer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Icetill Explorer", 388, Rarity.MYTHIC, mage.cards.i.IcetillExplorer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Illvoi Galeblade", 58, Rarity.COMMON, mage.cards.i.IllvoiGaleblade.class)); + cards.add(new SetCardInfo("Illvoi Infiltrator", 59, Rarity.UNCOMMON, mage.cards.i.IllvoiInfiltrator.class)); + cards.add(new SetCardInfo("Illvoi Light Jammer", 60, Rarity.COMMON, mage.cards.i.IllvoiLightJammer.class)); + cards.add(new SetCardInfo("Illvoi Operative", 61, Rarity.COMMON, mage.cards.i.IllvoiOperative.class)); + cards.add(new SetCardInfo("Infinite Guideline Station", 219, Rarity.RARE, mage.cards.i.InfiniteGuidelineStation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Infinite Guideline Station", 348, Rarity.RARE, mage.cards.i.InfiniteGuidelineStation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Insatiable Skittermaw", 108, Rarity.COMMON, mage.cards.i.InsatiableSkittermaw.class)); + cards.add(new SetCardInfo("Interceptor Mechan", 220, Rarity.UNCOMMON, mage.cards.i.InterceptorMechan.class)); + cards.add(new SetCardInfo("Intrepid Tenderfoot", 193, Rarity.COMMON, mage.cards.i.IntrepidTenderfoot.class)); + cards.add(new SetCardInfo("Invasive Maneuvers", 137, Rarity.UNCOMMON, mage.cards.i.InvasiveManeuvers.class)); cards.add(new SetCardInfo("Island", 263, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Island", 269, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 270, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 368, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Kav Landseeker", 138, Rarity.COMMON, mage.cards.k.KavLandseeker.class)); + cards.add(new SetCardInfo("Kavaron Harrier", 139, Rarity.UNCOMMON, mage.cards.k.KavaronHarrier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kavaron Harrier", 396, Rarity.UNCOMMON, mage.cards.k.KavaronHarrier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kavaron Skywarden", 140, Rarity.COMMON, mage.cards.k.KavaronSkywarden.class)); + cards.add(new SetCardInfo("Kavaron Turbodrone", 141, Rarity.COMMON, mage.cards.k.KavaronTurbodrone.class)); + cards.add(new SetCardInfo("Kavaron, Memorial World", 255, Rarity.MYTHIC, mage.cards.k.KavaronMemorialWorld.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kavaron, Memorial World", 281, Rarity.MYTHIC, mage.cards.k.KavaronMemorialWorld.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kavaron, Memorial World", 376, Rarity.MYTHIC, mage.cards.k.KavaronMemorialWorld.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Knight Luminary", 23, Rarity.COMMON, mage.cards.k.KnightLuminary.class)); + cards.add(new SetCardInfo("Larval Scoutlander", 194, Rarity.UNCOMMON, mage.cards.l.LarvalScoutlander.class)); + cards.add(new SetCardInfo("Lashwhip Predator", 195, Rarity.UNCOMMON, mage.cards.l.LashwhipPredator.class)); + cards.add(new SetCardInfo("Lightless Evangel", 109, Rarity.UNCOMMON, mage.cards.l.LightlessEvangel.class)); + cards.add(new SetCardInfo("Lightstall Inquisitor", 24, Rarity.RARE, mage.cards.l.LightstallInquisitor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lightstall Inquisitor", 320, Rarity.RARE, mage.cards.l.LightstallInquisitor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lithobraking", 142, Rarity.UNCOMMON, mage.cards.l.Lithobraking.class)); + cards.add(new SetCardInfo("Loading Zone", 196, Rarity.RARE, mage.cards.l.LoadingZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Loading Zone", 344, Rarity.RARE, mage.cards.l.LoadingZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lost in Space", 62, Rarity.COMMON, mage.cards.l.LostInSpace.class)); + cards.add(new SetCardInfo("Lumen-Class Frigate", 25, Rarity.RARE, mage.cards.l.LumenClassFrigate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lumen-Class Frigate", 321, Rarity.RARE, mage.cards.l.LumenClassFrigate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Luxknight Breacher", 26, Rarity.COMMON, mage.cards.l.LuxknightBreacher.class)); + cards.add(new SetCardInfo("Mechan Assembler", 63, Rarity.UNCOMMON, mage.cards.m.MechanAssembler.class)); + cards.add(new SetCardInfo("Mechan Navigator", 64, Rarity.UNCOMMON, mage.cards.m.MechanNavigator.class)); + cards.add(new SetCardInfo("Mechan Shieldmate", 65, Rarity.COMMON, mage.cards.m.MechanShieldmate.class)); + cards.add(new SetCardInfo("Mechanozoa", 66, Rarity.COMMON, mage.cards.m.Mechanozoa.class)); + cards.add(new SetCardInfo("Melded Moxite", 143, Rarity.COMMON, mage.cards.m.MeldedMoxite.class)); + cards.add(new SetCardInfo("Meltstrider Eulogist", 197, Rarity.UNCOMMON, mage.cards.m.MeltstriderEulogist.class)); + cards.add(new SetCardInfo("Meltstrider's Gear", 198, Rarity.COMMON, mage.cards.m.MeltstridersGear.class)); + cards.add(new SetCardInfo("Meltstrider's Resolve", 199, Rarity.UNCOMMON, mage.cards.m.MeltstridersResolve.class)); + cards.add(new SetCardInfo("Memorial Team Leader", 144, Rarity.UNCOMMON, mage.cards.m.MemorialTeamLeader.class)); + cards.add(new SetCardInfo("Memorial Vault", 145, Rarity.RARE, mage.cards.m.MemorialVault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Memorial Vault", 336, Rarity.RARE, mage.cards.m.MemorialVault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mental Modulation", 67, Rarity.COMMON, mage.cards.m.MentalModulation.class)); + cards.add(new SetCardInfo("Mightform Harmonizer", 200, Rarity.RARE, mage.cards.m.MightformHarmonizer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mightform Harmonizer", 297, Rarity.RARE, mage.cards.m.MightformHarmonizer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mm'menon, Uthros Exile", 221, Rarity.UNCOMMON, mage.cards.m.MmmenonUthrosExile.class)); + cards.add(new SetCardInfo("Mm'menon, the Right Hand", 290, Rarity.RARE, mage.cards.m.MmmenonTheRightHand.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mm'menon, the Right Hand", 68, Rarity.RARE, mage.cards.m.MmmenonTheRightHand.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Molecular Modifier", 146, Rarity.UNCOMMON, mage.cards.m.MolecularModifier.class)); + cards.add(new SetCardInfo("Monoist Circuit-Feeder", 110, Rarity.UNCOMMON, mage.cards.m.MonoistCircuitFeeder.class)); + cards.add(new SetCardInfo("Monoist Sentry", 111, Rarity.UNCOMMON, mage.cards.m.MonoistSentry.class)); + cards.add(new SetCardInfo("Moonlit Meditation", 327, Rarity.RARE, mage.cards.m.MoonlitMeditation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Moonlit Meditation", 69, Rarity.RARE, mage.cards.m.MoonlitMeditation.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 265, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 273, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 274, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 370, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mouth of the Storm", 70, Rarity.UNCOMMON, mage.cards.m.MouthOfTheStorm.class)); + cards.add(new SetCardInfo("Mutinous Massacre", 222, Rarity.RARE, mage.cards.m.MutinousMassacre.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutinous Massacre", 314, Rarity.RARE, mage.cards.m.MutinousMassacre.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutinous Massacre", 363, Rarity.MYTHIC, mage.cards.m.MutinousMassacre.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutinous Massacre", 389, Rarity.MYTHIC, mage.cards.m.MutinousMassacre.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nanoform Sentinel", 71, Rarity.COMMON, mage.cards.n.NanoformSentinel.class)); + cards.add(new SetCardInfo("Nebula Dragon", 147, Rarity.COMMON, mage.cards.n.NebulaDragon.class)); + cards.add(new SetCardInfo("Nova Hellkite", 148, Rarity.RARE, mage.cards.n.NovaHellkite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nova Hellkite", 309, Rarity.RARE, mage.cards.n.NovaHellkite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nutrient Block", 243, Rarity.COMMON, mage.cards.n.NutrientBlock.class)); + cards.add(new SetCardInfo("Orbital Plunge", 149, Rarity.COMMON, mage.cards.o.OrbitalPlunge.class)); + cards.add(new SetCardInfo("Oreplate Pangolin", 150, Rarity.COMMON, mage.cards.o.OreplatePangolin.class)); + cards.add(new SetCardInfo("Ouroboroid", 201, Rarity.MYTHIC, mage.cards.o.Ouroboroid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ouroboroid", 345, Rarity.MYTHIC, mage.cards.o.Ouroboroid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pain for All", 151, Rarity.RARE, mage.cards.p.PainForAll.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pain for All", 337, Rarity.RARE, mage.cards.p.PainForAll.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Perigee Beckoner", 112, Rarity.COMMON, mage.cards.p.PerigeeBeckoner.class)); + cards.add(new SetCardInfo("Pinnacle Emissary", 223, Rarity.RARE, mage.cards.p.PinnacleEmissary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pinnacle Emissary", 349, Rarity.RARE, mage.cards.p.PinnacleEmissary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pinnacle Kill-Ship", 244, Rarity.COMMON, mage.cards.p.PinnacleKillShip.class)); + cards.add(new SetCardInfo("Pinnacle Starcage", 27, Rarity.RARE, mage.cards.p.PinnacleStarcage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pinnacle Starcage", 322, Rarity.RARE, mage.cards.p.PinnacleStarcage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 262, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Plains", 267, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 268, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 367, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); - cards.add(new SetCardInfo("Sacred Foundry", 256, Rarity.RARE, mage.cards.s.SacredFoundry.class)); + cards.add(new SetCardInfo("Plasma Bolt", 152, Rarity.COMMON, mage.cards.p.PlasmaBolt.class)); + cards.add(new SetCardInfo("Possibility Technician", 153, Rarity.RARE, mage.cards.p.PossibilityTechnician.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Possibility Technician", 295, Rarity.RARE, mage.cards.p.PossibilityTechnician.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pull Through the Weft", 202, Rarity.UNCOMMON, mage.cards.p.PullThroughTheWeft.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pull Through the Weft", 397, Rarity.UNCOMMON, mage.cards.p.PullThroughTheWeft.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pulsar Squadron Ace", 28, Rarity.UNCOMMON, mage.cards.p.PulsarSquadronAce.class)); + cards.add(new SetCardInfo("Quantum Riddler", 305, Rarity.MYTHIC, mage.cards.q.QuantumRiddler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Quantum Riddler", 72, Rarity.MYTHIC, mage.cards.q.QuantumRiddler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Radiant Strike", 29, Rarity.COMMON, mage.cards.r.RadiantStrike.class)); + cards.add(new SetCardInfo("Ragost, Deft Gastronaut", 224, Rarity.RARE, mage.cards.r.RagostDeftGastronaut.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ragost, Deft Gastronaut", 300, Rarity.RARE, mage.cards.r.RagostDeftGastronaut.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rayblade Trooper", 30, Rarity.UNCOMMON, mage.cards.r.RaybladeTrooper.class)); + cards.add(new SetCardInfo("Red Tiger Mechan", 154, Rarity.COMMON, mage.cards.r.RedTigerMechan.class)); + cards.add(new SetCardInfo("Remnant Elemental", 155, Rarity.UNCOMMON, mage.cards.r.RemnantElemental.class)); + cards.add(new SetCardInfo("Requiem Monolith", 113, Rarity.RARE, mage.cards.r.RequiemMonolith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Requiem Monolith", 333, Rarity.RARE, mage.cards.r.RequiemMonolith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reroute Systems", 31, Rarity.UNCOMMON, mage.cards.r.RerouteSystems.class)); + cards.add(new SetCardInfo("Rescue Skiff", 32, Rarity.UNCOMMON, mage.cards.r.RescueSkiff.class)); + cards.add(new SetCardInfo("Rig for War", 156, Rarity.COMMON, mage.cards.r.RigForWar.class)); + cards.add(new SetCardInfo("Roving Actuator", 157, Rarity.UNCOMMON, mage.cards.r.RovingActuator.class)); + cards.add(new SetCardInfo("Ruinous Rampage", 158, Rarity.UNCOMMON, mage.cards.r.RuinousRampage.class)); + cards.add(new SetCardInfo("Rust Harvester", 159, Rarity.RARE, mage.cards.r.RustHarvester.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rust Harvester", 310, Rarity.RARE, mage.cards.r.RustHarvester.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sacred Foundry", 256, Rarity.RARE, mage.cards.s.SacredFoundry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sacred Foundry", 282, Rarity.RARE, mage.cards.s.SacredFoundry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sacred Foundry", 377, Rarity.RARE, mage.cards.s.SacredFoundry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sami's Curiosity", 203, Rarity.COMMON, mage.cards.s.SamisCuriosity.class)); cards.add(new SetCardInfo("Sami, Ship's Engineer", 225, Rarity.UNCOMMON, mage.cards.s.SamiShipsEngineer.class)); + cards.add(new SetCardInfo("Sami, Wildcat Captain", 226, Rarity.MYTHIC, mage.cards.s.SamiWildcatCaptain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sami, Wildcat Captain", 301, Rarity.MYTHIC, mage.cards.s.SamiWildcatCaptain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scour for Scrap", 73, Rarity.UNCOMMON, mage.cards.s.ScourForScrap.class)); + cards.add(new SetCardInfo("Scout for Survivors", 33, Rarity.UNCOMMON, mage.cards.s.ScoutForSurvivors.class)); + cards.add(new SetCardInfo("Scrounge for Eternity", 114, Rarity.UNCOMMON, mage.cards.s.ScroungeForEternity.class)); + cards.add(new SetCardInfo("Seam Rip", 34, Rarity.UNCOMMON, mage.cards.s.SeamRip.class)); + cards.add(new SetCardInfo("Secluded Starforge", 257, Rarity.RARE, mage.cards.s.SecludedStarforge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Secluded Starforge", 316, Rarity.RARE, mage.cards.s.SecludedStarforge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Secluded Starforge", 366, Rarity.MYTHIC, mage.cards.s.SecludedStarforge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Secluded Starforge", 392, Rarity.MYTHIC, mage.cards.s.SecludedStarforge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seedship Agrarian", 204, Rarity.UNCOMMON, mage.cards.s.SeedshipAgrarian.class)); + cards.add(new SetCardInfo("Seedship Broodtender", 227, Rarity.UNCOMMON, mage.cards.s.SeedshipBroodtender.class)); + cards.add(new SetCardInfo("Seedship Impact", 205, Rarity.UNCOMMON, mage.cards.s.SeedshipImpact.class)); + cards.add(new SetCardInfo("Selfcraft Mechan", 74, Rarity.COMMON, mage.cards.s.SelfcraftMechan.class)); + cards.add(new SetCardInfo("Shattered Wings", 206, Rarity.COMMON, mage.cards.s.ShatteredWings.class)); + cards.add(new SetCardInfo("Singularity Rupture", 228, Rarity.RARE, mage.cards.s.SingularityRupture.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Singularity Rupture", 350, Rarity.RARE, mage.cards.s.SingularityRupture.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Singularity Rupture", 398, Rarity.RARE, mage.cards.s.SingularityRupture.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sinister Cryologist", 75, Rarity.COMMON, mage.cards.s.SinisterCryologist.class)); + cards.add(new SetCardInfo("Skystinger", 207, Rarity.COMMON, mage.cards.s.Skystinger.class)); + cards.add(new SetCardInfo("Slagdrill Scrapper", 160, Rarity.COMMON, mage.cards.s.SlagdrillScrapper.class)); + cards.add(new SetCardInfo("Sledge-Class Seedship", 208, Rarity.RARE, mage.cards.s.SledgeClassSeedship.class, NON_FULL_USE_VARIOUS)); + 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", 386, Rarity.MYTHIC, mage.cards.s.SotheraTheSupervoid.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Stomping Ground", 258, Rarity.RARE, mage.cards.s.StompingGround.class)); + 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)); + cards.add(new SetCardInfo("Specimen Freighter", 76, Rarity.UNCOMMON, mage.cards.s.SpecimenFreighter.class)); + cards.add(new SetCardInfo("Squire's Lightblade", 36, Rarity.COMMON, mage.cards.s.SquiresLightblade.class)); + cards.add(new SetCardInfo("Starbreach Whale", 77, Rarity.COMMON, mage.cards.s.StarbreachWhale.class)); + cards.add(new SetCardInfo("Starfield Shepherd", 37, Rarity.UNCOMMON, mage.cards.s.StarfieldShepherd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starfield Shepherd", 393, Rarity.UNCOMMON, mage.cards.s.StarfieldShepherd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starfield Vocalist", 328, Rarity.RARE, mage.cards.s.StarfieldVocalist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starfield Vocalist", 359, Rarity.MYTHIC, mage.cards.s.StarfieldVocalist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starfield Vocalist", 385, Rarity.MYTHIC, mage.cards.s.StarfieldVocalist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starfield Vocalist", 78, Rarity.RARE, mage.cards.s.StarfieldVocalist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starfighter Pilot", 38, Rarity.COMMON, mage.cards.s.StarfighterPilot.class)); + cards.add(new SetCardInfo("Starport Security", 39, Rarity.COMMON, mage.cards.s.StarportSecurity.class)); + cards.add(new SetCardInfo("Starwinder", 291, Rarity.RARE, mage.cards.s.Starwinder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starwinder", 306, Rarity.RARE, mage.cards.s.Starwinder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starwinder", 79, Rarity.RARE, mage.cards.s.Starwinder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Station Monitor", 230, Rarity.UNCOMMON, mage.cards.s.StationMonitor.class)); + cards.add(new SetCardInfo("Steelswarm Operator", 80, Rarity.UNCOMMON, mage.cards.s.SteelswarmOperator.class)); + cards.add(new SetCardInfo("Stomping Ground", 258, Rarity.RARE, mage.cards.s.StompingGround.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stomping Ground", 283, Rarity.RARE, mage.cards.s.StompingGround.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stomping Ground", 378, Rarity.RARE, mage.cards.s.StompingGround.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sunset Saboteur", 116, Rarity.RARE, mage.cards.s.SunsetSaboteur.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sunset Saboteur", 334, Rarity.RARE, mage.cards.s.SunsetSaboteur.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sunstar Chaplain", 324, Rarity.RARE, mage.cards.s.SunstarChaplain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sunstar Chaplain", 40, Rarity.RARE, mage.cards.s.SunstarChaplain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sunstar Expansionist", 41, Rarity.UNCOMMON, mage.cards.s.SunstarExpansionist.class)); + cards.add(new SetCardInfo("Sunstar Lightsmith", 42, Rarity.UNCOMMON, mage.cards.s.SunstarLightsmith.class)); + cards.add(new SetCardInfo("Survey Mechan", 245, Rarity.UNCOMMON, mage.cards.s.SurveyMechan.class)); + cards.add(new SetCardInfo("Susur Secundi, Void Altar", 259, Rarity.MYTHIC, mage.cards.s.SusurSecundiVoidAltar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Susur Secundi, Void Altar", 284, Rarity.MYTHIC, mage.cards.s.SusurSecundiVoidAltar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Susur Secundi, Void Altar", 379, Rarity.MYTHIC, mage.cards.s.SusurSecundiVoidAltar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Susurian Dirgecraft", 117, Rarity.UNCOMMON, mage.cards.s.SusurianDirgecraft.class)); + cards.add(new SetCardInfo("Susurian Voidborn", 118, Rarity.UNCOMMON, mage.cards.s.SusurianVoidborn.class)); cards.add(new SetCardInfo("Swamp", 264, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 271, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 272, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 369, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swarm Culler", 119, Rarity.COMMON, mage.cards.s.SwarmCuller.class)); + cards.add(new SetCardInfo("Synthesizer Labship", 329, Rarity.RARE, mage.cards.s.SynthesizerLabship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Synthesizer Labship", 81, Rarity.RARE, mage.cards.s.SynthesizerLabship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Syr Vondam, Sunstar Exemplar", 231, Rarity.RARE, mage.cards.s.SyrVondamSunstarExemplar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Syr Vondam, Sunstar Exemplar", 302, Rarity.RARE, mage.cards.s.SyrVondamSunstarExemplar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Syr Vondam, the Lucent", 232, Rarity.UNCOMMON, mage.cards.s.SyrVondamTheLucent.class)); + cards.add(new SetCardInfo("Systems Override", 161, Rarity.UNCOMMON, mage.cards.s.SystemsOverride.class)); cards.add(new SetCardInfo("Tannuk, Memorial Ensign", 233, Rarity.UNCOMMON, mage.cards.t.TannukMemorialEnsign.class)); + cards.add(new SetCardInfo("Tannuk, Steadfast Second", 162, Rarity.MYTHIC, mage.cards.t.TannukSteadfastSecond.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tannuk, Steadfast Second", 296, Rarity.MYTHIC, mage.cards.t.TannukSteadfastSecond.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tapestry Warden", 209, Rarity.UNCOMMON, mage.cards.t.TapestryWarden.class)); + cards.add(new SetCardInfo("Temporal Intervention", 120, Rarity.COMMON, mage.cards.t.TemporalIntervention.class)); + cards.add(new SetCardInfo("Terminal Velocity", 163, Rarity.RARE, mage.cards.t.TerminalVelocity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terminal Velocity", 338, Rarity.RARE, mage.cards.t.TerminalVelocity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terrapact Intimidator", 164, Rarity.UNCOMMON, mage.cards.t.TerrapactIntimidator.class)); + cards.add(new SetCardInfo("Terrasymbiosis", 210, Rarity.RARE, mage.cards.t.Terrasymbiosis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terrasymbiosis", 312, Rarity.RARE, mage.cards.t.Terrasymbiosis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Territorial Bruntar", 165, Rarity.UNCOMMON, mage.cards.t.TerritorialBruntar.class)); cards.add(new SetCardInfo("Tezzeret, Cruel Captain", 2, Rarity.MYTHIC, mage.cards.t.TezzeretCruelCaptain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tezzeret, Cruel Captain", 287, Rarity.MYTHIC, mage.cards.t.TezzeretCruelCaptain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Watery Grave", 261, Rarity.RARE, mage.cards.w.WateryGrave.class)); + cards.add(new SetCardInfo("Thaumaton Torpedo", 246, Rarity.COMMON, mage.cards.t.ThaumatonTorpedo.class)); + cards.add(new SetCardInfo("Thawbringer", 211, Rarity.COMMON, mage.cards.t.Thawbringer.class)); + cards.add(new SetCardInfo("The Dominion Bracelet", 239, Rarity.MYTHIC, mage.cards.t.TheDominionBracelet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Dominion Bracelet", 352, Rarity.MYTHIC, mage.cards.t.TheDominionBracelet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Dominion Bracelet", 364, Rarity.MYTHIC, mage.cards.t.TheDominionBracelet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Dominion Bracelet", 390, Rarity.MYTHIC, mage.cards.t.TheDominionBracelet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Endstone", 240, Rarity.MYTHIC, mage.cards.t.TheEndstone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Endstone", 353, Rarity.MYTHIC, mage.cards.t.TheEndstone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Endstone", 365, Rarity.MYTHIC, mage.cards.t.TheEndstone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Endstone", 391, Rarity.MYTHIC, mage.cards.t.TheEndstone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Eternity Elevator", 241, Rarity.RARE, mage.cards.t.TheEternityElevator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Eternity Elevator", 354, Rarity.RARE, mage.cards.t.TheEternityElevator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Seriema", 323, Rarity.RARE, mage.cards.t.TheSeriema.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Seriema", 35, Rarity.RARE, mage.cards.t.TheSeriema.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thrumming Hivepool", 247, Rarity.RARE, mage.cards.t.ThrummingHivepool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thrumming Hivepool", 356, Rarity.RARE, mage.cards.t.ThrummingHivepool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Timeline Culler", 121, Rarity.UNCOMMON, mage.cards.t.TimelineCuller.class)); + cards.add(new SetCardInfo("Tractor Beam", 82, Rarity.UNCOMMON, mage.cards.t.TractorBeam.class)); + cards.add(new SetCardInfo("Tragic Trajectory", 122, Rarity.UNCOMMON, mage.cards.t.TragicTrajectory.class)); + cards.add(new SetCardInfo("Umbral Collar Zealot", 123, Rarity.UNCOMMON, mage.cards.u.UmbralCollarZealot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Umbral Collar Zealot", 395, Rarity.UNCOMMON, mage.cards.u.UmbralCollarZealot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Unravel", 83, Rarity.UNCOMMON, mage.cards.u.Unravel.class)); + cards.add(new SetCardInfo("Uthros Psionicist", 84, Rarity.UNCOMMON, mage.cards.u.UthrosPsionicist.class)); + cards.add(new SetCardInfo("Uthros Scanship", 85, Rarity.UNCOMMON, mage.cards.u.UthrosScanship.class)); + cards.add(new SetCardInfo("Uthros, Titanic Godcore", 260, Rarity.MYTHIC, mage.cards.u.UthrosTitanicGodcore.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Uthros, Titanic Godcore", 285, Rarity.MYTHIC, mage.cards.u.UthrosTitanicGodcore.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Uthros, Titanic Godcore", 380, Rarity.MYTHIC, mage.cards.u.UthrosTitanicGodcore.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vaultguard Trooper", 166, Rarity.UNCOMMON, mage.cards.v.VaultguardTrooper.class)); + cards.add(new SetCardInfo("Virulent Silencer", 248, Rarity.UNCOMMON, mage.cards.v.VirulentSilencer.class)); + cards.add(new SetCardInfo("Virus Beetle", 124, Rarity.COMMON, mage.cards.v.VirusBeetle.class)); + cards.add(new SetCardInfo("Voidforged Titan", 125, Rarity.UNCOMMON, mage.cards.v.VoidforgedTitan.class)); + cards.add(new SetCardInfo("Vote Out", 126, Rarity.UNCOMMON, mage.cards.v.VoteOut.class)); + cards.add(new SetCardInfo("Warmaker Gunship", 167, Rarity.RARE, mage.cards.w.WarmakerGunship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Warmaker Gunship", 339, Rarity.RARE, mage.cards.w.WarmakerGunship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Watery Grave", 261, Rarity.RARE, mage.cards.w.WateryGrave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Watery Grave", 286, Rarity.RARE, mage.cards.w.WateryGrave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Watery Grave", 381, Rarity.RARE, mage.cards.w.WateryGrave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Weapons Manufacturing", 168, Rarity.RARE, mage.cards.w.WeaponsManufacturing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Weapons Manufacturing", 311, Rarity.RARE, mage.cards.w.WeaponsManufacturing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wedgelight Rammer", 43, Rarity.UNCOMMON, mage.cards.w.WedgelightRammer.class)); + cards.add(new SetCardInfo("Weftblade Enhancer", 44, Rarity.COMMON, mage.cards.w.WeftbladeEnhancer.class)); + cards.add(new SetCardInfo("Weftstalker Ardent", 169, Rarity.UNCOMMON, mage.cards.w.WeftstalkerArdent.class)); + cards.add(new SetCardInfo("Weftwalking", 330, Rarity.MYTHIC, mage.cards.w.Weftwalking.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Weftwalking", 86, Rarity.MYTHIC, mage.cards.w.Weftwalking.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wurmwall Sweeper", 249, Rarity.COMMON, mage.cards.w.WurmwallSweeper.class)); + cards.add(new SetCardInfo("Xu-Ifit, Osteoharmonist", 127, Rarity.RARE, mage.cards.x.XuIfitOsteoharmonist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Xu-Ifit, Osteoharmonist", 294, Rarity.RARE, mage.cards.x.XuIfitOsteoharmonist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zealous Display", 45, Rarity.COMMON, mage.cards.z.ZealousDisplay.class)); + cards.add(new SetCardInfo("Zero Point Ballad", 128, Rarity.RARE, mage.cards.z.ZeroPointBallad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zero Point Ballad", 335, Rarity.RARE, mage.cards.z.ZeroPointBallad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zookeeper Mechan", 170, Rarity.COMMON, mage.cards.z.ZookeeperMechan.class)); } } diff --git a/Mage.Sets/src/mage/sets/EdgeOfEternitiesCommander.java b/Mage.Sets/src/mage/sets/EdgeOfEternitiesCommander.java index 4b361a8935a..7a3f0d71671 100644 --- a/Mage.Sets/src/mage/sets/EdgeOfEternitiesCommander.java +++ b/Mage.Sets/src/mage/sets/EdgeOfEternitiesCommander.java @@ -19,7 +19,196 @@ public final class EdgeOfEternitiesCommander extends ExpansionSet { super("Edge of Eternities Commander", "EOC", ExpansionSet.buildDate(2025, 8, 1), SetType.SUPPLEMENTAL); this.hasBasicLands = false; + cards.add(new SetCardInfo("Adarkar Wastes", 147, Rarity.RARE, mage.cards.a.AdarkarWastes.class)); + cards.add(new SetCardInfo("Aftermath Analyst", 91, Rarity.UNCOMMON, mage.cards.a.AftermathAnalyst.class)); + cards.add(new SetCardInfo("Alibou, Ancient Witness", 113, Rarity.MYTHIC, mage.cards.a.AlibouAncientWitness.class)); + cards.add(new SetCardInfo("Ancient Den", 148, Rarity.COMMON, mage.cards.a.AncientDen.class)); + cards.add(new SetCardInfo("Angel of the Ruins", 63, Rarity.RARE, mage.cards.a.AngelOfTheRuins.class)); + cards.add(new SetCardInfo("Arcane Signet", 53, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); + cards.add(new SetCardInfo("Astral Cornucopia", 131, Rarity.RARE, mage.cards.a.AstralCornucopia.class)); + cards.add(new SetCardInfo("Augur of Autumn", 92, Rarity.RARE, mage.cards.a.AugurOfAutumn.class)); + cards.add(new SetCardInfo("Baloth Prime", 13, Rarity.RARE, mage.cards.b.BalothPrime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Baloth Prime", 33, Rarity.RARE, mage.cards.b.BalothPrime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Battlefield Forge", 58, Rarity.RARE, mage.cards.b.BattlefieldForge.class)); + cards.add(new SetCardInfo("Beast Within", 93, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class)); + cards.add(new SetCardInfo("Binding the Old Gods", 52, Rarity.UNCOMMON, mage.cards.b.BindingTheOldGods.class)); + cards.add(new SetCardInfo("Blasphemous Act", 86, Rarity.RARE, mage.cards.b.BlasphemousAct.class)); + cards.add(new SetCardInfo("Bojuka Bog", 149, Rarity.COMMON, mage.cards.b.BojukaBog.class)); + cards.add(new SetCardInfo("Braids, Arisen Nightmare", 82, Rarity.RARE, mage.cards.b.BraidsArisenNightmare.class)); + cards.add(new SetCardInfo("Buried Ruin", 150, Rarity.UNCOMMON, mage.cards.b.BuriedRuin.class)); + cards.add(new SetCardInfo("Cabaretti Courtyard", 151, Rarity.COMMON, mage.cards.c.CabarettiCourtyard.class)); + cards.add(new SetCardInfo("Canyon Slough", 152, Rarity.RARE, mage.cards.c.CanyonSlough.class)); + cards.add(new SetCardInfo("Cascade Bluffs", 153, Rarity.RARE, mage.cards.c.CascadeBluffs.class)); + cards.add(new SetCardInfo("Centaur Vinecrasher", 94, Rarity.RARE, mage.cards.c.CentaurVinecrasher.class)); + cards.add(new SetCardInfo("Chain Reaction", 87, Rarity.RARE, mage.cards.c.ChainReaction.class)); + cards.add(new SetCardInfo("Chaos Warp", 49, Rarity.RARE, mage.cards.c.ChaosWarp.class)); + cards.add(new SetCardInfo("Chrome Host Seedshark", 68, Rarity.RARE, mage.cards.c.ChromeHostSeedshark.class)); + cards.add(new SetCardInfo("Cinder Glade", 154, Rarity.RARE, mage.cards.c.CinderGlade.class)); + cards.add(new SetCardInfo("Clifftop Retreat", 155, Rarity.RARE, mage.cards.c.ClifftopRetreat.class)); + cards.add(new SetCardInfo("Cloud Key", 54, Rarity.RARE, mage.cards.c.CloudKey.class)); + cards.add(new SetCardInfo("Command Tower", 59, Rarity.COMMON, mage.cards.c.CommandTower.class)); + cards.add(new SetCardInfo("Coretapper", 132, Rarity.UNCOMMON, mage.cards.c.Coretapper.class)); + cards.add(new SetCardInfo("Crystalline Crawler", 133, Rarity.RARE, mage.cards.c.CrystallineCrawler.class)); + cards.add(new SetCardInfo("Cultivate", 95, Rarity.COMMON, mage.cards.c.Cultivate.class)); + cards.add(new SetCardInfo("Cyberdrive Awakener", 69, Rarity.RARE, mage.cards.c.CyberdriveAwakener.class)); + cards.add(new SetCardInfo("Dakmor Salvage", 156, Rarity.UNCOMMON, mage.cards.d.DakmorSalvage.class)); + cards.add(new SetCardInfo("Darksteel Reactor", 134, Rarity.RARE, mage.cards.d.DarksteelReactor.class)); + cards.add(new SetCardInfo("Deepglow Skate", 70, Rarity.RARE, mage.cards.d.DeepglowSkate.class)); + cards.add(new SetCardInfo("Depthshaker Titan", 29, Rarity.RARE, mage.cards.d.DepthshakerTitan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Depthshaker Titan", 9, Rarity.RARE, mage.cards.d.DepthshakerTitan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dispatch", 64, Rarity.UNCOMMON, mage.cards.d.Dispatch.class)); + cards.add(new SetCardInfo("Empowered Autogenerator", 135, Rarity.RARE, mage.cards.e.EmpoweredAutogenerator.class)); + cards.add(new SetCardInfo("Emry, Lurker of the Loch", 71, Rarity.RARE, mage.cards.e.EmryLurkerOfTheLoch.class)); + cards.add(new SetCardInfo("Enthusiastic Mechanaut", 114, Rarity.UNCOMMON, mage.cards.e.EnthusiasticMechanaut.class)); + cards.add(new SetCardInfo("Escape Tunnel", 157, Rarity.COMMON, mage.cards.e.EscapeTunnel.class)); + cards.add(new SetCardInfo("Escape to the Wilds", 115, Rarity.RARE, mage.cards.e.EscapeToTheWilds.class)); + cards.add(new SetCardInfo("Etched Oracle", 136, Rarity.UNCOMMON, mage.cards.e.EtchedOracle.class)); + cards.add(new SetCardInfo("Etherium Sculptor", 72, Rarity.COMMON, mage.cards.e.EtheriumSculptor.class)); + cards.add(new SetCardInfo("Eumidian Hatchery", 20, Rarity.RARE, mage.cards.e.EumidianHatchery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eumidian Hatchery", 40, Rarity.RARE, mage.cards.e.EumidianHatchery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eumidian Wastewaker", 28, Rarity.RARE, mage.cards.e.EumidianWastewaker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eumidian Wastewaker", 8, Rarity.RARE, mage.cards.e.EumidianWastewaker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Evendo Brushrazer", 10, Rarity.RARE, mage.cards.e.EvendoBrushrazer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Evendo Brushrazer", 30, Rarity.RARE, mage.cards.e.EvendoBrushrazer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Everflowing Chalice", 137, Rarity.UNCOMMON, mage.cards.e.EverflowingChalice.class)); + cards.add(new SetCardInfo("Evolving Wilds", 158, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); + cards.add(new SetCardInfo("Exotic Orchard", 159, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); + cards.add(new SetCardInfo("Experimental Augury", 73, Rarity.COMMON, mage.cards.e.ExperimentalAugury.class)); + cards.add(new SetCardInfo("Exploration Broodship", 14, Rarity.RARE, mage.cards.e.ExplorationBroodship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exploration Broodship", 34, Rarity.RARE, mage.cards.e.ExplorationBroodship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fabled Passage", 60, Rarity.RARE, mage.cards.f.FabledPassage.class)); + cards.add(new SetCardInfo("Farseek", 50, Rarity.COMMON, mage.cards.f.Farseek.class)); + cards.add(new SetCardInfo("Festering Thicket", 21, Rarity.RARE, mage.cards.f.FesteringThicket.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Festering Thicket", 41, Rarity.RARE, mage.cards.f.FesteringThicket.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Formless Genesis", 96, Rarity.RARE, mage.cards.f.FormlessGenesis.class)); + cards.add(new SetCardInfo("Fumigate", 65, Rarity.RARE, mage.cards.f.Fumigate.class)); + cards.add(new SetCardInfo("Gavel of the Righteous", 55, Rarity.RARE, mage.cards.g.GavelOfTheRighteous.class)); + cards.add(new SetCardInfo("Gaze of Granite", 116, Rarity.RARE, mage.cards.g.GazeOfGranite.class)); + cards.add(new SetCardInfo("Glacial Fortress", 160, Rarity.RARE, mage.cards.g.GlacialFortress.class)); + cards.add(new SetCardInfo("Glittering Massif", 22, Rarity.RARE, mage.cards.g.GlitteringMassif.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Glittering Massif", 42, Rarity.RARE, mage.cards.g.GlitteringMassif.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("God-Eternal Bontu", 83, Rarity.MYTHIC, mage.cards.g.GodEternalBontu.class)); + cards.add(new SetCardInfo("Golem Foundry", 138, Rarity.COMMON, mage.cards.g.GolemFoundry.class)); + cards.add(new SetCardInfo("Great Furnace", 161, Rarity.COMMON, mage.cards.g.GreatFurnace.class)); + cards.add(new SetCardInfo("Groundskeeper", 97, Rarity.UNCOMMON, mage.cards.g.Groundskeeper.class)); + cards.add(new SetCardInfo("Hammer of Purphoros", 88, Rarity.RARE, mage.cards.h.HammerOfPurphoros.class)); + cards.add(new SetCardInfo("Hangarback Walker", 139, Rarity.RARE, mage.cards.h.HangarbackWalker.class)); + cards.add(new SetCardInfo("Harrow", 98, Rarity.COMMON, mage.cards.h.Harrow.class)); + cards.add(new SetCardInfo("Hearthhull, the Worldseed", 1, Rarity.MYTHIC, mage.cards.h.HearthhullTheWorldseed.class)); + cards.add(new SetCardInfo("Horizon Explorer", 15, Rarity.RARE, mage.cards.h.HorizonExplorer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Horizon Explorer", 35, Rarity.RARE, mage.cards.h.HorizonExplorer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Infernal Grasp", 84, Rarity.UNCOMMON, mage.cards.i.InfernalGrasp.class)); + cards.add(new SetCardInfo("Insight Engine", 26, Rarity.RARE, mage.cards.i.InsightEngine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Insight Engine", 6, Rarity.RARE, mage.cards.i.InsightEngine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inspirit, Flagship Vessel", 2, Rarity.MYTHIC, mage.cards.i.InspiritFlagshipVessel.class)); + cards.add(new SetCardInfo("Irrigated Farmland", 162, Rarity.RARE, mage.cards.i.IrrigatedFarmland.class)); + cards.add(new SetCardInfo("Jhoira, Weatherlight Captain", 118, Rarity.RARE, mage.cards.j.JhoiraWeatherlightCaptain.class)); + cards.add(new SetCardInfo("Juri, Master of the Revue", 119, Rarity.UNCOMMON, mage.cards.j.JuriMasterOfTheRevue.class)); + cards.add(new SetCardInfo("Kappa Cannoneer", 74, Rarity.RARE, mage.cards.k.KappaCannoneer.class)); + cards.add(new SetCardInfo("Karn's Bastion", 163, Rarity.RARE, mage.cards.k.KarnsBastion.class)); + cards.add(new SetCardInfo("Karplusan Forest", 164, Rarity.RARE, mage.cards.k.KarplusanForest.class)); cards.add(new SetCardInfo("Kilo, Apogee Mind", 3, Rarity.MYTHIC, mage.cards.k.KiloApogeeMind.class)); + cards.add(new SetCardInfo("Korvold, Fae-Cursed King", 120, Rarity.MYTHIC, mage.cards.k.KorvoldFaeCursedKing.class)); + cards.add(new SetCardInfo("Llanowar Wastes", 165, Rarity.RARE, mage.cards.l.LlanowarWastes.class)); + cards.add(new SetCardInfo("Loamcrafter Faun", 99, Rarity.RARE, mage.cards.l.LoamcrafterFaun.class)); + cards.add(new SetCardInfo("Lonely Sandbar", 166, Rarity.COMMON, mage.cards.l.LonelySandbar.class)); + cards.add(new SetCardInfo("Long-Range Sensor", 11, Rarity.RARE, mage.cards.l.LongRangeSensor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Long-Range Sensor", 31, Rarity.RARE, mage.cards.l.LongRangeSensor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lux Artillery", 140, Rarity.RARE, mage.cards.l.LuxArtillery.class)); + cards.add(new SetCardInfo("Lux Cannon", 141, Rarity.RARE, mage.cards.l.LuxCannon.class)); + cards.add(new SetCardInfo("Maestros Theater", 167, Rarity.COMMON, mage.cards.m.MaestrosTheater.class)); + cards.add(new SetCardInfo("Mayhem Devil", 121, Rarity.UNCOMMON, mage.cards.m.MayhemDevil.class)); + cards.add(new SetCardInfo("Mazirek, Kraul Death Priest", 122, Rarity.RARE, mage.cards.m.MazirekKraulDeathPriest.class)); + cards.add(new SetCardInfo("Mindless Automaton", 142, Rarity.UNCOMMON, mage.cards.m.MindlessAutomaton.class)); + cards.add(new SetCardInfo("Moraug, Fury of Akoum", 89, Rarity.MYTHIC, mage.cards.m.MoraugFuryOfAkoum.class)); + cards.add(new SetCardInfo("Mountain Valley", 61, Rarity.UNCOMMON, mage.cards.m.MountainValley.class)); + cards.add(new SetCardInfo("Moxite Refinery", 17, Rarity.RARE, mage.cards.m.MoxiteRefinery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Moxite Refinery", 37, Rarity.RARE, mage.cards.m.MoxiteRefinery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Multani, Yavimaya's Avatar", 100, Rarity.MYTHIC, mage.cards.m.MultaniYavimayasAvatar.class)); + cards.add(new SetCardInfo("Myriad Landscape", 169, Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class)); + cards.add(new SetCardInfo("Mystic Monastery", 170, Rarity.UNCOMMON, mage.cards.m.MysticMonastery.class)); + cards.add(new SetCardInfo("Nature's Lore", 101, Rarity.UNCOMMON, mage.cards.n.NaturesLore.class)); + cards.add(new SetCardInfo("Night's Whisper", 85, Rarity.COMMON, mage.cards.n.NightsWhisper.class)); + cards.add(new SetCardInfo("Omnath, Locus of Rage", 123, Rarity.MYTHIC, mage.cards.o.OmnathLocusOfRage.class)); + cards.add(new SetCardInfo("Oracle of Mul Daya", 102, Rarity.RARE, mage.cards.o.OracleOfMulDaya.class)); + cards.add(new SetCardInfo("Organic Extinction", 66, Rarity.RARE, mage.cards.o.OrganicExtinction.class)); + cards.add(new SetCardInfo("Patrolling Peacemaker", 25, Rarity.RARE, mage.cards.p.PatrollingPeacemaker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Patrolling Peacemaker", 5, Rarity.RARE, mage.cards.p.PatrollingPeacemaker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pentad Prism", 56, Rarity.UNCOMMON, mage.cards.p.PentadPrism.class)); + cards.add(new SetCardInfo("Pest Infestation", 103, Rarity.RARE, mage.cards.p.PestInfestation.class)); + cards.add(new SetCardInfo("Phyrexian Metamorph", 75, Rarity.RARE, mage.cards.p.PhyrexianMetamorph.class)); + cards.add(new SetCardInfo("Planetary Annihilation", 12, Rarity.RARE, mage.cards.p.PlanetaryAnnihilation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Planetary Annihilation", 32, Rarity.RARE, mage.cards.p.PlanetaryAnnihilation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pull from Tomorrow", 76, Rarity.RARE, mage.cards.p.PullFromTomorrow.class)); + cards.add(new SetCardInfo("Putrefy", 124, Rarity.UNCOMMON, mage.cards.p.Putrefy.class)); + cards.add(new SetCardInfo("Radiant Summit", 23, Rarity.RARE, mage.cards.r.RadiantSummit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Radiant Summit", 43, Rarity.RARE, mage.cards.r.RadiantSummit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rakdos Charm", 125, Rarity.UNCOMMON, mage.cards.r.RakdosCharm.class)); + cards.add(new SetCardInfo("Rampaging Baloths", 104, Rarity.RARE, mage.cards.r.RampagingBaloths.class)); + cards.add(new SetCardInfo("Razortide Bridge", 171, Rarity.COMMON, mage.cards.r.RazortideBridge.class)); + cards.add(new SetCardInfo("Resourceful Defense", 67, Rarity.RARE, mage.cards.r.ResourcefulDefense.class)); + cards.add(new SetCardInfo("Ripples of Potential", 77, Rarity.RARE, mage.cards.r.RipplesOfPotential.class)); + cards.add(new SetCardInfo("Riveteers Overlook", 172, Rarity.COMMON, mage.cards.r.RiveteersOverlook.class)); + cards.add(new SetCardInfo("Rocky Tar Pit", 173, Rarity.UNCOMMON, mage.cards.r.RockyTarPit.class)); + cards.add(new SetCardInfo("Roiling Regrowth", 105, Rarity.UNCOMMON, mage.cards.r.RoilingRegrowth.class)); + cards.add(new SetCardInfo("Rugged Prairie", 174, Rarity.RARE, mage.cards.r.RuggedPrairie.class)); + cards.add(new SetCardInfo("Rustvale Bridge", 175, Rarity.COMMON, mage.cards.r.RustvaleBridge.class)); + cards.add(new SetCardInfo("Satyr Wayfinder", 106, Rarity.COMMON, mage.cards.s.SatyrWayfinder.class)); + cards.add(new SetCardInfo("Scouring Swarm", 16, Rarity.RARE, mage.cards.s.ScouringSwarm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scouring Swarm", 36, Rarity.RARE, mage.cards.s.ScouringSwarm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seat of the Synod", 176, Rarity.COMMON, mage.cards.s.SeatOfTheSynod.class)); + cards.add(new SetCardInfo("Secluded Steppe", 177, Rarity.COMMON, mage.cards.s.SecludedSteppe.class)); + cards.add(new SetCardInfo("Sheltered Thicket", 178, Rarity.RARE, mage.cards.s.ShelteredThicket.class)); + cards.add(new SetCardInfo("Shivan Reef", 179, Rarity.RARE, mage.cards.s.ShivanReef.class)); + cards.add(new SetCardInfo("Silverbluff Bridge", 180, Rarity.COMMON, mage.cards.s.SilverbluffBridge.class)); + cards.add(new SetCardInfo("Skycloud Expanse", 181, Rarity.RARE, mage.cards.s.SkycloudExpanse.class)); + cards.add(new SetCardInfo("Skyshroud Claim", 107, Rarity.COMMON, mage.cards.s.SkyshroudClaim.class)); + cards.add(new SetCardInfo("Smoldering Marsh", 182, Rarity.RARE, mage.cards.s.SmolderingMarsh.class)); + cards.add(new SetCardInfo("Sol Ring", 57, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); + cards.add(new SetCardInfo("Solar Array", 18, Rarity.RARE, mage.cards.s.SolarArray.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Solar Array", 38, Rarity.RARE, mage.cards.s.SolarArray.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Soul of Windgrace", 126, Rarity.MYTHIC, mage.cards.s.SoulOfWindgrace.class)); + cards.add(new SetCardInfo("Soul-Guide Lantern", 143, Rarity.UNCOMMON, mage.cards.s.SoulGuideLantern.class)); + cards.add(new SetCardInfo("Spire of Industry", 183, Rarity.RARE, mage.cards.s.SpireOfIndustry.class)); + cards.add(new SetCardInfo("Splendid Reclamation", 108, Rarity.RARE, mage.cards.s.SplendidReclamation.class)); + cards.add(new SetCardInfo("Springbloom Druid", 51, Rarity.COMMON, mage.cards.s.SpringbloomDruid.class)); + cards.add(new SetCardInfo("Sprouting Goblin", 90, Rarity.UNCOMMON, mage.cards.s.SproutingGoblin.class)); + cards.add(new SetCardInfo("Steel Overseer", 144, Rarity.RARE, mage.cards.s.SteelOverseer.class)); + cards.add(new SetCardInfo("Sulfur Falls", 184, Rarity.RARE, mage.cards.s.SulfurFalls.class)); + cards.add(new SetCardInfo("Sulfurous Springs", 185, Rarity.RARE, mage.cards.s.SulfurousSprings.class)); + cards.add(new SetCardInfo("Surge Conductor", 19, Rarity.RARE, mage.cards.s.SurgeConductor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Surge Conductor", 39, Rarity.RARE, mage.cards.s.SurgeConductor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swan Song", 46, Rarity.RARE, mage.cards.s.SwanSong.class)); + cards.add(new SetCardInfo("Swords to Plowshares", 45, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); cards.add(new SetCardInfo("Szarel, Genesis Shepherd", 4, Rarity.MYTHIC, mage.cards.s.SzarelGenesisShepherd.class)); + cards.add(new SetCardInfo("Tear Asunder", 109, Rarity.UNCOMMON, mage.cards.t.TearAsunder.class)); + cards.add(new SetCardInfo("Tekuthal, Inquiry Dominus", 78, Rarity.MYTHIC, mage.cards.t.TekuthalInquiryDominus.class)); + cards.add(new SetCardInfo("Temple of Enlightenment", 186, Rarity.RARE, mage.cards.t.TempleOfEnlightenment.class)); + cards.add(new SetCardInfo("Temple of Epiphany", 187, Rarity.RARE, mage.cards.t.TempleOfEpiphany.class)); + cards.add(new SetCardInfo("Temple of Triumph", 188, Rarity.RARE, mage.cards.t.TempleOfTriumph.class)); + cards.add(new SetCardInfo("Terramorphic Expanse", 62, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class)); + cards.add(new SetCardInfo("Tezzeret's Gambit", 47, Rarity.UNCOMMON, mage.cards.t.TezzeretsGambit.class)); + cards.add(new SetCardInfo("The Gitrog Monster", 117, Rarity.MYTHIC, mage.cards.t.TheGitrogMonster.class)); + cards.add(new SetCardInfo("The Mycosynth Gardens", 168, Rarity.RARE, mage.cards.t.TheMycosynthGardens.class)); + cards.add(new SetCardInfo("Thirst for Knowledge", 48, Rarity.UNCOMMON, mage.cards.t.ThirstForKnowledge.class)); + cards.add(new SetCardInfo("Thought Monitor", 79, Rarity.RARE, mage.cards.t.ThoughtMonitor.class)); + cards.add(new SetCardInfo("Threefold Thunderhulk", 145, Rarity.RARE, mage.cards.t.ThreefoldThunderhulk.class)); + cards.add(new SetCardInfo("Thrummingbird", 80, Rarity.UNCOMMON, mage.cards.t.Thrummingbird.class)); + cards.add(new SetCardInfo("Tireless Tracker", 110, Rarity.RARE, mage.cards.t.TirelessTracker.class)); + cards.add(new SetCardInfo("Titan Forge", 146, Rarity.RARE, mage.cards.t.TitanForge.class)); + cards.add(new SetCardInfo("Titania, Protector of Argoth", 111, Rarity.MYTHIC, mage.cards.t.TitaniaProtectorOfArgoth.class)); + cards.add(new SetCardInfo("Twilight Mire", 189, Rarity.RARE, mage.cards.t.TwilightMire.class)); + cards.add(new SetCardInfo("Universal Surveillance", 81, Rarity.RARE, mage.cards.u.UniversalSurveillance.class)); + cards.add(new SetCardInfo("Uthros Research Craft", 27, Rarity.RARE, mage.cards.u.UthrosResearchCraft.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Uthros Research Craft", 7, Rarity.RARE, mage.cards.u.UthrosResearchCraft.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Uurg, Spawn of Turg", 127, Rarity.UNCOMMON, mage.cards.u.UurgSpawnOfTurg.class)); + cards.add(new SetCardInfo("Vernal Fen", 24, Rarity.RARE, mage.cards.v.VernalFen.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vernal Fen", 44, Rarity.RARE, mage.cards.v.VernalFen.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Viridescent Bog", 190, Rarity.RARE, mage.cards.v.ViridescentBog.class)); + cards.add(new SetCardInfo("Wake the Past", 128, Rarity.RARE, mage.cards.w.WakeThePast.class)); + cards.add(new SetCardInfo("Wastes", 191, Rarity.COMMON, mage.cards.w.Wastes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Windgrace's Judgment", 129, Rarity.RARE, mage.cards.w.WindgracesJudgment.class)); + cards.add(new SetCardInfo("World Breaker", 112, Rarity.MYTHIC, mage.cards.w.WorldBreaker.class)); + cards.add(new SetCardInfo("Worldsoul's Rage", 130, Rarity.RARE, mage.cards.w.WorldsoulsRage.class)); } } diff --git a/Mage.Sets/src/mage/sets/EdgeOfEternitiesStellarSights.java b/Mage.Sets/src/mage/sets/EdgeOfEternitiesStellarSights.java new file mode 100644 index 00000000000..5079f74d997 --- /dev/null +++ b/Mage.Sets/src/mage/sets/EdgeOfEternitiesStellarSights.java @@ -0,0 +1,203 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * @author ReSech + */ +public final class EdgeOfEternitiesStellarSights extends ExpansionSet { + + private static final EdgeOfEternitiesStellarSights instance = new EdgeOfEternitiesStellarSights(); + + public static EdgeOfEternitiesStellarSights getInstance() { + return instance; + } + + private EdgeOfEternitiesStellarSights() { + super("Edge of Eternities: Stellar Sights", "EOS", ExpansionSet.buildDate(2025, 8, 1), SetType.SUPPLEMENTAL); + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Ancient Tomb", 1, Rarity.MYTHIC, mage.cards.a.AncientTomb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ancient Tomb", 46, Rarity.MYTHIC, mage.cards.a.AncientTomb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ancient Tomb", 91, Rarity.MYTHIC, mage.cards.a.AncientTomb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ancient Tomb", 136, Rarity.MYTHIC, mage.cards.a.AncientTomb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blast Zone", 2, Rarity.RARE, mage.cards.b.BlastZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blast Zone", 47, Rarity.RARE, mage.cards.b.BlastZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blast Zone", 92, Rarity.RARE, mage.cards.b.BlastZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blast Zone", 137, Rarity.RARE, mage.cards.b.BlastZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blinkmoth Nexus", 3, Rarity.RARE, mage.cards.b.BlinkmothNexus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blinkmoth Nexus", 48, Rarity.RARE, mage.cards.b.BlinkmothNexus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blinkmoth Nexus", 93, Rarity.RARE, mage.cards.b.BlinkmothNexus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blinkmoth Nexus", 138, Rarity.RARE, mage.cards.b.BlinkmothNexus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bonders' Enclave", 4, Rarity.RARE, mage.cards.b.BondersEnclave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bonders' Enclave", 49, Rarity.RARE, mage.cards.b.BondersEnclave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bonders' Enclave", 94, Rarity.RARE, mage.cards.b.BondersEnclave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bonders' Enclave", 139, Rarity.RARE, mage.cards.b.BondersEnclave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cascading Cataracts", 5, Rarity.RARE, mage.cards.c.CascadingCataracts.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cascading Cataracts", 50, Rarity.RARE, mage.cards.c.CascadingCataracts.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cascading Cataracts", 95, Rarity.RARE, mage.cards.c.CascadingCataracts.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cascading Cataracts", 140, Rarity.RARE, mage.cards.c.CascadingCataracts.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cathedral of War", 6, Rarity.RARE, mage.cards.c.CathedralOfWar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cathedral of War", 51, Rarity.RARE, mage.cards.c.CathedralOfWar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cathedral of War", 96, Rarity.RARE, mage.cards.c.CathedralOfWar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cathedral of War", 141, Rarity.RARE, mage.cards.c.CathedralOfWar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Celestial Colonnade", 7, Rarity.RARE, mage.cards.c.CelestialColonnade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Celestial Colonnade", 52, Rarity.RARE, mage.cards.c.CelestialColonnade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Celestial Colonnade", 97, Rarity.RARE, mage.cards.c.CelestialColonnade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Celestial Colonnade", 142, Rarity.RARE, mage.cards.c.CelestialColonnade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Contested War Zone", 8, Rarity.RARE, mage.cards.c.ContestedWarZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Contested War Zone", 53, Rarity.RARE, mage.cards.c.ContestedWarZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Contested War Zone", 98, Rarity.RARE, mage.cards.c.ContestedWarZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Contested War Zone", 143, Rarity.RARE, mage.cards.c.ContestedWarZone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Creeping Tar Pit", 9, Rarity.RARE, mage.cards.c.CreepingTarPit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Creeping Tar Pit", 54, Rarity.RARE, mage.cards.c.CreepingTarPit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Creeping Tar Pit", 99, Rarity.RARE, mage.cards.c.CreepingTarPit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Creeping Tar Pit", 144, Rarity.RARE, mage.cards.c.CreepingTarPit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crystal Quarry", 10, Rarity.RARE, mage.cards.c.CrystalQuarry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crystal Quarry", 55, Rarity.RARE, mage.cards.c.CrystalQuarry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crystal Quarry", 100, Rarity.RARE, mage.cards.c.CrystalQuarry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crystal Quarry", 145, Rarity.RARE, mage.cards.c.CrystalQuarry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deserted Temple", 11, Rarity.MYTHIC, mage.cards.d.DesertedTemple.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deserted Temple", 56, Rarity.MYTHIC, mage.cards.d.DesertedTemple.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deserted Temple", 101, Rarity.MYTHIC, mage.cards.d.DesertedTemple.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deserted Temple", 146, Rarity.MYTHIC, mage.cards.d.DesertedTemple.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dust Bowl", 12, Rarity.MYTHIC, mage.cards.d.DustBowl.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dust Bowl", 57, Rarity.MYTHIC, mage.cards.d.DustBowl.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dust Bowl", 102, Rarity.MYTHIC, mage.cards.d.DustBowl.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dust Bowl", 147, Rarity.MYTHIC, mage.cards.d.DustBowl.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Echoing Deeps", 13, Rarity.RARE, mage.cards.e.EchoingDeeps.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Echoing Deeps", 58, Rarity.RARE, mage.cards.e.EchoingDeeps.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Echoing Deeps", 103, Rarity.RARE, mage.cards.e.EchoingDeeps.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Echoing Deeps", 148, Rarity.RARE, mage.cards.e.EchoingDeeps.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eldrazi Temple", 14, Rarity.RARE, mage.cards.e.EldraziTemple.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eldrazi Temple", 59, Rarity.RARE, mage.cards.e.EldraziTemple.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eldrazi Temple", 104, Rarity.RARE, mage.cards.e.EldraziTemple.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eldrazi Temple", 149, Rarity.RARE, mage.cards.e.EldraziTemple.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Endless Sands", 15, Rarity.RARE, mage.cards.e.EndlessSands.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Endless Sands", 60, Rarity.RARE, mage.cards.e.EndlessSands.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Endless Sands", 105, Rarity.RARE, mage.cards.e.EndlessSands.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Endless Sands", 150, Rarity.RARE, mage.cards.e.EndlessSands.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gemstone Caverns", 16, Rarity.MYTHIC, mage.cards.g.GemstoneCaverns.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gemstone Caverns", 61, Rarity.MYTHIC, mage.cards.g.GemstoneCaverns.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gemstone Caverns", 106, Rarity.MYTHIC, mage.cards.g.GemstoneCaverns.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gemstone Caverns", 151, Rarity.MYTHIC, mage.cards.g.GemstoneCaverns.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grove of the Burnwillows", 17, Rarity.MYTHIC, mage.cards.g.GroveOfTheBurnwillows.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grove of the Burnwillows", 62, Rarity.MYTHIC, mage.cards.g.GroveOfTheBurnwillows.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grove of the Burnwillows", 107, Rarity.MYTHIC, mage.cards.g.GroveOfTheBurnwillows.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grove of the Burnwillows", 152, Rarity.MYTHIC, mage.cards.g.GroveOfTheBurnwillows.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("High Market", 18, Rarity.RARE, mage.cards.h.HighMarket.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("High Market", 63, Rarity.RARE, mage.cards.h.HighMarket.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("High Market", 108, Rarity.RARE, mage.cards.h.HighMarket.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("High Market", 153, Rarity.RARE, mage.cards.h.HighMarket.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hissing Quagmire", 19, Rarity.RARE, mage.cards.h.HissingQuagmire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hissing Quagmire", 64, Rarity.RARE, mage.cards.h.HissingQuagmire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hissing Quagmire", 109, Rarity.RARE, mage.cards.h.HissingQuagmire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hissing Quagmire", 154, Rarity.RARE, mage.cards.h.HissingQuagmire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inkmoth Nexus", 20, Rarity.MYTHIC, mage.cards.i.InkmothNexus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inkmoth Nexus", 65, Rarity.MYTHIC, mage.cards.i.InkmothNexus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inkmoth Nexus", 110, Rarity.MYTHIC, mage.cards.i.InkmothNexus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inkmoth Nexus", 155, Rarity.MYTHIC, mage.cards.i.InkmothNexus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inventors' Fair", 21, Rarity.MYTHIC, mage.cards.i.InventorsFair.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inventors' Fair", 66, Rarity.MYTHIC, mage.cards.i.InventorsFair.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inventors' Fair", 111, Rarity.MYTHIC, mage.cards.i.InventorsFair.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inventors' Fair", 156, Rarity.MYTHIC, mage.cards.i.InventorsFair.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lavaclaw Reaches", 22, Rarity.RARE, mage.cards.l.LavaclawReaches.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lavaclaw Reaches", 67, Rarity.RARE, mage.cards.l.LavaclawReaches.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lavaclaw Reaches", 112, Rarity.RARE, mage.cards.l.LavaclawReaches.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lavaclaw Reaches", 157, Rarity.RARE, mage.cards.l.LavaclawReaches.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Field", 23, Rarity.MYTHIC, mage.cards.l.LotusField.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Field", 68, Rarity.MYTHIC, mage.cards.l.LotusField.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Field", 113, Rarity.MYTHIC, mage.cards.l.LotusField.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Field", 158, Rarity.MYTHIC, mage.cards.l.LotusField.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lumbering Falls", 24, Rarity.RARE, mage.cards.l.LumberingFalls.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lumbering Falls", 69, Rarity.RARE, mage.cards.l.LumberingFalls.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lumbering Falls", 114, Rarity.RARE, mage.cards.l.LumberingFalls.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lumbering Falls", 159, Rarity.RARE, mage.cards.l.LumberingFalls.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mana Confluence", 25, Rarity.MYTHIC, mage.cards.m.ManaConfluence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mana Confluence", 70, Rarity.MYTHIC, mage.cards.m.ManaConfluence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mana Confluence", 115, Rarity.MYTHIC, mage.cards.m.ManaConfluence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mana Confluence", 160, Rarity.MYTHIC, mage.cards.m.ManaConfluence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Meteor Crater", 26, Rarity.RARE, mage.cards.m.MeteorCrater.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Meteor Crater", 71, Rarity.RARE, mage.cards.m.MeteorCrater.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Meteor Crater", 116, Rarity.RARE, mage.cards.m.MeteorCrater.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Meteor Crater", 161, Rarity.RARE, mage.cards.m.MeteorCrater.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mirrorpool", 27, Rarity.MYTHIC, mage.cards.m.Mirrorpool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mirrorpool", 72, Rarity.MYTHIC, mage.cards.m.Mirrorpool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mirrorpool", 117, Rarity.MYTHIC, mage.cards.m.Mirrorpool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mirrorpool", 162, Rarity.MYTHIC, mage.cards.m.Mirrorpool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutavault", 28, Rarity.MYTHIC, mage.cards.m.Mutavault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutavault", 73, Rarity.MYTHIC, mage.cards.m.Mutavault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutavault", 118, Rarity.MYTHIC, mage.cards.m.Mutavault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutavault", 163, Rarity.MYTHIC, mage.cards.m.Mutavault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mystifying Maze", 29, Rarity.RARE, mage.cards.m.MystifyingMaze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mystifying Maze", 74, Rarity.RARE, mage.cards.m.MystifyingMaze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mystifying Maze", 119, Rarity.RARE, mage.cards.m.MystifyingMaze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mystifying Maze", 164, Rarity.RARE, mage.cards.m.MystifyingMaze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Needle Spires", 30, Rarity.RARE, mage.cards.n.NeedleSpires.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Needle Spires", 75, Rarity.RARE, mage.cards.n.NeedleSpires.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Needle Spires", 120, Rarity.RARE, mage.cards.n.NeedleSpires.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Needle Spires", 165, Rarity.RARE, mage.cards.n.NeedleSpires.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nesting Grounds", 31, Rarity.RARE, mage.cards.n.NestingGrounds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nesting Grounds", 76, Rarity.RARE, mage.cards.n.NestingGrounds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nesting Grounds", 121, Rarity.RARE, mage.cards.n.NestingGrounds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nesting Grounds", 166, Rarity.RARE, mage.cards.n.NestingGrounds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Petrified Field", 32, Rarity.MYTHIC, mage.cards.p.PetrifiedField.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Petrified Field", 77, Rarity.MYTHIC, mage.cards.p.PetrifiedField.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Petrified Field", 122, Rarity.MYTHIC, mage.cards.p.PetrifiedField.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Petrified Field", 167, Rarity.MYTHIC, mage.cards.p.PetrifiedField.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plaza of Heroes", 33, Rarity.MYTHIC, mage.cards.p.PlazaOfHeroes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plaza of Heroes", 78, Rarity.MYTHIC, mage.cards.p.PlazaOfHeroes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plaza of Heroes", 123, Rarity.MYTHIC, mage.cards.p.PlazaOfHeroes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plaza of Heroes", 168, Rarity.MYTHIC, mage.cards.p.PlazaOfHeroes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Power Depot", 34, Rarity.RARE, mage.cards.p.PowerDepot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Power Depot", 79, Rarity.RARE, mage.cards.p.PowerDepot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Power Depot", 124, Rarity.RARE, mage.cards.p.PowerDepot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Power Depot", 169, Rarity.RARE, mage.cards.p.PowerDepot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Raging Ravine", 35, Rarity.RARE, mage.cards.r.RagingRavine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Raging Ravine", 80, Rarity.RARE, mage.cards.r.RagingRavine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Raging Ravine", 125, Rarity.RARE, mage.cards.r.RagingRavine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Raging Ravine", 170, Rarity.RARE, mage.cards.r.RagingRavine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reflecting Pool", 36, Rarity.MYTHIC, mage.cards.r.ReflectingPool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reflecting Pool", 81, Rarity.MYTHIC, mage.cards.r.ReflectingPool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reflecting Pool", 126, Rarity.MYTHIC, mage.cards.r.ReflectingPool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reflecting Pool", 171, Rarity.MYTHIC, mage.cards.r.ReflectingPool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scavenger Grounds", 37, Rarity.RARE, mage.cards.s.ScavengerGrounds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scavenger Grounds", 82, Rarity.RARE, mage.cards.s.ScavengerGrounds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scavenger Grounds", 127, Rarity.RARE, mage.cards.s.ScavengerGrounds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scavenger Grounds", 172, Rarity.RARE, mage.cards.s.ScavengerGrounds.class, NON_FULL_USE_VARIOUS)); + 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("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)); + cards.add(new SetCardInfo("Stirring Wildwood", 174, Rarity.RARE, mage.cards.s.StirringWildwood.class, NON_FULL_USE_VARIOUS)); + 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)); + cards.add(new SetCardInfo("Sunken Citadel", 131, Rarity.RARE, mage.cards.s.SunkenCitadel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sunken Citadel", 176, Rarity.RARE, mage.cards.s.SunkenCitadel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swarmyard", 42, Rarity.RARE, mage.cards.s.Swarmyard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swarmyard", 87, Rarity.RARE, mage.cards.s.Swarmyard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swarmyard", 132, Rarity.RARE, mage.cards.s.Swarmyard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swarmyard", 177, Rarity.RARE, mage.cards.s.Swarmyard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terrain Generator", 43, Rarity.RARE, mage.cards.t.TerrainGenerator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terrain Generator", 88, Rarity.RARE, mage.cards.t.TerrainGenerator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terrain Generator", 133, Rarity.RARE, mage.cards.t.TerrainGenerator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terrain Generator", 178, Rarity.RARE, mage.cards.t.TerrainGenerator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thespian's Stage", 44, Rarity.RARE, mage.cards.t.ThespiansStage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thespian's Stage", 89, Rarity.RARE, mage.cards.t.ThespiansStage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thespian's Stage", 134, Rarity.RARE, mage.cards.t.ThespiansStage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thespian's Stage", 179, Rarity.RARE, mage.cards.t.ThespiansStage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wandering Fumarole", 45, Rarity.RARE, mage.cards.w.WanderingFumarole.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wandering Fumarole", 90, Rarity.RARE, mage.cards.w.WanderingFumarole.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wandering Fumarole", 135, Rarity.RARE, mage.cards.w.WanderingFumarole.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wandering Fumarole", 180, Rarity.RARE, mage.cards.w.WanderingFumarole.class, NON_FULL_USE_VARIOUS)); + } +} diff --git a/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java b/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java index 6b0c9612c26..b655cb8f7fc 100644 --- a/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java +++ b/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java @@ -33,7 +33,7 @@ public class MediaAndCollaborationPromos extends ExpansionSet { 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("Counterspell", "2021-1", Rarity.RARE, mage.cards.c.Counterspell.class)); - cards.add(new SetCardInfo("Crop Rotation", "2020-7ww", Rarity.RARE, mage.cards.c.CropRotation.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)); cards.add(new SetCardInfo("Cunning Sparkmage", "2010-2", Rarity.UNCOMMON, mage.cards.c.CunningSparkmage.class)); cards.add(new SetCardInfo("Dark Ritual", "2020-4", Rarity.RARE, mage.cards.d.DarkRitual.class)); diff --git a/Mage.Sets/src/mage/sets/SecretLairDrop.java b/Mage.Sets/src/mage/sets/SecretLairDrop.java index d8a6eb5b0f3..02ad3042856 100644 --- a/Mage.Sets/src/mage/sets/SecretLairDrop.java +++ b/Mage.Sets/src/mage/sets/SecretLairDrop.java @@ -1550,7 +1550,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Dire Undercurrents", 1578, Rarity.RARE, mage.cards.d.DireUndercurrents.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dire Undercurrents", "1578*", Rarity.RARE, mage.cards.d.DireUndercurrents.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Obeka, Brute Chronologist", 1579, Rarity.MYTHIC, mage.cards.o.ObekaBruteChronologist.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Obeka, Brute Chronologist", "1579*", Rarity.RARE, mage.cards.o.ObekaBruteChronologist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Obeka, Brute Chronologist", "1579*", Rarity.MYTHIC, mage.cards.o.ObekaBruteChronologist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Rose Noble", 1580, Rarity.RARE, mage.cards.r.RoseNoble.class)); cards.add(new SetCardInfo("The Meep", 1581, Rarity.RARE, mage.cards.t.TheMeep.class)); // cards.add(new SetCardInfo("The Celestial Toymaker", 1582, Rarity.RARE, mage.cards.t.TheCelestialToymaker.class)); diff --git a/Mage.Sets/src/mage/sets/SecretLairShowdown.java b/Mage.Sets/src/mage/sets/SecretLairShowdown.java index 2d6802320d5..dfc3fecd8be 100644 --- a/Mage.Sets/src/mage/sets/SecretLairShowdown.java +++ b/Mage.Sets/src/mage/sets/SecretLairShowdown.java @@ -25,7 +25,6 @@ public class SecretLairShowdown extends ExpansionSet { cards.add(new SetCardInfo("Dark Ritual", 16, Rarity.RARE, mage.cards.d.DarkRitual.class)); cards.add(new SetCardInfo("Death's Shadow", 8, Rarity.RARE, mage.cards.d.DeathsShadow.class)); cards.add(new SetCardInfo("Dragonlord Silumgar", 9, Rarity.MYTHIC, mage.cards.d.DragonlordSilumgar.class)); - cards.add(new SetCardInfo("Echo of Death's Wail", 356, Rarity.RARE, mage.cards.e.EchoOfDeathsWail.class)); cards.add(new SetCardInfo("Eldritch Evolution", 5, Rarity.RARE, mage.cards.e.EldritchEvolution.class)); cards.add(new SetCardInfo("Explore", 12, Rarity.RARE, mage.cards.e.Explore.class)); cards.add(new SetCardInfo("Expressive Iteration", 13, Rarity.RARE, mage.cards.e.ExpressiveIteration.class)); @@ -60,7 +59,6 @@ public class SecretLairShowdown extends ExpansionSet { cards.add(new SetCardInfo("Supreme Verdict", 26, Rarity.RARE, mage.cards.s.SupremeVerdict.class)); 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("Tribute to Horobi", 356, Rarity.RARE, mage.cards.t.TributeToHorobi.class)); cards.add(new SetCardInfo("Ugin, the Spirit Dragon", 6, Rarity.MYTHIC, mage.cards.u.UginTheSpiritDragon.class)); cards.add(new SetCardInfo("Unholy Heat", 4, Rarity.RARE, mage.cards.u.UnholyHeat.class)); cards.add(new SetCardInfo("Valakut, the Molten Pinnacle", 14, Rarity.RARE, mage.cards.v.ValakutTheMoltenPinnacle.class)); diff --git a/Mage.Sets/src/mage/sets/SpecialGuests.java b/Mage.Sets/src/mage/sets/SpecialGuests.java index fd069857867..68a36bb95f7 100644 --- a/Mage.Sets/src/mage/sets/SpecialGuests.java +++ b/Mage.Sets/src/mage/sets/SpecialGuests.java @@ -22,12 +22,15 @@ public final class SpecialGuests extends ExpansionSet { this.hasBasicLands = false; cards.add(new SetCardInfo("Akroma's Memorial", 81, Rarity.MYTHIC, mage.cards.a.AkromasMemorial.class)); + cards.add(new SetCardInfo("Arid Mesa", 109, Rarity.MYTHIC, mage.cards.a.AridMesa.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arid Mesa", 114, Rarity.MYTHIC, mage.cards.a.AridMesa.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bloom Tender", 79, Rarity.MYTHIC, mage.cards.b.BloomTender.class)); cards.add(new SetCardInfo("Bone Miser", 87, Rarity.MYTHIC, mage.cards.b.BoneMiser.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bone Miser", 97, Rarity.MYTHIC, mage.cards.b.BoneMiser.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Brazen Borrower", 30, Rarity.MYTHIC, mage.cards.b.BrazenBorrower.class)); cards.add(new SetCardInfo("Breeches, Brazen Plunderer", 6, Rarity.UNCOMMON, mage.cards.b.BreechesBrazenPlunderer.class)); cards.add(new SetCardInfo("Bridge from Below", 3, Rarity.RARE, mage.cards.b.BridgeFromBelow.class)); + cards.add(new SetCardInfo("Burgeoning", 126, Rarity.MYTHIC, mage.cards.b.Burgeoning.class, FULL_ART)); cards.add(new SetCardInfo("Carnage Tyrant", 10, Rarity.MYTHIC, mage.cards.c.CarnageTyrant.class)); cards.add(new SetCardInfo("Cavalier of Dawn", 84, Rarity.MYTHIC, mage.cards.c.CavalierOfDawn.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Cavalier of Dawn", 94, Rarity.MYTHIC, mage.cards.c.CavalierOfDawn.class, NON_FULL_USE_VARIOUS)); @@ -40,11 +43,15 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Crashing Footfalls", 25, Rarity.MYTHIC, mage.cards.c.CrashingFootfalls.class)); cards.add(new SetCardInfo("Damnation", 68, Rarity.MYTHIC, mage.cards.d.Damnation.class)); cards.add(new SetCardInfo("Dargo, the Shipwrecker", 7, Rarity.UNCOMMON, mage.cards.d.DargoTheShipwrecker.class)); + cards.add(new SetCardInfo("Darkness", 124, Rarity.MYTHIC, mage.cards.d.Darkness.class, FULL_ART)); + cards.add(new SetCardInfo("Deafening Silence", 120, Rarity.MYTHIC, mage.cards.d.DeafeningSilence.class, FULL_ART)); cards.add(new SetCardInfo("Desert", 37, Rarity.MYTHIC, mage.cards.d.Desert.class)); cards.add(new SetCardInfo("Desertion", 31, Rarity.MYTHIC, mage.cards.d.Desertion.class)); cards.add(new SetCardInfo("Dismember", 41, Rarity.MYTHIC, mage.cards.d.Dismember.class)); cards.add(new SetCardInfo("Drown in the Loch", 27, Rarity.MYTHIC, mage.cards.d.DrownInTheLoch.class)); + cards.add(new SetCardInfo("Eerie Ultimatum", 104, Rarity.MYTHIC, mage.cards.e.EerieUltimatum.class)); cards.add(new SetCardInfo("Embercleave", 77, Rarity.MYTHIC, mage.cards.e.Embercleave.class)); + cards.add(new SetCardInfo("Emergent Ultimatum", 105, Rarity.MYTHIC, mage.cards.e.EmergentUltimatum.class)); cards.add(new SetCardInfo("Endurance", 48, Rarity.MYTHIC, mage.cards.e.Endurance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Endurance", 53, Rarity.MYTHIC, mage.cards.e.Endurance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Expressive Iteration", 43, Rarity.MYTHIC, mage.cards.e.ExpressiveIteration.class)); @@ -58,13 +65,16 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Galvanic Blast", 100, Rarity.MYTHIC, mage.cards.g.GalvanicBlast.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Galvanic Blast", 90, Rarity.MYTHIC, mage.cards.g.GalvanicBlast.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gamble", 24, Rarity.MYTHIC, mage.cards.g.Gamble.class)); + cards.add(new SetCardInfo("Genesis Ultimatum", 106, Rarity.MYTHIC, mage.cards.g.GenesisUltimatum.class)); cards.add(new SetCardInfo("Ghalta, Primal Hunger", 11, Rarity.RARE, mage.cards.g.GhaltaPrimalHunger.class)); cards.add(new SetCardInfo("Ghostly Prison", 19, Rarity.MYTHIC, mage.cards.g.GhostlyPrison.class)); cards.add(new SetCardInfo("Goblin Bushwhacker", 78, Rarity.MYTHIC, mage.cards.g.GoblinBushwhacker.class)); + cards.add(new SetCardInfo("Green Sun's Zenith", 127, Rarity.MYTHIC, mage.cards.g.GreenSunsZenith.class, FULL_ART)); cards.add(new SetCardInfo("Grief", 46, Rarity.MYTHIC, mage.cards.g.Grief.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Grief", 51, Rarity.MYTHIC, mage.cards.g.Grief.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Grim Tutor", 76, Rarity.MYTHIC, mage.cards.g.GrimTutor.class)); cards.add(new SetCardInfo("Hallowed Haunting", 64, Rarity.MYTHIC, mage.cards.h.HallowedHaunting.class)); + cards.add(new SetCardInfo("Inspired Ultimatum", 107, Rarity.MYTHIC, mage.cards.i.InspiredUltimatum.class)); cards.add(new SetCardInfo("Kalamax, the Stormsire", 13, Rarity.MYTHIC, mage.cards.k.KalamaxTheStormsire.class)); cards.add(new SetCardInfo("Kindred Charge", 58, Rarity.MYTHIC, mage.cards.k.KindredCharge.class)); cards.add(new SetCardInfo("Ledger Shredder", 55, Rarity.MYTHIC, mage.cards.l.LedgerShredder.class)); @@ -73,6 +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("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)); @@ -81,13 +92,19 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Mana Crypt", "17e", Rarity.MYTHIC, mage.cards.m.ManaCrypt.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mana Crypt", "17f", Rarity.MYTHIC, mage.cards.m.ManaCrypt.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mana Crypt", 17, Rarity.MYTHIC, mage.cards.m.ManaCrypt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Marsh Flats", 110, Rarity.MYTHIC, mage.cards.m.MarshFlats.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Marsh Flats", 115, Rarity.MYTHIC, mage.cards.m.MarshFlats.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mephidross Vampire", 4, Rarity.RARE, mage.cards.m.MephidrossVampire.class)); cards.add(new SetCardInfo("Mirri, Weatherlight Duelist", 15, Rarity.MYTHIC, mage.cards.m.MirriWeatherlightDuelist.class)); + cards.add(new SetCardInfo("Misty Rainforest", 111, Rarity.MYTHIC, mage.cards.m.MistyRainforest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Misty Rainforest", 116, Rarity.MYTHIC, mage.cards.m.MistyRainforest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Morbid Opportunist", 32, Rarity.MYTHIC, mage.cards.m.MorbidOpportunist.class)); cards.add(new SetCardInfo("Mystic Snake", 35, Rarity.MYTHIC, mage.cards.m.MysticSnake.class)); + cards.add(new SetCardInfo("Nexus of Fate", 122, Rarity.MYTHIC, mage.cards.n.NexusOfFate.class, FULL_ART)); cards.add(new SetCardInfo("Notion Thief", 36, Rarity.MYTHIC, mage.cards.n.NotionThief.class)); cards.add(new SetCardInfo("Noxious Revival", 73, Rarity.MYTHIC, mage.cards.n.NoxiousRevival.class)); cards.add(new SetCardInfo("Paradise Druid", 80, Rarity.MYTHIC, mage.cards.p.ParadiseDruid.class)); + cards.add(new SetCardInfo("Paradox Haze", 123, Rarity.MYTHIC, mage.cards.p.ParadoxHaze.class, FULL_ART)); cards.add(new SetCardInfo("Pathbreaker Ibex", 101, Rarity.MYTHIC, mage.cards.p.PathbreakerIbex.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pathbreaker Ibex", 91, Rarity.MYTHIC, mage.cards.p.PathbreakerIbex.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Persist", 42, Rarity.MYTHIC, mage.cards.p.Persist.class)); @@ -100,12 +117,17 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Rampaging Ferocidon", 8, Rarity.RARE, mage.cards.r.RampagingFerocidon.class)); cards.add(new SetCardInfo("Rat Colony", 56, Rarity.MYTHIC, mage.cards.r.RatColony.class)); cards.add(new SetCardInfo("Relentless Rats", 57, Rarity.MYTHIC, mage.cards.r.RelentlessRats.class)); + cards.add(new SetCardInfo("Robe of Stars", 121, Rarity.MYTHIC, mage.cards.r.RobeOfStars.class, FULL_ART)); + cards.add(new SetCardInfo("Ruinous Ultimatum", 108, Rarity.MYTHIC, mage.cards.r.RuinousUltimatum.class)); cards.add(new SetCardInfo("Sacrifice", 69, Rarity.MYTHIC, mage.cards.s.Sacrifice.class)); + cards.add(new SetCardInfo("Scalding Tarn", 112, Rarity.MYTHIC, mage.cards.s.ScaldingTarn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scalding Tarn", 117, Rarity.MYTHIC, mage.cards.s.ScaldingTarn.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Scapeshift", 34, Rarity.MYTHIC, mage.cards.s.Scapeshift.class)); cards.add(new SetCardInfo("Secluded Courtyard", 63, Rarity.MYTHIC, mage.cards.s.SecludedCourtyard.class)); 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("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)); @@ -127,7 +149,10 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Tragic Slip", 22, Rarity.MYTHIC, mage.cards.t.TragicSlip.class)); cards.add(new SetCardInfo("Underworld Breach", 9, Rarity.RARE, mage.cards.u.UnderworldBreach.class)); cards.add(new SetCardInfo("Unholy Heat", 71, Rarity.MYTHIC, mage.cards.u.UnholyHeat.class)); + cards.add(new SetCardInfo("Verdant Catacombs", 113, Rarity.MYTHIC, mage.cards.v.VerdantCatacombs.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Verdant Catacombs", 118, Rarity.MYTHIC, mage.cards.v.VerdantCatacombs.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Victimize", 23, Rarity.MYTHIC, mage.cards.v.Victimize.class)); + cards.add(new SetCardInfo("Warping Wail", 119, Rarity.MYTHIC, mage.cards.w.WarpingWail.class, FULL_ART)); cards.add(new SetCardInfo("Whir of Invention", 86, Rarity.MYTHIC, mage.cards.w.WhirOfInvention.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Whir of Invention", 96, Rarity.MYTHIC, mage.cards.w.WhirOfInvention.class, NON_FULL_USE_VARIOUS)); } diff --git a/Mage.Sets/src/mage/sets/Warhammer40000Commander.java b/Mage.Sets/src/mage/sets/Warhammer40000Commander.java index c009b193fdd..2bb6b66df29 100644 --- a/Mage.Sets/src/mage/sets/Warhammer40000Commander.java +++ b/Mage.Sets/src/mage/sets/Warhammer40000Commander.java @@ -30,7 +30,6 @@ public final class Warhammer40000Commander extends ExpansionSet { cards.add(new SetCardInfo("Abundance", 210, Rarity.RARE, mage.cards.a.Abundance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Acolyte Hybrid", "70*", Rarity.UNCOMMON, mage.cards.a.AcolyteHybrid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Acolyte Hybrid", 70, Rarity.UNCOMMON, mage.cards.a.AcolyteHybrid.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Adaptive Automaton", 322, Rarity.RARE, mage.cards.a.AdaptiveAutomaton.class)); cards.add(new SetCardInfo("Aetherize", "191*", Rarity.UNCOMMON, mage.cards.a.Aetherize.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aetherize", 191, Rarity.UNCOMMON, mage.cards.a.Aetherize.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("And They Shall Know No Fear", "9*", Rarity.UNCOMMON, mage.cards.a.AndTheyShallKnowNoFear.class, NON_FULL_USE_VARIOUS)); 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 new file mode 100644 index 00000000000..d1aa17af189 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationStabilityAITest.java @@ -0,0 +1,237 @@ +package org.mage.test.AI.basic; + +import mage.abilities.Ability; +import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.Game; +import mage.player.ai.ComputerPlayer; +import mage.player.ai.ComputerPlayer7; +import mage.util.ThreadUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * Tests for AI simulation stability (AI must process simulations with freezes or errors) + * + * @author JayDi85 + */ +public class SimulationStabilityAITest extends CardTestPlayerBaseWithAIHelps { + + @Before + public void prepare() { + // 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); + } + + @Test + public void test_GameFreeze_OnlyGoodAbilities() { + removeAllCardsFromLibrary(playerA); + + // possible combinations: from 1 to 3 abilities - all fine + addFreezeAbility("good 1", false); + addFreezeAbility("good 2", false); + addFreezeAbility("good 3", false); + + // AI must activate all +3 life + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 3); + } + + @Test + public void test_GameFreeze_OnlyFreezeAbilities() { + removeAllCardsFromLibrary(playerA); + + // possible combinations: from 1 to 3 bad abilities - all bad + addFreezeAbility("freeze 1", true); + addFreezeAbility("freeze 2", true); + addFreezeAbility("freeze 3", true); + + // AI can't finish any simulation and do not choose to activate in real game + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + } + + @Test + @Ignore + // TODO: AI actions simulation do not support multithreading, so whole next action search + // will fail on any problem (enable after new simulation implement) + public void test_GameFreeze_GoodAndFreezeAbilities() { + removeAllCardsFromLibrary(playerA); + + // possible combinations: some good chains, some bad chains + addFreezeAbility("good 1", false); + addFreezeAbility("good 2", false); + addFreezeAbility("good 3", false); + addFreezeAbility("freeze 1", true); + + // AI must see and filter bad combinations with freeze ability in the chain + // so only 1 + 2 + 3 will give best score and will be chosen for real game + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 3); + } + + @Test + public void test_GameError_OnlyGoodAbilities() { + removeAllCardsFromLibrary(playerA); + + // possible combinations: from 1 to 3 abilities - all fine + addErrorAbility("good 1", false); + addErrorAbility("good 2", false); + addErrorAbility("good 3", false); + + // AI must activate all +3 life + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 3); + } + + @Test + public void test_GameError_OnlyErrorAbilities() { + removeAllCardsFromLibrary(playerA); + + // it's ok to have error logs in output + + // possible combinations: from 1 to 3 bad abilities - all bad + addErrorAbility("error 1", true); + addErrorAbility("error 2", true); + addErrorAbility("error 3", true); + + // AI can't finish any simulation and do not choose to activate in real game + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + } + + @Test + @Ignore + // TODO: AI actions simulation do not support multithreading, so whole next action search + // will fail on any problem (enable after new simulation implement) + public void test_GameError_GoodAndFreezeAbilities() { + removeAllCardsFromLibrary(playerA); + + // it's ok to have error logs in output + + // possible combinations: some good chains, some bad chains + addErrorAbility("good 1", false); + addErrorAbility("good 2", false); + addErrorAbility("good 3", false); + addErrorAbility("error 1", true); + + // AI must see and filter bad combinations with error ability in the chain + // so only 1 + 2 + 3 will give best score and will be chosen for real game + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 3); + } + + private void addFreezeAbility(String name, boolean isFreeze) { + // change max think timeout to lower value, so test can work faster + ComputerPlayer7 aiPlayer = (ComputerPlayer7) playerA.getRealPlayer(); + aiPlayer.setMaxThinkTimeSecs(1); + + Effect effect; + if (isFreeze) { + effect = new GameFreezeEffect(); + } else { + effect = new GainLifeEffect(1); + } + effect.setText(name); + addCustomCardWithAbility(name, playerA, new LimitedTimesPerTurnActivatedAbility(effect, new ManaCostsImpl<>("{G}"))); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + } + + private void addErrorAbility(String name, boolean isError) { + // change error processing, so test can continue simulations after catch error - like a real game + playerA.setFastFailInTestMode(false); + + Effect effect; + if (isError) { + effect = new GameErrorEffect(); + } else { + effect = new GainLifeEffect(1); + } + effect.setText(name); + addCustomCardWithAbility(name, playerA, new LimitedTimesPerTurnActivatedAbility(effect, new ManaCostsImpl<>("{G}"))); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + } +} + +class GameFreezeEffect extends OneShotEffect { + + GameFreezeEffect() { + super(Outcome.Benefit); + } + + private GameFreezeEffect(final GameFreezeEffect effect) { + super(effect); + } + + public GameFreezeEffect copy() { + return new GameFreezeEffect(this); + } + + public boolean apply(Game game, Ability source) { + // freeze simulation, AI must close sim thread by timeout + System.out.println("apply freeze effect on " + game); // for debug only, show logs from any sim thread + while (true) { + ThreadUtils.sleep(1000); + } + } +} + +class GameErrorEffect extends OneShotEffect { + + GameErrorEffect() { + super(Outcome.Benefit); + } + + private GameErrorEffect(final GameErrorEffect effect) { + super(effect); + } + + public GameErrorEffect copy() { + return new GameErrorEffect(this); + } + + public boolean apply(Game game, Ability source) { + // error simulation, AI must close error thread, do not use rollback + System.out.println("apply error effect on " + game); // for debug only, show logs from any sim thread + throw new IllegalStateException("Test error"); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/StationTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/StationTest.java new file mode 100644 index 00000000000..c62568f44bf --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/StationTest.java @@ -0,0 +1,97 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +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; + +/** + * @author TheElk801 + */ +public class StationTest extends CardTestPlayerBase { + + private static final String sawship = "Galvanizing Sawship"; + + private void checkSpacecraft(int counters) { + boolean isLeveled = counters >= 3; + assertCounterCount(playerA, sawship, CounterType.CHARGE, counters); + assertAbility(playerA, sawship, HasteAbility.getInstance(), isLeveled); + assertAbility(playerA, sawship, FlyingAbility.getInstance(), isLeveled); + assertType(sawship, CardType.ARTIFACT, SubType.SPACECRAFT); + assertType(sawship, CardType.CREATURE, isLeveled); + if (isLeveled) { + assertPowerToughness(playerA, sawship, 6, 5); + } + } + + @Test + public void testNoStation() { + addCard(Zone.BATTLEFIELD, playerA, sawship); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + checkSpacecraft(0); + } + + private static final String devils = "Riot Devils"; + + @Test + public void testStationInsufficient() { + addCard(Zone.BATTLEFIELD, playerA, sawship); + addCard(Zone.BATTLEFIELD, playerA, devils); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Station"); + setChoice(playerA, devils); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(devils, true); + checkSpacecraft(2); + } + + private static final String giant = "Hill Giant"; + + @Test + public void testStationSufficient() { + addCard(Zone.BATTLEFIELD, playerA, sawship); + addCard(Zone.BATTLEFIELD, playerA, giant); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Station"); + setChoice(playerA, giant); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(giant, true); + checkSpacecraft(3); + } + + private static final String warden = "Tapestry Warden"; + + @Test + public void testTapestryWarden() { + addCard(Zone.BATTLEFIELD, playerA, sawship); + addCard(Zone.BATTLEFIELD, playerA, warden); + addCard(Zone.BATTLEFIELD, playerA, devils); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Station"); + setChoice(playerA, devils); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(devils, true); + checkSpacecraft(3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WarpTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WarpTest.java new file mode 100644 index 00000000000..1addf929649 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WarpTest.java @@ -0,0 +1,190 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +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 WarpTest extends CardTestPlayerBase { + + private static final String colossus = "Bygone Colossus"; + + @Test + public void testRegular() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 9); + addCard(Zone.HAND, playerA, colossus); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, colossus); + + waitStackResolved(1, PhaseStep.END_TURN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, colossus, 1); + } + + @Test + public void testWarpExile() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.HAND, playerA, colossus); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, colossus + " with Warp"); + + waitStackResolved(1, PhaseStep.END_TURN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, colossus, 1); + } + + @Test + public void testWarpExileCast() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 9); + addCard(Zone.HAND, playerA, colossus); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, colossus + " with Warp"); + + waitStackResolved(1, PhaseStep.END_TURN, playerA); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, colossus); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, colossus, 1); + } + + @Test + public void testWarpExileOptions() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.HAND, playerA, colossus); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, colossus + " with Warp"); + + waitStackResolved(1, PhaseStep.END_TURN, playerA); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, colossus + " with Warp"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + try { + execute(); + } catch (Throwable e) { + Assert.assertEquals( + "Should fail to be able to cast " + colossus + " with warp", + "Can't find ability to activate command: Cast " + colossus + " with Warp", + e.getMessage() + ); + } + } + + private static final String bolt = "Plasma Bolt"; + + @Test + public void testVoid() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 1); + addCard(Zone.HAND, playerA, colossus); + addCard(Zone.HAND, playerA, bolt); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, colossus + " with Warp"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bolt, playerB); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, colossus, 1); + assertLife(playerB, 20 - 3); + } + + @Test + public void testNoVoid() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 9 + 1); + addCard(Zone.HAND, playerA, colossus); + addCard(Zone.HAND, playerA, bolt); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, colossus); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bolt, playerB); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, colossus, 1); + assertLife(playerB, 20 - 2); + } + + private static final String culler = "Timeline Culler"; + + @Test + public void testTimelineCuller() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.GRAVEYARD, playerA, culler); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, culler + " with Warp"); + + waitStackResolved(1, PhaseStep.END_TURN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, culler, 1); + assertLife(playerA, 20 - 2); + } + + private static final String bore = "Full Bore"; + + @Test + public void testFullBoreWithoutWarp() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 9 + 1); + addCard(Zone.HAND, playerA, colossus); + addCard(Zone.HAND, playerA, bore); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, colossus); + + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, bore, colossus); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPowerToughness(playerA, colossus, 9 + 3, 9 + 2); + assertAbility(playerA, colossus, TrampleAbility.getInstance(), false); + assertAbility(playerA, colossus, HasteAbility.getInstance(), false); + } + + @Test + public void testFullBoreWithWarp() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 1); + addCard(Zone.HAND, playerA, colossus); + addCard(Zone.HAND, playerA, bore); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, colossus + " with Warp"); + + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, bore, colossus); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPowerToughness(playerA, colossus, 9 + 3, 9 + 2); + assertAbility(playerA, colossus, TrampleAbility.getInstance(), true); + assertAbility(playerA, colossus, HasteAbility.getInstance(), true); + } +} 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 686b7ae7597..0475b151603 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 @@ -74,4 +74,70 @@ public class OneShotNonTargetTest extends CardTestPlayerBase { assertPowerToughness(playerA, "Squire", 3, 4); assertPowerToughness(playerA, "Soldier Token", 2, 2); } + + @Test + public void MuseVesselTest() { + String muse = "Muse Vessel"; + addCard(Zone.HAND, playerA, muse); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 30); + addCard(Zone.BATTLEFIELD, playerA, "Vizier of Tumbling Sands", 2); + + addCard(Zone.HAND, playerA, "Squire"); + addCard(Zone.HAND, playerA, "Alpha Myr"); + addCard(Zone.HAND, playerA, "Void Snare"); + addCard(Zone.HAND, playerB, "Island"); + addCard(Zone.HAND, playerB, "Corridor Monitor"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, muse, true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Target player", playerB); + setChoice(playerB, "Island"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Untap", muse); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Target player", playerB); + setChoice(playerB, "Corridor Monitor"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Untap", muse); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Target player", playerA); + setChoice(playerA, "Squire"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}: Choose"); + setChoice(playerA, "Island"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}: Choose"); + setChoice(playerA, "Squire"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Squire", true); + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Void Snare", muse, true); // Bounce the vessel to hand, check exile ID correctly managed + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, muse, true); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{1}: Choose"); // Should activate but no possible choices + + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{3}, {T}: Target player", playerA); + setChoice(playerA, "Alpha Myr"); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{1}: Choose"); + setChoice(playerA, "Alpha Myr"); + checkPlayableAbility("Can cast on current turn", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Alpha Myr", true); + checkPlayableAbility("Can't cast on future turn", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Alpha Myr", false); + + activateAbility(3, PhaseStep.BEGIN_COMBAT, playerA, "{1}: Choose"); + setChoice(playerA, "Alpha Myr"); + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Alpha Myr"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertGraveyardCount(playerA, 1); // Void Snare + assertGraveyardCount(playerB, 0); + assertExileCount(playerA, 0); + assertExileCount(playerB, 1); // Corridor Monitor remains in exile + assertPermanentCount(playerA, "Island", 1); + assertPermanentCount(playerA, "Squire", 1); + assertPermanentCount(playerA, "Alpha Myr", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ImpelledGiantTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ImpelledGiantTest.java index 8a556458f3e..65858a16f46 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ImpelledGiantTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/ImpelledGiantTest.java @@ -15,7 +15,7 @@ public class ImpelledGiantTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Impelled Giant"); // Creature 3/3 addCard(Zone.BATTLEFIELD, playerA, "Hurloon Minotaur"); // Creature 2/3 - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tap an untapped red creature you control other than Impelled Giant"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tap an untapped red creature you control other than"); setChoice(playerA, "Hurloon Minotaur"); setStopAt(1, PhaseStep.BEGIN_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java index 126a34706ff..217d755e029 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java @@ -2,13 +2,15 @@ package org.mage.test.cards.asthough; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.player.ai.ComputerPlayer7; +import org.junit.Ignore; import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; /** * @author JayDi85 */ -public class PlayTopCardFromLibraryTest extends CardTestPlayerBase { +public class PlayTopCardFromLibraryTest extends CardTestPlayerBaseWithAIHelps { /* Bolas's Citadel @@ -218,4 +220,146 @@ public class PlayTopCardFromLibraryTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Balduvian Bears", 0); assertPermanentCount(playerA, "Balduvian Bears", 1); } + + @Test + public void test_EtaliPrimalStorm_NoCards_Manual() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromLibrary(playerB); + + // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, + // then you may cast any number of nonland cards exiled this way without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Etali, Primal Storm", 1); // 6/6 + // + addCard(Zone.LIBRARY, playerA, "Forest", 1); + addCard(Zone.LIBRARY, playerB, "Forest", 1); + + // nothing to free cast + attack(1, playerA, "Etali, Primal Storm"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 6); + } + + @Test + public void test_EtaliPrimalStorm_NoCards_AI() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromLibrary(playerB); + + // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, + // then you may cast any number of nonland cards exiled this way without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Etali, Primal Storm", 1); // 6/6 + // + addCard(Zone.LIBRARY, playerA, "Forest", 1); + addCard(Zone.LIBRARY, playerB, "Forest", 1); + + // ai must attack and nothing to free cast + attack(1, playerA, "Etali, Primal Storm"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 6); + } + + @Test + public void test_EtaliPrimalStorm_OneCard_Manual() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromLibrary(playerB); + + // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, + // then you may cast any number of nonland cards exiled this way without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Etali, Primal Storm", 1); // 6/6 + // + addCard(Zone.LIBRARY, playerA, "Lightning Bolt", 1); + addCard(Zone.LIBRARY, playerB, "Forest", 1); + + attack(1, playerA, "Etali, Primal Storm"); + setChoice(playerA, true); // use free cast + addTarget(playerA, playerB); // to damage + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 6 - 3); + } + + @Test + public void test_EtaliPrimalStorm_OneCard_AI() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromLibrary(playerB); + + // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, + // then you may cast any number of nonland cards exiled this way without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Etali, Primal Storm", 1); // 6/6 + // + addCard(Zone.LIBRARY, playerA, "Lightning Bolt", 1); + addCard(Zone.LIBRARY, playerB, "Forest", 1); + + // ai must attack and free cast bolt to opponent's + aiPlayStep(1, PhaseStep.DECLARE_ATTACKERS, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 6 - 3); + } + + @Test + public void test_EtaliPrimalStorm_MultipleCards_Manual() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromLibrary(playerB); + + // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, + // then you may cast any number of nonland cards exiled this way without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Etali, Primal Storm", 1); // 6/6 + // + addCard(Zone.LIBRARY, playerA, "Lightning Bolt", 1); // 3 damage + addCard(Zone.LIBRARY, playerB, "Cleansing Screech", 1); // 4 damage + + // choose cards one by one + attack(1, playerA, "Etali, Primal Storm"); + // first card + setChoice(playerA, "Cleansing Screech"); + setChoice(playerA, true); // use free + addTarget(playerA, playerB); + // last card (auto-chosen) + setChoice(playerA, true); // use free + addTarget(playerA, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 6 - 4 - 3); + } + + @Test + public void test_EtaliPrimalStorm_MultipleCards_AI() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromLibrary(playerB); + + // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, + // then you may cast any number of nonland cards exiled this way without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Etali, Primal Storm", 1); // 6/6 + // + addCard(Zone.LIBRARY, playerA, "Lightning Bolt", 1); // 3 damage + addCard(Zone.LIBRARY, playerB, "Cleansing Screech", 1); // 4 damage + + // ai must attack and free cast two cards + // possible bug 1: game freeze due wrong dialog/selection logic + // possible bug 2: TargetCard can't find ALL zone + aiPlayStep(1, PhaseStep.DECLARE_ATTACKERS, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 6 - 4 - 3); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControlTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControlTest.java index 5da74079cf0..9114099f95c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControlTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControlTest.java @@ -183,12 +183,13 @@ public class ExileAndReturnUnderYourControlTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Villainous Wealth", playerB); setChoice(playerA, "X=3"); + // first card setChoice(playerA, "Mox Emerald"); setChoice(playerA, "Yes"); - + // second card setChoice(playerA, "Mox Sapphire"); setChoice(playerA, "Yes"); - + // last card // Quicken is auto-chosen since it's the last of the 3 cards. Only need to say Yes to casting for free. setChoice(playerA, "Yes"); 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 new file mode 100644 index 00000000000..e425d65dc53 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java @@ -0,0 +1,70 @@ +package org.mage.test.cards.single.afc; + +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; + +/** + * {@link mage.cards.m.MantleOfTheAncients Mantle of the Ancients} + * {3}{W}{W} + * Enchantment — Aura + * Enchant creature you control + * When this Aura enters, return any number of target Aura and/or Equipment cards from your graveyard to the battlefield attached to enchanted creature. + * Enchanted creature gets +1/+1 for each Aura and Equipment attached to it. + * + * @author notgreat + */ +public class MantleOfTheAncientsTest extends CardTestPlayerBase { + + /** + * Ensure that cards that can't be attached are not returned, and that cards that can be are correctly attached + */ + @Test + public void testCardReturnsCorrectAttachments() { + String creature = "Skylasher";// Protection from Blue, 2/2 Creature and +1/+1 from first Mantle + + addCard(Zone.HAND, playerA, "Mantle of the Ancients", 2); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 10); + addCard(Zone.BATTLEFIELD, playerA, creature); + addCard(Zone.BATTLEFIELD, playerA, "Grim Guardian"); // Counts number of enchantments entering + + addCard(Zone.GRAVEYARD, playerA, "Konda's Banner"); // No attach, Not legendary, but is returned + addCard(Zone.GRAVEYARD, playerA, "O-Naginata"); // Yes attach, Pow >= 3 + + addCard(Zone.GRAVEYARD, playerA, "Aether Tunnel"); // No attach, Pro Blue, then Yes attach on 2nd try + addCard(Zone.GRAVEYARD, playerA, "Reprobation"); // Yes attach, Enchant Creature and removes Pro Blue ability + addCard(Zone.GRAVEYARD, playerA, "Indestructibility"); // Yes attach, Enchant Permanent + addCard(Zone.GRAVEYARD, playerA, "Abundant Growth"); // No attach, Enchant Land + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mantle of the Ancients", creature); + setChoice(playerA, "Constellation"); //Stack trigger, Mantle + Grim + addTarget(playerA, "Konda's Banner^O-Naginata^Aether Tunnel^Reprobation^Indestructibility^Abundant Growth"); + setChoice(playerA, "Constellation"); //Stack trigger, Grim x2 + checkPermanentCount("Gate Smasher not returned", 1, PhaseStep.BEGIN_COMBAT, playerA, "Gate Smasher",0); + checkPermanentCount("Aether Tunnel not returned", 1, PhaseStep.BEGIN_COMBAT, playerA, "Aether Tunnel",0); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Mantle of the Ancients", creature); + setChoice(playerA, "Constellation"); //Stack trigger, Mantle -> Grim + addTarget(playerA, "Gate Smasher^Aether Tunnel"); + addTarget(playerA, TestPlayer.TARGET_SKIP); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAttachedTo(playerA, "Konda's Banner", creature, false); + assertPermanentCount(playerA, "Konda's Banner", 1); + assertAttachedTo(playerA, "O-Naginata", creature, true); + + assertAttachedTo(playerA, "Aether Tunnel", creature, true); + assertAttachedTo(playerA, "Reprobation", creature, true); + assertAttachedTo(playerA, "Indestructibility", creature, true); + assertPermanentCount(playerA, "Abundant Growth", 0); + + assertPermanentCount(playerA, "Mantle of the Ancients", 2); + assertPowerToughness(playerA, creature, 16, 13); // base 0/1, 6 attachments so +12/+12, O-Naginata plus Aether Tunnel +4/+0 + assertLife(playerB, 15); // Mantle, Reprobation, Indestructibility + Mantle, Aether Tunnel + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/AlaniaDivergentStormTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/AlaniaDivergentStormTest.java index 094831ef36e..1b3ff6e412b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/AlaniaDivergentStormTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/AlaniaDivergentStormTest.java @@ -161,9 +161,9 @@ public class AlaniaDivergentStormTest extends CardTestPlayerBase { // Test that the "first sorcery you've cast this turn" clause works // Also copies an adventure sorcery - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); - addCard(Zone.BATTLEFIELD, playerA, "Island", 5); - addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); + addCard(Zone.BATTLEFIELD, playerA, "Island", 10); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 10); addCard(Zone.HAND, playerA, "Faerie Guidemother"); // adventure card addCard(Zone.HAND, playerA, "Maximize Velocity"); addCard(Zone.HAND, playerA, alania); @@ -181,7 +181,7 @@ public class AlaniaDivergentStormTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - // Copied: Gift of the Fae, Not copied: Ancestral Recall + // Copied: Gift of the Fae, Not copied: Maximize Velocity assertPowerToughness(playerA, alania, 3 + 2*2 + 1, 5 + 2 + 1); assertAbility(playerA, alania, FlyingAbility.getInstance(), true); assertAbility(playerA, alania, HasteAbility.getInstance(), true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/SoulSeizerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/SoulSeizerTest.java index 1b904c4f136..7219846c3c3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/SoulSeizerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/SoulSeizerTest.java @@ -19,7 +19,11 @@ public class SoulSeizerTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Craw Wurm"); attack(1, playerA, "Soul Seizer"); + addTarget(playerA, "Craw Wurm"); + setChoice(playerA, true); + setStopAt(1, PhaseStep.END_COMBAT); + setStrictChooseMode(true); execute(); assertLife(playerA, 20); @@ -38,8 +42,13 @@ public class SoulSeizerTest extends CardTestPlayerBase { attack(1, playerA, "Soul Seizer"); + addTarget(playerA, "Craw Wurm"); + setChoice(playerA, true); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Clear", "Ghastly Haunting"); + setStopAt(2, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); execute(); assertLife(playerA, 20); @@ -59,7 +68,11 @@ public class SoulSeizerTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Battlegrowth", "Soul Seizer"); attack(1, playerA, "Soul Seizer"); + addTarget(playerA, "Craw Wurm"); + setChoice(playerA, true); + setStopAt(1, PhaseStep.END_COMBAT); + setStrictChooseMode(true); execute(); assertLife(playerA, 20); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoc/BalothPrimeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoc/BalothPrimeTest.java new file mode 100644 index 00000000000..314065d6012 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoc/BalothPrimeTest.java @@ -0,0 +1,47 @@ +package org.mage.test.cards.single.eoc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class BalothPrimeTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.b.BalothPrime Baloth Prime} {3}{G} + * Creature — Beast + * This creature enters tapped with six stun counters on it. (If a permanent with a stun counter would become untapped, remove one from it instead.) + * Whenever you sacrifice a land, create a tapped 4/4 green Beast creature token and untap this creature. + * {4}, Sacrifice a land: You gain 2 life. + * 10/10 + */ + private static final String baloth = "Baloth Prime"; + + @Test + public void test_Simple() { + addCard(Zone.HAND, playerA, baloth); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 10); + addCard(Zone.BATTLEFIELD, playerA, "Wastes", 2); + + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {C}", 2); // activate both Wastes + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, baloth, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}, Sacrifice a land: You gain 2 life"); + setChoice(playerA, "Wastes"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}, Sacrifice a land: You gain 2 life"); + setChoice(playerA, "Wastes"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Wastes", 2); + assertTappedCount("Forest", true, 10); + assertCounterCount(playerA, baloth, CounterType.STUN, 6 - 2); + assertLife(playerA, 20 + 2 + 2); + assertPermanentCount(playerA, "Beast Token", 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/CryoshatterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/CryoshatterTest.java new file mode 100644 index 00000000000..ccec94ddb03 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/CryoshatterTest.java @@ -0,0 +1,55 @@ +package org.mage.test.cards.single.eoe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class CryoshatterTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.c.Cryoshatter Cryoshatter} {U} + * Enchant creature + * Enchanted creature gets -5/-0. + * When enchanted creature becomes tapped or is dealt damage, destroy it. + */ + private static final String shatter = "Cryoshatter"; + + @Test + public void test_tapped() { + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + addCard(Zone.HAND, playerA, shatter); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, shatter, "Grizzly Bears"); + attack(1, playerA, "Grizzly Bears", playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertLife(playerB, 20); + } + + @Test + public void test_damage() { + addCard(Zone.BATTLEFIELD, playerA, "Centaur Courser"); + addCard(Zone.HAND, playerA, shatter); + addCard(Zone.HAND, playerA, "Shock"); + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, shatter, "Centaur Courser"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shock", "Centaur Courser"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Centaur Courser", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DepressurizeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DepressurizeTest.java new file mode 100644 index 00000000000..40328f25acf --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DepressurizeTest.java @@ -0,0 +1,50 @@ +package org.mage.test.cards.single.eoe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class DepressurizeTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.d.Depressurize Depressurize} {1}{B} + * Instant + * Target creature gets -3/-0 until end of turn. Then if that creature’s power is 0 or less, destroy it. + */ + private static final String depressurize = "Depressurize"; + + @Test + public void test_Destroy() { + addCard(Zone.BATTLEFIELD, playerA, "Centaur Courser"); + addCard(Zone.HAND, playerA, depressurize, 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, depressurize, "Centaur Courser"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Centaur Courser", 1); + } + + @Test + public void test_NoDestroy() { + addCard(Zone.BATTLEFIELD, playerA, "Alpine Grizzly"); + addCard(Zone.HAND, playerA, depressurize, 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, depressurize, "Alpine Grizzly"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, "Alpine Grizzly", 1); + assertPowerToughness(playerA, "Alpine Grizzly", 4 - 3, 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DivertDisasterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DivertDisasterTest.java new file mode 100644 index 00000000000..335189673a0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/DivertDisasterTest.java @@ -0,0 +1,79 @@ +package org.mage.test.cards.single.eoe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class DivertDisasterTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.d.DivertDisaster Divert Disaster} {1}{U} + * Instant + * Counter target spell unless its controller pays {2}. If they do, you create a Lander token. + */ + private static final String disaster = "Divert Disaster"; + + @Test + public void test_Pay() { + addCard(Zone.HAND, playerA, disaster); + addCard(Zone.HAND, playerB, "Elite Vanguard"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Elite Vanguard"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, disaster, "Elite Vanguard", "Elite Vanguard"); + setChoice(playerB, true); // yes to "pays {2}" + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertTappedCount("Plains", true, 3); + assertPermanentCount(playerA, "Lander Token", 1); + assertPermanentCount(playerB, "Elite Vanguard", 1); + } + + @Test + public void test_NoPay() { + addCard(Zone.HAND, playerA, disaster); + addCard(Zone.HAND, playerB, "Elite Vanguard"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Elite Vanguard"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, disaster, "Elite Vanguard", "Elite Vanguard"); + setChoice(playerB, false); // yes to "pays {2}" + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertTappedCount("Plains", true, 1); + assertPermanentCount(playerA, "Lander Token", 0); + assertGraveyardCount(playerB, "Elite Vanguard", 1); + } + + @Test + public void test_CantPay() { + addCard(Zone.HAND, playerA, disaster); + addCard(Zone.HAND, playerB, "Elite Vanguard"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 1); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Elite Vanguard"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, disaster, "Elite Vanguard", "Elite Vanguard"); + setChoice(playerB, false); // yes to "pays {2}" + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertTappedCount("Plains", true, 1); + assertPermanentCount(playerA, "Lander Token", 0); + assertGraveyardCount(playerB, "Elite Vanguard", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/KavLandseekerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/KavLandseekerTest.java new file mode 100644 index 00000000000..779f7e16ab3 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/KavLandseekerTest.java @@ -0,0 +1,56 @@ +package org.mage.test.cards.single.eoe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class KavLandseekerTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.k.KavLandseeker Kav Landseeker} {3}{R} + * Creature — Kavu Soldier + * Menace (This creature can’t be blocked except by two or more creatures.) + * When this creature enters, create a Lander token. At the beginning of the end step on your next turn, sacrifice that token. (It’s an artifact with “{2}, {T}, Sacrifice this token: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.”) + * 4/3 + */ + private static final String kav = "Kav Landseeker"; + + @Test + public void test_Simple() { + addCard(Zone.HAND, playerA, kav); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kav); + + checkPermanentCount("T3: playerA has a Lander Token", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, playerA, "Lander Token", 1); + + setStopAt(4, PhaseStep.PRECOMBAT_MAIN); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, "Lander Token", 0); + } + + @Test + public void test_TimeStop() { + addCard(Zone.HAND, playerA, kav); + addCard(Zone.HAND, playerA, "Time Stop"); // end the turn + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 6); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kav); + + checkPermanentCount("T3: playerA has a Lander Token", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, playerA, "Lander Token", 1); + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Time Stop"); + + setStopAt(6, PhaseStep.PRECOMBAT_MAIN); + setStrictChooseMode(true); + execute(); + + // the delayed trigger has been cleaned up since the "next turn" had no end step. + assertPermanentCount(playerA, "Lander Token", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/LightstallInquisitorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/LightstallInquisitorTest.java new file mode 100644 index 00000000000..0cff620f231 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/LightstallInquisitorTest.java @@ -0,0 +1,61 @@ +package org.mage.test.cards.single.eoe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class LightstallInquisitorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.l.LightstallInquisitor Lightstall Inquisitor} {W} + * Creature — Angel Wizard + * Vigilance + * When this creature enters, each opponent exiles a card from their hand and may play that card for as long as it remains exiled. Each spell cast this way costs {1} more to cast. Each land played this way enters tapped. + * 2/1 + */ + private static final String inquisitor = "Lightstall Inquisitor"; + + @Test + public void test_Land() { + addCard(Zone.HAND, playerA, inquisitor); + addCard(Zone.HAND, playerB, "Savannah", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, inquisitor); + setChoice(playerB, "Savannah"); + + playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Savannah"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerB, "Savannah", 1); + assertTappedCount("Savannah", true, 1); + } + + @Test + public void test_NonLand() { + addCard(Zone.HAND, playerA, inquisitor); + addCard(Zone.HAND, playerB, "Centaur Courser", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, inquisitor); + setChoice(playerB, "Centaur Courser"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Centaur Courser"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerB, "Centaur Courser", 1); + assertTappedCount("Forest", true, 4); + assertTappedCount("Centaur Courser", false, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/SamiWildcatCaptainTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/SamiWildcatCaptainTest.java new file mode 100644 index 00000000000..6649ea3e9eb --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/SamiWildcatCaptainTest.java @@ -0,0 +1,43 @@ +package org.mage.test.cards.single.eoe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SamiWildcatCaptainTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SamiWildcatCaptain Sami, Wildcat Captain} {4}{R}{W} + * Legendary Creature — Human Artificer Rogue + * Double strike, vigilance + * Spells you cast have affinity for artifacts. (They cost {1} less to cast for each artifact you control.) + * 4/4 + */ + private static final String sami = "Sami, Wildcat Captain"; + + @Test + public void test_Simple() { + addCard(Zone.BATTLEFIELD, playerA, sami); + addCard(Zone.BATTLEFIELD, playerA, "Chrome Steed"); // Artifact + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.HAND, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Arcane Encyclopedia"); // {3} Artifact + addCard(Zone.HAND, playerA, "Fearless Halberdier"); // {2}{R} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arcane Encyclopedia", true); + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fearless Halberdier"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertTappedCount("Mountain", true, 3); + assertPermanentCount(playerA, "Arcane Encyclopedia", 1); + assertPermanentCount(playerA, "Fearless Halberdier", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/SeedshipImpactTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/SeedshipImpactTest.java new file mode 100644 index 00000000000..b8899823f46 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/SeedshipImpactTest.java @@ -0,0 +1,53 @@ +package org.mage.test.cards.single.eoe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SeedshipImpactTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SeedshipImpact Seedship Impact} {1}{G} + * Instant + * Destroy target artifact or enchantment. If its mana value was 2 or less, create a Lander token. + * (It's an artifact with "{2}, {T}, Sacrifice this token: Search your library for a basic land card, + * put it onto the battlefield tapped, then shuffle.") + */ + private static final String impact = "Seedship Impact"; + + @Test + public void test_NoLander() { + addCard(Zone.HAND, playerA, impact); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerB, "Crucible of Worlds"); // {3} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, impact, "Crucible of Worlds"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerB, "Crucible of Worlds", 1); + assertPermanentCount(playerA, "Lander Token", 0); + } + + @Test + public void test_Lander() { + addCard(Zone.HAND, playerA, impact); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerB, "Anthem of Champions"); // {W}{G} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, impact, "Anthem of Champions"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerB, "Anthem of Champions", 1); + assertPermanentCount(playerA, "Lander Token", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/TerritorialBruntarTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/TerritorialBruntarTest.java new file mode 100644 index 00000000000..3fb3fd782c0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/eoe/TerritorialBruntarTest.java @@ -0,0 +1,42 @@ +package org.mage.test.cards.single.eoe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class TerritorialBruntarTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.TerritorialBruntar Territorial Bruntar} {4}{R}{R} + * Creature — Beast + * Reach + * Landfall — Whenever a land you control enters, exile cards from the top of your library until you exile a nonland card. You may cast that card this turn. + * 6/6 + */ + private static final String bruntar = "Territorial Bruntar"; + + @Test + public void test_Simple() { + addCard(Zone.BATTLEFIELD, playerA, bruntar); + addCard(Zone.HAND, playerA, "Mountain", 1); + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, "Lightning Bolt"); + addCard(Zone.LIBRARY, playerA, "Forest", 8); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertTappedCount("Mountain", true, 1); + assertExileCount(playerA, "Forest", 8); + assertLife(playerB, 20 - 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ktk/MeanderingTowershellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ktk/MeanderingTowershellTest.java new file mode 100644 index 00000000000..ed1a651293d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ktk/MeanderingTowershellTest.java @@ -0,0 +1,64 @@ +package org.mage.test.cards.single.ktk; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class MeanderingTowershellTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.m.MeanderingTowershell Meandering Towershell} {3}{G}{G} + * Creature — Turtle + * Islandwalk (This creature can’t be blocked as long as defending player controls an Island.) + * Whenever this creature attacks, exile it. Return it to the battlefield under your control tapped and attacking at the beginning of the declare attackers step on your next turn. + * 5/9 + */ + private static final String towershell = "Meandering Towershell"; + + @Test + public void test_Simple() { + addCard(Zone.BATTLEFIELD, playerA, towershell); + + attack(1, playerA, towershell, playerB); + + checkPermanentCount("T3 First Main: no Towershell", 3, PhaseStep.PRECOMBAT_MAIN, playerA, playerA, "Meandering Powershell", 0); + checkLife("T3 First Main: playerB at 20 life", 3, PhaseStep.PRECOMBAT_MAIN, playerB, 20); + + // Meandering Towershell comes back attacking. + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, towershell, 1); + assertLife(playerB, 20 - 5); + } + + @Test + public void test_TimeStop() { + addCard(Zone.BATTLEFIELD, playerA, towershell); + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + addCard(Zone.HAND, playerA, "Time Stop"); + + attack(1, playerA, towershell, playerB); + + checkPermanentCount("T3 First Main: no Towershell", 3, PhaseStep.PRECOMBAT_MAIN, playerA, playerA, "Meandering Powershell", 0); + checkLife("T3 First Main: playerB at 20 life", 3, PhaseStep.PRECOMBAT_MAIN, playerB, 20); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Time Stop"); + + // Meandering Towershell never comes back on future turns. + + setStrictChooseMode(true); + setStopAt(6, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertExileCount(playerA, towershell, 1); + assertPermanentCount(playerA, towershell, 0); + assertLife(playerB, 20); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mrd/MirrorGolemTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mrd/MirrorGolemTest.java new file mode 100644 index 00000000000..6403ba5502a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mrd/MirrorGolemTest.java @@ -0,0 +1,70 @@ +package org.mage.test.cards.single.mrd; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class MirrorGolemTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.m.MirrorGolem Mirror Golem} {6} + * Artifact Creature — Golem + * Imprint — When this creature enters, you may exile target card from a graveyard. + * This creature has protection from each of the exiled card's card types. + * 3/4 + */ + private static final String golem = "Mirror Golem"; + + @Test + public void test_ProtectionSorcery() { + addCard(Zone.HAND, playerA, golem); + addCard(Zone.HAND, playerA, "Pyroclasm"); + addCard(Zone.GRAVEYARD, playerA, "Divination"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, golem); + addTarget(playerA, "Divination"); + setChoice(playerA, true); // yes to "you may exile" + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pyroclasm"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, "Divination", 1); + assertGraveyardCount(playerA, "Pyroclasm", 1); + assertDamageReceived(playerA, golem, 0); + } + + @Test + public void test_TokenCopy() { + addCard(Zone.BATTLEFIELD, playerA, golem); + addCard(Zone.HAND, playerA, "Relm's Sketching"); + addCard(Zone.HAND, playerA, "Storm's Wrath"); + addCard(Zone.GRAVEYARD, playerA, "Divination"); + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Relm's Sketching", golem); + addTarget(playerA, "Divination"); + setChoice(playerA, true); // yes to "you may exile" + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Storm's Wrath"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, "Divination", 1); + assertGraveyardCount(playerA, "Storm's Wrath", 1); + assertPermanentCount(playerA, golem, 1); + assertGraveyardCount(playerA, golem, 1); + assertDamageReceived(playerA, golem, 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/TaigamMasterOpportunistTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/TaigamMasterOpportunistTest.java index 9cda471dcce..dd0358703b5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/TaigamMasterOpportunistTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/TaigamMasterOpportunistTest.java @@ -20,6 +20,7 @@ public class TaigamMasterOpportunistTest extends CardTestPlayerBase { setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, TAIGAM); + addCard(Zone.BATTLEFIELD, playerA, "Yawgmoth's Bargain"); // Can shuffle to the top of the library, prevent drawing it addCard(Zone.HAND, playerA, ORNITHOPTER); addCard(Zone.HAND, playerA, TWINMAW); addCard(Zone.BATTLEFIELD, playerA, "Plateau", 6); @@ -102,4 +103,4 @@ public class TaigamMasterOpportunistTest extends CardTestPlayerBase { } -} \ No newline at end of file +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/UnfinishedBusinessTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/UnfinishedBusinessTest.java index 0a6684a6bd3..31a630235e8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/UnfinishedBusinessTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/UnfinishedBusinessTest.java @@ -92,6 +92,7 @@ public class UnfinishedBusinessTest extends CardTestPlayerBase { // EEB should never have been attached and therefore the White knight should be untapped assertTapped(APOSTLE,false); assertAttachedTo(playerA, EEB, APOSTLE,false); + assertPermanentCount(playerA, EEB, 1); // Check that Ghoulflesh never entered the battlefield assertLife(playerA, 20); diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java index e9965808b0f..eec1b306034 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java @@ -54,8 +54,8 @@ public class LoadTest { private static final String TEST_AI_RANDOM_DECK_SETS = ""; // sets list for random generated decks (GRN,ACR for specific sets, empty for all sets, PELP for lands only - communication test) private static final String TEST_AI_RANDOM_DECK_COLORS_FOR_EMPTY_GAME = "GR"; // colors list for deck generation, empty for all colors private static final String TEST_AI_RANDOM_DECK_COLORS_FOR_AI_GAME = "WUBRG"; - private static final String TEST_AI_CUSTOM_DECK_PATH_1 = ""; // custom deck file instead random for player 1 (empty for random) - private static final String TEST_AI_CUSTOM_DECK_PATH_2 = ""; // custom deck file instead random for player 2 (empty for random) + private static String TEST_AI_CUSTOM_DECK_PATH_1 = ""; // custom deck file instead random for player 1 (empty for random) + private static String TEST_AI_CUSTOM_DECK_PATH_2 = ""; // custom deck file instead random for player 2 (empty for random) @BeforeClass public static void initDatabase() { @@ -150,6 +150,10 @@ public class LoadTest { Thread.sleep(minimumSleepTime); Assert.assertEquals("Can't see users count change 2", startUsersCount + 2, monitor.getAllRoomUsers().size()); Assert.assertNotNull("Can't find user 2", monitor.findUser(player2.userName)); + + player1.disconnect(); + player2.disconnect(); + monitor.disconnect(); } @Test @@ -213,6 +217,10 @@ public class LoadTest { logger.error(e.getMessage(), e); } } + + player1.disconnect(); + player2.disconnect(); + monitor.disconnect(); } public void playTwoAIGame(String gameName, Integer taskNumber, TasksProgress tasksProgress, long randomSeed, String deckColors, String deckAllowedSets, LoadTestGameResult gameResult) { @@ -251,12 +259,21 @@ public class LoadTest { String finishInfo = ""; if (state == TableState.FINISHED) { - finishInfo = gameView == null ? "??" : gameView.getStep().getStepShortText().toLowerCase(Locale.ENGLISH); + finishInfo = gameView == null || gameView.getStep() == null ? "??" : gameView.getStep().getStepShortText().toLowerCase(Locale.ENGLISH); } tasksProgress.update(taskNumber, finishInfo, gameView == null ? 0 : gameView.getTurn()); String globalProgress = tasksProgress.getInfo(); monitor.client.updateGlobalProgress(globalProgress); + // disconnected by unknown reason TODO: research the reason + if (!monitor.session.isConnected()) { + logger.error(monitor.userName + " disconnected from server on game " + gameView); + if (gameView != null) { + gameResult.finish(gameView); + } + break; + } + if (gameView != null && checkGame != null) { logger.info(globalProgress + ", " + checkGame.getTableName() + ": ---"); logger.info(String.format("%s, %s: turn %d, step %s, state %s", @@ -314,7 +331,7 @@ public class LoadTest { } // all done, can disconnect now - monitor.session.connectStop(false, false); + monitor.disconnect(); } @Test @@ -330,6 +347,31 @@ public class LoadTest { printGameResults(gameResults); } + @Test + @Ignore + public void test_TwoAIPlayGame_Debug() { + // usage: + // - run test_TwoAIPlayGame_Multiple + // - wait some freeze games and stop + // - find active table and game in server's games history + // - set deck files here + // - run single game and debug on server side like stack dump + long randomSeed = 1708140198; // 0 for random + TEST_AI_CUSTOM_DECK_PATH_1 = ".\\deck_player_1.dck"; + TEST_AI_CUSTOM_DECK_PATH_2 = ".\\deck_player_2.dck"; + + LoadTestGameResultsList gameResults = new LoadTestGameResultsList(); + if (randomSeed == 0) { + randomSeed = RandomUtil.nextInt(); + } + LoadTestGameResult gameResult = gameResults.createGame(0, "test game", randomSeed); + TasksProgress tasksProgress = new TasksProgress(); + tasksProgress.update(1, "", 0); + playTwoAIGame("Single AI game", 1, tasksProgress, randomSeed, "WGUBR", TEST_AI_RANDOM_DECK_SETS, gameResult); + + printGameResults(gameResults); + } + @Test @Ignore public void test_TwoAIPlayGame_Multiple() { @@ -673,7 +715,8 @@ public class LoadTest { this.client.setSession(this.session); this.roomID = this.session.getMainRoomId(); - Assert.assertTrue("client must be connected to server", this.session.isServerReady()); + Assert.assertTrue("client must be connected to server", this.session.isConnected()); + Assert.assertTrue("client must get server data", this.session.isServerReady()); } public ArrayList getAllRoomUsers() { 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 257266776f3..ca3a223ea3b 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 @@ -775,7 +775,12 @@ public class TestPlayer implements Player { AIRealGameControlUntil = endStep; // disable on end step computerPlayer.priority(game); actions.remove(action); - computerPlayer.resetPassed(); // remove AI's pass, so runtime/check commands can be executed in same priority + // remove AI's pass, so runtime/check commands can be executed in same priority + // aiPlayStep can cause double priority call, but it's better to have workable checkXXX commands + // (AI will do nothing on second priority call anyway) + if (!actions.isEmpty()) { + computerPlayer.resetPassed(); + } return true; } @@ -2097,12 +2102,12 @@ public class TestPlayer implements Player { return "Ability: null"; } - private String getInfo(Target target, Ability source, Game game) { + private String getInfo(Target target, Ability source, Game game, Cards cards) { if (target == null) { return "Target: null"; } UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId()); - Set possibleTargets = target.possibleTargets(abilityControllerId, source, game); + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards); return "Target: selected " + target.getSize() + ", possible " + possibleTargets.size() + ", " + target.getClass().getSimpleName() + ": " + target.getMessage(game); @@ -2475,7 +2480,7 @@ public class TestPlayer implements Player { } } - this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); + this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, source, game, null)); return computerPlayer.choose(outcome, target, source, game, options); } @@ -2801,7 +2806,7 @@ public class TestPlayer implements Player { Assert.fail(message); } - this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); + this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, source, game, null)); return computerPlayer.chooseTarget(outcome, target, source, game); } @@ -2844,7 +2849,7 @@ public class TestPlayer implements Player { LOGGER.warn("Wrong target"); } - this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); + this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, source, game, cards)); return computerPlayer.chooseTarget(outcome, cards, target, source, game); } @@ -3438,7 +3443,7 @@ public class TestPlayer implements Player { @Override public boolean isComputer() { // all players in unit tests are computers, so it allows testing different logic (Human vs AI) - if (isTestsMode()) { + if (isTestMode()) { // AIRealGameSimulation = true - full plyable AI // AIRealGameSimulation = false - choose assisted AI (Human) return AIRealGameSimulation; @@ -3874,8 +3879,8 @@ public class TestPlayer implements Player { } @Override - public boolean isTestsMode() { - return computerPlayer.isTestsMode(); + public boolean isTestMode() { + return computerPlayer.isTestMode(); } @Override @@ -3883,6 +3888,16 @@ public class TestPlayer implements Player { computerPlayer.setTestMode(value); } + @Override + public boolean isFastFailInTestMode() { + return computerPlayer.isFastFailInTestMode(); + } + + @Override + public void setFastFailInTestMode(boolean value) { + computerPlayer.setFastFailInTestMode(value); + } + @Override public boolean isTopCardRevealed() { return computerPlayer.isTopCardRevealed(); @@ -4305,7 +4320,7 @@ public class TestPlayer implements Player { assertWrongChoiceUsage(choices.size() > 0 ? choices.get(0) : "empty list"); } - this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); + this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, source, game, cards)); return computerPlayer.choose(outcome, cards, target, source, game); } @@ -4392,7 +4407,7 @@ public class TestPlayer implements Player { } } - this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); + this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, source, game, null)); return computerPlayer.chooseTargetAmount(outcome, target, source, game); } @@ -4741,7 +4756,7 @@ public class TestPlayer implements Player { Assert.fail(String.format("Found wrong choice command (%s):\n%s\n%s\n%s", reason, lastChoice, - getInfo(target, source, game), + getInfo(target, source, game, null), getInfo(source, game) )); } 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 a0c6cf587e1..436daf44d55 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 @@ -13,6 +13,7 @@ import mage.cards.decks.importer.DeckImporter; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.cards.repository.CardScanner; +import mage.collectors.DataCollectorServices; import mage.constants.*; import mage.counters.CounterType; import mage.filter.Filter; @@ -30,6 +31,7 @@ import mage.players.ManaPool; import mage.players.Player; import mage.server.game.GameSessionPlayer; import mage.util.CardUtil; +import mage.util.DebugUtil; import mage.util.ThreadUtils; import mage.utils.SystemUtil; import mage.view.GameView; @@ -244,6 +246,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement ThreadUtils.ensureRunInGameThread(); + DataCollectorServices.init( + true, + DebugUtil.TESTS_DATA_COLLECTORS_ENABLE_SAVE_GAME_HISTORY + ); + // check stop command int maxTurn = 1; int maxPhase = 0; diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java index baad13f6a4a..bc6b62530da 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/CommanderDeckValidationTest.java @@ -106,4 +106,80 @@ public class CommanderDeckValidationTest extends MageTestPlayerBase { deckTester.validate( "Commanders with the 'Choose a Background' ability should be able to have a background as an additional commander"); } + + @Test() + public void testDoctorsCompanion() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Plains", 98); + + deckTester.addSideboard("The First Doctor", 1); + deckTester.addSideboard("Barbara Wright", 1); + + deckTester.validate("You can have two commanders if one is a Time Lord Doctor and the other has 'Doctor's companion'"); + } + + @Test(expected = AssertionError.class) + public void testDoctorsCompanion2() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Plains", 98); + + deckTester.addSideboard("The First Doctor", 1); + deckTester.addSideboard("Isamaru, Hound of Konda", 1); + + deckTester.validate("You can't have two commanders if one is a Time Lord Doctor and the other doesn't have 'Doctor's companion'"); + } + + @Test(expected = AssertionError.class) + public void testDoctorsCompanion3() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Plains", 98); + + deckTester.addSideboard("Mistform Ultimus", 1); + deckTester.addSideboard("Barbara Wright", 1); + + deckTester.validate( + "You can't have two commanders if one has 'Doctor's companion' " + + "but the other has additional creature types in addition to being a Time Lord Doctor" + ); + } + + @Test() + public void testVehicles1() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Plains", 99); + + deckTester.addSideboard("Parhelion II", 1); + + deckTester.validate("Legendary Vehicles should be able to be a commander"); + } + + @Test(expected = AssertionError.class) + public void testVehicles2() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Plains", 99); + + deckTester.addSideboard("Dragonfly Suit", 1); + + deckTester.validate("Nonlegendary Vehicles should not be able to be a commander"); + } + + @Test() + public void testSpacecraft1() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Plains", 99); + + deckTester.addSideboard("The Seriema", 1); + + deckTester.validate("Legendary Spacecraft should be able to be a commander if they can become a creature"); + } + + @Test(expected = AssertionError.class) + public void testSpacecraft2() { + DeckTester deckTester = new DeckTester(new Commander()); + deckTester.addMaindeck("Wastes", 99); + + deckTester.addSideboard("The Eternity Elevator", 1); + + deckTester.validate("Legendary Spacecraft should not be able to be a commander if they can't become a creature"); + } } 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 fcbeae82ef9..2adafa6e7d0 100644 --- a/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java +++ b/Mage.Verify/src/main/java/mage/verify/mtgjson/MtgJsonCard.java @@ -42,6 +42,7 @@ public final class MtgJsonCard { public boolean isFullArt; public String frameVersion; public List printings; // set codes with that card + public boolean isFunny; @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 c58738dac2f..e6f773d2bc9 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -40,7 +40,9 @@ import mage.filter.predicate.Predicates; import mage.game.FakeGame; import mage.game.Game; import mage.game.command.Dungeon; +import mage.game.command.Emblem; import mage.game.command.Plane; +import mage.game.command.emblems.EmblemOfCard; import mage.game.draft.DraftCube; import mage.game.permanent.token.Token; import mage.game.permanent.token.TokenImpl; @@ -922,6 +924,27 @@ public class VerifyCardDataTest { } } + private static final Set ignoreBoosterSets = new HashSet<>(); + + static { + // temporary, TODO: remove after set release and mtgjson get info + ignoreBoosterSets.add("Edge of Eternities"); + // jumpstart, TODO: must implement from JumpstartPoolGenerator, see #13264 + ignoreBoosterSets.add("Jumpstart"); + ignoreBoosterSets.add("Jumpstart 2022"); + ignoreBoosterSets.add("Foundations Jumpstart"); + ignoreBoosterSets.add("Ravnica: Clue Edition"); + // joke or un-sets, low implemented cards + ignoreBoosterSets.add("Unglued"); + ignoreBoosterSets.add("Unhinged"); + ignoreBoosterSets.add("Unstable"); + ignoreBoosterSets.add("Unfinity"); + // other + ignoreBoosterSets.add("Secret Lair Drop"); // cards shop + ignoreBoosterSets.add("Zendikar Rising Expeditions"); // box toppers + ignoreBoosterSets.add("March of the Machine: The Aftermath"); // epilogue boosters aren't for draft + } + @Test public void test_checkMissingSetData() { Collection errorsList = new ArrayList<>(); @@ -1072,24 +1095,6 @@ public class VerifyCardDataTest { } // CHECK: miss booster settings - Set ignoreBoosterSets = new HashSet<>(); - // temporary, TODO: remove after set release and mtgjson get info - ignoreBoosterSets.add("Final Fantasy"); - // jumpstart, TODO: must implement from JumpstartPoolGenerator, see #13264 - ignoreBoosterSets.add("Jumpstart"); - ignoreBoosterSets.add("Jumpstart 2022"); - ignoreBoosterSets.add("Foundations Jumpstart"); - ignoreBoosterSets.add("Ravnica: Clue Edition"); - // joke or un-sets, low implemented cards - ignoreBoosterSets.add("Unglued"); - ignoreBoosterSets.add("Unhinged"); - ignoreBoosterSets.add("Unstable"); - ignoreBoosterSets.add("Unfinity"); - // other - ignoreBoosterSets.add("Secret Lair Drop"); // cards shop - ignoreBoosterSets.add("Zendikar Rising Expeditions"); // box toppers - ignoreBoosterSets.add("March of the Machine: The Aftermath"); // epilogue boosters aren't for draft - // make sure mtgjson has booster data boolean hasBoostersInfo = MtgJsonService.sets().values().stream().anyMatch(s -> s.booster != null && !s.booster.isEmpty()); for (ExpansionSet set : sets) { @@ -1697,6 +1702,64 @@ public class VerifyCardDataTest { } } + @Test + // TODO: add same images verify for tokens/dungeons and other + // TODO: add same verify for Speed and other new command objects + public void test_checkMissingEmblemsData() { + Collection errorsList = new ArrayList<>(); + + // prepare DBs + CardScanner.scan(); + + Reflections reflections = new Reflections("mage."); + Set> emblemClassesList = reflections.getSubTypesOf(Emblem.class).stream() + .filter(c -> !c.equals(EmblemOfCard.class)) // ignore emblem card + .collect(Collectors.toSet()); + + // 1. correct class name + for (Class emblemClass : emblemClassesList) { + if (!emblemClass.getName().endsWith("Emblem")) { + String className = extractShortClass(emblemClass); + errorsList.add("Error: emblem class must ends with Emblem: " + className + " from " + emblemClass.getName()); + } + } + + // 2. correct package + for (Class emblemClass : emblemClassesList) { + String fullClass = emblemClass.getName(); + if (!fullClass.startsWith("mage.game.command.emblems.")) { + String className = extractShortClass(emblemClass); + errorsList.add("Error: emblem must be stored in mage.game.command.emblems package: " + className + " from " + emblemClass.getName()); + } + } + + // 3. correct constructor + MageObject fakeObject = CardRepository.instance.findCard("Forest").createCard(); + for (Class emblemClass : emblemClassesList) { + String className = extractShortClass(emblemClass); + Emblem emblem; + try { + emblem = (Emblem) createNewObject(emblemClass); + + // 4. correct tokens-database (image) + try { + emblem.setSourceObjectAndInitImage(fakeObject); + } catch (Throwable e) { + e.printStackTrace(); + errorsList.add("Error: can't setup emblem's image data - make sure tokens-database.txt has it: " + className + " from " + emblemClass.getName()); + } + } catch (Throwable e) { + e.printStackTrace(); + errorsList.add("Error: can't create emblem with default constructor: " + className + " from " + emblemClass.getName()); + } + } + + printMessages(errorsList); + if (errorsList.size() > 0) { + Assert.fail("Found emblem errors: " + errorsList.size()); + } + } + @Test @Ignore // experimental test to find potentially fail conditions with NPE see https://github.com/magefree/mage/issues/13752 @@ -2055,7 +2118,13 @@ public class VerifyCardDataTest { expected.removeIf(subtypesToIgnore::contains); for (SubType subType : card.getSubtype()) { - if (!subType.isCustomSet() && !subType.canGain(card)) { + if (subType.isCustomSet()) { + if (!ref.isFunny) { + fail(card, "subtypes", "subtype " + subType + " is marked as \"custom\" but is in an official set"); + } + continue; + } + if (!subType.canGain(card)) { String cardTypeString = card .getCardType() .stream() @@ -2268,7 +2337,11 @@ public class VerifyCardDataTest { String additionalName; if (card instanceof CardWithSpellOption) { // adventure/omen cards - additionalName = ((CardWithSpellOption) card).getSpellCard().getName(); + if (card.getName().equals("Bloomvine Regent") || card.getName().equals("Marang River Regent") || card.getName().equals("Scavenger Regent")) { + additionalName = null; + } else { + additionalName = ((CardWithSpellOption) card).getSpellCard().getName(); + } } else if (card.isTransformable() && !card.isNightCard()) { additionalName = card.getSecondCardFace().getName(); } else { @@ -2418,6 +2491,7 @@ public class VerifyCardDataTest { private static final List selfRefNamedSubtypes = Arrays.asList( SubType.EQUIPMENT, SubType.VEHICLE, + SubType.SPACECRAFT, SubType.AURA, SubType.CLASS, SubType.SAGA, diff --git a/Mage/pom.xml b/Mage/pom.xml index f148552c45f..8759c48b385 100644 --- a/Mage/pom.xml +++ b/Mage/pom.xml @@ -22,10 +22,14 @@ com.google.code.gson gson
+ + org.jsoup + jsoup + com.google.protobuf protobuf-java - 3.19.3 + 3.25.8 @@ -42,34 +46,34 @@ - com.github.os72 protoc-jar-maven-plugin 3.11.4 - generate-sources - - run - - - - com.google.protobuf:protoc:3.18.0 - - ${project.basedir}/src/main/proto - - - - java - none - ${project.build.directory}/generated-sources - - - + generate-sources + + run + + + + com.google.protobuf:protoc:3.25.8 + + ${project.basedir}/src/main/proto + + + + java + none + ${project.build.directory}/generated-sources + + + + org.codehaus.mojo @@ -89,12 +93,10 @@ - + mage - - diff --git a/Mage/src/main/java/mage/MageIdentifier.java b/Mage/src/main/java/mage/MageIdentifier.java index a4fc6d9cc7a..3cca48c6e40 100644 --- a/Mage/src/main/java/mage/MageIdentifier.java +++ b/Mage/src/main/java/mage/MageIdentifier.java @@ -84,7 +84,8 @@ public enum MageIdentifier { QuilledGreatwurmAlternateCast, WickerfolkIndomitableAlternateCast, UriangerAugureltAlternateCast, - ValgavothTerrorEaterAlternateCast; + ValgavothTerrorEaterAlternateCast, + LightstallInquisitorAlternateCast; /** * Additional text if there is need to differentiate two very similar effects diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 6366a6a4209..50340177526 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -377,7 +377,7 @@ public abstract class AbilityImpl implements Ability { // unit tests only: it allows to add targets/choices by two ways: // 1. From cast/activate command params (process it here) // 2. From single addTarget/setChoice, it's a preferred method for tests (process it in normal choose dialogs like human player) - if (controller.isTestsMode()) { + if (controller.isTestMode()) { if (!controller.addTargets(this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java index 3d9a78b5acb..bb683bce3ea 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java @@ -27,6 +27,7 @@ public class TriggeredAbilities extends LinkedHashMap // reason: game engine can generate additional events and triggers while checking another one, // it can generate multiple bugs, freeze, etc, see https://github.com/magefree/mage/issues/8426 // all checks can be catches by existing tests + private boolean enableIntegrityChecks = false; // TODO: disabled by default, enable for load tests or debug only private boolean enableIntegrityCheck1_MustKeepSameTriggersOrder = true; // good private boolean enableIntegrityCheck2_MustKeepSameTriggersList = false; // bad, impossible to fix due dynamic triggers gen private boolean enableIntegrityCheck3_CantStartEventProcessingBeforeFinishPrev = false; // bad, impossible to fix due dynamic triggers gen @@ -52,6 +53,7 @@ public class TriggeredAbilities extends LinkedHashMap sources.put(entry.getKey(), entry.getValue()); } + this.enableIntegrityChecks = abilities.enableIntegrityChecks; this.enableIntegrityCheck1_MustKeepSameTriggersOrder = abilities.enableIntegrityCheck1_MustKeepSameTriggersOrder; this.enableIntegrityCheck2_MustKeepSameTriggersList = abilities.enableIntegrityCheck2_MustKeepSameTriggersList; this.enableIntegrityCheck3_CantStartEventProcessingBeforeFinishPrev = abilities.enableIntegrityCheck3_CantStartEventProcessingBeforeFinishPrev; @@ -66,7 +68,7 @@ public class TriggeredAbilities extends LinkedHashMap this.processingDone = CardUtil.deepCopyObject(abilities.processingDone); // runtime check: triggers order (not required by paper rules, by required by xmage to make same result for all game instances) - if (this.enableIntegrityCheck1_MustKeepSameTriggersOrder) { + if (this.enableIntegrityChecks && this.enableIntegrityCheck1_MustKeepSameTriggersOrder) { if (!Objects.equals(this.values().stream().findFirst().orElse(null) + "", abilities.values().stream().findFirst().orElse(null) + "")) { // how-to fix: use LinkedHashMap instead HashMap/ConcurrentHashMap @@ -119,7 +121,12 @@ public class TriggeredAbilities extends LinkedHashMap } private void makeSureNotProcessing(GameEvent newEvent) { - if (this.enableIntegrityCheck2_MustKeepSameTriggersList + if (!this.enableIntegrityChecks) { + return; + } + + if (this.enableIntegrityChecks + && this.enableIntegrityCheck2_MustKeepSameTriggersList && this.processingStarted) { List info = new ArrayList<>(); info.add("old event: " + this.processingStartedEvent); @@ -131,6 +138,10 @@ public class TriggeredAbilities extends LinkedHashMap } private void processingStart(GameEvent newEvent) { + if (!this.enableIntegrityChecks) { + return; + } + makeSureNotProcessing(newEvent); this.processingStarted = true; @@ -141,12 +152,21 @@ public class TriggeredAbilities extends LinkedHashMap } private void processingDone(TriggeredAbility trigger) { + if (!this.enableIntegrityChecks) { + return; + } + this.processingDone.add(trigger); } private void processingEnd(boolean needErrorChecks) { + if (!this.enableIntegrityChecks) { + return; + } + if (needErrorChecks) { - if (this.enableIntegrityCheck3_CantStartEventProcessingBeforeFinishPrev + if (this.enableIntegrityChecks + && this.enableIntegrityCheck3_CantStartEventProcessingBeforeFinishPrev && !this.processingStarted) { throw new IllegalArgumentException("Triggers integrity failed: can't finish event before start"); } @@ -170,19 +190,22 @@ public class TriggeredAbilities extends LinkedHashMap + "\n" + "Done: " + "\n" + (doneInfo.isEmpty() ? "-" : doneInfo); - if (this.enableIntegrityCheck4_EventMustProcessAllOldTriggers + if (this.enableIntegrityChecks + && this.enableIntegrityCheck4_EventMustProcessAllOldTriggers && this.processingDone.size() < this.processingNeed.size()) { throw new IllegalArgumentException("Triggers integrity failed: event processing miss some triggers" + errorInfo); } - if (this.enableIntegrityCheck5_EventMustProcessInSameOrder + if (this.enableIntegrityChecks + && this.enableIntegrityCheck5_EventMustProcessInSameOrder && this.processingDone.size() > 0 && this.processingDone.size() == this.processingNeed.size() && !needIds.toString().equals(doneIds.toString())) { throw new IllegalArgumentException("Triggers integrity failed: event processing used wrong order" + errorInfo); } - if (this.enableIntegrityCheck6_EventMustNotProcessNewTriggers + if (this.enableIntegrityChecks + && this.enableIntegrityCheck6_EventMustNotProcessNewTriggers && this.processingDone.size() > this.processingNeed.size()) { throw new IllegalArgumentException("Triggers integrity failed: event processing must not process new triggers" + errorInfo); } diff --git a/Mage/src/main/java/mage/abilities/common/ActivateAsSorceryActivatedAbility.java b/Mage/src/main/java/mage/abilities/common/ActivateAsSorceryActivatedAbility.java index 4eb998547f3..e53490ee343 100644 --- a/Mage/src/main/java/mage/abilities/common/ActivateAsSorceryActivatedAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ActivateAsSorceryActivatedAbility.java @@ -9,8 +9,6 @@ import mage.constants.Zone; public class ActivateAsSorceryActivatedAbility extends ActivatedAbilityImpl { - private boolean showActivateText = true; - public ActivateAsSorceryActivatedAbility(Effect effect, Cost cost) { this(Zone.BATTLEFIELD, effect, cost); } @@ -22,7 +20,6 @@ public class ActivateAsSorceryActivatedAbility extends ActivatedAbilityImpl { protected ActivateAsSorceryActivatedAbility(final ActivateAsSorceryActivatedAbility ability) { super(ability); - this.showActivateText = ability.showActivateText; } @Override @@ -30,18 +27,9 @@ public class ActivateAsSorceryActivatedAbility extends ActivatedAbilityImpl { return new ActivateAsSorceryActivatedAbility(this); } - public ActivateAsSorceryActivatedAbility withShowActivateText(boolean showActivateText) { - this.showActivateText = showActivateText; - return this; - } - @Override public String getRule() { String superRule = super.getRule(); - if (!showActivateText) { - return superRule; - } - String newText = (mayActivate == TargetController.OPPONENT ? " Only your opponents may activate this ability and only as a sorcery." : " Activate only as a sorcery."); diff --git a/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java b/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java index fac6a4a26b6..fc11c56c0e7 100644 --- a/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java @@ -4,35 +4,30 @@ import mage.abilities.Ability; import mage.abilities.effects.common.InfoEffect; import mage.constants.Zone; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.Target; +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())); - addTarget(target); + this.attachable = target.copy(); } private AttachableToRestrictedAbility(AttachableToRestrictedAbility ability) { super(ability); + this.attachable = ability.attachable; // Since we never modify the target, we don't need to re-copy it } - public boolean canEquip(Permanent toEquip, Ability source, Game game) { - for (Target target : getTargets()) { - if (source == null) { - if (!target.canTarget(toEquip.getId(), game)) { - return false; - } - } else if (!target.canTarget(toEquip.getId(), source, game)) { - return false; - } - } - return true; + public boolean canEquip(UUID toEquip, Ability source, Game game) { + if (source == null) { + return attachable.canTarget(toEquip, game); + } else return attachable.canTarget(toEquip, source, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTappedAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTappedAttachedTriggeredAbility.java index f84505313f8..6adca2ad8d2 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTappedAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTappedAttachedTriggeredAbility.java @@ -1,29 +1,39 @@ package mage.abilities.common; +import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; /** * @author LoneFox */ public class BecomesTappedAttachedTriggeredAbility extends TriggeredAbilityImpl { + private final SetTargetPointer setTargetPointer; + public BecomesTappedAttachedTriggeredAbility(Effect effect, String description) { this(effect, description, false); } public BecomesTappedAttachedTriggeredAbility(Effect effect, String description, boolean isOptional) { + this(effect, description, isOptional, SetTargetPointer.NONE); + } + + public BecomesTappedAttachedTriggeredAbility(Effect effect, String description, boolean isOptional, SetTargetPointer setTargetPointer) { super(Zone.BATTLEFIELD, effect, isOptional); setTriggerPhrase(getWhen() + description + " becomes tapped, "); + this.setTargetPointer = setTargetPointer; } protected BecomesTappedAttachedTriggeredAbility(final BecomesTappedAttachedTriggeredAbility ability) { super(ability); + this.setTargetPointer = ability.setTargetPointer; } @Override @@ -43,6 +53,17 @@ public class BecomesTappedAttachedTriggeredAbility extends TriggeredAbilityImpl return false; } Permanent enchanted = game.getPermanent(enchantment.getAttachedTo()); - return enchanted != null && event.getTargetId().equals(enchanted.getId()); + if (enchanted == null || !event.getTargetId().equals(enchanted.getId())) { + return false; + } + switch (setTargetPointer) { + case PERMANENT: + getEffects().setTargetPointer(new FixedTarget(enchanted, game)); + case NONE: + break; + default: + throw new IllegalArgumentException("Unsupported SetTargetPointer in BecomesTappedAttachedTriggeredAbility"); + } + return true; } } diff --git a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceDuringEachOfYourTurnAbility.java b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceDuringEachOfYourTurnAbility.java index 2da7dc64489..9b58511d6e2 100644 --- a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceDuringEachOfYourTurnAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceDuringEachOfYourTurnAbility.java @@ -8,7 +8,7 @@ import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; import mage.abilities.costs.CostsImpl; import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.common.replacement.MorEnteringTappedEffect; +import mage.abilities.effects.common.replacement.SpellMorEnteringTappedEffect; import mage.cards.Card; import mage.constants.*; import mage.filter.FilterCard; @@ -170,7 +170,7 @@ class CastFromGraveyardOnceWatcher extends Watcher { if (target != null) { MageObjectReference mor = new MageObjectReference(target, game); game.getState().addEffect( - new MorEnteringTappedEffect(mor), + new SpellMorEnteringTappedEffect(mor), event.getApprovingObject().getApprovingAbility() // ability that approved the cast is the source of the tapping. ); } diff --git a/Mage/src/main/java/mage/abilities/common/PlayLandOrCastSpellFromExileTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PlayLandOrCastSpellFromExileTriggeredAbility.java deleted file mode 100644 index ff24dcc6cd6..00000000000 --- a/Mage/src/main/java/mage/abilities/common/PlayLandOrCastSpellFromExileTriggeredAbility.java +++ /dev/null @@ -1,38 +0,0 @@ -package mage.abilities.common; - -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; - -/** - * @author TheElk801 - */ -public class PlayLandOrCastSpellFromExileTriggeredAbility extends TriggeredAbilityImpl { - - public PlayLandOrCastSpellFromExileTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect); - setTriggerPhrase("Whenever you play a land from exile or cast a spell from exile, "); - } - - private PlayLandOrCastSpellFromExileTriggeredAbility(final PlayLandOrCastSpellFromExileTriggeredAbility ability) { - super(ability); - } - - @Override - public PlayLandOrCastSpellFromExileTriggeredAbility copy() { - return new PlayLandOrCastSpellFromExileTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.LAND_PLAYED - || event.getType() == GameEvent.EventType.SPELL_CAST; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return isControlledBy(event.getPlayerId()) && event.getZone() == Zone.EXILED; - } -} diff --git a/Mage/src/main/java/mage/abilities/common/PlayLandOrCastSpellTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PlayLandOrCastSpellTriggeredAbility.java new file mode 100644 index 00000000000..3585f5d9265 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/PlayLandOrCastSpellTriggeredAbility.java @@ -0,0 +1,46 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * @author TheElk801 + */ +public class PlayLandOrCastSpellTriggeredAbility extends TriggeredAbilityImpl { + + private final boolean fromExile; + + public PlayLandOrCastSpellTriggeredAbility(Effect effect) { + this(effect, false, false); + } + + public PlayLandOrCastSpellTriggeredAbility(Effect effect, boolean fromExile, boolean optional) { + super(Zone.BATTLEFIELD, effect, optional); + this.fromExile = fromExile; + setTriggerPhrase("Whenever you play a land" + (fromExile ? " from exile" : "") + " or cast a spell" + (fromExile ? " from exile" : "") + ", "); + } + + private PlayLandOrCastSpellTriggeredAbility(final PlayLandOrCastSpellTriggeredAbility ability) { + super(ability); + this.fromExile = ability.fromExile; + } + + @Override + public PlayLandOrCastSpellTriggeredAbility copy() { + return new PlayLandOrCastSpellTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.LAND_PLAYED + || event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return isControlledBy(event.getPlayerId()) && (!fromExile || event.getZone() == Zone.EXILED); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java index a0d5ac75c7a..aa6e3349a91 100644 --- a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java @@ -32,7 +32,7 @@ public class PutIntoGraveFromBattlefieldAllTriggeredAbility extends TriggeredAbi this.onlyToControllerGraveyard = onlyToControllerGraveyard; this.setTargetPointer = setTargetPointer; setTriggerPhrase("Whenever " + CardUtil.addArticle(filter.getMessage()) + " is put into " + - (onlyToControllerGraveyard ? "your" : "a") + " graveyard, "); + (onlyToControllerGraveyard ? "your" : "a") + " graveyard from the battlefield, "); } protected PutIntoGraveFromBattlefieldAllTriggeredAbility(final PutIntoGraveFromBattlefieldAllTriggeredAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility.java new file mode 100644 index 00000000000..f6ceb05b7c0 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility.java @@ -0,0 +1,83 @@ +package mage.abilities.common.delayed; + +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.constants.Duration; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * @author Susucr + */ +public class AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAbility { + + private GameEvent.EventType stepEvent; + private int nextTurn = -1; // once the controller starts a new turn, register it to trigger that turn. + private boolean isActive = true; + + public AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility(Effect effect, GameEvent.EventType stepEvent) { + this(effect, stepEvent, false); + } + + public AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility(Effect effect, GameEvent.EventType stepEvent, boolean optional) { + super(effect, Duration.Custom, true, optional); + this.stepEvent = stepEvent; + this.setTriggerPhrase(generateTriggerPhrase()); + } + + private AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility(final AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility ability) { + super(ability); + this.nextTurn = ability.nextTurn; + this.isActive = ability.isActive; + this.stepEvent = ability.stepEvent; + } + + @Override + public AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility copy() { + return new AtTheBeginOfStepOfYourNextTurnDelayedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == stepEvent + || event.getType() == GameEvent.EventType.BEGIN_TURN; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!isControlledBy(event.getPlayerId())) { + // not your turn. + return false; + } + + int turn = game.getTurnNum(); + switch (event.getType()) { + case BEGIN_TURN: + // We register the turn number at the start of your next turn. + // This is in order to not trigger if that turn ends without an end step. + if (this.nextTurn == -1) { + this.nextTurn = turn; + } else if (turn > this.nextTurn) { + this.isActive = false; // to have the delayed trigger being cleaned up + } + return false; + default: + return turn == this.nextTurn && event.getType() == stepEvent; + } + } + + @Override + public boolean isInactive(Game game) { + return super.isInactive(game) || !isActive; + } + + private String generateTriggerPhrase() { + switch (stepEvent) { + case END_TURN_STEP_PRE: + return "At the beginning of the end step on your next turn, "; + case DECLARE_ATTACKERS_STEP_PRE: + return "At the beginning of the declare attackers step on your next turn, "; + } + throw new IllegalArgumentException("stepEvent only supports steps events"); + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/TwoTappedCreaturesCondition.java b/Mage/src/main/java/mage/abilities/condition/common/TwoTappedCreaturesCondition.java new file mode 100644 index 00000000000..76c6288d5b2 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/TwoTappedCreaturesCondition.java @@ -0,0 +1,41 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; + +/** + * @author TheElk801 + */ +public enum TwoTappedCreaturesCondition implements Condition { + instance; + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(TappedPredicate.TAPPED); + } + + private static final Hint hint = new ValueHint( + "Tapped creatures you control", new PermanentsOnBattlefieldCount(filter) + ); + + public static Hint getHint() { + return hint; + } + + @Override + public boolean apply(Game game, Ability source) { + return game.getBattlefield().contains(filter, source.getControllerId(), source, game, 2); + } + + @Override + public String toString() { + return "you control two or more tapped creatures"; + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/VoidCondition.java b/Mage/src/main/java/mage/abilities/condition/common/VoidCondition.java new file mode 100644 index 00000000000..c701de76610 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/VoidCondition.java @@ -0,0 +1,32 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.game.Game; +import mage.watchers.common.VoidWatcher; + +/** + * Requires {@link mage.watchers.common.VoidWatcher} + * + * @author TheElk801 + */ +public enum VoidCondition 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 VoidWatcher.checkPlayer(source.getControllerId(), game); + } + + @Override + public String toString() { + return "a nonland permanent left the battlefield this turn or a spell was warped this turn"; + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/AbilityCosts.java b/Mage/src/main/java/mage/abilities/costs/AbilityCosts.java deleted file mode 100644 index a63ebc1b81a..00000000000 --- a/Mage/src/main/java/mage/abilities/costs/AbilityCosts.java +++ /dev/null @@ -1,15 +0,0 @@ - - -package mage.abilities.costs; - -import java.io.Serializable; - -/** - * - * @author BetaSteward_at_googlemail.com - */ -public class AbilityCosts implements Serializable { - - - -} diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileAttachmentCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileAttachmentCost.java new file mode 100644 index 00000000000..995052193b7 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileAttachmentCost.java @@ -0,0 +1,65 @@ +package mage.abilities.costs.common; + +import mage.abilities.Ability; +import mage.abilities.costs.Cost; +import mage.abilities.costs.SacrificeCost; +import mage.abilities.costs.UseAttachedCost; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class ExileAttachmentCost extends UseAttachedCost implements SacrificeCost { + + public ExileAttachmentCost() { + super(); + } + + protected ExileAttachmentCost(final ExileAttachmentCost cost) { + super(cost); + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + if (mageObjectReference == null) { + return false; + } + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent == null) { + return paid; + } + for (UUID attachmentId : permanent.getAttachments()) { + if (!this.mageObjectReference.refersTo(attachmentId, game)) { + continue; + } + Permanent attachment = game.getPermanent(attachmentId); + + paid = attachment != null + && Optional + .ofNullable(controllerId) + .map(game::getPlayer) + .filter(player -> player.moveCards(attachment, Zone.EXILED, source, game)) + .isPresent(); + if (paid) { + break; + } + } + + return paid; + } + + @Override + public ExileAttachmentCost copy() { + return new ExileAttachmentCost(this); + } + + @Override + public String getText() { + return "exile " + this.name; + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/common/RemoveVariableCountersTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/RemoveVariableCountersTargetCost.java index 727b70a674c..9bd130c7a99 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RemoveVariableCountersTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RemoveVariableCountersTargetCost.java @@ -8,19 +8,21 @@ import mage.counters.Counter; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.TargetPermanent; +import mage.util.CardUtil; import java.util.UUID; +import java.util.stream.IntStream; /** * @author LevelX */ public class RemoveVariableCountersTargetCost extends VariableCostImpl { - protected FilterPermanent filter; - protected CounterType counterTypeToRemove; - protected int minValue; + protected final FilterPermanent filter; + protected final CounterType counterTypeToRemove; + protected final int minValue; + protected final boolean onlyOne; public RemoveVariableCountersTargetCost(FilterPermanent filter) { this(filter, null); @@ -35,15 +37,20 @@ public class RemoveVariableCountersTargetCost extends VariableCostImpl { } public RemoveVariableCountersTargetCost(FilterPermanent filter, CounterType counterTypeToRemove, String xText, int minValue, String text) { + this(filter, counterTypeToRemove, xText, minValue, false, text); + } + + public RemoveVariableCountersTargetCost(FilterPermanent filter, CounterType counterTypeToRemove, String xText, int minValue, boolean onlyOne, String text) { super(VariableCostType.NORMAL, xText, new StringBuilder(counterTypeToRemove != null ? counterTypeToRemove.getName() + ' ' : "").append("counters to remove").toString()); this.filter = filter; this.counterTypeToRemove = counterTypeToRemove; + this.minValue = minValue; + this.onlyOne = onlyOne; if (text != null && !text.isEmpty()) { this.text = text; } else { this.text = setText(); } - this.minValue = minValue; } protected RemoveVariableCountersTargetCost(final RemoveVariableCountersTargetCost cost) { @@ -51,6 +58,7 @@ public class RemoveVariableCountersTargetCost extends VariableCostImpl { this.filter = cost.filter; this.counterTypeToRemove = cost.counterTypeToRemove; this.minValue = cost.minValue; + this.onlyOne = cost.onlyOne; } @Override @@ -59,11 +67,19 @@ public class RemoveVariableCountersTargetCost extends VariableCostImpl { } private String setText() { - StringBuilder sb = new StringBuilder("Remove ").append(xText); + StringBuilder sb = new StringBuilder("Remove "); + sb.append(xText); if (counterTypeToRemove != null) { - sb.append(' ').append(counterTypeToRemove.getName()); + sb.append(' '); + sb.append(counterTypeToRemove.getName()); + } + sb.append(" counters from "); + if (onlyOne) { + sb.append(CardUtil.addArticle(filter.getMessage())); + } else { + sb.append("among "); + sb.append(filter.getMessage()); } - sb.append(" counters from among ").append(filter.getMessage()); return sb.toString(); } @@ -84,22 +100,24 @@ public class RemoveVariableCountersTargetCost extends VariableCostImpl { @Override public int getMaxValue(Ability source, Game game) { - int maxValue = 0; - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) { - if (counterTypeToRemove != null) { - maxValue += permanent.getCounters(game).getCount(counterTypeToRemove); - } else { - for (Counter counter : permanent.getCounters(game).values()) { - maxValue += counter.getCount(); - } - } + IntStream stream = game + .getBattlefield() + .getAllActivePermanents(filter, source.getControllerId(), game) + .stream() + .map(permanent -> permanent.getCounters(game)) + .mapToInt(counters -> counterTypeToRemove == null + ? counters.values().stream().mapToInt(Counter::getCount).sum() + : counters.getCount(counterTypeToRemove)); + if (onlyOne) { + return stream.max().orElse(0); } - return maxValue; + return stream.sum(); } @Override public Cost getFixedCostsFromAnnouncedValue(int xValue) { - return new RemoveCounterCost(new TargetPermanent(minValue, Integer.MAX_VALUE, filter, true), counterTypeToRemove, xValue); + return new RemoveCounterCost( + new TargetPermanent(minValue, onlyOne ? 1 : Integer.MAX_VALUE, filter, true), counterTypeToRemove, xValue + ); } - } diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java index cdd3ec99fd6..213aad6c054 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java @@ -132,6 +132,13 @@ public class ConditionalOneShotEffect extends OneShotEffect { return this; } + @Override + public ConditionalOneShotEffect withTargetDescription(String target) { + effects.forEach(effect -> effect.withTargetDescription(target)); + otherwiseEffects.forEach(effect -> effect.withTargetDescription(target)); + return this; + } + @Override public Condition getCondition() { return condition; diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CreaturesYouControlCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CreaturesYouControlCount.java index a82e8d10239..b5b96fb800f 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CreaturesYouControlCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CreaturesYouControlCount.java @@ -11,16 +11,25 @@ import mage.game.Game; */ public enum CreaturesYouControlCount implements DynamicValue { - instance; + PLURAL(true), + SINGULAR(false); + private final boolean plural; + + CreaturesYouControlCount(boolean plural) { + this.plural = plural; + } @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - return game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_CREATURES, sourceAbility.getControllerId(), sourceAbility, game); + return game.getBattlefield().count( + StaticFilters.FILTER_CONTROLLED_CREATURES, + sourceAbility.getControllerId(), sourceAbility, game + ); } @Override public CreaturesYouControlCount copy() { - return instance; + return this; } @Override @@ -30,6 +39,6 @@ public enum CreaturesYouControlCount implements DynamicValue { @Override public String getMessage() { - return "creatures you control"; + return "creature" + (plural ? "s" : "") + " you control"; } } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/DifferentlyNamedPermanentCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/DifferentlyNamedPermanentCount.java new file mode 100644 index 00000000000..7edc5aaee7b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/DifferentlyNamedPermanentCount.java @@ -0,0 +1,61 @@ +package mage.abilities.dynamicvalue.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.filter.FilterPermanent; +import mage.game.Game; + +/** + * @author TheElk801 + */ +public class DifferentlyNamedPermanentCount implements DynamicValue { + + private final FilterPermanent filter; + private final Hint hint; + + public DifferentlyNamedPermanentCount(FilterPermanent filter) { + this.filter = filter; + this.hint = new ValueHint("Differently named " + filter.getMessage(), this); + } + + private DifferentlyNamedPermanentCount(final DifferentlyNamedPermanentCount dynamicValue) { + this.filter = dynamicValue.filter; + this.hint = dynamicValue.hint; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getBattlefield() + .getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility, game) + .stream() + .map(MageObject::getName) + .filter(s -> !s.isEmpty()) + .distinct() + .mapToInt(x -> 1) + .sum(); + } + + @Override + public DifferentlyNamedPermanentCount copy() { + return new DifferentlyNamedPermanentCount(this); + } + + @Override + public String getMessage() { + return "the number of differently named " + filter.getMessage(); + } + + @Override + public String toString() { + return "X"; + } + + public Hint getHint() { + return hint; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java index c8a5b78dd0e..7db5097dcc8 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java @@ -448,6 +448,12 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu return this; } + @Override + public ContinuousEffect withTargetDescription(String target) { + super.withTargetDescription(target); + return this; + } + /** * Auto-generates dependencies on different effects (what's apply first and * what's apply second) diff --git a/Mage/src/main/java/mage/abilities/effects/Effect.java b/Mage/src/main/java/mage/abilities/effects/Effect.java index 60e57c9f887..26f8f56f06e 100644 --- a/Mage/src/main/java/mage/abilities/effects/Effect.java +++ b/Mage/src/main/java/mage/abilities/effects/Effect.java @@ -59,6 +59,11 @@ public interface Effect extends Serializable, Copyable { TargetPointer getTargetPointer(); + /** + * Sets the target pointer's description to the given string. + */ + Effect withTargetDescription(String target); + void setValue(String key, Object value); Object getValue(String key); diff --git a/Mage/src/main/java/mage/abilities/effects/EffectImpl.java b/Mage/src/main/java/mage/abilities/effects/EffectImpl.java index c5a81b5f93c..cfe01700099 100644 --- a/Mage/src/main/java/mage/abilities/effects/EffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/EffectImpl.java @@ -93,7 +93,7 @@ public abstract class EffectImpl implements Effect { // first target pointer is default throw new IllegalArgumentException("Wrong code usage: target pointer can't be set to null: " + this); } - + targetPointer.setTargetDescription(this.targetPointer.getTargetDescription()); // copies the null if not set this.targetPointer = targetPointer; initNewTargetPointer(); return this; @@ -104,6 +104,12 @@ public abstract class EffectImpl implements Effect { return this.targetPointer; } + @Override + public Effect withTargetDescription(String target) { + this.targetPointer.setTargetDescription(target); + return this; + } + @Override public void newId() { if (!(this instanceof MageSingleton)) { diff --git a/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java b/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java index 3463066e012..3abb209108d 100644 --- a/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java @@ -42,6 +42,12 @@ public abstract class OneShotEffect extends EffectImpl { return this; } + @Override + public OneShotEffect withTargetDescription(String target) { + super.withTargetDescription(target); + return this; + } + @Override abstract public OneShotEffect copy(); } diff --git a/Mage/src/main/java/mage/abilities/effects/OneShotNonTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/OneShotNonTargetEffect.java index acb09c56ac1..d5194c82f23 100644 --- a/Mage/src/main/java/mage/abilities/effects/OneShotNonTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/OneShotNonTargetEffect.java @@ -72,6 +72,12 @@ public class OneShotNonTargetEffect extends OneShotEffect { return super.setTargetPointer(targetPointer); } + @Override + public OneShotEffect withTargetDescription(String target) { + effect.withTargetDescription(target); + return super.withTargetDescription(target); + } + @Override public void setValue(String key, Object value) { effect.setValue(key, value); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java index fa5b1363f3a..d36beca17a9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java @@ -5,6 +5,7 @@ import mage.abilities.Mode; import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; import mage.constants.PutCards; @@ -14,13 +15,14 @@ import mage.players.Player; import mage.util.ManaUtil; /** - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, Susucr */ public class CounterUnlessPaysEffect extends OneShotEffect { protected Cost cost; protected DynamicValue genericMana; private final boolean exile; + private Effect effectIfTheyDo = null; // optional "If they do, [...]" effect public CounterUnlessPaysEffect(Cost cost) { this(cost, false); @@ -51,6 +53,17 @@ public class CounterUnlessPaysEffect extends OneShotEffect { this.genericMana = effect.genericMana.copy(); } this.exile = effect.exile; + if (effect.effectIfTheyDo != null) { + this.effectIfTheyDo = effect.effectIfTheyDo.copy(); + } + } + + public CounterUnlessPaysEffect withIfTheyDo(Effect effect) { + if (effectIfTheyDo != null) { + throw new IllegalStateException("Wrong code usage: only a single 'if they do' effect is expected."); + } + effectIfTheyDo = effect.copy(); + return this; } @Override @@ -91,6 +104,9 @@ public class CounterUnlessPaysEffect extends OneShotEffect { game.getStack().counter(spell.getId(), source, game, exile ? PutCards.EXILED : PutCards.GRAVEYARD); } else { game.informPlayers(player.getLogName() + " chooses to pay " + costValueMessage + " to prevent the counter effect"); + if (effectIfTheyDo != null) { + effectIfTheyDo.apply(game, source); + } } return true; } @@ -115,6 +131,10 @@ public class CounterUnlessPaysEffect extends OneShotEffect { if (exile) { sb.append(". If that spell is countered this way, exile it instead of putting it into its owner's graveyard"); } + if (effectIfTheyDo != null) { + sb.append(". If they do, "); + sb.append(effectIfTheyDo.getText(mode)); + } return sb.toString(); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java index 80b35e51bec..0017910e08d 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java @@ -14,9 +14,10 @@ import mage.util.CardUtil; */ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect { - protected DelayedTriggeredAbility ability; - protected boolean copyTargets; - protected String rulePrefix; + private final DelayedTriggeredAbility ability; + private final boolean copyTargets; + private final String rulePrefix; + private boolean copyToPointer = false; public CreateDelayedTriggeredAbilityEffect(DelayedTriggeredAbility ability) { this(ability, true); @@ -38,6 +39,7 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect { this.ability = effect.ability.copy(); this.copyTargets = effect.copyTargets; this.rulePrefix = effect.rulePrefix; + this.copyToPointer = effect.copyToPointer; } @Override @@ -49,7 +51,7 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { DelayedTriggeredAbility delayedAbility = ability.copy(); if (this.copyTargets) { - if (source.getTargets().isEmpty()) { + if (copyToPointer || source.getTargets().isEmpty()) { delayedAbility.getEffects().setTargetPointer(this.getTargetPointer().copy()); } else { delayedAbility.getTargets().addAll(source.getTargets()); @@ -89,4 +91,16 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect { super.setTargetPointer(targetPointer); return this; } + + public CreateDelayedTriggeredAbilityEffect withCopyToPointer(boolean copyToPointer) { + this.copyToPointer = copyToPointer; + return this; + } + + @Override + public CreateDelayedTriggeredAbilityEffect withTargetDescription(String target) { + ability.getEffects().forEach(effect -> effect.withTargetDescription(target)); + super.withTargetDescription(target); + return this; + } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/MillHalfLibraryTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MillHalfLibraryTargetEffect.java index 155bcf15157..8f9a78084cd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/MillHalfLibraryTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/MillHalfLibraryTargetEffect.java @@ -7,6 +7,8 @@ import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** * @author TheElk801 */ @@ -31,12 +33,15 @@ public class MillHalfLibraryTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (player == null) { - return false; + for (UUID playerId : getTargetPointer().getTargets(game, source)) { + Player player = game.getPlayer(playerId); + if (player == null) { + return false; + } + int count = player.getLibrary().size(); + player.millCards(count / 2 + (roundUp ? count % 2 : 0), source, game); } - int count = player.getLibrary().size(); - return player.millCards(count / 2 + (roundUp ? count % 2 : 0), source, game).size() > 0; + return true; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java index 152345b76e6..0124edb2af8 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java @@ -105,6 +105,9 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect } sb.append(yourGrave ? " to" : " onto"); sb.append(" the battlefield"); + if (!yourGrave) { + sb.append(" under your control"); + } if (tapped && attacking) { sb.append(" tapped and attacking"); } else if (tapped) { @@ -112,9 +115,6 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect } else if (attacking) { sb.append(" attacking"); } - if (!yourGrave) { - sb.append(" under your control"); - } return sb.toString(); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java index 1ec72646208..0240a4151c7 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java @@ -160,4 +160,13 @@ public class RollDieWithResultTableEffect extends OneShotEffect { super.setTargetPointer(targetPointer); return this; } + + + @Override + public RollDieWithResultTableEffect withTargetDescription(String target) { + resultsTable.forEach(tableEntry -> tableEntry.effects.forEach( + effect -> effect.withTargetDescription(target))); + super.withTargetDescription(target); + return this; + } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ShuffleHandGraveyardIntoLibraryEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ShuffleHandGraveyardIntoLibraryEffect.java new file mode 100644 index 00000000000..626f3a66544 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ShuffleHandGraveyardIntoLibraryEffect.java @@ -0,0 +1,40 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +/** + * @author TheElk801 + */ +public class ShuffleHandGraveyardIntoLibraryEffect extends OneShotEffect { + + public ShuffleHandGraveyardIntoLibraryEffect() { + super(Outcome.Discard); + this.staticText = "shuffle your hand and graveyard into your library"; + } + + protected ShuffleHandGraveyardIntoLibraryEffect(final ShuffleHandGraveyardIntoLibraryEffect effect) { + super(effect); + } + + @Override + public ShuffleHandGraveyardIntoLibraryEffect copy() { + return new ShuffleHandGraveyardIntoLibraryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(player.getHand()); + cards.addAll(player.getGraveyard()); + return player.shuffleCardsToLibrary(cards, game, source); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/WishEffect.java b/Mage/src/main/java/mage/abilities/effects/common/WishEffect.java index b9b8d3793b6..9d76b9a82f3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/WishEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/WishEffect.java @@ -125,7 +125,7 @@ public class WishEffect extends OneShotEffect { TargetCard target = new TargetCard(Zone.ALL, filter); target.withNotTarget(true); - if (controller.choose(Outcome.Benefit, filteredCards, target, source, game)) { + if (controller.choose(Outcome.PutCardInPlay, filteredCards, target, source, game)) { Card card = controller.getSideboard().get(target.getFirstTarget(), game); if (card == null && alsoFromExile) { card = game.getCard(target.getFirstTarget()); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java index a5b2ce86db3..75102356986 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityWithAttachmentEffect.java @@ -1,6 +1,7 @@ package mage.abilities.effects.common.continuous; import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; @@ -20,6 +21,7 @@ import mage.target.Targets; import mage.target.targetpointer.FixedTarget; import java.util.Collections; +import java.util.function.Consumer; /** * @author TheElk801 @@ -30,18 +32,24 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { private final Targets targets = new Targets(); private final Costs costs = new CostsImpl<>(); protected final UseAttachedCost useAttachedCost; + private final Consumer consumer; public GainAbilityWithAttachmentEffect(String rule, Effect effect, Target target, UseAttachedCost attachedCost, Cost... costs) { this(rule, new Effects(effect), new Targets(target), attachedCost, costs); } public GainAbilityWithAttachmentEffect(String rule, Effects effects, Targets targets, UseAttachedCost attachedCost, Cost... costs) { + this(rule, effects, targets, attachedCost, null, costs); + } + + public GainAbilityWithAttachmentEffect(String rule, Effects effects, Targets targets, UseAttachedCost attachedCost, Consumer consumer, Cost... costs) { super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); this.staticText = rule; this.effects.addAll(effects); this.targets.addAll(targets); Collections.addAll(this.costs, costs); this.useAttachedCost = attachedCost; + this.consumer = consumer; this.generateGainAbilityDependencies(makeAbility(null, null), null); } @@ -51,6 +59,7 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { this.targets.addAll(effect.targets); this.costs.addAll(effect.costs); this.useAttachedCost = effect.useAttachedCost == null ? null : effect.useAttachedCost.copy(); + this.consumer = effect.consumer; } @Override @@ -94,7 +103,7 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { } protected Ability makeAbility(Game game, Ability source) { - Ability ability = new SimpleActivatedAbility(null, null); + ActivatedAbility ability = new SimpleActivatedAbility(null, null); for (Effect effect : effects) { if (effect == null) { continue; @@ -116,6 +125,9 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl { if (source != null && game != null) { ability.addCost(useAttachedCost.copy().setMageObjectReference(source, game)); } + if (consumer != null) { + consumer.accept(ability); + } return ability; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/SwitchPowerToughnessTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/SwitchPowerToughnessTargetEffect.java index 0ece62d0101..b4885a2a020 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/SwitchPowerToughnessTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/SwitchPowerToughnessTargetEffect.java @@ -10,6 +10,8 @@ import mage.abilities.effects.ContinuousEffectImpl; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.UUID; + /** * @author ayratn */ @@ -30,13 +32,15 @@ public class SwitchPowerToughnessTargetEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent target = game.getPermanent(this.getTargetPointer().getFirst(game, source)); - if (target == null) { - return false; + int affectedTargets = 0; + for (UUID uuid : getTargetPointer().getTargets(game, source)) { + Permanent target = game.getPermanent(uuid); + if (target != null) { + target.switchPowerToughness(); + affectedTargets++; + } } - - target.switchPowerToughness(); - return true; + return affectedTargets > 0; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java index c2f35e9449b..86e32efe3ff 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java @@ -51,8 +51,8 @@ public class AddPoisonCounterTargetEffect extends OneShotEffect { if (staticText != null && !staticText.isEmpty()) { return staticText; } - return getTargetPointer().describeTargets(mode.getTargets(), "it") + + return getTargetPointer().describeTargets(mode.getTargets(), "that player") + (getTargetPointer().isPlural(mode.getTargets()) ? " get " : " gets ") + - CardUtil.getSimpleCountersText(amount, "a", "poison"); + CardUtil.getSimpleCountersText(amount, "a", "poison"); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/CardMorEnteringTappedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/CardMorEnteringTappedEffect.java new file mode 100644 index 00000000000..718bc120090 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/CardMorEnteringTappedEffect.java @@ -0,0 +1,66 @@ +package mage.abilities.effects.common.replacement; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * Used to affect a card that will enter the battlefield (land played, or card put into play by effect). + * The permanent it becomes enters tapped. + *

+ * Short-lived replacement effect, auto-cleanup if the mor is no longer current. + * + * @author Susucr + */ +public class CardMorEnteringTappedEffect extends ReplacementEffectImpl { + + private final MageObjectReference mor; + + public CardMorEnteringTappedEffect(MageObjectReference mor) { + super(Duration.OneUse, Outcome.Tap); + this.staticText = "That permanent enters the battlefield tapped"; + this.mor = mor; + } + + private CardMorEnteringTappedEffect(final CardMorEnteringTappedEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public CardMorEnteringTappedEffect copy() { + return new CardMorEnteringTappedEffect(this); + } + + @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) { + Card morCard = mor.getCard(game); + if (morCard == null) { + // cleanup if something other than resolving happens to the spell. + discard(); + return false; + } + return event.getTargetId().equals(morCard.getId()); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget(); + if (permanent != null) { + permanent.setTapped(true); + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/MorEnteringTappedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/SpellMorEnteringTappedEffect.java similarity index 85% rename from Mage/src/main/java/mage/abilities/effects/common/replacement/MorEnteringTappedEffect.java rename to Mage/src/main/java/mage/abilities/effects/common/replacement/SpellMorEnteringTappedEffect.java index 66bc00ef654..8e1b23a279a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/replacement/MorEnteringTappedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/SpellMorEnteringTappedEffect.java @@ -19,24 +19,24 @@ import mage.game.stack.Spell; * * @author Susucr */ -public class MorEnteringTappedEffect extends ReplacementEffectImpl { +public class SpellMorEnteringTappedEffect extends ReplacementEffectImpl { private final MageObjectReference mor; - public MorEnteringTappedEffect(MageObjectReference mor) { + public SpellMorEnteringTappedEffect(MageObjectReference mor) { super(Duration.OneUse, Outcome.Tap); this.staticText = "That permanent enters the battlefield tapped"; this.mor = mor; } - private MorEnteringTappedEffect(final MorEnteringTappedEffect effect) { + private SpellMorEnteringTappedEffect(final SpellMorEnteringTappedEffect effect) { super(effect); this.mor = effect.mor; } @Override - public MorEnteringTappedEffect copy() { - return new MorEnteringTappedEffect(this); + public SpellMorEnteringTappedEffect copy() { + return new SpellMorEnteringTappedEffect(this); } @Override diff --git a/Mage/src/main/java/mage/abilities/hint/common/CreaturesYouControlHint.java b/Mage/src/main/java/mage/abilities/hint/common/CreaturesYouControlHint.java index f596d3c5904..7abb548a02c 100644 --- a/Mage/src/main/java/mage/abilities/hint/common/CreaturesYouControlHint.java +++ b/Mage/src/main/java/mage/abilities/hint/common/CreaturesYouControlHint.java @@ -12,7 +12,7 @@ import mage.game.Game; public enum CreaturesYouControlHint implements Hint { instance; - private static final Hint hint = new ValueHint("Creatures you control", CreaturesYouControlCount.instance); + private static final Hint hint = new ValueHint("Creatures you control", CreaturesYouControlCount.PLURAL); @Override public String getText(Game game, Ability ability) { diff --git a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java index 99909e27fd1..2035e57cfcf 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java @@ -20,8 +20,11 @@ import mage.watchers.common.CommanderPlaysCountWatcher; */ public class CommanderStormAbility extends TriggeredAbilityImpl { - public CommanderStormAbility() { - super(Zone.STACK, new CommanderStormEffect()); + public CommanderStormAbility(boolean newTargetsText) { + super(Zone.STACK, new CommanderStormEffect().setText("copy it for each time you've " + + "cast your commander from the command zone this game." + + (newTargetsText ? " You may choose new targets for the copies." : ""))); + this.setTriggerPhrase("When you cast this spell, "); this.setRuleAtTheTop(true); } @@ -54,13 +57,6 @@ public class CommanderStormAbility extends TriggeredAbilityImpl { } return true; } - - @Override - public String getRule() { - return "When you cast this spell, copy it for each time you've " - + "cast your commander from the command zone this game. " - + "You may choose new targets for the copies."; - } } class CommanderStormEffect extends OneShotEffect { diff --git a/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java b/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java index 7c068c514b4..7108aa802e1 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java @@ -6,12 +6,12 @@ import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.common.UntapTargetEffect; import mage.constants.Duration; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.constants.SetTargetPointer; +import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; +import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster; import java.util.UUID; @@ -31,27 +31,16 @@ public class ProvokeAbility extends AttacksTriggeredAbility { } public ProvokeAbility(String text) { - super(new UntapTargetEffect(), true, text); + super(new UntapTargetEffect(), true, text, SetTargetPointer.PLAYER); this.addEffect(new ProvokeRequirementEffect()); + this.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE)); + this.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); } protected ProvokeAbility(final ProvokeAbility ability) { super(ability); } - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (super.checkTrigger(event, game)) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("creature defending player controls"); - UUID defendingPlayerId = game.getCombat().getDefendingPlayerId(sourceId, game); - filter.add(new ControllerIdPredicate(defendingPlayerId)); - this.getTargets().clear(); - this.addTarget(new TargetPermanent(filter)); - return true; - } - return false; - } - @Override public ProvokeAbility copy() { return new ProvokeAbility(this); diff --git a/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java b/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java index c1b08e6947e..6a4eb980ccd 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java @@ -2,19 +2,15 @@ package mage.abilities.keyword; -import mage.constants.ComparisonType; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.constants.ComparisonType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.predicate.mageobject.ManaValuePredicate; -import mage.game.Game; -import mage.game.events.GameEvent; import mage.target.common.TargetCardInYourGraveyard; - -import java.util.UUID; +import mage.target.targetadjustment.ManaValueTargetAdjuster; /** * 702.45. Soulshift @@ -38,6 +34,10 @@ public class SoulshiftAbility extends DiesSourceTriggeredAbility { public SoulshiftAbility(DynamicValue amount) { super(new ReturnToHandTargetEffect()); this.amount = amount; + FilterCard filter = new FilterCard("Spirit card from your graveyard"); + filter.add(SubType.SPIRIT.getPredicate()); + this.addTarget(new TargetCardInYourGraveyard(filter)); + this.setTargetAdjuster(new ManaValueTargetAdjuster(amount, ComparisonType.OR_LESS)); } protected SoulshiftAbility(final SoulshiftAbility ability) { @@ -45,17 +45,6 @@ public class SoulshiftAbility extends DiesSourceTriggeredAbility { this.amount = ability.amount; } - @Override - public void trigger(Game game, UUID controllerId, GameEvent triggeringEvent) { - this.getTargets().clear(); - int intValue = amount.calculate(game, this, null); - FilterCard filter = new FilterCard("Spirit card with mana value " + intValue + " or less from your graveyard"); - filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, intValue + 1)); - filter.add(SubType.SPIRIT.getPredicate()); - this.addTarget(new TargetCardInYourGraveyard(filter)); - super.trigger(game, controllerId, triggeringEvent); - } - @Override public SoulshiftAbility copy() { return new SoulshiftAbility(this); diff --git a/Mage/src/main/java/mage/abilities/keyword/StationAbility.java b/Mage/src/main/java/mage/abilities/keyword/StationAbility.java new file mode 100644 index 00000000000..e918dc283d2 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/StationAbility.java @@ -0,0 +1,91 @@ +package mage.abilities.keyword; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.constants.TimingRule; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +import java.util.List; + +/** + * @author TheElk801 + */ +public class StationAbility extends SimpleActivatedAbility { + + private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("another creature you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TappedPredicate.UNTAPPED); + } + + public StationAbility() { + super(Zone.BATTLEFIELD, new StationAbilityEffect(), new TapTargetCost(filter)); + this.timing = TimingRule.SORCERY; + } + + private StationAbility(final StationAbility ability) { + super(ability); + } + + @Override + public StationAbility copy() { + return new StationAbility(this); + } + + @Override + public String getRule() { + return "Station (Tap another creature you control: Put charge counters equal to its power on {this}. Station only as a sorcery.)"; + } +} + +class StationAbilityEffect extends OneShotEffect { + + StationAbilityEffect() { + super(Outcome.Benefit); + } + + private StationAbilityEffect(final StationAbilityEffect effect) { + super(effect); + } + + @Override + public StationAbilityEffect copy() { + return new StationAbilityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + return false; + } + List creatures = (List) getValue("tappedPermanents"); + if (creatures == null) { + return false; + } + int power = 0; + for (Permanent creature : creatures) { + GameEvent event = GameEvent.getEvent( + GameEvent.EventType.STATION_PERMANENT, creature.getId(), + source, source.getControllerId(), creature.getPower().getValue() + ); + if (game.replaceEvent(event)) { + continue; + } + power += event.getAmount(); + } + return power > 0 && permanent.addCounters(CounterType.CHARGE.createInstance(power), source, game); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/StationLevelAbility.java b/Mage/src/main/java/mage/abilities/keyword/StationLevelAbility.java new file mode 100644 index 00000000000..20791ca75f9 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/StationLevelAbility.java @@ -0,0 +1,159 @@ +package mage.abilities.keyword; + +import mage.abilities.Ability; +import mage.abilities.StaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public class StationLevelAbility extends StaticAbility { + + private final int level; + + public StationLevelAbility(int level) { + super(Zone.BATTLEFIELD, null); + this.level = level; + } + + private StationLevelAbility(final StationLevelAbility ability) { + super(ability); + this.level = ability.level; + } + + @Override + public StationLevelAbility copy() { + return new StationLevelAbility(this); + } + + public StationLevelAbility withLevelAbility(Ability ability) { + this.addEffect(new StationLevelAbilityEffect(ability, level)); + return this; + } + + public StationLevelAbility withPT(int power, int toughness) { + this.addEffect(new StationLevelCreatureEffect(power, toughness, level)); + return this; + } + + @Override + public String getRule() { + return "STATION " + level + "+
" + this + .getEffects() + .stream() + .map(effect -> effect.getText(this.getModes().getMode())) + .map(CardUtil::getTextWithFirstCharUpperCase) + .collect(Collectors.joining("
")); + } + + public boolean hasPT() { + return this.getEffects().stream().anyMatch(StationLevelCreatureEffect.class::isInstance); + } +} + +class StationLevelAbilityEffect extends ContinuousEffectImpl { + + private final Ability ability; + private final int level; + + StationLevelAbilityEffect(Ability ability, int level) { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.ability = ability; + this.level = level; + this.ability.setRuleVisible(false); + this.staticText = ability.getRule(); + } + + private StationLevelAbilityEffect(final StationLevelAbilityEffect effect) { + super(effect); + this.ability = effect.ability; + this.level = effect.level; + } + + @Override + public StationLevelAbilityEffect copy() { + return new StationLevelAbilityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null || permanent.getCounters(game).getCount(CounterType.CHARGE) < level) { + return false; + } + permanent.addAbility(ability, source.getSourceId(), game); + return true; + } +} + +class StationLevelCreatureEffect extends ContinuousEffectImpl { + + private final int power; + private final int toughness; + private final int level; + + StationLevelCreatureEffect(int power, int toughness, int level) { + super(Duration.WhileOnBattlefield, Outcome.BecomeCreature); + this.power = power; + this.toughness = toughness; + this.level = level; + staticText = power + "/" + toughness; + } + + private StationLevelCreatureEffect(final StationLevelCreatureEffect effect) { + super(effect); + this.power = effect.power; + this.toughness = effect.toughness; + this.level = effect.level; + } + + @Override + public StationLevelCreatureEffect copy() { + return new StationLevelCreatureEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null || permanent.getCounters(game).getCount(CounterType.CHARGE) < level) { + return false; + } + switch (layer) { + case TypeChangingEffects_4: + permanent.addCardType(game, CardType.ARTIFACT, CardType.CREATURE); + return true; + case PTChangingEffects_7: + if (sublayer != SubLayer.SetPT_7b) { + return false; + } + permanent.getPower().setModifiedBaseValue(power); + permanent.getToughness().setModifiedBaseValue(toughness); + return true; + default: + return false; + } + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + switch (layer) { + case TypeChangingEffects_4: + case PTChangingEffects_7: + return true; + default: + return false; + } + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/WarpAbility.java b/Mage/src/main/java/mage/abilities/keyword/WarpAbility.java new file mode 100644 index 00000000000..f3de58b05e7 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/WarpAbility.java @@ -0,0 +1,157 @@ +package mage.abilities.keyword; + +import mage.MageIdentifier; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class WarpAbility extends SpellAbility { + + public static final String WARP_ACTIVATION_VALUE_KEY = "warpActivation"; + private final boolean allowGraveyard; + + public WarpAbility(Card card, String manaString) { + this(card, manaString, false); + } + + public WarpAbility(Card card, String manaString, boolean allowGraveyard) { + super(card.getSpellAbility()); + this.newId(); + this.setCardName(card.getName() + " with Warp"); + this.zone = Zone.HAND; + this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; + this.timing = TimingRule.SORCERY; + this.clearManaCosts(); + this.clearManaCostsToPay(); + this.addCost(new ManaCostsImpl<>(manaString)); + this.setAdditionalCostsRuleVisible(false); + this.allowGraveyard = allowGraveyard; + } + + private WarpAbility(final WarpAbility ability) { + super(ability); + this.allowGraveyard = ability.allowGraveyard; + } + + // The ability sets up a delayed trigger which can't be set up using the cost tag system + public static void addDelayedTrigger(SpellAbility spellAbility, Game game) { + if (spellAbility instanceof WarpAbility) { + game.addDelayedTriggeredAbility( + new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new WarpExileEffect()), spellAbility + ); + } + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + switch (game.getState().getZone(getSourceId())) { + case GRAVEYARD: + if (!allowGraveyard) { + break; + } + case HAND: + return super.canActivate(playerId, game); + } + return ActivationStatus.getFalse(); + } + + @Override + public boolean activate(Game game, Set allowedIdentifiers, boolean noMana) { + if (!super.activate(game, allowedIdentifiers, noMana)) { + return false; + } + this.setCostsTag(WARP_ACTIVATION_VALUE_KEY, null); + return true; + } + + @Override + public WarpAbility copy() { + return new WarpAbility(this); + } + + @Override + public String getRule() { + StringBuilder sb = new StringBuilder("Warp"); + if (getCosts().isEmpty()) { + sb.append(' '); + } else { + sb.append("—"); + } + sb.append(getManaCosts().getText()); + if (!getCosts().isEmpty()) { + sb.append(", "); + sb.append(getCosts().getText()); + sb.append('.'); + } + return sb.toString(); + } + + public static String makeWarpString(UUID playerId) { + return playerId + "- Warped"; + } +} + +class WarpExileEffect extends OneShotEffect { + + private static class WarpCondition implements Condition { + + private final int turnNumber; + + WarpCondition(Game game) { + this.turnNumber = game.getTurnNum(); + } + + @Override + public boolean apply(Game game, Ability source) { + return game.getTurnNum() > turnNumber; + } + } + + WarpExileEffect() { + super(Outcome.Benefit); + staticText = "exile this creature if it was cast for its warp cost"; + } + + private WarpExileEffect(final WarpExileEffect effect) { + super(effect); + } + + @Override + public WarpExileEffect copy() { + return new WarpExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent == null || permanent.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter() + 1) { + return false; + } + player.moveCardsToExile( + permanent, source, game, true, + CardUtil.getExileZoneId(WarpAbility.makeWarpString(player.getId()), game), + "Warped by " + player.getLogName() + ); + CardUtil.makeCardPlayable( + game, source, permanent.getMainCard(), true, + Duration.Custom, false, player.getId(), new WarpCondition(game) + ); + return true; + } +} diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 2edbf43c918..90acef7ccce 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -918,27 +918,41 @@ public abstract class CardImpl extends MageObjectImpl implements Card { @Override public boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode) { + boolean canAttach = true; for (ProtectionAbility ability : this.getAbilities(game).getProtectionAbilities()) { if ((!attachment.hasSubtype(SubType.AURA, game) || ability.removesAuras()) && (!attachment.hasSubtype(SubType.EQUIPMENT, game) || ability.removesEquipment()) && !attachment.getId().equals(ability.getAuraIdNotToBeRemoved()) && !ability.canTarget(attachment, game)) { - return !ability.getDoesntRemoveControlled() || Objects.equals(getControllerOrOwnerId(), game.getControllerId(attachment.getId())); + canAttach &= ability.getDoesntRemoveControlled() && Objects.equals(getControllerOrOwnerId(), game.getControllerId(attachment.getId())); } } - boolean canAttach = true; - Permanent attachmentPermanent = game.getPermanent(attachment.getId()); // If attachment is an aura, ensures this permanent can still be legally enchanted, according to the enchantment's Enchant ability - if (attachment.hasSubtype(SubType.AURA, game) - && attachmentPermanent != null - && attachmentPermanent.getSpellAbility() != null - && !attachmentPermanent.getSpellAbility().getTargets().isEmpty()) { - // Line of code below functionally gets the target of the aura's Enchant ability, then compares to this permanent. Enchant improperly implemented in XMage, see #9583 - // Note: stillLegalTarget used exclusively to account for Dream Leash. Can be made canTarget in the event that that card is rewritten (and "stillLegalTarget" removed from TargetImpl). - canAttach = attachmentPermanent.getSpellAbility().getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(attachmentPermanent.getControllerId(), this.getId(), source, game); + if (attachment.hasSubtype(SubType.AURA, game)) { + SpellAbility spellAbility = null; + UUID controller = null; + Permanent attachmentPermanent = game.getPermanent(attachment.getId()); + if (attachmentPermanent != null) { + spellAbility = attachmentPermanent.getSpellAbility(); // Permanent's SpellAbility might be modified, so if possible use that one + controller = attachmentPermanent.getControllerId(); + } else { // Used for checking if it can be attached from the graveyard, such as Unfinished Business + Card attachmentCard = game.getCard(attachment.getId()); + if (attachmentCard != null) { + spellAbility = attachmentCard.getSpellAbility(); + if (source != null) { + controller = source.getControllerId(); + } else { + controller = attachmentCard.getControllerOrOwnerId(); + } + } + } + if (controller != null && spellAbility != null && !spellAbility.getTargets().isEmpty()){ + // Line of code below functionally gets the target of the aura's Enchant ability, then compares to this permanent. Enchant improperly implemented in XMage, see #9583 + // Note: stillLegalTarget used exclusively to account for Dream Leash. Can be made canTarget in the event that that card is rewritten (and "stillLegalTarget" removed from TargetImpl). + canAttach &= spellAbility.getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(controller, this.getId(), source, game); + } } - return !canAttach || game.getContinuousEffects().preventedByRuleModification(new StayAttachedEvent(this.getId(), attachment.getId(), source), null, game, silentMode); } diff --git a/Mage/src/main/java/mage/cards/repository/TokenRepository.java b/Mage/src/main/java/mage/cards/repository/TokenRepository.java index 75ff94d16bf..65d80a97c93 100644 --- a/Mage/src/main/java/mage/cards/repository/TokenRepository.java +++ b/Mage/src/main/java/mage/cards/repository/TokenRepository.java @@ -270,6 +270,7 @@ public enum TokenRepository { res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 10, "https://api.scryfall.com/cards/tdsk/1/en?format=image")); res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 11, "https://api.scryfall.com/cards/tacr/1/en?format=image")); res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 12, "https://api.scryfall.com/cards/tpip/1/en?format=image")); + res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 13, "https://api.scryfall.com/cards/teoc/1/en?format=image")); // City's Blessing // https://scryfall.com/search?q=type%3Atoken+include%3Aextras+unique%3Aprints+City%27s+Blessing+&unique=cards&as=grid&order=name diff --git a/Mage/src/main/java/mage/collectors/DataCollector.java b/Mage/src/main/java/mage/collectors/DataCollector.java new file mode 100644 index 00000000000..db3471a4acd --- /dev/null +++ b/Mage/src/main/java/mage/collectors/DataCollector.java @@ -0,0 +1,68 @@ +package mage.collectors; + +import mage.game.Game; +import mage.game.Table; + +import java.util.UUID; + +/** + * Data collection for better debugging. Can collect server/table/game events and process related data. + *

+ * 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) + *

+ * How-to enable or disable: + * - use java params like -Dxmage.dataCollectors.saveGameHistory=true + *

+ * How-to add new service: + * - create new class and extends EmptyDataCollector + * - each service must use unique service code + * - override only needed events + * - modify DataCollectorServices.init with new class + * - make sure it's fast and never raise errors + * + * @author JayDi85 + */ +public interface DataCollector { + + /** + * Return unique service code to enable by command line + */ + String getServiceCode(); + + /** + * Show some hints on service enabled, e.g. root folder path + */ + String getInitInfo(); + + void onServerStart(); + + void onTableStart(Table table); + + void onTableEnd(Table table); + + void onGameStart(Game game); + + void onGameLog(Game game, String message); + + void onGameEnd(Game game); + + /** + * @param userName can be null for system messages + */ + void onChatRoom(UUID roomId, String userName, String message); + + void onChatTourney(UUID tourneyId, String userName, String message); + + 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 + */ + void onChatGame(UUID gameId, String userName, String message); +} diff --git a/Mage/src/main/java/mage/collectors/DataCollectorServices.java b/Mage/src/main/java/mage/collectors/DataCollectorServices.java new file mode 100644 index 00000000000..dc2740f078a --- /dev/null +++ b/Mage/src/main/java/mage/collectors/DataCollectorServices.java @@ -0,0 +1,143 @@ +package mage.collectors; + +import mage.collectors.services.PrintGameLogsDataCollector; +import mage.collectors.services.SaveGameHistoryDataCollector; +import mage.game.Game; +import mage.game.Table; +import org.apache.log4j.Logger; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; + +/** + * Not a real data collector. It's a global service to inject and collect data all around the code. + * + * @author JayDi85 + */ +final public class DataCollectorServices implements DataCollector { + + // usage example: -Dxmage.dataCollectors.saveGameHistory=true + private static final String COMMAND_LINE_DATA_COLLECTORS_PREFIX = "xmage.dataCollectors."; + + private static final Logger logger = Logger.getLogger(DataCollectorServices.class); + + private static DataCollectorServices instance = null; + + // fill on server startup, so it's thread safe + Set allServices = new LinkedHashSet<>(); + Set activeServices = new LinkedHashSet<>(); + + public static DataCollectorServices getInstance() { + if (instance == null) { + instance = new DataCollectorServices(); + } + return instance; + } + + /** + * Init data service on server's startup + * + * @param enablePrintGameLogs use for unit tests to enable additional logs for better debugging + * @param enableSaveGameHistory use to save full game history with logs, decks, etc + */ + public static void init(boolean enablePrintGameLogs, boolean enableSaveGameHistory) { + if (instance != null) { + // unit tests: init on first test run, all other will use same process + // real server: init on server startup + return; + } + + // fill all possible services + getInstance().allServices.add(new PrintGameLogsDataCollector()); + getInstance().allServices.add(new SaveGameHistoryDataCollector()); + logger.info(String.format("Data collectors: found %d services", getInstance().allServices.size())); + + // enable only needed + getInstance().allServices.forEach(service -> { + boolean isDefault = false; + isDefault |= enablePrintGameLogs && service.getServiceCode().equals(PrintGameLogsDataCollector.SERVICE_CODE); + isDefault |= enableSaveGameHistory && service.getServiceCode().equals(SaveGameHistoryDataCollector.SERVICE_CODE); + boolean isEnable = isServiceEnable(service.getServiceCode(), isDefault); + if (isEnable) { + getInstance().activeServices.add(service); + } + String info = isEnable ? String.format(" (%s)", service.getInitInfo()) : ""; + logger.info(String.format("Data collectors: %s - %s%s", service.getServiceCode(), isEnable ? "enabled" : "disabled", info)); + }); + } + + private static boolean isServiceEnable(String dataCollectorCode, boolean isEnableByDefault) { + String needCommand = COMMAND_LINE_DATA_COLLECTORS_PREFIX + dataCollectorCode; + boolean isEnable; + if (System.getProperty(needCommand) != null) { + isEnable = System.getProperty(needCommand, "false").equals("true"); + } else { + isEnable = isEnableByDefault; + } + return isEnable; + } + + @Override + public String getServiceCode() { + throw new IllegalStateException("Wrong code usage. Use it by static methods only"); + } + + @Override + public String getInitInfo() { + throw new IllegalStateException("Wrong code usage. Use it by static methods only"); + } + + @Override + public void onServerStart() { + activeServices.forEach(DataCollector::onServerStart); + } + + @Override + public void onTableStart(Table table) { + activeServices.forEach(c -> c.onTableStart(table)); + } + + @Override + public void onTableEnd(Table table) { + activeServices.forEach(c -> c.onTableEnd(table)); + } + + @Override + public void onGameStart(Game game) { + if (game.isSimulation()) return; + activeServices.forEach(c -> c.onGameStart(game)); + } + + @Override + public void onGameLog(Game game, String message) { + if (game.isSimulation()) return; + activeServices.forEach(c -> c.onGameLog(game, message)); + } + + @Override + public void onGameEnd(Game game) { + if (game.isSimulation()) return; + activeServices.forEach(c -> c.onGameEnd(game)); + } + + @Override + public void onChatRoom(UUID roomId, String userName, String message) { + activeServices.forEach(c -> c.onChatRoom(roomId, userName, message)); + } + + @Override + public void onChatTourney(UUID tourneyId, String userName, String message) { + activeServices.forEach(c -> c.onChatTourney(tourneyId, userName, message)); + } + + @Override + public void onChatTable(UUID tableId, String userName, String message) { + activeServices.forEach(c -> c.onChatTable(tableId, userName, message)); + } + + @Override + public void onChatGame(UUID gameId, String userName, String message) { + activeServices.forEach(c -> c.onChatGame(gameId, userName, message)); + } +} diff --git a/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java b/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java new file mode 100644 index 00000000000..2e5f1793e81 --- /dev/null +++ b/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java @@ -0,0 +1,70 @@ +package mage.collectors.services; + +import mage.collectors.DataCollector; +import mage.game.Game; +import mage.game.Table; + +import java.util.UUID; + +/** + * Base implementation of Data Collector, do nothing. Use it to implement own or simple collectors, e.g. chats only collectors + * + * @author JayDi85 + */ +public abstract class EmptyDataCollector implements DataCollector { + + @Override + public String getInitInfo() { + return ""; + } + + @Override + public void onServerStart() { + // nothing + } + + @Override + public void onTableStart(Table table) { + // nothing + } + + @Override + public void onTableEnd(Table table) { + // nothing + } + + @Override + public void onGameStart(Game game) { + // nothing + } + + @Override + public void onGameLog(Game game, String message) { + // nothing + } + + @Override + public void onGameEnd(Game game) { + // nothing + } + + @Override + public void onChatRoom(UUID roomId, String userName, String message) { + // nothing + } + + @Override + public void onChatTourney(UUID tourneyId, String userName, String message) { + // nothing + } + + @Override + public void onChatTable(UUID tableId, String userName, String message) { + // nothing + } + + @Override + public void onChatGame(UUID gameId, String userName, String message) { + // nothing + } +} diff --git a/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java b/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java new file mode 100644 index 00000000000..f8fc9a8966a --- /dev/null +++ b/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java @@ -0,0 +1,48 @@ +package mage.collectors.services; + +import mage.game.Game; +import mage.util.CardUtil; +import org.apache.log4j.Logger; +import org.jsoup.Jsoup; + +/** + * Data collector to print game logs in console output. Used for better unit tests debugging. + * + * @author JayDi85 + */ +public class PrintGameLogsDataCollector extends EmptyDataCollector { + + private static final Logger logger = Logger.getLogger(PrintGameLogsDataCollector.class); + public static final String SERVICE_CODE = "printGameLogs"; + + private void writeLog(String message) { + logger.info(message); + } + + private void writeLog(String category, String event, String details) { + writeLog(String.format("[%s][%s] %s", + category, + event, + details + )); + } + + @Override + public String getServiceCode() { + return SERVICE_CODE; + } + + @Override + public String getInitInfo() { + return "print game logs in server logs"; + } + + @Override + public void onGameLog(Game game, String message) { + String needMessage = Jsoup.parse(message).text(); + writeLog("GAME", "LOG", String.format("%s: %s", + CardUtil.getTurnInfo(game), + needMessage + )); + } +} diff --git a/Mage/src/main/java/mage/collectors/services/SaveGameHistoryDataCollector.java b/Mage/src/main/java/mage/collectors/services/SaveGameHistoryDataCollector.java new file mode 100644 index 00000000000..7d6212f0fbf --- /dev/null +++ b/Mage/src/main/java/mage/collectors/services/SaveGameHistoryDataCollector.java @@ -0,0 +1,383 @@ +package mage.collectors.services; + +import mage.cards.decks.DeckFormats; +import mage.constants.TableState; +import mage.game.Game; +import mage.game.Table; +import mage.game.match.MatchPlayer; +import mage.players.Player; +import mage.util.CardUtil; +import org.apache.log4j.Logger; +import org.jsoup.Jsoup; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Stream; + +/** + * Data collector that can collect and save whole games history and store it in disk system. + *

+ * WARNING, it's not production ready yet, use for load tests only (see todos below) + *

+ * Possible use cases: + * - load tests debugging to find freeze AI games; + * - public server debugging to find human freeze games; + * - AI learning data collection; + * - fast access to saved decks for public tourneys, e.g. after draft + * Tasks: + * - TODO: drafts - save picks history per player; + * - TODO: tourneys - fix chat logs + * - TODO: tourneys - fix miss end events on table or server quite (active table/game freeze bug) + *

+ * Data structure example: + * - gamesHistory + * - 2025-07-04 + * - tables_active + * - tables_done + * - table 1 - UUID + * - table_logs.txt + * - games_done + * - games_active + * - game 1 - UUID + * - game 2 - UUID + * - game_logs.html + * - chat_logs.html + * - deck_player_1.dck + * - deck_player_2.dck + * + * @author JayDi85 + */ +public class SaveGameHistoryDataCollector extends EmptyDataCollector { + + private static final Logger logger = Logger.getLogger(SaveGameHistoryDataCollector.class); + public static final String SERVICE_CODE = "saveGameHistory"; + + private static final String DIR_NAME_ROOT = "gamesHistory"; + private static final SimpleDateFormat DIR_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + private static final String DIR_NAME_TABLES_ACTIVE = "tables_active"; + private static final String DIR_NAME_TABLES_DONE = "tables_done"; + private static final String DIR_NAME_GAMES_ACTIVE = "games_active"; + private static final String DIR_NAME_GAMES_DONE = "games_done"; + + private static final String TABLE_LOGS_FILE_NAME = "table_logs.txt"; + private static final String TABLE_CHAT_FILE_NAME = "table_chat.txt"; + private static final String DECK_FILE_NAME_FORMAT = "deck_player_%d.dck"; + private static final String GAME_LOGS_FILE_NAME = "game_logs.html"; + private static final String GAME_CHAT_FILE_NAME = "game_chat.txt"; + + private static final UUID NO_TABLE_ID = UUID.randomUUID(); + private static final String NO_TABLE_NAME = "SINGLE"; // need for unit tests + + // global switch + boolean enabled; + + // prepared dirs for each table or game - if it returns empty string then logs will be disabled, e.g. on too many data + Map tableDirs = new ConcurrentHashMap<>(); + Map gameDirs = new ConcurrentHashMap<>(); + + // all write operations must be done in single thread + // TODO: analyse load tests performance and split locks per table/game + // TODO: limit file sizes for possible game freeze? + ReentrantLock writeLock = new ReentrantLock(); + + public SaveGameHistoryDataCollector() { + // prepare + Path root = Paths.get(DIR_NAME_ROOT); + if (!Files.exists(root)) { + try { + Files.createDirectories(root); + } catch (IOException e) { + logger.error("Can't create root dir, games history data collector will be disabled - " + e, e); + this.enabled = false; + return; + } + } + + this.enabled = true; + } + + @Override + public String getServiceCode() { + return SERVICE_CODE; + } + + @Override + public String getInitInfo() { + return "save all game history to " + Paths.get(DIR_NAME_ROOT, DIR_DATE_FORMAT.format(new Date())).toAbsolutePath(); + } + + @Override + public void onTableStart(Table table) { + if (!this.enabled) return; + writeToTableLogsFile(table, new Date() + " [START] " + table.getId() + ", " + table); + } + + @Override + public void onTableEnd(Table table) { + if (!this.enabled) return; + writeToTableLogsFile(table, new Date() + " [END] " + table.getId() + ", " + table); + + // good end - move all files to done folder and change dir refs for possible game and other logs + writeLock.lock(); + try { + String oldFolder = getOrCreateTableDir(table.getId(), table.getParentTableId(), table.getTableIndex(), false); + if (oldFolder.contains(DIR_NAME_TABLES_ACTIVE)) { + // move files + String newFolder = oldFolder.replace(DIR_NAME_TABLES_ACTIVE, DIR_NAME_TABLES_DONE); + Files.createDirectories(Paths.get(newFolder)); + Files.move(Paths.get(oldFolder), Paths.get(newFolder), StandardCopyOption.REPLACE_EXISTING); + + // update all refs (table and games) + this.tableDirs.put(table.getId(), newFolder); + this.gameDirs.replaceAll((gameId, gameFolder) -> + gameFolder.startsWith(oldFolder) + ? gameFolder.replace(oldFolder, newFolder) + : gameFolder + ); + innerDirDeleteEmptyParent(oldFolder); + } + } catch (IOException e) { + logger.error("Can't move table files to done folder: " + e, e); + } finally { + writeLock.unlock(); + } + } + + @Override + public void onGameStart(Game game) { + if (!this.enabled) return; + writeToGameLogsFile(game, new Date() + " [START] " + game.getId() + ", " + game); + + // save deck files + writeLock.lock(); + try { + String gameDir = getOrCreateGameDir(game, isActive(game)); + if (gameDir.isEmpty()) { + return; + } + int playerNum = 0; + for (Player player : game.getPlayers().values()) { + playerNum++; + MatchPlayer matchPlayer = player.getMatchPlayer(); + if (matchPlayer != null && matchPlayer.getDeck() != null) { + String deckFile = Paths.get(gameDir, String.format(DECK_FILE_NAME_FORMAT, playerNum)).toString(); + DeckFormats.XMAGE.getExporter().writeDeck(deckFile, matchPlayer.getDeckForViewer().prepareCardsOnlyDeck()); + } + } + } catch (IOException e) { + logger.error("Can't write deck file for game " + game.getId() + ": " + e, e); + } finally { + writeLock.unlock(); + } + } + + @Override + public void onGameLog(Game game, String message) { + if (!this.enabled) return; + writeToGameLogsFile(game, new Date() + " [LOG] " + CardUtil.getTurnInfo(game) + ": " + message); + } + + @Override + public void onGameEnd(Game game) { + if (!this.enabled) return; + writeToGameLogsFile(game, new Date() + " [END] " + game.getId() + ", " + game); + + // good end - move game data to done folder + writeLock.lock(); + try { + String oldFolder = getOrCreateGameDir(game, false); + if (oldFolder.contains(DIR_NAME_GAMES_ACTIVE)) { + // move files + String newFolder = oldFolder.replace(DIR_NAME_GAMES_ACTIVE, DIR_NAME_GAMES_DONE); + Files.createDirectories(Paths.get(newFolder)); + Files.move(Paths.get(oldFolder), Paths.get(newFolder), StandardCopyOption.REPLACE_EXISTING); + + // update all refs + this.gameDirs.replaceAll((gameId, gameFolder) -> + gameFolder.startsWith(oldFolder) + ? gameFolder.replace(oldFolder, newFolder) + : gameFolder + ); + innerDirDeleteEmptyParent(oldFolder); + } + } catch (IOException e) { + logger.error("Can't move game files to done folder: " + e, e); + } finally { + writeLock.unlock(); + } + } + + @Override + public void onChatTourney(UUID tourneyId, String userName, String message) { + // TODO: implement? + } + + @Override + public void onChatTable(UUID tableId, String userName, String message) { + if (!this.enabled) return; + String needMessage = Jsoup.parse(message).text(); // convert html to txt format, so users can't break something + writeToTableChatFile(tableId, new Date() + " [CHAT] " + (userName == null ? "system" : userName) + ": " + needMessage); + } + + @Override + public void onChatGame(UUID gameId, String userName, String message) { + if (!this.enabled) return; + String needMessage = Jsoup.parse(message).text(); // convert html to txt format, so users can't break something + writeToGameChatFile(gameId, new Date() + " [CHAT] " + (userName == null ? "system" : userName) + ": " + needMessage); + } + + + private String getOrCreateTableDir(UUID tableId) { + return this.tableDirs.getOrDefault(tableId, ""); + } + + private String getOrCreateTableDir(UUID tableId, UUID parentTableId, Integer tableIndex, boolean isActive) { + // unit tests don't have tables, so write it to custom table + String needName; + UUID needId; + if (tableId == null) { + needId = NO_TABLE_ID; + needName = String.format("table %s", NO_TABLE_NAME); + } else { + needId = tableId; + if (parentTableId == null) { + needName = String.format("table %s - %s", tableIndex, tableId); + } else { + needName = String.format("table %s - %s_%s", tableIndex, parentTableId, tableId); + } + } + + String needDate = DIR_DATE_FORMAT.format(new Date()); + String needStatus = isActive ? DIR_NAME_TABLES_ACTIVE : DIR_NAME_TABLES_DONE; + + String tableDir = Paths.get( + DIR_NAME_ROOT, + needDate, + needStatus, + needName + ).toString(); + + return this.tableDirs.computeIfAbsent(needId, x -> innerDirCreate(tableDir)); + } + + private String getOrCreateGameDir(UUID gameId) { + // some events don't have full game info and must come after real game start + return this.gameDirs.getOrDefault(gameId, ""); + } + + private String getOrCreateGameDir(Game game, boolean isActive) { + AtomicBoolean isNewDir = new AtomicBoolean(false); + String res = this.gameDirs.computeIfAbsent(game.getId(), x -> { + isNewDir.set(true); + // if you find "table 0 - UUID" folders with real server then game logs come before table then + // it's almost impossible and nothing to do with it + String tableDir = getOrCreateTableDir(game.getTableId(), null, 0, true); + if (tableDir.isEmpty()) { + // disabled + return ""; + } + + String needStatus = isActive ? DIR_NAME_GAMES_ACTIVE : DIR_NAME_GAMES_DONE; + String needName = String.format("game %s - %s", game.getGameIndex(), game.getId()); + String gameDir = Paths.get( + tableDir, + needStatus, + needName + ).toString(); + + return innerDirCreate(gameDir); + }); + + // workaround for good background color in html logs + if (isNewDir.get()) { + writeToGameLogsFile(game, ""); + } + + return res; + } + + private String innerDirCreate(String destFileOrDir) { + Path dir = Paths.get(destFileOrDir); + if (!Files.exists(dir)) { + try { + Files.createDirectories(dir); + } catch (IOException ignore) { + return ""; + } + } + return dir.toString(); + } + + private void innerDirDeleteEmptyParent(String dir) throws IOException { + // delete empty parent folder, e.g. after move all games from active to done folder + Path parentPath = Paths.get(dir).getParent(); + if (Files.exists(parentPath) && Files.isDirectory(parentPath)) { + try (Stream entries = Files.list(parentPath)) { + if (!entries.findAny().isPresent()) { + Files.delete(parentPath); + } + } + } + } + + private void writeToTableLogsFile(Table table, String data) { + String tableDir = getOrCreateTableDir(table.getId(), table.getParentTableId(), table.getTableIndex(), isActive(table)); + if (tableDir.isEmpty()) { + return; + } + writeToFile(Paths.get(tableDir, TABLE_LOGS_FILE_NAME).toString(), data, "\n"); + } + + private void writeToTableChatFile(UUID tableId, String data) { + String gameDir = getOrCreateTableDir(tableId); + if (gameDir.isEmpty()) { + return; + } + writeToFile(Paths.get(gameDir, TABLE_CHAT_FILE_NAME).toString(), data, "\n"); + } + + private void writeToGameLogsFile(Game game, String data) { + String gameDir = getOrCreateGameDir(game, isActive(game)); + if (gameDir.isEmpty()) { + return; + } + writeToFile(Paths.get(gameDir, GAME_LOGS_FILE_NAME).toString(), data, "\n
\n"); + } + + private void writeToGameChatFile(UUID gameId, String data) { + String gameDir = getOrCreateGameDir(gameId); + if (gameDir.isEmpty()) { + return; + } + writeToFile(Paths.get(gameDir, GAME_CHAT_FILE_NAME).toString(), data, "\n"); + } + + private void writeToFile(String destFile, String data, String newLine) { + writeLock.lock(); + try { + try { + String newData = newLine + data; + Files.write(Paths.get(destFile), newData.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } catch (IOException ignore) { + } + } finally { + writeLock.unlock(); + } + } + + private boolean isActive(Table table) { + return !TableState.FINISHED.equals(table.getState()); + } + + private boolean isActive(Game game) { + return game.getState() == null || !game.getState().isGameOver(); + } +} diff --git a/Mage/src/main/java/mage/constants/AbilityWord.java b/Mage/src/main/java/mage/constants/AbilityWord.java index 6e79b03c841..c8044d3c38c 100644 --- a/Mage/src/main/java/mage/constants/AbilityWord.java +++ b/Mage/src/main/java/mage/constants/AbilityWord.java @@ -63,6 +63,7 @@ public enum AbilityWord { THRESHOLD("Threshold"), UNDERGROWTH("Undergrowth"), VALIANT("Valiant"), + VOID("Void"), WILL_OF_THE_COUNCIL("Will of the council"), WILL_OF_THE_PLANESWALKERS("Will of the planeswalkers"); diff --git a/Mage/src/main/java/mage/constants/AffinityType.java b/Mage/src/main/java/mage/constants/AffinityType.java index a49fe5f9bfc..de6fb741683 100644 --- a/Mage/src/main/java/mage/constants/AffinityType.java +++ b/Mage/src/main/java/mage/constants/AffinityType.java @@ -41,6 +41,7 @@ public enum AffinityType { LIZARDS(new FilterControlledPermanent(SubType.LIZARD, "Lizards")), BIRDS(new FilterControlledPermanent(SubType.BIRD, "Birds")), CITIZENS(new FilterControlledPermanent(SubType.CITIZEN, "Citizens")), + SLIVERS(new FilterControlledPermanent(SubType.SLIVER, "Slivers")), TOWNS(new FilterControlledPermanent(SubType.TOWN, "Towns")), GATES(new FilterControlledPermanent(SubType.GATE, "Gates"), GatesYouControlHint.instance), SNOW_LANDS(AffinityFilters.SNOW_LANDS), diff --git a/Mage/src/main/java/mage/constants/ModeChoice.java b/Mage/src/main/java/mage/constants/ModeChoice.java index 1759d2dece8..329a086285d 100644 --- a/Mage/src/main/java/mage/constants/ModeChoice.java +++ b/Mage/src/main/java/mage/constants/ModeChoice.java @@ -18,7 +18,7 @@ public enum ModeChoice { SULTAI("Sultai"), MIRRAN("Mirran"), - PHYREXIAN("Phyrexian "), + PHYREXIAN("Phyrexian"), ODD("odd"), EVEN("even"), diff --git a/Mage/src/main/java/mage/constants/Outcome.java b/Mage/src/main/java/mage/constants/Outcome.java index 1e976f9ad7b..c6bdd119cd2 100644 --- a/Mage/src/main/java/mage/constants/Outcome.java +++ b/Mage/src/main/java/mage/constants/Outcome.java @@ -14,14 +14,14 @@ public enum Outcome { LoseLife(false), ExtraTurn(true), BecomeCreature(true), - PutCreatureInPlay(true), - PutCardInPlay(true), - PutLandInPlay(true), + PutCreatureInPlay(true, true), + PutCardInPlay(true, true), + PutLandInPlay(true, true), GainControl(false), DrawCard(true), Discard(false), Sacrifice(false), - PlayForFree(true), + PlayForFree(true, true), ReturnToHand(false), Exile(false), Protect(true), @@ -46,7 +46,7 @@ public enum Outcome { // AI sorting targets by priorities (own or opponents) and selects most valueable or weakest private final boolean good; - // no different between own or opponent targets (example: copy must choose from all permanents) + // no different between own or opponent targets (example: copy must choose from all permanents, free cast from selected cards, etc) private boolean anyTargetHasSameValue; Outcome(boolean good) { diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 78d376b1d53..b354e9dca7f 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -31,6 +31,7 @@ public enum SubType { GATE("Gate", SubTypeSet.NonBasicLandType), LAIR("Lair", SubTypeSet.NonBasicLandType), LOCUS("Locus", SubTypeSet.NonBasicLandType), + PLANET("Planet", SubTypeSet.NonBasicLandType), SPHERE("Sphere", SubTypeSet.NonBasicLandType), URZAS("Urza's", SubTypeSet.NonBasicLandType), MINE("Mine", SubTypeSet.NonBasicLandType), @@ -61,8 +62,10 @@ public enum SubType { GOLD("Gold", SubTypeSet.ArtifactType), INCUBATOR("Incubator", SubTypeSet.ArtifactType), JUNK("Junk", SubTypeSet.ArtifactType), + LANDER("Lander", SubTypeSet.ArtifactType), MAP("Map", SubTypeSet.ArtifactType), POWERSTONE("Powerstone", SubTypeSet.ArtifactType), + SPACECRAFT("Spacecraft", SubTypeSet.ArtifactType), TREASURE("Treasure", SubTypeSet.ArtifactType), VEHICLE("Vehicle", SubTypeSet.ArtifactType), // 205.3m : Creatures and kindreds share their lists of subtypes; these subtypes are called creature types. @@ -157,6 +160,7 @@ public enum SubType { DRAGON("Dragon", SubTypeSet.CreatureType), DRAKE("Drake", SubTypeSet.CreatureType), DREADNOUGHT("Dreadnought", SubTypeSet.CreatureType), + DRIX("Drix", SubTypeSet.CreatureType), DROID("Droid", SubTypeSet.CreatureType, true), // Star Wars DRONE("Drone", SubTypeSet.CreatureType), DRUID("Druid", SubTypeSet.CreatureType), @@ -186,7 +190,7 @@ public enum SubType { FROG("Frog", SubTypeSet.CreatureType), FUNGUS("Fungus", SubTypeSet.CreatureType), // G - GAMER("Gamer", SubTypeSet.CreatureType, true), // Un-sets + GAMER("Gamer", SubTypeSet.CreatureType), GAMORREAN("Gamorrean", SubTypeSet.CreatureType, true), // Star Wars GAND("Gand", SubTypeSet.CreatureType, true), // Star Wars GARGOYLE("Gargoyle", SubTypeSet.CreatureType), @@ -261,7 +265,7 @@ public enum SubType { LICID("Licid", SubTypeSet.CreatureType), LIZARD("Lizard", SubTypeSet.CreatureType), LLAMA("Llama", SubTypeSet.CreatureType), - LOBSTER("Lobster", SubTypeSet.CreatureType, true), // Unglued + LOBSTER("Lobster", SubTypeSet.CreatureType), // M MANTELLIAN("Mantellian", SubTypeSet.CreatureType, true), // Star Wars MANTICORE("Manticore", SubTypeSet.CreatureType), @@ -355,7 +359,7 @@ public enum SubType { SAPROLING("Saproling", SubTypeSet.CreatureType), SATYR("Satyr", SubTypeSet.CreatureType), SCARECROW("Scarecrow", SubTypeSet.CreatureType), - SCIENTIST("Scientist", SubTypeSet.CreatureType, true), // Unstable + SCIENTIST("Scientist", SubTypeSet.CreatureType), SCION("Scion", SubTypeSet.CreatureType), SCORPION("Scorpion", SubTypeSet.CreatureType), SCOUT("Scout", SubTypeSet.CreatureType), @@ -426,7 +430,7 @@ public enum SubType { VAMPIRE("Vampire", SubTypeSet.CreatureType), VARMINT("Varmint", SubTypeSet.CreatureType), VEDALKEN("Vedalken", SubTypeSet.CreatureType), - VILLAIN("Villain", SubTypeSet.CreatureType, true), // Unstable + VILLAIN("Villain", SubTypeSet.CreatureType), VOLVER("Volver", SubTypeSet.CreatureType), // W WALL("Wall", SubTypeSet.CreatureType), diff --git a/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java b/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java index 5592a991ce2..3d0967649a4 100644 --- a/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java +++ b/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java @@ -48,14 +48,18 @@ public class FilterPermanentThisOrAnother extends FilterPermanent { } protected static String generateFilterMessage(FilterPermanent otherFilter) { + StringBuilder sb = new StringBuilder("{this} or another "); // Remove the indefinite article from the beginning of the message: - String otherFilterMessage = otherFilter.getMessage(); - if (otherFilterMessage.startsWith("a ")) { - otherFilterMessage = otherFilterMessage.substring(2); - } else if (otherFilterMessage.startsWith("an ")) { - otherFilterMessage = otherFilterMessage.substring(3); + String message = otherFilter.getMessage(); + if (message.startsWith("a ")) { + sb.append(message.substring(2)); + } else if (message.startsWith("an ")) { + sb.append(message.substring(3)); + } else if (message.startsWith("another ")) { + sb.append(message.substring(8)); + } else { + sb.append(message); } - - return "{this} or another " + otherFilterMessage; + return sb.toString(); } } diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 6680018c2fb..0a5b6fdf79a 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -1,6 +1,7 @@ package mage.filter; import mage.ObjectColor; +import mage.abilities.keyword.FlyingAbility; import mage.constants.*; import mage.counters.CounterType; import mage.filter.common.*; @@ -412,6 +413,13 @@ public final class StaticFilters { FILTER_CONTROLLED_A_PERMANENT.setLockedFilter(true); } + public static final FilterControlledPermanent FILTER_CONTROLLED_UNTAPPED_PERMANENT = new FilterControlledPermanent("an untapped permanent you control"); + + static { + FILTER_CONTROLLED_UNTAPPED_PERMANENT.add(TappedPredicate.UNTAPPED); + FILTER_CONTROLLED_UNTAPPED_PERMANENT.setLockedFilter(true); + } + public static final FilterControlledPermanent FILTER_CONTROLLED_ANOTHER_PERMANENT = new FilterControlledPermanent("another permanent you control"); static { @@ -909,6 +917,13 @@ public final class StaticFilters { FILTER_PERMANENT_BATTLES.setLockedFilter(true); } + public static final FilterPermanent FILTER_PERMANENT_NON_CREATURE = new FilterPermanent("noncreature permanent"); + + static { + FILTER_PERMANENT_NON_CREATURE.add(Predicates.not(CardType.CREATURE.getPredicate())); + FILTER_PERMANENT_NON_CREATURE.setLockedFilter(true); + } + public static final FilterNonlandPermanent FILTER_PERMANENT_NON_LAND = new FilterNonlandPermanent(); static { @@ -1145,6 +1160,13 @@ public final class StaticFilters { FILTER_CREATURES_NON_TOKEN.setLockedFilter(true); } + public static final FilterCreaturePermanent FILTER_CREATURE_FLYING = new FilterCreaturePermanent("creature with flying"); + + static { + FILTER_CREATURE_FLYING.add(new AbilityPredicate(FlyingAbility.class)); + FILTER_CREATURE_FLYING.setLockedFilter(true); + } + public static final FilterControlledCreaturePermanent FILTER_A_CONTROLLED_CREATURE_P1P1 = new FilterControlledCreaturePermanent("a creature you control with a +1/+1 counter on it"); static { diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 13846b795b8..ad0cd9232d4 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -48,6 +48,11 @@ import java.util.stream.Collectors; public interface Game extends MageItem, Serializable, Copyable { + /** + * Return global game index (used for better logs and history) + */ + Integer getGameIndex(); + MatchType getGameType(); int getNumPlayers(); @@ -137,6 +142,7 @@ public interface Game extends MageItem, Serializable, Copyable { Map getPermanentsEntering(); Map> getLKI(); + Map> getPermanentCostsTags(); /** @@ -171,9 +177,9 @@ public interface Game extends MageItem, Serializable, Copyable { PlayerList getPlayerList(); /** - * Returns opponents list in range for the given playerId. Use it to interate by starting turn order. - * - * Warning, it will return leaved players until end of turn. For dialogs and one shot effects use excludeLeavedPlayers + * Returns opponents list in range for the given playerId. Use it to interate by starting turn order. + *

+ * Warning, it will return leaved players until end of turn. For dialogs and one shot effects use excludeLeavedPlayers */ // TODO: check usage of getOpponents in cards and replace with correct call of excludeLeavedPlayers, see #13289 default Set getOpponents(UUID playerId) { @@ -181,8 +187,8 @@ public interface Game extends MageItem, Serializable, Copyable { } /** - * Returns opponents list in range for the given playerId. Use it to interate by starting turn order. - * Warning, it will return dead players until end of turn. + * Returns opponents list in range for the given playerId. Use it to interate by starting turn order. + * Warning, it will return dead players until end of turn. * * @param excludeLeavedPlayers exclude dead player immediately without waiting range update on next turn */ @@ -245,7 +251,7 @@ public interface Game extends MageItem, Serializable, Copyable { /** * Id of the player the current turn it is. - * + *

* Player can be under control of another player, so search a real GUI's controller by Player->getTurnControlledBy * * @return @@ -563,14 +569,15 @@ public interface Game extends MageItem, Serializable, Copyable { */ void processAction(); - @Deprecated // TODO: must research usage and remove it from all non engine code (example: Bestow ability, ProcessActions must be used instead) + @Deprecated + // TODO: must research usage and remove it from all non engine code (example: Bestow ability, ProcessActions must be used instead) boolean checkStateAndTriggered(); /** * Play priority by all players * * @param activePlayerId starting priority player - * @param resuming false to reset passed priority and ask it again + * @param resuming false to reset passed priority and ask it again */ void playPriority(UUID activePlayerId, boolean resuming); @@ -600,6 +607,7 @@ public interface Game extends MageItem, Serializable, Copyable { /** * TODO: remove logic changed, must research each usage of removeBookmark and replace it with new code + * * @param bookmark */ void removeBookmark_v2(int bookmark); @@ -620,6 +628,7 @@ public interface Game extends MageItem, Serializable, Copyable { // game cheats (for tests only) void cheat(UUID ownerId, Map commands); + void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard, List command, List exiled); // controlling the behaviour of replacement effects while permanents entering the battlefield @@ -811,4 +820,8 @@ public interface Game extends MageItem, Serializable, Copyable { boolean isGameStopped(); boolean isTurnOrderReversed(); + + UUID getTableId(); + + void setTableId(UUID tableId); } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index e48ef287395..e3f59becee3 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -27,6 +27,7 @@ import mage.cards.*; import mage.cards.decks.Deck; import mage.cards.decks.DeckCardInfo; import mage.choices.Choice; +import mage.collectors.DataCollectorServices; import mage.constants.*; import mage.counters.CounterType; import mage.counters.Counters; @@ -94,6 +95,8 @@ import java.util.stream.Collectors; */ public abstract class GameImpl implements Game { + private final static AtomicInteger GLOBAL_INDEX = new AtomicInteger(); + private static final int ROLLBACK_TURNS_MAX = 4; private static final String UNIT_TESTS_ERROR_TEXT = "Error in unit tests"; private static final Logger logger = Logger.getLogger(GameImpl.class); @@ -108,6 +111,8 @@ public abstract class GameImpl implements Game { protected AtomicInteger totalErrorsCount = new AtomicInteger(); // for debug only: error stats protected final UUID id; + protected final Integer gameIndex; // for better logs and history + protected UUID tableId = null; protected boolean ready; protected transient TableEventSource tableEventSource = new TableEventSource(); @@ -171,6 +176,7 @@ public abstract class GameImpl implements Game { public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int minimumDeckSize, int startingLife, int startingHandSize) { this.id = UUID.randomUUID(); + this.gameIndex = GLOBAL_INDEX.incrementAndGet(); this.range = range; this.mulligan = mulligan; this.attackOption = attackOption; @@ -191,6 +197,8 @@ public abstract class GameImpl implements Game { this.checkPlayableState = game.checkPlayableState; this.id = game.id; + this.gameIndex = game.gameIndex; + this.tableId = game.tableId; this.totalErrorsCount.set(game.totalErrorsCount.get()); this.ready = game.ready; @@ -250,6 +258,11 @@ public abstract class GameImpl implements Game { */ } + @Override + public Integer getGameIndex() { + return this.gameIndex; + } + @Override public boolean isSimulation() { return simulation; @@ -887,6 +900,12 @@ public abstract class GameImpl implements Game { if (state.isGameOver()) { return true; } + + // stop on game thread ended by third party tools or AI's timeout + if (Thread.currentThread().isInterrupted()) { + return true; + } + int remainingPlayers = 0; int numLosers = 0; for (Player player : state.getPlayers().values()) { @@ -897,7 +916,10 @@ public abstract class GameImpl implements Game { numLosers++; } } - if (remainingPlayers <= 1 || numLosers >= state.getPlayers().size() - 1) { + + // stop on no more active players + boolean noMorePlayers = remainingPlayers <= 1 || numLosers >= state.getPlayers().size() - 1; + if (noMorePlayers) { end(); if (remainingPlayers == 0 && logger.isDebugEnabled()) { logger.debug("DRAW for gameId: " + getId()); @@ -1038,6 +1060,7 @@ public abstract class GameImpl implements Game { @Override public void start(UUID choosingPlayerId) { startTime = new Date(); + DataCollectorServices.getInstance().onGameStart(this); if (state.getPlayers().values().iterator().hasNext()) { init(choosingPlayerId); play(startingPlayerId); @@ -1553,6 +1576,11 @@ public abstract class GameImpl implements Game { endTime = new Date(); state.endGame(); + // cancel all player dialogs/feedbacks + for (Player player : state.getPlayers().values()) { + player.abort(); + } + // inform players about face down cards state.getBattlefield().getAllPermanents() .stream() @@ -1572,10 +1600,7 @@ public abstract class GameImpl implements Game { .sorted() .forEach(this::informPlayers); - // cancel all player dialogs/feedbacks - for (Player player : state.getPlayers().values()) { - player.abort(); - } + DataCollectorServices.getInstance().onGameEnd(this); } } @@ -1782,7 +1807,7 @@ public abstract class GameImpl implements Game { // count total errors Player activePlayer = this.getPlayer(getActivePlayerId()); - if (activePlayer != null && !activePlayer.isTestsMode()) { + if (activePlayer != null && !activePlayer.isTestMode() && !activePlayer.isFastFailInTestMode()) { // real game - try to continue priorityErrorsCount++; continue; @@ -2794,7 +2819,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, null, this)) { + if (!((AttachableToRestrictedAbility) ability).canEquip(attachedTo.getId(), null, this)) { attachedTo = null; break; } @@ -3164,6 +3189,8 @@ public abstract class GameImpl implements Game { @Override public void informPlayers(String message) { + DataCollectorServices.getInstance().onGameLog(this, message); + // Uncomment to print game messages // System.out.println(message.replaceAll("\\<.*?\\>", "")); if (simulation) { @@ -4239,4 +4266,14 @@ public abstract class GameImpl implements Game { .append(this.getState().isGameOver() ? "; FINISHED: " + this.getWinner() : ""); return sb.toString(); } + + @Override + public UUID getTableId() { + return this.tableId; + } + + @Override + public void setTableId(UUID tableId) { + this.tableId = tableId; + } } diff --git a/Mage/src/main/java/mage/game/Table.java b/Mage/src/main/java/mage/game/Table.java index a7c84529f82..1126d199058 100644 --- a/Mage/src/main/java/mage/game/Table.java +++ b/Mage/src/main/java/mage/game/Table.java @@ -3,7 +3,10 @@ package mage.game; import java.io.Serializable; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + import mage.cards.decks.DeckValidator; +import mage.collectors.DataCollectorServices; import mage.constants.TableState; import mage.game.draft.Draft; import mage.game.events.Listener; @@ -20,7 +23,10 @@ import mage.players.PlayerType; */ public class Table implements Serializable { + private final static AtomicInteger GLOBAL_INDEX = new AtomicInteger(); + private UUID tableId; + private Integer tableIndex; // for better logs and history private UUID roomId; private String name; private String controllerName; @@ -54,17 +60,23 @@ public class Table implements Serializable { this.tournament = tournament; this.isTournament = true; setState(TableState.WAITING); + + DataCollectorServices.getInstance().onTableStart(this); } public Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List playerTypes, TableRecorder recorder, Match match, Set bannedUsernames, boolean isPlaneChase) { this(roomId, gameType, name, controllerName, validator, playerTypes, recorder, bannedUsernames, isPlaneChase); this.match = match; + this.match.setTableId(this.getId()); this.isTournament = false; setState(TableState.WAITING); + + DataCollectorServices.getInstance().onTableStart(this); } protected Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List playerTypes, TableRecorder recorder, Set bannedUsernames, boolean isPlaneChase) { - tableId = UUID.randomUUID(); + this.tableId = UUID.randomUUID(); + this.tableIndex = GLOBAL_INDEX.incrementAndGet(); this.roomId = roomId; this.numSeats = playerTypes.size(); this.gameType = gameType; @@ -91,6 +103,10 @@ public class Table implements Serializable { return tableId; } + public Integer getTableIndex() { + return tableIndex; + } + public UUID getParentTableId() { return parentTableId; } @@ -132,6 +148,8 @@ public class Table implements Serializable { setState(TableState.FINISHED); // otherwise the table can be removed completely } this.validator = null; + + DataCollectorServices.getInstance().onTableEnd(this); } /** diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 333e786b95a..1fa776ffc2f 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -695,7 +695,8 @@ public class Combat implements Serializable, Copyable { // real game: send warning // test: fast fail game.informPlayers(controller.getLogName() + ": WARNING - AI can't find good blocker combination and will skip it - report your battlefield to github - " + game.getCombat()); - if (controller.isTestsMode()) { + if (controller.isTestMode() && controller.isFastFailInTestMode()) { + // fast fail in tests // how-to fix: AI code must support failed abilities or use cases throw new IllegalArgumentException("AI can't find good blocker combination"); } diff --git a/Mage/src/main/java/mage/game/command/emblems/AurraSingBaneOfJediEmblem.java b/Mage/src/main/java/mage/game/command/emblems/AurraSingBaneOfJediEmblem.java index 746ea66785b..fc9f282fefa 100644 --- a/Mage/src/main/java/mage/game/command/emblems/AurraSingBaneOfJediEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/AurraSingBaneOfJediEmblem.java @@ -13,7 +13,7 @@ public final class AurraSingBaneOfJediEmblem extends Emblem { // Whenever a nontoken creature you control leaves the battlefied, discard a card. public AurraSingBaneOfJediEmblem() { - super("Emblem Aurra Sing, Bane of Jedi"); + super("Emblem Aurra Sing"); getAbilities().add(new LeavesBattlefieldAllTriggeredAbility(Zone.COMMAND, new DiscardControllerEffect(1), StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN, false)); } diff --git a/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java b/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java index 7f72bf4b36c..c401b0d2bfb 100644 --- a/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java @@ -1,5 +1,6 @@ package mage.game.command.emblems; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.condition.Condition; import mage.abilities.effects.OneShotEffect; @@ -34,6 +35,13 @@ public class RadiationEmblem extends Emblem { Zone.ALL, TargetController.YOU, new RadiationEffect(), false ).withInterveningIf(RadiationCondition.instance).setTriggerPhrase("At the beginning of each player's precombat main phase, ")); + // radiation don't have source, so image can be initialized immediately + setSourceObjectAndInitImage(null); + } + + @Override + public void setSourceObjectAndInitImage(MageObject sourceObject) { + this.sourceObject = sourceObject; TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_RADIATION, null); if (foundInfo != null) { this.setExpansionSetCode(foundInfo.getSetCode()); diff --git a/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java b/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java index 3d30bdf66ce..50b64a0d6c7 100644 --- a/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java @@ -2,7 +2,7 @@ package mage.game.command.emblems; import mage.abilities.Ability; import mage.abilities.common.DiesCreatureTriggeredAbility; -import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.constants.Zone; import mage.filter.StaticFilters; @@ -21,7 +21,7 @@ public final class SephirothOneWingedAngelEmblem extends Emblem { Zone.COMMAND, new LoseLifeTargetEffect(1), false, StaticFilters.FILTER_PERMANENT_A_CREATURE, false ); - ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); ability.addTarget(new TargetOpponent()); this.getAbilities().add(ability); } diff --git a/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java b/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java index e5fb21ccf99..225f5f55f23 100644 --- a/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java @@ -1,5 +1,6 @@ package mage.game.command.emblems; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; @@ -40,11 +41,21 @@ public final class TheRingEmblem extends Emblem { filter.add(TheRingEmblemPredicate.instance); } + public TheRingEmblem() { + this((UUID) null); // require for verify test + } + public TheRingEmblem(UUID controllerId) { super("The Ring"); this.setControllerId(controllerId); // ring don't have source, so image can be initialized immediately + setSourceObjectAndInitImage(null); + } + + @Override + public void setSourceObjectAndInitImage(MageObject sourceObject) { + this.sourceObject = sourceObject; TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_THE_RING, null); if (foundInfo != null) { this.setExpansionSetCode(foundInfo.getSetCode()); diff --git a/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java b/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java index d4f396adaa8..d051f212ef6 100644 --- a/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java @@ -1,5 +1,6 @@ package mage.game.command.emblems; +import mage.MageObject; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.InfoEffect; import mage.abilities.hint.Hint; @@ -20,6 +21,13 @@ public class XmageHelperEmblem extends Emblem { super("Helper Emblem"); this.frameStyle = FrameStyle.M15_NORMAL; + // helper don't have source, so image can be initialized immediately + setSourceObjectAndInitImage(null); + } + + @Override + public void setSourceObjectAndInitImage(MageObject sourceObject) { + this.sourceObject = sourceObject; TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_HELPER_EMBLEM, null); if (foundInfo != null) { this.setExpansionSetCode(foundInfo.getSetCode()); diff --git a/Mage/src/main/java/mage/game/command/emblems/YodaEmblem.java b/Mage/src/main/java/mage/game/command/emblems/YodaEmblem.java index c816a1051eb..a128a6df188 100644 --- a/Mage/src/main/java/mage/game/command/emblems/YodaEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/YodaEmblem.java @@ -18,7 +18,7 @@ public final class YodaEmblem extends Emblem { // You get an emblem with "Hexproof, you and your creatures have." public YodaEmblem() { - super("Emblem Yoda, Jedi Master"); + super("Emblem Yoda"); Effect effect = new GainAbilityControllerEffect(HexproofAbility.getInstance(), Duration.EndOfGame); effect.setText("Hexproof, you"); Ability ability = new SimpleStaticAbility(Zone.COMMAND, effect); diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 9a12cc41e44..632c2ede065 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -40,6 +40,7 @@ public class GameEvent implements Serializable { PREVENT_DAMAGE, PREVENTED_DAMAGE, //Turn-based events PLAY_TURN, EXTRA_TURN, + BEGIN_TURN, // event fired on actual begin of turn. CHANGE_PHASE, PHASE_CHANGED, CHANGE_STEP, STEP_CHANGED, BEGINNING_PHASE, BEGINNING_PHASE_PRE, BEGINNING_PHASE_POST, // The normal beginning phase -- at the beginning of turn @@ -227,6 +228,13 @@ public class GameEvent implements Serializable { sourceId sourceId of the mount playerId the id of the controlling player */ + STATION_PERMANENT, + /* STATION_PERMANENT + targetId the id of the creature stationing + sourceId sourceId of the spaceship or planet + playerId the id of the controlling player + amount how many counters are being added + */ CAST_SPELL, CAST_SPELL_LATE, /* SPELL_CAST, CAST_SPELL_LATE diff --git a/Mage/src/main/java/mage/game/events/MageEvent.java b/Mage/src/main/java/mage/game/events/MageEvent.java deleted file mode 100644 index 540890dc3e7..00000000000 --- a/Mage/src/main/java/mage/game/events/MageEvent.java +++ /dev/null @@ -1,13 +0,0 @@ - - -package mage.game.events; - -import java.io.Serializable; - -/** - * - * @author BetaSteward_at_googlemail.com - */ -public interface MageEvent extends Serializable { - -} diff --git a/Mage/src/main/java/mage/game/match/Match.java b/Mage/src/main/java/mage/game/match/Match.java index 76e74e71683..7f080674871 100644 --- a/Mage/src/main/java/mage/game/match/Match.java +++ b/Mage/src/main/java/mage/game/match/Match.java @@ -10,7 +10,7 @@ import mage.game.GameException; import mage.game.GameInfo; import mage.game.events.Listener; import mage.game.events.TableEvent; -import mage.game.result.ResultProtos.MatchProto; +import mage.game.result.ResultProtos.MatchProto; // on unknown package error must run and auto-generate proto classes by "mvn install -DskipTests" import mage.players.Player; /** diff --git a/Mage/src/main/java/mage/game/match/MatchImpl.java b/Mage/src/main/java/mage/game/match/MatchImpl.java index 3fb5def5119..0e0e2b1d119 100644 --- a/Mage/src/main/java/mage/game/match/MatchImpl.java +++ b/Mage/src/main/java/mage/game/match/MatchImpl.java @@ -192,6 +192,7 @@ public abstract class MatchImpl implements Match { } protected void initGame(Game game) throws GameException { + game.setTableId(this.tableId); addGame(); // raises only the number shufflePlayers(); for (MatchPlayer matchPlayer : this.players) { @@ -291,6 +292,9 @@ public abstract class MatchImpl implements Match { @Override public void setTableId(UUID tableId) { this.tableId = tableId; + + // sync tableId with all games (needs for data collectors) + this.games.forEach(game -> game.setTableId(tableId)); } @Override diff --git a/Mage/src/main/java/mage/game/permanent/token/DroneToken2.java b/Mage/src/main/java/mage/game/permanent/token/DroneToken2.java new file mode 100644 index 00000000000..99afb0362fd --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/DroneToken2.java @@ -0,0 +1,34 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.common.CanBlockOnlyFlyingAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class DroneToken2 extends TokenImpl { + + public DroneToken2() { + super("Drone Token", "1/1 colorless Drone artifact creature token with flying and \"This token can block only creatures with flying.\""); + cardType.add(CardType.ARTIFACT); + cardType.add(CardType.CREATURE); + subtype.add(SubType.DRONE); + power = new MageInt(1); + toughness = new MageInt(1); + + addAbility(FlyingAbility.getInstance()); + addAbility(new CanBlockOnlyFlyingAbility()); + } + + private DroneToken2(final DroneToken2 token) { + super(token); + } + + @Override + public DroneToken2 copy() { + return new DroneToken2(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/LanderToken.java b/Mage/src/main/java/mage/game/permanent/token/LanderToken.java new file mode 100644 index 00000000000..06d780af86c --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/LanderToken.java @@ -0,0 +1,45 @@ +package mage.game.permanent.token; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInLibrary; + +/** + * @author TheElk801 + */ +public final class LanderToken extends TokenImpl { + + public static String getReminderText() { + return "(It's an artifact with \"{2}, {T}, Sacrifice this token: " + + "Search your library for a basic land card, put it onto the " + + "battlefield tapped, then shuffle.\")"; + } + + public LanderToken() { + super("Lander Token", "Lander token"); + cardType.add(CardType.ARTIFACT); + subtype.add(SubType.LANDER); + + Ability ability = new SimpleActivatedAbility(new SearchLibraryPutInPlayEffect( + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND_A), true + ), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + private LanderToken(final LanderToken token) { + super(token); + } + + public LanderToken copy() { + return new LanderToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/MunitionsToken.java b/Mage/src/main/java/mage/game/permanent/token/MunitionsToken.java new file mode 100644 index 00000000000..846c32a77ec --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/MunitionsToken.java @@ -0,0 +1,30 @@ +package mage.game.permanent.token; + +import mage.abilities.Ability; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.constants.CardType; +import mage.target.common.TargetAnyTarget; + +/** + * @author TheElk801 + */ +public final class MunitionsToken extends TokenImpl { + + public MunitionsToken() { + super("Munitions", "colorless artifact token named Munitions with \"When this token leaves the battlefield, it deals 2 damage to any target.\""); + cardType.add(CardType.ARTIFACT); + + Ability ability = new LeavesBattlefieldTriggeredAbility(new DamageTargetEffect(2, "it")); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + } + + private MunitionsToken(final MunitionsToken token) { + super(token); + } + + public MunitionsToken copy() { + return new MunitionsToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/Spirit31Token.java b/Mage/src/main/java/mage/game/permanent/token/Spirit31Token.java new file mode 100644 index 00000000000..75e905ac67a --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/Spirit31Token.java @@ -0,0 +1,32 @@ +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 Spirit31Token extends TokenImpl { + + public Spirit31Token() { + super("Spirit Token", "3/1 white Spirit creature token with flying"); + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.SPIRIT); + power = new MageInt(3); + toughness = new MageInt(1); + + addAbility(FlyingAbility.getInstance()); + } + + private Spirit31Token(final Spirit31Token token) { + super(token); + } + + @Override + public Spirit31Token copy() { + return new Spirit31Token(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/VoiceOfResurgenceToken.java b/Mage/src/main/java/mage/game/permanent/token/VoiceOfResurgenceToken.java index 875a723aa46..27022d0b08c 100644 --- a/Mage/src/main/java/mage/game/permanent/token/VoiceOfResurgenceToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/VoiceOfResurgenceToken.java @@ -6,7 +6,6 @@ import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; /** * @author spjspj @@ -25,7 +24,7 @@ public final class VoiceOfResurgenceToken extends TokenImpl { // This creature's power and toughness are each equal to the number of creatures you control. this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessSourceEffect( - CreaturesYouControlCount.instance))); + CreaturesYouControlCount.PLURAL))); } private VoiceOfResurgenceToken(final VoiceOfResurgenceToken token) { diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 7cc905df6b6..70e66ba58ce 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -8,6 +8,7 @@ import mage.abilities.costs.mana.ManaCosts; import mage.abilities.keyword.BestowAbility; import mage.abilities.keyword.PrototypeAbility; import mage.abilities.keyword.TransformAbility; +import mage.abilities.keyword.WarpAbility; import mage.cards.*; import mage.constants.*; import mage.counters.Counter; @@ -421,6 +422,7 @@ public class Spell extends StackObjectImpl implements Card { } else { MageObjectReference mor = new MageObjectReference(getSpellAbility()); game.storePermanentCostsTags(mor, getSpellAbility()); + WarpAbility.addDelayedTrigger(getSpellAbility(), game); return controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null); } } diff --git a/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java b/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java index 2f19c2c2d97..7fdc106b767 100644 --- a/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java +++ b/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java @@ -42,14 +42,11 @@ public class MultiplayerRound { return this.roundNum; } - public void setMatch (Match match) { + public void setMatchAndTable(Match match, UUID tableId) { this.match = match; - } - - public void setTableId (UUID tableId) { this.tableId = tableId; } - + public boolean isRoundOver() { boolean roundIsOver = true; if (this.match != null) { diff --git a/Mage/src/main/java/mage/game/tournament/TournamentPairing.java b/Mage/src/main/java/mage/game/tournament/TournamentPairing.java index 3939ce40f0c..986887c6711 100644 --- a/Mage/src/main/java/mage/game/tournament/TournamentPairing.java +++ b/Mage/src/main/java/mage/game/tournament/TournamentPairing.java @@ -42,8 +42,9 @@ public class TournamentPairing { return match; } - public void setMatch(Match match) { + public void setMatchAndTable(Match match, UUID tableId) { this.match = match; + this.tableId = tableId; } /** @@ -88,10 +89,6 @@ public class TournamentPairing { return tableId; } - public void setTableId(UUID tableId) { - this.tableId = tableId; - } - public boolean isAlreadyPublished() { return alreadyPublished; } diff --git a/Mage/src/main/java/mage/game/turn/Turn.java b/Mage/src/main/java/mage/game/turn/Turn.java index 995c0b3c48f..d6597719b75 100644 --- a/Mage/src/main/java/mage/game/turn/Turn.java +++ b/Mage/src/main/java/mage/game/turn/Turn.java @@ -4,6 +4,7 @@ import mage.abilities.Ability; import mage.constants.PhaseStep; import mage.constants.TurnPhase; import mage.game.Game; +import mage.game.events.GameEvent; import mage.game.events.PhaseChangedEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; @@ -106,6 +107,8 @@ public class Turn implements Serializable { checkTurnIsControlledByOtherPlayer(game, activePlayer.getId()); game.getPlayer(activePlayer.getId()).beginTurn(game); + GameEvent event = new GameEvent(GameEvent.EventType.BEGIN_TURN, null, null, activePlayer.getId()); + game.fireEvent(event); for (Phase phase : phases) { if (game.isPaused() || game.checkIfGameIsOver()) { return false; diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index eb4fe3f69c0..5e3e89f8a8d 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -79,7 +79,13 @@ public interface Player extends MageItem, Copyable { */ boolean isHuman(); - boolean isTestsMode(); + boolean isTestMode(); + + void setTestMode(boolean value); + + boolean isFastFailInTestMode(); + + void setFastFailInTestMode(boolean value); /** * Current player is AI. Use it in card's code and all other places. @@ -398,8 +404,6 @@ public interface Player extends MageItem, Copyable { */ void setGameUnderYourControl(Game game, boolean value, boolean fullRestore); - void setTestMode(boolean value); - void setAllowBadMoves(boolean allowBadMoves); /** diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index e830b0d70bb..94d126e11f5 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -147,6 +147,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected Set inRange = new HashSet<>(); // players list in current range of influence (updates each turn due rules) protected boolean isTestMode = false; + protected boolean isFastFailInTestMode = true; protected boolean canGainLife = true; protected boolean canLoseLife = true; protected PayLifeCostLevel payLifeCostLevel = PayLifeCostLevel.allAbilities; @@ -2833,8 +2834,10 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public boolean canRespond() { // abort is checked here to get out of player requests (as example: after disconnect) - return isInGame() && !abort; + public boolean canRespond() { + // abort is checked here to get out of player requests (as example: after disconnect) + // thread is checked here to get out of AI game simulations or close by third party tools + return isInGame() && !abort && !Thread.currentThread().isInterrupted(); } @Override @@ -4602,7 +4605,7 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public boolean isTestsMode() { + public boolean isTestMode() { return isTestMode; } @@ -4611,6 +4614,16 @@ public abstract class PlayerImpl implements Player, Serializable { this.isTestMode = value; } + @Override + public boolean isFastFailInTestMode() { + return isFastFailInTestMode; + } + + @Override + public void setFastFailInTestMode(boolean value) { + this.isFastFailInTestMode = value; + } + @Override public boolean isTopCardRevealed() { return topCardRevealed; diff --git a/Mage/src/main/java/mage/target/Target.java b/Mage/src/main/java/mage/target/Target.java index b95d18c8550..c04395b2cc5 100644 --- a/Mage/src/main/java/mage/target/Target.java +++ b/Mage/src/main/java/mage/target/Target.java @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; /** * @author BetaSteward_at_googlemail.com @@ -63,6 +64,13 @@ public interface Target extends Copyable, Serializable { */ Set possibleTargets(UUID sourceControllerId, Ability source, Game game); + default Set possibleTargets(UUID sourceControllerId, Ability source, Game game, Set cards) { + // do not override + return possibleTargets(sourceControllerId, source, game).stream() + .filter(id -> cards == null || cards.contains(id)) + .collect(Collectors.toSet()); + } + /** * Priority method to make a choice from cards and other places, not a player.chooseXXX */ diff --git a/Mage/src/main/java/mage/target/TargetCard.java b/Mage/src/main/java/mage/target/TargetCard.java index 118ca88ed9e..3abc11591dc 100644 --- a/Mage/src/main/java/mage/target/TargetCard.java +++ b/Mage/src/main/java/mage/target/TargetCard.java @@ -110,6 +110,12 @@ public class TargetCard extends TargetObject { 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; @@ -214,6 +220,25 @@ public class TargetCard extends TargetObject { 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); @@ -245,6 +270,11 @@ public class TargetCard extends TargetObject { case COMMAND: possibleTargets.addAll(getAllPossibleTargetInCommandZone(game, player, sourceControllerId, source, filter, isNotTarget())); break; + case ALL: + possibleTargets.addAll(getAllPossibleTargetInAnyZone(game, player, sourceControllerId, source, filter, isNotTarget())); + break; + default: + throw new IllegalArgumentException("Unsupported TargetCard zone: " + zone); } } } @@ -332,6 +362,22 @@ public class TargetCard extends TargetObject { return possibleTargets; } + /** + * set of all matching target in ANY zone + */ + 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()); + } + } + } + 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 diff --git a/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java index a9e90993042..3536e38a3f3 100644 --- a/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java +++ b/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java @@ -120,6 +120,7 @@ public class EachTargetPointer extends TargetPointerImpl { @Override public String describeTargets(Targets targets, String defaultDescription) { + if (targetDescription != null) return targetDescription; if (targets.isEmpty()) { return defaultDescription; } diff --git a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java index 1a3b3d926bd..8d97aa4b5a1 100644 --- a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java +++ b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java @@ -40,6 +40,7 @@ public class FixedTargets extends TargetPointerImpl { this(token.getLastAddedTokenIds() .stream() .map(game::getPermanent) + .filter(Objects::nonNull) .collect(Collectors.toList()), game); } diff --git a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java index cb79e86e0a5..d4da5b9f329 100644 --- a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java +++ b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java @@ -148,6 +148,7 @@ public abstract class NthTargetPointer extends TargetPointerImpl { @Override public String describeTargets(Targets targets, String defaultDescription) { + if (targetDescription != null) return targetDescription; if (targets.size() <= this.targetIndex) { // TODO: need research, is it used for non setup targets ?! // Typical usage example: trigger sets fixed target pointer diff --git a/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java index 2dcff217576..fab982b18e9 100644 --- a/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java +++ b/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java @@ -57,9 +57,13 @@ public interface TargetPointer extends Serializable, Copyable { /** * Describes the appropriate subset of targets for ability text. */ - default String describeTargets(Targets targets, String defaultDescription) { - return defaultDescription; - } + String describeTargets(Targets targets, String defaultDescription); + + /** + * Overwrite the default target description + */ + void setTargetDescription(String description); + String getTargetDescription(); default boolean isPlural(Targets targets) { return false; diff --git a/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java b/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java index 857061689ad..ae593646a90 100644 --- a/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java +++ b/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java @@ -5,6 +5,7 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.players.Player; +import mage.target.Targets; import java.util.HashMap; import java.util.Map; @@ -18,6 +19,7 @@ public abstract class TargetPointerImpl implements TargetPointer { private Map data; private boolean initialized = false; + protected String targetDescription = null; protected TargetPointerImpl() { super(); @@ -30,6 +32,7 @@ public abstract class TargetPointerImpl implements TargetPointer { this.data.putAll(targetPointer.data); } this.initialized = targetPointer.initialized; + this.targetDescription = targetPointer.targetDescription; } @Override @@ -74,4 +77,18 @@ public abstract class TargetPointerImpl implements TargetPointer { return this; } + @Override + public String describeTargets(Targets targets, String defaultDescription) { + return targetDescription != null ? targetDescription : defaultDescription; + } + + @Override + public void setTargetDescription(String description) { + targetDescription = description; + } + @Override + public String getTargetDescription() { + return targetDescription; + } + } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index d4c968bd4dd..abc4f0bbe53 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1556,7 +1556,7 @@ public final class CardUtil { return result; } - private static boolean checkForPlayable(Cards cards, FilterCard filter, Ability source, Player player, Game game, SpellCastTracker spellCastTracker, boolean playLand) { + private static boolean cardsHasCastableParts(Cards cards, FilterCard filter, Ability source, Player player, Game game, SpellCastTracker spellCastTracker, boolean playLand) { return cards .getCards(game) .stream() @@ -1580,19 +1580,40 @@ public final class CardUtil { CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter); return; } - int spellsCast = 0; + int castCount = 0; + int maxCastCount = Integer.min(cards.size(), maxSpells); cards.removeZone(Zone.STACK, game); - while (player.canRespond() && spellsCast < maxSpells && !cards.isEmpty()) { - if (CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter, spellCastTracker, playLand)) { - spellsCast++; - cards.removeZone(Zone.STACK, game); - } else if (!checkForPlayable( - cards, filter, source, player, game, spellCastTracker, playLand - ) || !player.chooseUse( - Outcome.PlayForFree, "Continue casting spells?", source, game - )) { + if (!cardsHasCastableParts(cards, filter, source, player, game, spellCastTracker, playLand)) { + return; + } + + while (player.canRespond()) { + boolean wasCast = CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter, spellCastTracker, playLand); + + // nothing to cast + cards.removeZone(Zone.STACK, game); + if (cards.isEmpty() || !cardsHasCastableParts(cards, filter, source, player, game, spellCastTracker, playLand)) { break; } + + if (wasCast) { + // no more tries to cast + castCount++; + if (castCount >= maxCastCount) { + break; + } + } else { + // player want to cancel + if (player.isComputer()) { + // AI can't choose good spell, so stop + break; + } else { + // Human can choose wrong spell part, so allow to continue + if (!player.chooseUse(Outcome.PlayForFree, "Continue casting spells?", source, game)) { + break; + } + } + } } } diff --git a/Mage/src/main/java/mage/util/DebugUtil.java b/Mage/src/main/java/mage/util/DebugUtil.java index c76f83243eb..fcad86ea034 100644 --- a/Mage/src/main/java/mage/util/DebugUtil.java +++ b/Mage/src/main/java/mage/util/DebugUtil.java @@ -17,6 +17,12 @@ public class DebugUtil { public static boolean AI_ENABLE_DEBUG_MODE = false; public static boolean AI_SHOW_TARGET_OPTIMIZATION_LOGS = false; // works with target amount + // SERVER + // data collectors - enable additional logs and data collection for better AI and human games debugging + public static boolean TESTS_DATA_COLLECTORS_ENABLE_SAVE_GAME_HISTORY = false; // WARNING, for debug only, can generate too much files + public static boolean SERVER_DATA_COLLECTORS_ENABLE_PRINT_GAME_LOGS = false; + public static boolean SERVER_DATA_COLLECTORS_ENABLE_SAVE_GAME_HISTORY = false; + // GAME // print detail target info for activate/cast/trigger only, not a single choose dialog // can be useful to debug unit tests, auto-choose or AI diff --git a/Mage/src/main/java/mage/util/FuzzyTestsUtil.java b/Mage/src/main/java/mage/util/FuzzyTestsUtil.java index 1523c9040a5..1c7f29a43b6 100644 --- a/Mage/src/main/java/mage/util/FuzzyTestsUtil.java +++ b/Mage/src/main/java/mage/util/FuzzyTestsUtil.java @@ -58,7 +58,7 @@ public class FuzzyTestsUtil { return; } Player samplePlayer = game.getPlayers().values().stream().findFirst().orElse(null); - if (samplePlayer == null || !samplePlayer.isTestsMode()) { + if (samplePlayer == null || !samplePlayer.isTestMode()) { return; } diff --git a/Mage/src/main/java/mage/watchers/common/VoidWatcher.java b/Mage/src/main/java/mage/watchers/common/VoidWatcher.java new file mode 100644 index 00000000000..879387dd50c --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/VoidWatcher.java @@ -0,0 +1,60 @@ +package mage.watchers.common; + +import mage.abilities.keyword.WarpAbility; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class VoidWatcher extends Watcher { + + // need to track separately for each player as it needs to be in range + private final Set players = new HashSet<>(); + + public VoidWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case SPELL_CAST: + Spell spell = game.getSpell(event.getTargetId()); + if (spell != null && spell.getSpellAbility() instanceof WarpAbility) { + players.addAll(game.getState().getPlayersInRange(spell.getControllerId(), game)); + } + return; + case ZONE_CHANGE: + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (Zone.BATTLEFIELD.match(zEvent.getFromZone()) + && zEvent.getTarget() != null + && !zEvent.getTarget().isLand(game)) { + players.addAll(game.getState().getPlayersInRange(zEvent.getTarget().getControllerId(), game)); + } + } + } + + @Override + public void reset() { + super.reset(); + players.clear(); + } + + public static boolean checkPlayer(UUID playerId, Game game) { + return game + .getState() + .getWatcher(VoidWatcher.class) + .players + .contains(playerId); + } +} diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index df62e048268..8d0dacf120a 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -73,6 +73,8 @@ |Generate|EMBLEM:MD1|Emblem Elspeth|||ElspethKnightErrantEmblem| |Generate|EMBLEM:SWS|Emblem Obi-Wan Kenobi|||ObiWanKenobiEmblem| |Generate|EMBLEM:SWS|Emblem Luke Skywalker|||LukeSkywalkerEmblem| +|Generate|EMBLEM:SWS|Emblem Yoda|||YodaEmblem| +|Generate|EMBLEM:SWS|Emblem Aurra Sing|||AurraSingBaneOfJediEmblem| |Generate|EMBLEM:RIX|Emblem Huatli|||HuatliRadiantChampionEmblem| |Generate|EMBLEM:RNA|Emblem Domri|||DomriChaosBringerEmblem| |Generate|EMBLEM:WAR|Emblem Nissa|||NissaWhoShakesTheWorldEmblem| @@ -146,6 +148,7 @@ |Generate|EMBLEM:INR|Emblem Wrenn|||WrennAndSevenEmblem| |Generate|EMBLEM:DFT|Emblem Chandra|||ChandraSparkHunterEmblem| |Generate|EMBLEM:FIN|Emblem Sephiroth|||SephirothOneWingedAngelEmblem| +|Generate|EMBLEM:EOE|Emblem Tezzeret|||TezzeretCruelCaptainEmblem| # ALL PLANES @@ -1527,12 +1530,18 @@ |Generate|TOK:SLD|Food|3||FoodToken| |Generate|TOK:SLD|Food|4||FoodToken| |Generate|TOK:SLD|Food|5||FoodToken| +|Generate|TOK:SLD|Food|6||FoodToken| |Generate|TOK:SLD|Goblin|||GoblinToken| |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|Myr|||MyrToken| |Generate|TOK:SLD|Saproling|||SaprolingToken| +|Generate|TOK:SLD|Shapeshifter|1||ShapeshifterBlueToken| +|Generate|TOK:SLD|Shapeshifter|2||ShapeshifterBlueToken| +|Generate|TOK:SLD|Shapeshifter|3||ShapeshifterBlueToken| +|Generate|TOK:SLD|Shapeshifter|4||ShapeshifterBlueToken| |Generate|TOK:SLD|Shrine|||ShrineToken| |Generate|TOK:SLD|Spirit|1||SpiritWhiteToken| |Generate|TOK:SLD|Spirit|2||SpiritToken| @@ -1541,6 +1550,8 @@ |Generate|TOK:SLD|Treasure|2||TreasureToken| |Generate|TOK:SLD|Treasure|3||TreasureToken| |Generate|TOK:SLD|Treasure|4||TreasureToken| +|Generate|TOK:SLD|Treasure|5||TreasureToken| +|Generate|TOK:SLD|Treasure|6||TreasureToken| |Generate|TOK:SLD|Walker|1||WalkerToken| |Generate|TOK:SLD|Walker|2||WalkerToken| |Generate|TOK:SLD|Walker|3||WalkerToken| @@ -2227,22 +2238,15 @@ |Generate|TOK:WOE|Young Hero|||YoungHeroRoleToken| # WOC -|Generate|TOK:WOC|Cat|1||CatToken2| -|Generate|TOK:WOC|Cat|2||CatToken| -|Generate|TOK:WOC|Elephant|||ElephantToken| |Generate|TOK:WOC|Faerie|||FaerieToken| |Generate|TOK:WOC|Faerie Rogue|1||FaerieRogueToken| |Generate|TOK:WOC|Faerie Rogue|2||OonaQueenFaerieRogueToken| -|Generate|TOK:WOC|Human Monk|||HumanMonkToken| |Generate|TOK:WOC|Human Soldier|||HumanSoldierToken| |Generate|TOK:WOC|Monster|||MonsterRoleToken| -|Generate|TOK:WOC|Ox|||OxToken| |Generate|TOK:WOC|Pegasus|||PegasusToken2| |Generate|TOK:WOC|Pirate|||NettlingNuisancePirateToken| |Generate|TOK:WOC|Royal|||RoyalRoleToken| |Generate|TOK:WOC|Saproling|||SaprolingToken| -|Generate|TOK:WOC|Sorcerer|||SorcererRoleToken| -|Generate|TOK:WOC|Spirit|||WhiteBlackSpiritToken| |Generate|TOK:WOC|Virtuous|||VirtuousRoleToken| # WHO @@ -2786,7 +2790,8 @@ |Generate|TOK:DSK|Primo, the Indivisible|||PrimoTheIndivisibleToken| |Generate|TOK:DSK|Shard|||ShardToken| |Generate|TOK:DSK|Spider|||Spider22Token| -|Generate|TOK:DSK|Spirit|||SpiritBlueToken| +|Generate|TOK:DSK|Spirit|1||Spirit31Token| +|Generate|TOK:DSK|Spirit|2||SpiritBlueToken| |Generate|TOK:DSK|Treasure|||TreasureToken| # FIN @@ -2824,7 +2829,7 @@ |Generate|TOK:FIN|Treasure|1||TreasureToken| |Generate|TOK:FIN|Treasure|2||TreasureToken| -# FIN +# FIC |Generate|TOK:FIC|Human Soldier|||HumanSoldierToken| |Generate|TOK:FIC|Soldier|||SoldierToken| |Generate|TOK:FIC|Spirit|||SpiritWhiteToken| @@ -2835,6 +2840,36 @@ |Generate|TOK:FIC|The Blackjack|||TheBlackjackToken| |Generate|TOK:FIC|Clue|||ClueArtifactToken| +#EOE +|Generate|TOK:EOE|Drone|||DroneToken2| +|Generate|TOK:EOE|Human Soldier|||HumanSoldierToken| +|Generate|TOK:EOE|Lander|1||LanderToken| +|Generate|TOK:EOE|Lander|2||LanderToken| +|Generate|TOK:EOE|Lander|3||LanderToken| +|Generate|TOK:EOE|Lander|4||LanderToken| +|Generate|TOK:EOE|Lander|5||LanderToken| +|Generate|TOK:EOE|Munitions|||MunitionsToken| +|Generate|TOK:EOE|Robot|||RobotToken| +|Generate|TOK:EOE|Sliver|||SliverToken| + +#EOC +|Generate|TOK:EOC|Beast|1||BeastToken| +|Generate|TOK:EOC|Beast|2||BeastToken2| +|Generate|TOK:EOC|Bird|||SwanSongBirdToken| +|Generate|TOK:EOC|Clue|||ClueArtifactToken| +|Generate|TOK:EOC|Elemental|1||TitaniaProtectorOfArgothElementalToken| +|Generate|TOK:EOC|Elemental|2||OmnathElementalToken| +|Generate|TOK:EOC|Gnome|||GnomeToken| +|Generate|TOK:EOC|Golem|1||GolemToken| +|Generate|TOK:EOC|Golem|2||HammerOfPurphorosGolemToken| +|Generate|TOK:EOC|Golem|3||TitanForgeGolemToken| +|Generate|TOK:EOC|Incubator|||IncubatorToken| +|Generate|TOK:EOC|Insect|||XiraBlackInsectToken| +|Generate|TOK:EOC|Pest|||Pest11GainLifeToken| +|Generate|TOK:EOC|Phyrexian|||Phyrexian00Token| +|Generate|TOK:EOC|Shapeshifter|||ShapeshifterDeathtouchToken| +|Generate|TOK:EOC|Thopter|||ThopterColorlessToken| + # JVC |Generate|TOK:JVC|Elemental Shaman|||ElementalShamanToken| diff --git a/Utils/keywords.txt b/Utils/keywords.txt index ea6b857b8ce..5dd4945cf5d 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -128,6 +128,7 @@ Spectacle|card, cost| Spree|card| Squad|cost| Start your engines!|new| +Station|new| Storm|new| Sunburst|new| Suspend|number, cost, card| @@ -147,4 +148,5 @@ Unleash|new| Vanishing|number| Vigilance|instance| Ward|cost| +Warp|card, manaString| Wither|instance| diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 9ee840f5d31..a8509dc657a 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -59108,34 +59108,595 @@ Green Goblin, Nemesis|Marvel's Spider-Man Eternal|23|R|{2}{B}{R}|Legendary Creat Doc Ock, Evil Inventor|Marvel's Spider-Man Eternal|24|R|{5}{U}{B}|Legendary Creature - Human Scientist Villain|8|8|At the beginning of combat on your turn, target noncreature artifact you control becomes an 8/8 Robot Villain artifact creature in addition to its other types.| Sensational Spider-Man|Marvel's Spider-Man Eternal|25|R|{1}{W}{U}|Legendary Creature - Spider Human Hero|3|3|Whenever Sensational Spider-Man attacks, tap target creature defending player controls and put a stun counter on it. Then you may remove up to three stun counters from among all permanents. Draw cards equal to the number of stun counters removed this way.| Pumpkin Bombs|Marvel's Spider-Man Eternal|26|R|{1}{R}|Artifact|||{T}, Discard two cards: Draw three cards, then put a fuse counter on this artifact. It deals damage equal to the number of fuse counters on it to target opponent. They gain control of this artifact.| -Tezzeret, Cruel Captain|Edge of Eternities|2|M|{3}|Legendary Planeswalker - Tezzeret|4|Whenever an artifact you control enters, put a loyalty counter on Tezzeret.$0: Untap target artifact or creature. If it's an artifact creature, put a +1/+1 counter on it.$-3: Search your library for an artifact card with mana value 1 or less, reveal it, put it in your hand, then shuffle.$-7: You get an emblem with "At the beginning of combat on your turn, put three +1/+1 counters on target artifact you control. If it's not a creature, it becomes a 0/0 Robot artifact creature."| +Anticausal Vestige|Edge of Eternities|1|R|{6}|Creature - Eldrazi|7|5|When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped.$Warp {4}| +Tezzeret, Cruel Captain|Edge of Eternities|2|M|{3}|Legendary Planeswalker - Tezzeret|4|Whenever an artifact you control enters, put a loyalty counter on Tezzeret.$0: Untap target artifact or creature. If it's an artifact creature, put a +1/+1 counter on it.$-3: Search your library for an artifact card with mana value 1 or less, reveal it, put it into your hand, then shuffle.$-7: You get an emblem with "At the beginning of combat on your turn, put three +1/+1 counters on target artifact you control. If it's not a creature, it becomes a 0/0 Robot artifact creature."| +All-Fates Stalker|Edge of Eternities|3|U|{3}{W}|Creature - Drix Assassin|2|3|When this creature enters, exile up to one target non-Assassin creature until this creature leaves the battlefield.$Warp {1}{W}| +Astelli Reclaimer|Edge of Eternities|4|R|{3}{W}{W}|Creature - Angel Warrior|5|4|Flying$When this creature enters, return target noncreature, nonland permanent card with mana value X or less from your graveyard to the battlefield, where X is the amount of mana spent to cast this creature.$Warp {2}{W}| +Auxiliary Boosters|Edge of Eternities|5|C|{4}{W}|Artifact - Equipment|||When this Equipment enters, create a 2/2 colorless Robot artifact creature token and attach this Equipment to it.$Equipped creature gets +1/+2 and has flying.$Equip {3}| +Banishing Light|Edge of Eternities|6|C|{2}{W}|Enchantment|||When this enchantment enters, exile target nonland permanent an opponent controls until this enchantment leaves the battlefield.| +Beyond the Quiet|Edge of Eternities|7|R|{3}{W}{W}|Sorcery|||Exile all creatures and Spacecraft.| +Brightspear Zealot|Edge of Eternities|8|C|{2}{W}|Creature - Human Soldier|2|4|Vigilance$This creature gets +2/+0 as long as you've cast two or more spells this turn.| +Cosmogrand Zenith|Edge of Eternities|9|M|{2}{W}|Creature - Human Soldier|2|4|Whenever you cast your second spell each turn, choose one --$* Create two 1/1 white Human Soldier creature tokens.$* Put a +1/+1 counter on each creature you control.| +Dawnstrike Vanguard|Edge of Eternities|10|U|{5}{W}|Creature - Human Knight|4|5|Lifelink$At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on each creature you control other than this creature.| +Dockworker Drone|Edge of Eternities|11|C|{1}{W}|Artifact Creature - Robot|1|1|This creature enters with a +1/+1 counter on it.$When this creature dies, put its counters on target creature you control.| +Dual-Sun Adepts|Edge of Eternities|12|U|{2}{W}|Creature - Human Soldier|2|2|Double strike${5}: Creatures you control get +1/+1 until end of turn.| +Dual-Sun Technique|Edge of Eternities|13|U|{1}{W}|Instant|||Target creature you control gains double strike until end of turn. If it has a +1/+1 counter on it, draw a card.| +Emergency Eject|Edge of Eternities|14|U|{2}{W}|Instant|||Destroy target nonland permanent. Its controller creates a Lander token.| +Exalted Sunborn|Edge of Eternities|15|M|{3}{W}{W}|Creature - Angel Wizard|4|5|Flying, lifelink$If one or more tokens would be created under your control, twice that many of those tokens are created instead.$Warp {1}{W}| +Exosuit Savior|Edge of Eternities|16|C|{2}{W}|Creature - Human Soldier|2|2|Flying$When this creature enters, return up to one other target permanent you control to its owner's hand.| +Flight-Deck Coordinator|Edge of Eternities|17|C|{2}{W}|Creature - Human Soldier|3|3|At the beginning of your end step, if you control two or more tapped creatures, you gain 2 life.| +Focus Fire|Edge of Eternities|18|C|{W}|Instant|||Focus Fire deals X damage to target attacking or blocking creature, where X is 2 plus the number of creatures and/or Spacecraft you control.| +Haliya, Guided by Light|Edge of Eternities|19|R|{2}{W}|Legendary Creature - Human Soldier|3|3|Whenever Haliya or another creature or artifact you control enters, you gain 1 life.$At the beginning of your end step, draw a card if you've gained 3 or more life this turn.$Warp {W}| +Hardlight Containment|Edge of Eternities|20|R|{W}|Enchantment - Aura|||Enchant artifact you control$When this Aura enters, exile target creature an opponent controls until this Aura leaves the battlefield.$Enchanted permanent has ward {1}.| +Honor|Edge of Eternities|21|U|{W}|Sorcery|||Put a +1/+1 counter on target creature.$Draw a card.| +Honored Knight-Captain|Edge of Eternities|22|U|{1}{W}|Creature - Human Advisor Knight|1|1|When this creature enters, create a 1/1 white Human Soldier creature token.${4}{W}{W}, Sacrifice this creature: Search your library for an Equipment card, put it onto the battlefield, then shuffle.| +Knight Luminary|Edge of Eternities|23|C|{3}{W}|Creature - Human Knight|3|2|When this creature enters, create a 1/1 white Human Soldier creature token.$Warp {1}{W}| +Lightstall Inquisitor|Edge of Eternities|24|R|{W}|Creature - Angel Wizard|2|1|Vigilance$When this creature enters, each opponent exiles a card from their hand and may play that card for as long as it remains exiled. Each spell cast this way costs {1} more to cast. Each land played this way enters tapped.| +Lumen-Class Frigate|Edge of Eternities|25|R|{1}{W}|Artifact - Spacecraft|||Station$STATION 2+$Other creatures you control get +1/+1.$STATION 12+$Flying, lifelink$3/5| +Luxknight Breacher|Edge of Eternities|26|C|{3}{W}|Creature - Human Knight|2|2|This creature enters with a +1/+1 counter on it for each other creature and/or artifact you control.| +Pinnacle Starcage|Edge of Eternities|27|R|{1}{W}{W}|Artifact|||When this artifact enters, exile all artifacts and creatures with mana value 2 or less until this artifact leaves the battlefield.${6}{W}{W}: Put each card exiled with this artifact into its owner's graveyard, then create a 2/2 colorless Robot artifact token for each card put into a graveyard this way. Sacrifice this artifact.| +Pulsar Squadron Ace|Edge of Eternities|28|U|{1}{W}|Creature - Human Pilot|1|2|When this creature enters, look at the top five cards of your library. You may reveal a Spacecraft card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. If you didn't put a card into your hand this way, put a +1/+1 counter on this creature.| +Radiant Strike|Edge of Eternities|29|C|{3}{W}|Instant|||Destroy target artifact or tapped creature. You gain 3 life.| +Rayblade Trooper|Edge of Eternities|30|U|{2}{W}|Creature - Human Soldier|2|2|When this creature enters, put a +1/+1 counter on target creature you control.$Whenever a nontoken creature you control with a +1/+1 counter on it dies, create a 1/1 white Human Soldier creature token.$Warp {1}{W}| +Reroute Systems|Edge of Eternities|31|U|{W}|Instant|||Choose one --$* Target artifact or creature gains indestructible until end of turn.$* Reroute Systems deals 2 damage to target tapped creature.| +Rescue Skiff|Edge of Eternities|32|U|{5}{W}|Artifact - Spacecraft|||When this Spacecraft enters, return target creature or enchantment card from your graveyard to the battlefield.$Station$STATION 10+$Flying$5/6| +Scout for Survivors|Edge of Eternities|33|U|{2}{W}|Sorcery|||Return up to three target creature cards with total mana value 3 or less from your graveyard to the battlefield. Put a +1/+1 counter on each of them.| +Seam Rip|Edge of Eternities|34|U|{W}|Enchantment|||When this enchantment enters, exile target nonland permanent an opponent controls with mana value 2 or less until this enchantment leaves the battlefield.| The Seriema|Edge of Eternities|35|R|{1}{W}{W}|Legendary Artifact - Spacecraft|||When The Seriema enters, search your library for a legendary creature card, reveal it, put it into your hand, then shuffle.$Station$STATION 7+$Flying$Other tapped legendary creatures you control have indestructible.$5/5| +Squire's Lightblade|Edge of Eternities|36|C|{W}|Artifact - Equipment|||Flash$When this Equipment enters, attach it to target creature you control. That creature gains first strike until end of turn.$Equipped creature gets +1/+0.$Equip {3}| +Starfield Shepherd|Edge of Eternities|37|U|{3}{W}{W}|Creature - Angel|3|2|Flying$When this creature enters, search your library for a basic Plains card or a creature card with mana value 1 or less, reveal it, put it into your hand, then shuffle.$Warp {1}{W}| +Starfighter Pilot|Edge of Eternities|38|C|{1}{W}|Creature - Human Pilot|2|2|Whenever this creature becomes tapped, surveil 1.| +Starport Security|Edge of Eternities|39|C|{W}|Artifact Creature - Robot Soldier|1|1|{3}{W}, {T}: Tap another target creature. This ability costs {2} less to activate if you control a creature with a +1/+1 counter on it.| +Sunstar Chaplain|Edge of Eternities|40|R|{1}{W}|Creature - Human Cleric|3|2|At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on target creature you control.${2}, Remove a +1/+1 counter from a creature you control: Tap target artifact or creature.| +Sunstar Expansionist|Edge of Eternities|41|U|{1}{W}|Creature - Human Knight|2|3|When this creature enters, if an opponent controls more lands than you, create a Lander token.$Landfall -- Whenever a land you control enters, this creature gets +1/+0 until end of turn.| +Sunstar Lightsmith|Edge of Eternities|42|U|{3}{W}|Creature - Human Artificer|3|3|Whenever you cast your second spell each turn, put a +1/+1 counter on this creature and draw a card.| +Wedgelight Rammer|Edge of Eternities|43|U|{3}{W}|Artifact - Spacecraft|||When this Spacecraft enters, create a 2/2 colorless Robot artifact creature token.$Station$STATION 9+$Flying, first strike$3/4| +Weftblade Enhancer|Edge of Eternities|44|C|{5}{W}|Creature - Drix Artificer|3|4|When this creature enters, put a +1/+1 counter on each of up to two target creatures.$Warp {2}{W}| +Zealous Display|Edge of Eternities|45|C|{2}{W}|Instant|||Creatures you control get +2/+0 until end of turn. If it's not your turn, untap those creatures.| +Annul|Edge of Eternities|46|U|{U}|Instant|||Counter target artifact or enchantment spell.| +Atomic Microsizer|Edge of Eternities|47|U|{U}|Artifact - Equipment|||Equipped creature gets +1/+0.$Whenever equipped creature attacks, choose up to one target creature. That creature can't be blocked this turn and has base power and toughness 1/1 until end of turn.$Equip {2}| +Cerebral Download|Edge of Eternities|48|U|{4}{U}|Instant|||Surveil X, where X is the number of artifacts you control. Then draw three cards.| +Cloudsculpt Technician|Edge of Eternities|49|C|{2}{U}|Creature - Jellyfish Artificer|1|4|Flying$As long as you control an artifact, this creature gets +1/+0.| +Codecracker Hound|Edge of Eternities|50|U|{2}{U}|Creature - Dog|2|1|When this creature enters, look at the top two cards of your library. Put one into your hand and the other into your graveyard.$Warp {2}{U}| +Consult the Star Charts|Edge of Eternities|51|R|{1}{U}|Instant|||Kicker {1}{U}$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.| +Cryogen Relic|Edge of Eternities|52|C|{1}{U}|Artifact|||When this artifact enters or leaves the battlefield, draw a card.${1}{U}, Sacrifice this artifact: Put a stun counter on up to one target tapped creature.| +Cryoshatter|Edge of Eternities|53|C|{U}|Enchantment - Aura|||Enchant creature$Enchanted creature gets -5/-0.$When enchanted creature becomes tapped or is dealt damage, destroy it.| +Desculpting Blast|Edge of Eternities|54|U|{1}{U}|Instant|||Return target nonland permanent to its owner's hand. If it was attacking, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying."| +Divert Disaster|Edge of Eternities|55|C|{1}{U}|Instant|||Counter target spell unless its controller pays {2}. If they do, you create a Lander token.| +Emissary Escort|Edge of Eternities|56|R|{1}{U}|Artifact Creature - Robot Soldier|0|4|This creature gets +X/+0, where X is the greatest mana value among other artifacts you control.| +Gigastorm Titan|Edge of Eternities|57|U|{4}{U}|Creature - Elemental|4|4|This spell costs {3} less to cast if you've cast another spell this turn.| +Illvoi Galeblade|Edge of Eternities|58|C|{U}|Creature - Jellyfish Warrior|1|1|Flash$Flying${2}, Sacrifice this creature: Draw a card.| +Illvoi Infiltrator|Edge of Eternities|59|U|{2}{U}|Creature - Jellyfish Rogue|1|3|This creature can't be blocked if you've cast two or more spells this turn.$Whenever this creature deals combat damage to a player, draw a card.| +Illvoi Light Jammer|Edge of Eternities|60|C|{1}{U}|Artifact - Equipment|||Flash$When this Equipment enters, attach it to target creature you control. That creature gains hexproof until end of turn.$Equipped creature gets +1/+2.$Equip {3}| +Illvoi Operative|Edge of Eternities|61|C|{1}{U}|Creature - Jellyfish Rogue|2|1|Whenever you cast your second spell each turn, put a +1/+1 counter on this creature.| +Lost in Space|Edge of Eternities|62|C|{3}{U}|Instant|||Target artifact or creature's owner puts it on their choice of the top or bottom of their library. Surveil 1.| +Mechan Assembler|Edge of Eternities|63|U|{4}{U}|Artifact Creature - Robot Artificer|4|4|Whenever another artifact you control enters, create a 2/2 colorless Robot artifact creature token. This ability triggers only once each turn.| +Mechan Navigator|Edge of Eternities|64|U|{1}{U}|Artifact Creature - Robot Pilot|2|1|Whenever this creature becomes tapped, draw a card, then discard a card.| +Mechan Shieldmate|Edge of Eternities|65|C|{1}{U}|Artifact Creature - Robot Soldier|3|2|Defender$As long as an artifact entered the battlefield under your control this turn, this creature can attack as though it didn't have defender.| +Mechanozoa|Edge of Eternities|66|C|{4}{U}{U}|Artifact Creature - Robot Jellyfish|5|5|When this creature enters, tap target artifact or creature an opponent controls and put a stun counter on it.$Warp {2}{U}| +Mental Modulation|Edge of Eternities|67|C|{1}{U}|Instant|||This spell costs {1} less to cast during your turn.$Tap target artifact or creature.$Draw a card.| +Mm'menon, the Right Hand|Edge of Eternities|68|R|{3}{U}{U}|Legendary Creature - Jellyfish Advisor|3|4|Flying$You may look at the top card of your library any time.$You may cast artifact spells from the top of your library.$Artifacts you control have "{T}: Add {U}. Spend this mana only to cast a spell from anywhere other than your hand."| +Moonlit Meditation|Edge of Eternities|69|R|{2}{U}|Enchantment - Aura|||Enchant artifact or creature you control$The first time you would create one or more tokens each turn, you may instead create that many tokens that are copies of enchanted permanent.| +Mouth of the Storm|Edge of Eternities|70|U|{6}{U}|Creature - Elemental|6|6|Flying$Ward {2}$When this creature enters, creatures your opponents control get -3/-0 until your next turn.| +Nanoform Sentinel|Edge of Eternities|71|C|{2}{U}|Artifact Creature - Robot|3|2|Whenever this creature becomes tapped, untap another target permanent. This ability triggers only once each turn.| +Quantum Riddler|Edge of Eternities|72|M|{3}{U}{U}|Creature - Sphinx|4|6|Flying$When this creature enters, draw a card.$As long as you have one or fewer cards in hand, if you would draw one or more cards, you draw that many cards plus one instead.$Warp {1}{U}| +Scour for Scrap|Edge of Eternities|73|U|{3}{U}|Instant|||Choose one or both --$* Search your library for an artifact card, reveal it, put it into your hand, then shuffle.$* Return target artifact card from your graveyard to your hand.| +Selfcraft Mechan|Edge of Eternities|74|C|{3}{U}|Artifact Creature - Robot Artificer|3|4|When this creature enters, you may sacrifice an artifact. When you do, put a +1/+1 counter on target creature and draw a card.| +Sinister Cryologist|Edge of Eternities|75|C|{2}{U}|Creature - Jellyfish Wizard|2|3|When this creature enters, target creature an opponent controls gets -3/-0 until end of turn.$Warp {U}| +Specimen Freighter|Edge of Eternities|76|U|{5}{U}|Artifact - Spacecraft|||When this Spacecraft enters, return up to two target non-Spacecraft creatures to their owners' hands.$Station$STATION 9+$Flying$Whenever this Spacecraft attacks, defending player mills four cards.$4/7| +Starbreach Whale|Edge of Eternities|77|C|{4}{U}|Creature - Whale|3|5|Flying$When this creature enters, surveil 2.$Warp {1}{U}| +Starfield Vocalist|Edge of Eternities|78|R|{3}{U}|Creature - Human Bard|3|4|If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.$Warp {1}{U}| +Starwinder|Edge of Eternities|79|R|{5}{U}{U}|Creature - Leviathan|7|7|Whenever a creature you control deals combat damage to a player, you may draw that many cards.$Warp {2}{U}{U}| +Steelswarm Operator|Edge of Eternities|80|U|{1}{U}|Artifact Creature - Robot Soldier|1|1|Flying${T}: Add {U}. Spend this mana only to cast an artifact spell.${T}: Add {U}{U}. Spend this mana only to activate abilities of artifact sources.| +Synthesizer Labship|Edge of Eternities|81|R|{U}|Artifact - Spacecraft|||Station$STATION 2+$At the beginning of combat on your turn, up to one other target artifact you control becomes an artifact creature with base power and toughness 2/2 and gains flying until end of turn.$STATION 9+$Flying, vigilance$4/4| +Tractor Beam|Edge of Eternities|82|U|{2}{U}{U}|Enchantment - Aura|||Enchant creature or Spacecraft$When this Aura enters, tap enchanted permanent.$You control enchanted permanent.$Enchanted permanent doesn't untap during its controller's untap step.| +Unravel|Edge of Eternities|83|U|{1}{U}{U}|Instant|||Counter target spell. If the amount of mana spent to cast that spell was less than its mana value, you draw a card.| +Uthros Psionicist|Edge of Eternities|84|U|{2}{U}|Creature - Jellyfish Scientist|2|4|The second spell you cast each turn costs {2} less to cast.| +Uthros Scanship|Edge of Eternities|85|U|{3}{U}|Artifact - Spacecraft|||When this Spacecraft enters, draw two cards, then discard a card.$Station$STATION 8+$Flying$4/4| +Weftwalking|Edge of Eternities|86|M|{4}{U}{U}|Enchantment|||When this enchantment enters, if you cast it, shuffle your hand and graveyard into your library, then draw seven cards.$The first spell each player casts during each of their turns may be cast without paying its mana cost.| +Alpharael, Stonechosen|Edge of Eternities|87|M|{3}{B}{B}|Legendary Creature - Human Cleric|3|3|Ward--Discard a card at random.$Void -- Whenever Alpharael attacks, if a nonland permanent left the battlefield this turn or a spell was warped this turn, defending player loses half their life, rounded up.| +Archenemy's Charm|Edge of Eternities|88|R|{B}{B}{B}|Instant|||Choose one --$* Exile target creature or planeswalker.$* Return one or two target creature and/or planeswalker cards from your graveyard to your hand.$* Put two +1/+1 counters on target creature you control. It gains lifelink until end of turn.| +Beamsaw Prospector|Edge of Eternities|89|C|{1}{B}|Creature - Human Artificer|2|1|When this creature dies, create a Lander token.| +Blade of the Swarm|Edge of Eternities|90|U|{3}{B}|Creature - Insect Assassin|3|1|When this creature enters, choose one --$* Put two +1/+1 counters on this creature.$* Put target exiled card with warp on the bottom of its owner's library.| +Chorale of the Void|Edge of Eternities|91|R|{3}{B}|Enchantment - Aura|||Enchant creature you control$Whenever enchanted creature attacks, put target creature card from defending player's graveyard onto the battlefield under your control tapped and attacking.$Void -- At the beginning of your end step, sacrifice this Aura unless a nonland permanent left the battlefield this turn or a spell was warped this turn.| +Comet Crawler|Edge of Eternities|92|C|{2}{B}|Creature - Insect Horror|2|3|Lifelink$Whenever this creature attacks, you may sacrifice another creature or artifact. If you do, this creature gets +2/+0 until end of turn.| +Dark Endurance|Edge of Eternities|93|C|{1}{B}|Instant|||This spell costs {1} less to cast if it targets a blocking creature.$Target creature gets +2/+0 and gains indestructible until end of turn.| +Decode Transmissions|Edge of Eternities|94|C|{2}{B}|Sorcery|||You draw two cards and lose 2 life.$Void -- If a nonland permanent left the battlefield this turn or a spell was warped this turn, instead you draw two cards and each opponent loses 2 life.| +Depressurize|Edge of Eternities|95|C|{1}{B}|Instant|||Target creature gets -3/-0 until end of turn. Then if that creature's power is 0 or less, destroy it.| +Dubious Delicacy|Edge of Eternities|96|U|{2}{B}|Artifact - Food|||Flash$When this artifact enters, up to one target creature gets -3/-3 until end of turn.${2}, {T}, Sacrifice this artifact: You gain 3 life.${2}, {T}, Sacrifice this artifact: Target opponent loses 3 life.| +Elegy Acolyte|Edge of Eternities|97|R|{2}{B}{B}|Creature - Human Cleric|4|4|Lifelink$Whenever one or more creatures you control deal combat damage to a player, you draw a card and lose 1 life.$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, create a 2/2 colorless Robot artifact creature token.| +Embrace Oblivion|Edge of Eternities|98|C|{B}|Sorcery|||As an additional cost to cast this spell, sacrifice an artifact or creature.$Destroy target creature or Spacecraft.| +Entropic Battlecruiser|Edge of Eternities|99|R|{3}{B}|Artifact - Spacecraft|||Station$STATION 1+$Whenever an opponent discards a card, they lose 3 life.$STATION 8+$Flying, deathtouch$Whenever this Spacecraft attacks, each opponent discards a card. Each opponent who doesn't loses 3 life.$3/10| +Faller's Faithful|Edge of Eternities|100|U|{2}{B}|Creature - Human Wizard|3|1|When this creature enters, destroy up to one other target creature. If that creature wasn't dealt damage this turn, its controller draws two cards.| +Fell Gravship|Edge of Eternities|101|U|{2}{B}|Artifact - Spacecraft|||When this Spacecraft enters, mill three cards, then return a creature or Spacecraft card from your graveyard to your hand.$Station$STATION 8+$Flying, lifelink$3/2| +Gravblade Heavy|Edge of Eternities|102|C|{3}{B}|Creature - Human Soldier|3|4|As long as you control an artifact, this creature gets +1/+0 and has deathtouch.| +Gravkill|Edge of Eternities|103|C|{3}{B}|Instant|||Exile target creature or Spacecraft.| +Gravpack Monoist|Edge of Eternities|104|C|{2}{B}|Creature - Human Scout|2|1|Flying$When this creature dies, create a tapped 2/2 colorless Robot artifact creature token.| +Hullcarver|Edge of Eternities|105|C|{B}|Artifact Creature - Robot Assassin|1|1|Deathtouch| +Hylderblade|Edge of Eternities|106|U|{B}|Artifact - Equipment|||Equipped creature gets +3/+1.$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, attach this Equipment to target creature you control.$Equip {4}| +Hymn of the Faller|Edge of Eternities|107|U|{1}{B}|Sorcery|||Surveil 1, then you draw a card and lose 1 life.$Void -- If a nonland permanent left the battlefield this turn or a spell was warped this turn, draw another card.| +Insatiable Skittermaw|Edge of Eternities|108|C|{2}{B}|Creature - Insect Horror|2|2|Menace$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, put a +1/+1 counter on this creature.| +Lightless Evangel|Edge of Eternities|109|U|{1}{B}|Creature - Vampire Cleric|2|2|Whenever you sacrifice another creature or artifact, put a +1/+1 counter on this creature.| +Monoist Circuit-Feeder|Edge of Eternities|110|U|{4}{B}{B}|Artifact Creature - Nautilus|4|4|Flying$When this creature enters, until end of turn, target creature you control gets +X/+0 and target creature an opponent controls gets -0/-X, where X is the number of artifacts you control.| +Monoist Sentry|Edge of Eternities|111|U|{B}|Artifact Creature - Robot|4|1|Defender| +Perigee Beckoner|Edge of Eternities|112|C|{4}{B}|Creature - Horror|4|5|When this creature enters, until end of turn, another target creature you control gets +2/+0 and gains "When this creature dies, return it to the battlefield tapped under its owner's control."$Warp {1}{B}| +Requiem Monolith|Edge of Eternities|113|R|{2}{B}|Artifact|||{T}: Until end of turn, target creature gains "Whenever this creature is dealt damage, you draw that many cards and lose that much life." That creature's controller may have this artifact deal 1 damage to it. Activate only as a sorcery.| +Scrounge for Eternity|Edge of Eternities|114|U|{2}{B}|Sorcery|||As an additional cost to cast this spell, sacrifice an artifact or creature.$Return target creature or Spacecraft card with mana value 5 or less from your graveyard to the battlefield. Then create a Lander token.| Sothera, the Supervoid|Edge of Eternities|115|M|{2}{B}{B}|Legendary Enchantment|||Whenever a creature you control dies, each opponent chooses a creature they control and exiles it.$At the beginning of your end step, if a player controls no creatures, sacrifice Sothera, then put a creature card exiled with it onto the battlefield under your control with two additional +1/+1 counters on it.| +Sunset Saboteur|Edge of Eternities|116|R|{1}{B}|Creature - Human Rogue|4|1|Menace$Ward--Discard a card.$Whenever this creature attacks, put a +1/+1 counter on target creature an opponent controls.| +Susurian Dirgecraft|Edge of Eternities|117|U|{4}{B}|Artifact - Spacecraft|||When this Spacecraft enters, each opponent sacrifices a nontoken creature.$Station$STATION 7+$Flying$4/3| +Susurian Voidborn|Edge of Eternities|118|U|{2}{B}|Creature - Vampire Soldier|2|2|Whenever this creature or another creature or artifact you control dies, target opponent loses 1 life and you gain 1 life.$Warp {B}| +Swarm Culler|Edge of Eternities|119|C|{3}{B}|Creature - Insect Warrior|2|4|Flying$Whenever this creature becomes tapped, you may sacrifice another creature or artifact. If you do, draw a card.| +Temporal Intervention|Edge of Eternities|120|C|{2}{B}|Sorcery|||Void -- This spell costs {2} less to cast if a nonland permanent left the battlefield this turn or a spell was warped this turn.$Target opponent reveals their hand. You choose a nonland card from it. That player discards that card.| +Timeline Culler|Edge of Eternities|121|U|{B}{B}|Creature - Drix Warlock|2|2|Haste$You may cast this card from your graveyard using its warp ability.$Warp--{B}, Pay 2 life.| +Tragic Trajectory|Edge of Eternities|122|U|{B}|Sorcery|||Target creature gets -2/-2 until end of turn.$Void -- That creature gets -10/-10 until end of turn instead if a nonland permanent left the battlefield this turn or a spell was warped this turn.| +Umbral Collar Zealot|Edge of Eternities|123|U|{1}{B}|Creature - Human Cleric|3|2|Sacrifice another creature or artifact: Surveil 1.| +Virus Beetle|Edge of Eternities|124|C|{1}{B}|Artifact Creature - Insect|1|1|When this creature enters, each opponent discards a card.| +Voidforged Titan|Edge of Eternities|125|U|{4}{B}|Artifact Creature - Robot Warrior|5|4|Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, you draw a card and lose 1 life.| +Vote Out|Edge of Eternities|126|U|{3}{B}|Sorcery|||Convoke$Destroy target creature.| +Xu-Ifit, Osteoharmonist|Edge of Eternities|127|R|{1}{B}{B}|Legendary Creature - Human Wizard|2|3|{T}: Return target creature card from your graveyard to the battlefield. It's a Skeleton in addition to its other types and has no abilities. Activate only as a sorcery.| +Zero Point Ballad|Edge of Eternities|128|R|{X}{B}|Sorcery|||Destroy all creatures with toughness X or less. You lose X life. If X is 6 or more, return a creature card put into a graveyard this way to the battlefield under your control.| +Bombard|Edge of Eternities|129|C|{2}{R}|Instant|||Bombard deals 4 damage to target creature.| +Cut Propulsion|Edge of Eternities|130|U|{2}{R}|Instant|||Target creature deals damage to itself equal to its power. If that creature has flying, it deals twice that much damage to itself instead.| +Debris Field Crusher|Edge of Eternities|131|U|{4}{R}|Artifact - Spacecraft|||When this Spacecraft enters, it deals 3 damage to any target.$Station$STATION 8+$Flying${1}{R}: This Spacecraft gets +2/+0 until end of turn.$1/5| +Devastating Onslaught|Edge of Eternities|132|M|{X}{X}{R}|Sorcery|||Create X tokens that are copies of target artifact or creature you control. Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step.| +Drill Too Deep|Edge of Eternities|133|C|{1}{R}|Instant|||Choose one --$* Put five charge counters on target Spacecraft or Planet you control.$* Destroy target artifact.| +Frontline War-Rager|Edge of Eternities|134|C|{2}{R}|Creature - Kavu Soldier|2|3|At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on this creature.| +Full Bore|Edge of Eternities|135|U|{R}|Instant|||Target creature you control gets +3/+2 until end of turn. If that creature was cast for its warp cost, it also gains trample and haste until end of turn.| +Galvanizing Sawship|Edge of Eternities|136|U|{5}{R}|Artifact - Spacecraft|||Station$STATION 3+$Flying, haste$6/5| +Invasive Maneuvers|Edge of Eternities|137|U|{1}{R}|Instant|||Invasive Maneuvers deals 3 damage to target creature. It deals 5 damage instead if you control a Spacecraft.| +Kav Landseeker|Edge of Eternities|138|C|{3}{R}|Creature - Kavu Soldier|4|3|Menace$When this creature enters, create a Lander token. At the beginning of the end step on your next turn, sacrifice that token.| +Kavaron Harrier|Edge of Eternities|139|U|{R}|Artifact Creature - Robot Soldier|2|1|Whenever this creature attacks, you may pay {2}. If you do, create a 2/2 colorless Robot artifact creature token that's tapped and attacking. Sacrifice that token at end of combat.| +Kavaron Skywarden|Edge of Eternities|140|C|{4}{R}|Creature - Kavu Soldier|4|5|Reach$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, put a +1/+1 counter on this creature.| +Kavaron Turbodrone|Edge of Eternities|141|C|{2}{R}|Artifact Creature - Robot Scout|2|3|{T}: Target creature you control gets +1/+1 and gains haste until end of turn. Activate only as a sorcery.| +Lithobraking|Edge of Eternities|142|U|{2}{R}|Instant|||Create a Lander token. Then you may sacrifice an artifact. When you do, Lithobraking deals 2 damage to each creature.| +Melded Moxite|Edge of Eternities|143|C|{1}{R}|Artifact|||When this artifact enters, you may discard a card. If you do, draw two cards.${3}, Sacrifice this artifact: Create a tapped 2/2 colorless Robot artifact creature token.| +Memorial Team Leader|Edge of Eternities|144|U|{3}{R}|Creature - Kavu Soldier|4|3|During your turn, other creatures you control get +1/+0.$Warp {1}{R}| +Memorial Vault|Edge of Eternities|145|R|{3}{R}|Artifact|||{T}, Sacrifice another artifact: Exile the top X cards of your library, where X is one plus the mana value of the sacrificed artifact. You may play those cards this turn.| +Molecular Modifier|Edge of Eternities|146|U|{2}{R}|Creature - Kavu Artificer|2|2|At the beginning of combat on your turn, target creature you control gets +1/+0 and gains first strike until end of turn.| +Nebula Dragon|Edge of Eternities|147|C|{6}{R}|Creature - Dragon|4|4|Flying$When this creature enters, it deals 3 damage to any target.| +Nova Hellkite|Edge of Eternities|148|R|{3}{R}{R}|Creature - Dragon|4|5|Flying, haste$When this creature enters, it deals 1 damage to target creature an opponent controls.$Warp {2}{R}| +Orbital Plunge|Edge of Eternities|149|C|{3}{R}|Sorcery|||Orbital Plunge deals 6 damage to target creature. If excess damage was dealt this way, create a Lander token.| +Oreplate Pangolin|Edge of Eternities|150|C|{1}{R}|Artifact Creature - Robot Pangolin|2|2|Whenever another artifact you control enters, you may pay {1}. If you do, put a +1/+1 counter on this creature.| +Pain for All|Edge of Eternities|151|R|{2}{R}|Enchantment - Aura|||Enchant creature you control$When this Aura enters, enchanted creature deals damage equal to its power to any other target.$Whenever enchanted creature is dealt damage, it deals that much damage to each opponent.| +Plasma Bolt|Edge of Eternities|152|C|{R}|Sorcery|||Plasma Bolt deals 2 damage to any target.$Void -- Plasma Bolt deals 3 damage instead if a nonland permanent left the battlefield this turn or a spell was warped this turn.| +Possibility Technician|Edge of Eternities|153|R|{2}{R}|Creature - Kavu Artificer|3|3|Whenever this creature or another Kavu you control enters, exile the top card of your library. For as long as that card remains exiled, you may play it if you control a Kavu.$Warp {1}{R}| +Red Tiger Mechan|Edge of Eternities|154|C|{3}{R}|Artifact Creature - Robot Cat|3|3|Haste$Warp {1}{R}| +Remnant Elemental|Edge of Eternities|155|U|{1}{R}|Creature - Elemental|0|4|Reach$Landfall -- Whenever a land you control enters, this creature gets +2/+0 until end of turn.| +Rig for War|Edge of Eternities|156|C|{1}{R}|Instant|||Target creature gets +3/+0 and gains first strike and reach until end of turn.| +Roving Actuator|Edge of Eternities|157|U|{3}{R}|Artifact Creature - Robot|3|4|Void -- When this creature enters, if a nonland permanent left the battlefield this turn or a spell was warped this turn, exile up to one target instant or sorcery card with mana value 2 or less from your graveyard. Copy it. You may cast the copy without paying its mana cost.| +Ruinous Rampage|Edge of Eternities|158|U|{1}{R}{R}|Sorcery|||Choose one --$* Ruinous Rampage deals 3 damage to each opponent.$* Exile all artifacts with mana value 3 or less.| +Rust Harvester|Edge of Eternities|159|R|{R}|Artifact Creature - Robot|1|1|Menace${2}, {T}, Exile an artifact card from your graveyard: Put a +1/+1 counter on this creature, then it deals damage equal to its power to any target.| +Slagdrill Scrapper|Edge of Eternities|160|C|{R}|Artifact Creature - Robot Scout|1|2|{2}, {T}, Sacrifice another artifact or land: Draw a card.| +Systems Override|Edge of Eternities|161|U|{2}{R}|Sorcery|||Gain control of target artifact or creature until end of turn. Untap that permanent. It gains haste until end of turn. If it's a Spacecraft, put ten charge counters on it. If you do, remove ten charge counters from it at the beginning of the next end step.| +Tannuk, Steadfast Second|Edge of Eternities|162|M|{2}{R}{R}|Legendary Creature - Kavu Pilot|3|5|Other creatures you control have haste.$Artifact cards and red creature cards in your hand have warp {2}{R}.| +Terminal Velocity|Edge of Eternities|163|R|{4}{R}{R}|Sorcery|||You may put an artifact or creature card from your hand onto the battlefield. That permanent gains haste, "When this permanent leaves the battlefield, it deals damage equal to its mana value to each creature," and "At the beginning of your end step, sacrifice this permanent."| +Terrapact Intimidator|Edge of Eternities|164|U|{1}{R}|Creature - Kavu Scout|2|1|When this creature enters, target opponent may have you create two Lander tokens. If they don't, put two +1/+1 counters on this creature.| +Territorial Bruntar|Edge of Eternities|165|U|{4}{R}{R}|Creature - Beast|6|6|Reach$Landfall -- Whenever a land you control enters, exile cards from the top of your library until you exile a nonland card. You may cast that card this turn.| +Vaultguard Trooper|Edge of Eternities|166|U|{4}{R}|Creature - Kavu Soldier|5|5|At the beginning of your end step, if you control two or more tapped creatures, you may discard your hand. If you do, draw two cards.| +Warmaker Gunship|Edge of Eternities|167|R|{2}{R}|Artifact - Spacecraft|||When this Spacecraft enters, it deals damage equal to the number of artifacts you control to target creature an opponent controls.$Station$STATION 6+$Flying$4/3| +Weapons Manufacturing|Edge of Eternities|168|R|{1}{R}|Enchantment|||Whenever a nontoken artifact you control enters, create a colorless artifact token named Munitions with "When this token leaves the battlefield, it deals 2 damage to any target."| +Weftstalker Ardent|Edge of Eternities|169|U|{2}{R}|Creature - Drix Artificer|2|3|Whenever another creature or artifact you control enters, this creature deals 1 damage to each opponent.$Warp {R}| +Zookeeper Mechan|Edge of Eternities|170|C|{1}{R}|Artifact Creature - Robot|1|3|{T}: Add {R}.${6}{R}: Target creature you control gets +4/+0 until end of turn. Activate only as a sorceery.| +Atmospheric Greenhouse|Edge of Eternities|171|U|{4}{G}|Artifact - Spacecraft|||When this Spacecraft enters, put a +1/+1 counter on each creature you control.$Station$STATION 8+$Flying, trample$5/4| +Bioengineered Future|Edge of Eternities|172|R|{1}{G}{G}|Enchantment|||When this enchantment enters, create a Lander token.$Each creature you control enters with an additional +1/+1 counter on it for each land that entered the battlefield under your control this turn.| +Biosynthic Burst|Edge of Eternities|173|C|{1}{G}|Instant|||Put a +1/+1 counter on target creature you control. It gains reach, trample, and indestructible until end of turn. Untap it.| +Blooming Stinger|Edge of Eternities|174|C|{1}{G}|Creature - Plant Scorpion|2|2|Deathtouch$When this creature enters, another target creature you control gains deathtouch until end of turn.| +Broodguard Elite|Edge of Eternities|175|U|{X}{G}{G}|Creature - Insect Knight|0|0|This creature enters with X +1/+1 counters on it.$When this creature leaves the battlefield, put its counters on target creature you control.$Warp {X}{G}| +Close Encounter|Edge of Eternities|176|U|{1}{G}|Instant|||As an additional cost to cast this spell, choose a creature you control or a warped creature card you own in exile.$Close Encounter deals damage equal to the power of the chosen creature or card to target creature.| +Diplomatic Relations|Edge of Eternities|177|C|{2}{G}|Instant|||Target creature gets +1/+0 and gains vigilance until end of turn. It deals damage equal to its power to target creature an opponent controls.| +Drix Fatemaker|Edge of Eternities|178|C|{3}{G}|Creature - Drix Wizard|3|2|When this creature enters, put a +1/+1 counter on target creature.$Each creature you control with a +1/+1 counter on it has trample.$Warp {1}{G}| +Edge Rover|Edge of Eternities|179|U|{G}|Artifact Creature - Robot Scout|2|2|Reach$When this creature dies, each player creates a Lander token.| +Eumidian Terrabotanist|Edge of Eternities|180|U|{1}{G}|Creature - Insect Druid|2|3|Landfall -- Whenever a land you control enters, you gain 1 life.| +Eusocial Engineering|Edge of Eternities|181|U|{3}{G}{G}|Enchantment|||Landfall -- Whenever a land you control enters, create a 2/2 colorless Robot artifact creature token.$Warp {1}{G}| +Famished Worldsire|Edge of Eternities|182|M|{5}{G}{G}{G}|Creature - Leviathan|0|0|Ward {3}$Devour land 3$When this creature enters, look at the top X cards of your library, where X is this creature's power. Put any number of land cards from among them onto the battlefield tapped, then shuffle.| +Frenzied Baloth|Edge of Eternities|183|R|{G}{G}|Creature - Beast|3|2|This spell can't be countered.$Trample, haste$Creature spells you control can't be countered.$Combat damage can't be prevented.| +Fungal Colossus|Edge of Eternities|184|C|{6}{G}|Creature - Fungus Beast|5|5|This spell costs {X} less to cast, where X is the number of differently named lands you control.| +Galactic Wayfarer|Edge of Eternities|185|C|{2}{G}|Creature - Human Scout|3|3|When this creature enters, create a Lander token.| +Gene Pollinator|Edge of Eternities|186|C|{G}|Artifact Creature - Robot Insect|1|2|{T}, Tap an untapped permanent you control: Add one mana of any color.| +Germinating Wurm|Edge of Eternities|187|C|{4}{G}|Creature - Plant Wurm|5|5|When this creature enters, you gain 2 life.$Warp {1}{G}| +Glacier Godmaw|Edge of Eternities|188|U|{5}{G}{G}|Creature - Leviathan|6|6|Trample$When this creature enters, create a Lander token.$Landfall -- Whenever a land you control enters, creatures you control get +1/+1 and gain vigilance and haste until end of turn.| Harmonious Grovestrider|Edge of Eternities|189|U|{3}{G}{G}|Creature - Beast|*|*|Ward {2}$This creature's power and toughness are each equal to the number of lands you control.| -Alpharael, Dreaming Acolyte|Edge of Eternities|212|U|{1}{U}{B}|Legendary Creature - Human Cleric|2|3|When Alpharael enters, draw two cards. Then discard two cards unless you discard an artifact card. During your turn, Alpharael has deathtouch.| +Hemosymbic Mite|Edge of Eternities|190|U|{G}|Creature - Mite|1|1|Whenever this creature becomes tapped, another target creature you control gets +X/+X until end of turn, where X is this creature's power.| +Icecave Crasher|Edge of Eternities|191|C|{3}{G}|Creature - Beast|4|4|Trample$Landfall -- Whenever a land you control enters, this creature gets +1/+0 until end of turn.| +Icetill Explorer|Edge of Eternities|192|R|{2}{G}{G}|Creature - Insect Scout|2|4|You may play an additional land on each of your turns.$You may play lands from your graveyard.$Landfall -- Whenever a land you control enters, mill a card.| +Intrepid Tenderfoot|Edge of Eternities|193|C|{1}{G}|Creature - Insect Citizen|2|2|{3}: Put a +1/+1 counter on this creature. Activate only as a sorcery.| +Larval Scoutlander|Edge of Eternities|194|U|{2}{G}|Artifact - Spacecraft|||When this Spacecraft enters, you may sacrifice a land or a Lander. If you do, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.$Station$STATION 7+$Flying$3/3| +Lashwhip Predator|Edge of Eternities|195|U|{4}{G}{G}|Creature - Plant Beast|5|7|This spell costs {2} less to cast if your opponents control three or more creatures.$Reach| +Loading Zone|Edge of Eternities|196|R|{3}{G}|Enchantment|||If one or more counters would be put on a creature, Spacecraft, or Planet you control, twice that many of each of those kinds of counters are put on it instead.$Warp {G}| +Meltstrider Eulogist|Edge of Eternities|197|U|{2}{G}|Creature - Insect Soldier|3|3|Whenever a creature you control with a +1/+1 counter on it dies, draw a card.| +Meltstrider's Gear|Edge of Eternities|198|C|{G}|Artifact - Equipment|||When this Equipment enters, attach it to target creature you control.$Equipped creature gets +2/+1 and has reach.$Equip {5}| +Meltstrider's Resolve|Edge of Eternities|199|U|{G}|Enchantment - Aura|||Enchant creature you control$When this Aura enters, enchanted creature fights up to one target creature an opponent controls.$Enchanted creature gets +0/+2 and can't be blocked by more than one creature.| +Mightform Harmonizer|Edge of Eternities|200|R|{2}{G}{G}|Creature - Insect Druid|4|4|Landfall -- Whenever a land you control enters, double the power of target creature you control until end of turn.$Warp {2}{G}| +Ouroboroid|Edge of Eternities|201|M|{2}{G}{G}|Creature - Plant Wurm|1|3|At the beginning of combat on your turn, put X +1/+1 counters on each creature you control, where X is this creature's power.| +Pull Through the Weft|Edge of Eternities|202|U|{3}{G}{G}|Sorcery|||Return up to two target nonland permanent cards from your graveyard to your hand, then return up to two target land cards from your graveyard to the battlefield tapped.| +Sami's Curiosity|Edge of Eternities|203|C|{G}|Sorcery|||You gain 2 life. Create a Lander token.| +Seedship Agrarian|Edge of Eternities|204|U|{3}{G}|Creature - Insect Scientist|3|3|Whenever this creature becomes tapped, create a Lander token.$Landfall -- Whenever a land you control enters, put a +1/+1 counter on this creature.| +Seedship Impact|Edge of Eternities|205|U|{1}{G}|Instant|||Destroy target artifact or enchantment. If its mana value was 2 or less, create a Lander token.| +Shattered Wings|Edge of Eternities|206|C|{2}{G}|Sorcery|||Destroy target artifact, enchantment, or creature with flying. Surveil 1.| +Skystinger|Edge of Eternities|207|C|{2}{G}|Creature - Insect Warrior|3|3|Reach$Whenever this creature blocks a creature with flying, this creature gets +5/+0 until end of turn.| +Sledge-Class Seedship|Edge of Eternities|208|R|{2}{G}|Artifact - Spacecraft|||Station$STATION 7+$Flying$Whenever this Spacecraft attacks, you may put a creature from your hand onto the battlefield.$4/5| +Tapestry Warden|Edge of Eternities|209|U|{3}{G}|Artifact Creature - Robot Soldier|3|4|Vigilance$Each creature you control with toughness greater than its power assigns combat damage equal to its toughness rather than its power.$Each creature you control with toughness greater than its power stations permanents using its toughness rather than its power.| +Terrasymbiosis|Edge of Eternities|210|R|{2}{G}|Enchantment|||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.| +Thawbringer|Edge of Eternities|211|C|{2}{G}|Creature - Insect Scout|4|2|When this creature enters or dies, surveil 1.| +Alpharael, Dreaming Acolyte|Edge of Eternities|212|U|{1}{U}{B}|Legendary Creature - Human Cleric|2|3|When Alpharael enters, draw two cards. Then discard two cards unless you discard an artifact card.$During your turn, Alpharael has deathtouch.| +Biomechan Engineer|Edge of Eternities|213|U|{G}{U}|Creature - Insect Artificer|2|2|When this creature enters, create a Lander token.${8}: Draw two cards and create a 2/2 colorless Robot artifact creature token.| +Biotech Specialist|Edge of Eternities|214|R|{R}{G}|Creature - Insect Scientist|1|3|When this creature enters, create a Lander token.$Whenever you sacrifice an artifact, this creature deals 2 damage to target opponent.| +Cosmogoyf|Edge of Eternities|215|R|{B}{G}|Creature - Elemental Lhurgoyf|*|1+*|This creature's power is equal to the number of cards you own in exile and its toughness is equal to that number plus 1.| +Dyadrine, Synthesis Amalgam|Edge of Eternities|216|R|{X}{G}{W}|Legendary Artifact Creature - Construct|0|1|Trample$Dyadrine enters with a number of +1/+1 counters on it equal to the amount of mana spent to cast it.$Whenever you attack, you may remove a +1/+1 counter from each of two creatures you control. If you do, draw a card and create a 2/2 colorless Robot artifact creature token.| +Genemorph Imago|Edge of Eternities|217|R|{G}{U}|Creature - Insect Druid|1|3|Flying$Landfall -- Whenever a land you control enters, target creature has base power and toughness 3/3 until end of turn. If you control six or more lands, that creature has base power and toughness 6/6 until end of turn instead.| +Haliya, Ascendant Cadet|Edge of Eternities|218|U|{2}{G}{W}{W}|Legendary Creature - Human Soldier|3|3|Whenever Haliya enters or attacks, put a +1/+1 counter on target creature you control.$Whenever one or more creatures you control with +1/+1 counters on them deal combat damage to a player, draw a card.| +Infinite Guideline Station|Edge of Eternities|219|R|{W}{U}{B}{R}{G}|Legendary Artifact - Spacecraft|||When Infinite Guideline Station enters, create a tapped 2/2 colorless Robot artifact creature token for each multicolored permanent you control.$Station$STATION 12+$Flying$Whenever Infinite Guideline Station attacks, draw a card for each multicolored permanent you control.$7/15| +Interceptor Mechan|Edge of Eternities|220|U|{2}{B}{R}|Artifact Creature - Robot|2|2|Flying$When this creature enters, return target artifact or creature card from your graveyard to your hand.$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, put a +1/+1 counter on this creature.| +Mm'menon, Uthros Exile|Edge of Eternities|221|U|{1}{U}{R}|Legendary Creature - Jellyfish Advisor|1|3|Flying$Whenever an artifact you control enters, put a +1/+1 counter on target creature.| +Mutinous Massacre|Edge of Eternities|222|R|{3}{B}{B}{R}{R}|Sorcery|||Choose odd or even. Destroy each creature with mana value of the chosen quality. Then gain control of all creatures until end of turn. Untap them. They gain haste until end of turn.| +Pinnacle Emissary|Edge of Eternities|223|R|{1}{U}{R}|Artifact Creature - Robot|3|3|Whenever you cast an artifact spell, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying."$Warp {U/R}| +Ragost, Deft Gastronaut|Edge of Eternities|224|R|{R}{W}|Legendary Creature - Lobster Citizen|2|2|Artifacts you control are Foods in addition to their other types and have "{2}, {T}, Sacrifice this artifact: You gain 3 life."${1}, {T}, Sacrifice a Food: Ragost deals 3 damage to each opponent.$At the beginning of each end step, if you gained life this turn, untap Ragost.| Sami, Ship's Engineer|Edge of Eternities|225|U|{2}{R}{W}|Legendary Creature - Human Artificer|2|4|At the beginning of your end step, if you control two or more tapped creatures, create a tapped 2/2 colorless Robot artifact creature token.| -Tannuk, Memorial Ensign|Edge of Eternities|233|U|{1}{R}{G}|Legendary Creature - Kavu Pilot|2|4|Landfall - Whenever a land you control enters, Tannuk deals 1 damage to each opponent. If this is the second time this ability has resolved this turn, draw a card.| +Sami, Wildcat Captain|Edge of Eternities|226|M|{4}{R}{W}|Legendary Creature - Human Artificer Rogue|4|4|Double strike, vigilance$Spells you cast have affinity for artifacts.| +Seedship Broodtender|Edge of Eternities|227|U|{B}{G}|Creature - Insect Citizen|2|3|When this creature enters, mill three cards.${3}{B}{G}, Sacrifice this creature: Return target creature or Spacecraft card from your graveyard to the battlefield. Activate only as a sorcery.| +Singularity Rupture|Edge of Eternities|228|R|{3}{U}{B}{B}|Sorcery|||Destroy all creatures, then any number of target players each mill half their library, rounded down.| +Space-Time Anomaly|Edge of Eternities|229|R|{2}{W}{U}|Sorcery|||Target player mills cards equal to your life total.| +Station Monitor|Edge of Eternities|230|U|{W}{U}|Creature - Lizard Artificer|2|2|Whenever you cast your second spell each turn, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying."| +Syr Vondam, Sunstar Exemplar|Edge of Eternities|231|R|{W}{B}|Legendary Creature - Human Knight|2|2|Vigilance, menace$Whenever another creature you control dies or is put into exile, put a +1/+1 counter on Syr Vondam and you gain 1 life.$When Syr Vondam dies or is put into exile while its power is 4 or greater, destroy up to one target nonland permanent.| +Syr Vondam, the Lucent|Edge of Eternities|232|U|{2}{W}{B}{B}|Legendary Creature - Human Knight|4|4|Deathtouch, lifelink$Whenever Syr Vondam enters or attacks, other creatures you control get +1/+0 and gain deathtouch until end of turn.| +Tannuk, Memorial Ensign|Edge of Eternities|233|U|{1}{R}{G}|Legendary Creature - Kavu Pilot|2|4|Landfall -- Whenever a land you control enters, Tannuk deals 1 damage to each opponent. If this is the second time this ability has resolved this turn, draw a card.| +All-Fates Scroll|Edge of Eternities|234|U|{3}|Artifact|||{T}: Add one mana of any color.${7}, {T}, Sacrifice this artifact: Draw X cards, where X is the number of differently named lands you control.| +Bygone Colossus|Edge of Eternities|235|U|{9}|Artifact Creature - Robot Giant|9|9|Warp {3}| +Chrome Companion|Edge of Eternities|236|C|{2}|Artifact Creature - Dog|2|1|Whenever this creature becomes tapped, you gain 1 life.${2}, {T}: Put target card from a graveyard on the bottom of its owner's library.| +Dauntless Scrapbot|Edge of Eternities|237|U|{3}|Artifact Creature - Robot|3|1|When this creature enters, exile each opponent's graveyard. Create a Lander token.| +Dawnsire, Sunstar Dreadnought|Edge of Eternities|238|M|{5}|Legendary Artifact - Spacecraft|||Station$STATION 10+$Whenever you attack, Dawnsire deals 100 damage to up to one target creature or planeswalker.$STATION 20+$Flying$20/20| +The Dominion Bracelet|Edge of Eternities|239|M|{2}|Legendary Artifact - Equipment|||Equipped creature gets +1/+1 and has "{15}, Exile The Dominion Bracelet: You control target opponent during their next turn. This ability costs {X} less to activate, where X is this creature's power. Activate only as a sorcery."$Equip {1}| +The Endstone|Edge of Eternities|240|M|{7}|Legendary Artifact|||Whenever you play a land or cast a spell, draw a card.$At the beginning of your end step, your life total becomes half your starting life total, rounded up.| +The Eternity Elevator|Edge of Eternities|241|R|{5}|Legendary Artifact - Spacecraft|||{T}: Add {C}{C}{C}.$Station$STATION 20+${T}: Add X mana of any one color, where X is the number of charge counters on The Eternity Elevator.| +Extinguisher Battleship|Edge of Eternities|242|R|{8}|Artifact - Spacecraft|||When this Spacecraft enters, destroy target noncreature permanent. Then this Spacecraft deals 4 damage to each creature.$Station$STATION 5+$Flying, trample$10/10| +Nutrient Block|Edge of Eternities|243|C|{1}|Artifact - Food|||Indestructible${2}, {T}, Sacrifice this artifact: You gain 3 life.$When this artifact is put into a graveyard from the battlefield, draw a card.| +Pinnacle Kill-Ship|Edge of Eternities|244|C|{7}|Artifact - Spacecraft|||When this Spacecraft enters, it deals 10 damage to up to one target creature.$Station$STATION 7+$Flying$7/7| +Survey Mechan|Edge of Eternities|245|U|{4}|Artifact Creature - Robot|1|3|Flying$Hexproof${10}, Sacrifice this creature: It deals 3 damage to any target. Target player draws three cards and gains 3 life. This ability costs {X} less to activate, where X is the number of differently named lands you control.| +Thaumaton Torpedo|Edge of Eternities|246|C|{1}|Artifact|||{6}, {T}, Sacrifice this artifact: Destroy target nonland permanent. This ability costs {3} less to activate if you attacked with a Spacecraft this turn.| +Thrumming Hivepool|Edge of Eternities|247|R|{6}|Artifact|||Affinity for Slivers$Slivers you control have double strike and haste.$At the beginning of your upkeep, create two 1/1 colorless Sliver creature tokens.| +Virulent Silencer|Edge of Eternities|248|U|{3}|Artifact Creature - Robot Assassin|2|3|Whenever a nontoken artifact creature you control deals combat damage to a player, that player gets two poison counters.| +Wurmwall Sweeper|Edge of Eternities|249|C|{2}|Artifact - Spacecraft|||When this Spacecraft enters, surveil 2.$Station$STATION 4+$Flying$2/2| +Adagia, Windswept Bastion|Edge of Eternities|250|M||Land - Planet|||This land enters tapped.${T}: Add {W}.$Station$STATION 12+${3}{W}, {T}: Create a token that's a copy of target artifact or enchantment you control, except it's legendary. Activate only as a sorcery.| Breeding Pool|Edge of Eternities|251|R||Land - Forest Island|||({T}: Add {G} or {U}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Command Bridge|Edge of Eternities|252|C||Land|||This land enters tapped.$When this land enters, sacrifice it unless you tap an untapped permanent you control.${T}: Add one mana of any color.| +Evendo, Waking Haven|Edge of Eternities|253|M||Land - Planet|||This land enters tapped.${T}: Add {G}.$Station$STATION 12+${G}, {T}: Add {G} for each creature you control.| Godless Shrine|Edge of Eternities|254|R||Land - Plains Swamp|||({T}: Add {W} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Kavaron, Memorial World|Edge of Eternities|255|M||Land - Planet|||This land enters tapped.${T}: Add {R}.$Station$STATION 12+${1}{R}, {T}, Sacrifice a land: Create a 2/2 colorless Robot artifact creature token, then creatures you control get +1/+0 and gain haste until end of turn.| Sacred Foundry|Edge of Eternities|256|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Secluded Starforge|Edge of Eternities|257|R||Land|||{T}: Add {C}.${2}, {T}, Tap X untapped artifacts you control: Target creature gets +X/+0 until end of turn. Activate only as a sorcery.${5}, {T}: Create a 2/2 colorless Robot artifact creature token.| Stomping Ground|Edge of Eternities|258|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Susur Secundi, Void Altar|Edge of Eternities|259|M||Land - Planet|||This land enters tapped.${T}: Add {B}.$Station$STATION 12+${1}{B}, {T}, Pay 2 life, Sacrifice a creature: Draw cards equal to the sacrificed creature's power. Activate only as a sorcery.| +Uthros, Titanic Godcore|Edge of Eternities|260|M||Land - Planet|||This land enters tapped.${T}: Add {U}.$Station$STATION 12+${U}, {T}: Add {U} for each artifact you control.| Watery Grave|Edge of Eternities|261|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| Plains|Edge of Eternities|262|C||Basic Land - Plains|||({T}: Add {W}.)| Island|Edge of Eternities|263|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Edge of Eternities|264|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|Edge of Eternities|265|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|Edge of Eternities|266|C||Basic Land - Forest|||({T}: Add {G}.)| -Tezzeret, Cruel Captain|Edge of Eternities|287|M|{3}|Legendary Planeswalker - Tezzeret|4|Whenever an artifact you control enters, put a loyalty counter on Tezzeret.$0: Untap target artifact or creature. If it's an artifact creature, put a +1/+1 counter on it.$-3: Search your library for an artifact card with mana value 1 or less, reveal it, put it in your hand, then shuffle.$-7: You get an emblem with "At the beginning of combat on your turn, put three +1/+1 counters on target artifact you control. If it's not a creature, it becomes a 0/0 Robot artifact creature."| +Plains|Edge of Eternities|267|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Edge of Eternities|268|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Edge of Eternities|269|C||Basic Land - Island|||({T}: Add {U}.)| +Island|Edge of Eternities|270|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Edge of Eternities|271|C||Basic Land - Swamp|||({T}: Add {B}.)| +Swamp|Edge of Eternities|272|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Edge of Eternities|273|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Edge of Eternities|274|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Edge of Eternities|275|C||Basic Land - Forest|||({T}: Add {G}.)| +Forest|Edge of Eternities|276|C||Basic Land - Forest|||({T}: Add {G}.)| +Adagia, Windswept Bastion|Edge of Eternities|277|M||Land - Planet|||This land enters tapped.${T}: Add {W}.$Station$STATION 12+${3}{W}, {T}: Create a token that's a copy of target artifact or enchantment you control, except it's legendary. Activate only as a sorcery.| +Breeding Pool|Edge of Eternities|278|R||Land - Forest Island|||({T}: Add {G} or {U}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Evendo, Waking Haven|Edge of Eternities|279|M||Land - Planet|||This land enters tapped.${T}: Add {G}.$Station$STATION 12+${G}, {T}: Add {G} for each creature you control.| +Godless Shrine|Edge of Eternities|280|R||Land - Plains Swamp|||({T}: Add {W} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Kavaron, Memorial World|Edge of Eternities|281|M||Land - Planet|||This land enters tapped.${T}: Add {R}.$Station$STATION 12+${1}{R}, {T}, Sacrifice a land: Create a 2/2 colorless Robot artifact creature token, then creatures you control get +1/+0 and gain haste until end of turn.| +Sacred Foundry|Edge of Eternities|282|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Stomping Ground|Edge of Eternities|283|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Susur Secundi, Void Altar|Edge of Eternities|284|M||Land - Planet|||This land enters tapped.${T}: Add {B}.$Station$STATION 12+${1}{B}, {T}, Pay 2 life, Sacrifice a creature: Draw cards equal to the sacrificed creature's power. Activate only as a sorcery.| +Uthros, Titanic Godcore|Edge of Eternities|285|M||Land - Planet|||This land enters tapped.${T}: Add {U}.$Station$STATION 12+${U}, {T}: Add {U} for each artifact you control.| +Watery Grave|Edge of Eternities|286|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Tezzeret, Cruel Captain|Edge of Eternities|287|M|{3}|Legendary Planeswalker - Tezzeret|4|Whenever an artifact you control enters, put a loyalty counter on Tezzeret.$0: Untap target artifact or creature. If it's an artifact creature, put a +1/+1 counter on it.$-3: Search your library for an artifact card with mana value 1 or less, reveal it, put it into your hand, then shuffle.$-7: You get an emblem with "At the beginning of combat on your turn, put three +1/+1 counters on target artifact you control. If it's not a creature, it becomes a 0/0 Robot artifact creature."| +Astelli Reclaimer|Edge of Eternities|288|R|{3}{W}{W}|Creature - Angel Warrior|5|4|Flying$When this creature enters, return target noncreature, nonland permanent card with mana value X or less from your graveyard to the battlefield, where X is the amount of mana spent to cast this creature.$Warp {2}{W}| +Haliya, Guided by Light|Edge of Eternities|289|R|{2}{W}|Legendary Creature - Human Soldier|3|3|Whenever Haliya or another creature or artifact you control enters, you gain 1 life.$At the beginning of your end step, draw a card if you've gained 3 or more life this turn.$Warp {W}| +Mm'menon, the Right Hand|Edge of Eternities|290|R|{3}{U}{U}|Legendary Creature - Jellyfish Advisor|3|4|Flying$You may look at the top card of your library any time.$You may cast artifact spells from the top of your library.$Artifacts you control have "{T}: Add {U}. Spend this mana only to cast a spell from anywhere other than your hand."| +Starwinder|Edge of Eternities|291|R|{5}{U}{U}|Creature - Leviathan|7|7|Whenever a creature you control deals combat damage to a player, you may draw that many cards.$Warp {2}{U}{U}| +Alpharael, Stonechosen|Edge of Eternities|292|M|{3}{B}{B}|Legendary Creature - Human Cleric|3|3|Ward--Discard a card at random.$Void -- Whenever Alpharael attacks, if a nonland permanent left the battlefield this turn or a spell was warped this turn, defending player loses half their life, rounded up.| +Elegy Acolyte|Edge of Eternities|293|R|{2}{B}{B}|Creature - Human Cleric|4|4|Lifelink$Whenever one or more creatures you control deal combat damage to a player, you draw a card and lose 1 life.$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, create a 2/2 colorless Robot artifact creature token.| +Xu-Ifit, Osteoharmonist|Edge of Eternities|294|R|{1}{B}{B}|Legendary Creature - Human Wizard|2|3|{T}: Return target creature card from your graveyard to the battlefield. It's a Skeleton in addition to its other types and has no abilities. Activate only as a sorcery.| +Possibility Technician|Edge of Eternities|295|R|{2}{R}|Creature - Kavu Artificer|3|3|Whenever this creature or another Kavu you control enters, exile the top card of your library. For as long as that card remains exiled, you may play it if you control a Kavu.$Warp {1}{R}| +Tannuk, Steadfast Second|Edge of Eternities|296|M|{2}{R}{R}|Legendary Creature - Kavu Pilot|3|5|Other creatures you control have haste.$Artifact cards and red creature cards in your hand have warp {2}{R}.| +Mightform Harmonizer|Edge of Eternities|297|R|{2}{G}{G}|Creature - Insect Druid|4|4|Landfall -- Whenever a land you control enters, double the power of target creature you control until end of turn.$Warp {2}{G}| +Dyadrine, Synthesis Amalgam|Edge of Eternities|298|R|{X}{G}{W}|Legendary Artifact Creature - Construct|0|1|Trample$Dyadrine enters with a number of +1/+1 counters on it equal to the amount of mana spent to cast it.$Whenever you attack, you may remove a +1/+1 counter from each of two creatures you control. If you do, draw a card and create a 2/2 colorless Robot artifact creature token.| +Genemorph Imago|Edge of Eternities|299|R|{G}{U}|Creature - Insect Druid|1|3|Flying$Landfall -- Whenever a land you control enters, target creature has base power and toughness 3/3 until end of turn. If you control six or more lands, that creature has base power and toughness 6/6 until end of turn instead.| +Ragost, Deft Gastronaut|Edge of Eternities|300|R|{R}{W}|Legendary Creature - Lobster Citizen|2|2|Artifacts you control are Foods in addition to their other types and have "{2}, {T}, Sacrifice this artifact: You gain 3 life."${1}, {T}, Sacrifice a Food: Ragost deals 3 damage to each opponent.$At the beginning of each end step, if you gained life this turn, untap Ragost.| +Sami, Wildcat Captain|Edge of Eternities|301|M|{4}{R}{W}|Legendary Creature - Human Artificer Rogue|4|4|Double strike, vigilance$Spells you cast have affinity for artifacts.| +Syr Vondam, Sunstar Exemplar|Edge of Eternities|302|R|{W}{B}|Legendary Creature - Human Knight|2|2|Vigilance, menace$Whenever another creature you control dies or is put into exile, put a +1/+1 counter on Syr Vondam and you gain 1 life.$When Syr Vondam dies or is put into exile while its power is 4 or greater, destroy up to one target nonland permanent.| +Beyond the Quiet|Edge of Eternities|303|R|{3}{W}{W}|Sorcery|||Exile all creatures and Spacecraft.| +Cosmogrand Zenith|Edge of Eternities|304|M|{2}{W}|Creature - Human Soldier|2|4|Whenever you cast your second spell each turn, choose one --$* Create two 1/1 white Human Soldier creature tokens.$* Put a +1/+1 counter on each creature you control.| +Quantum Riddler|Edge of Eternities|305|M|{3}{U}{U}|Creature - Sphinx|4|6|Flying$When this creature enters, draw a card.$As long as you have one or fewer cards in hand, if you would draw one or more cards, you draw that many cards plus one instead.$Warp {1}{U}| +Starwinder|Edge of Eternities|306|R|{5}{U}{U}|Creature - Leviathan|7|7|Whenever a creature you control deals combat damage to a player, you may draw that many cards.$Warp {2}{U}{U}| +Archenemy's Charm|Edge of Eternities|307|R|{B}{B}{B}|Instant|||Choose one --$* Exile target creature or planeswalker.$* Return one or two target creature and/or planeswalker cards from your graveyard to your hand.$* Put two +1/+1 counters on target creature you control. It gains lifelink until end of turn.| +Devastating Onslaught|Edge of Eternities|308|M|{X}{X}{R}|Sorcery|||Create X tokens that are copies of target artifact or creature you control. Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step.| +Nova Hellkite|Edge of Eternities|309|R|{3}{R}{R}|Creature - Dragon|4|5|Flying, haste$When this creature enters, it deals 1 damage to target creature an opponent controls.$Warp {2}{R}| +Rust Harvester|Edge of Eternities|310|R|{R}|Artifact Creature - Robot|1|1|Menace${2}, {T}, Exile an artifact card from your graveyard: Put a +1/+1 counter on this creature, then it deals damage equal to its power to any target.| +Weapons Manufacturing|Edge of Eternities|311|R|{1}{R}|Enchantment|||Whenever a nontoken artifact you control enters, create a colorless artifact token named Munitions with "When this token leaves the battlefield, it deals 2 damage to any target."| +Terrasymbiosis|Edge of Eternities|312|R|{2}{G}|Enchantment|||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.| +Cosmogoyf|Edge of Eternities|313|R|{B}{G}|Creature - Elemental Lhurgoyf|*|1+*|This creature's power is equal to the number of cards you own in exile and its toughness is equal to that number plus 1.| +Mutinous Massacre|Edge of Eternities|314|R|{3}{B}{B}{R}{R}|Sorcery|||Choose odd or even. Destroy each creature with mana value of the chosen quality. Then gain control of all creatures until end of turn. Untap them. They gain haste until end of turn.| +Space-Time Anomaly|Edge of Eternities|315|R|{2}{W}{U}|Sorcery|||Target player mills cards equal to your life total.| +Secluded Starforge|Edge of Eternities|316|R||Land|||{T}: Add {C}.${2}, {T}, Tap X untapped artifacts you control: Target creature gets +X/+0 until end of turn. Activate only as a sorcery.${5}, {T}: Create a 2/2 colorless Robot artifact creature token.| +Anticausal Vestige|Edge of Eternities|317|R|{6}|Creature - Eldrazi|7|5|When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped.$Warp {4}| +Exalted Sunborn|Edge of Eternities|318|M|{3}{W}{W}|Creature - Angel Wizard|4|5|Flying, lifelink$If one or more tokens would be created under your control, twice that many of those tokens are created instead.$Warp {1}{W}| +Hardlight Containment|Edge of Eternities|319|R|{W}|Enchantment - Aura|||Enchant artifact you control$When this Aura enters, exile target creature an opponent controls until this Aura leaves the battlefield.$Enchanted permanent has ward {1}.| +Lightstall Inquisitor|Edge of Eternities|320|R|{W}|Creature - Angel Wizard|2|1|Vigilance$When this creature enters, each opponent exiles a card from their hand and may play that card for as long as it remains exiled. Each spell cast this way costs {1} more to cast. Each land played this way enters tapped.| +Lumen-Class Frigate|Edge of Eternities|321|R|{1}{W}|Artifact - Spacecraft|||Station$STATION 2+$Other creatures you control get +1/+1.$STATION 12+$Flying, lifelink$3/5| +Pinnacle Starcage|Edge of Eternities|322|R|{1}{W}{W}|Artifact|||When this artifact enters, exile all artifacts and creatures with mana value 2 or less until this artifact leaves the battlefield.${6}{W}{W}: Put each card exiled with this artifact into its owner's graveyard, then create a 2/2 colorless Robot artifact token for each card put into a graveyard this way. Sacrifice this artifact.| The Seriema|Edge of Eternities|323|R|{1}{W}{W}|Legendary Artifact - Spacecraft|||When The Seriema enters, search your library for a legendary creature card, reveal it, put it into your hand, then shuffle.$Station$STATION 7+$Flying$Other tapped legendary creatures you control have indestructible.$5/5| +Sunstar Chaplain|Edge of Eternities|324|R|{1}{W}|Creature - Human Cleric|3|2|At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on target creature you control.${2}, Remove a +1/+1 counter from a creature you control: Tap target artifact or creature.| +Consult the Star Charts|Edge of Eternities|325|R|{1}{U}|Instant|||Kicker {1}{U}$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.| +Emissary Escort|Edge of Eternities|326|R|{1}{U}|Artifact Creature - Robot Soldier|0|4|This creature gets +X/+0, where X is the greatest mana value among other artifacts you control.| +Moonlit Meditation|Edge of Eternities|327|R|{2}{U}|Enchantment - Aura|||Enchant artifact or creature you control$The first time you would create one or more tokens each turn, you may instead create that many tokens that are copies of enchanted permanent.| +Starfield Vocalist|Edge of Eternities|328|R|{3}{U}|Creature - Human Bard|3|4|If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.$Warp {1}{U}| +Synthesizer Labship|Edge of Eternities|329|R|{U}|Artifact - Spacecraft|||Station$STATION 2+$At the beginning of combat on your turn, up to one other target artifact you control becomes an artifact creature with base power and toughness 2/2 and gains flying until end of turn.$STATION 9+$Flying, vigilance$4/4| +Weftwalking|Edge of Eternities|330|M|{4}{U}{U}|Enchantment|||When this enchantment enters, if you cast it, shuffle your hand and graveyard into your library, then draw seven cards.$The first spell each player casts during each of their turns may be cast without paying its mana cost.| +Chorale of the Void|Edge of Eternities|331|R|{3}{B}|Enchantment - Aura|||Enchant creature you control$Whenever enchanted creature attacks, put target creature card from defending player's graveyard onto the battlefield under your control tapped and attacking.$Void -- At the beginning of your end step, sacrifice this Aura unless a nonland permanent left the battlefield this turn or a spell was warped this turn.| +Entropic Battlecruiser|Edge of Eternities|332|R|{3}{B}|Artifact - Spacecraft|||Station$STATION 1+$Whenever an opponent discards a card, they lose 3 life.$STATION 8+$Flying, deathtouch$Whenever this Spacecraft attacks, each opponent discards a card. Each opponent who doesn't loses 3 life.$3/10| +Requiem Monolith|Edge of Eternities|333|R|{2}{B}|Artifact|||{T}: Until end of turn, target creature gains "Whenever this creature is dealt damage, you draw that many cards and lose that much life." That creature's controller may have this artifact deal 1 damage to it. Activate only as a sorcery.| +Sunset Saboteur|Edge of Eternities|334|R|{1}{B}|Creature - Human Rogue|4|1|Menace$Ward--Discard a card.$Whenever this creature attacks, put a +1/+1 counter on target creature an opponent controls.| +Zero Point Ballad|Edge of Eternities|335|R|{X}{B}|Sorcery|||Destroy all creatures with toughness X or less. You lose X life. If X is 6 or more, return a creature card put into a graveyard this way to the battlefield under your control.| +Memorial Vault|Edge of Eternities|336|R|{3}{R}|Artifact|||{T}, Sacrifice another artifact: Exile the top X cards of your library, where X is one plus the mana value of the sacrificed artifact. You may play those cards this turn.| +Pain for All|Edge of Eternities|337|R|{2}{R}|Enchantment - Aura|||Enchant creature you control$When this Aura enters, enchanted creature deals damage equal to its power to any other target.$Whenever enchanted creature is dealt damage, it deals that much damage to each opponent.| +Terminal Velocity|Edge of Eternities|338|R|{4}{R}{R}|Sorcery|||You may put an artifact or creature card from your hand onto the battlefield. That permanent gains haste, "When this permanent leaves the battlefield, it deals damage equal to its mana value to each creature," and "At the beginning of your end step, sacrifice this permanent."| +Warmaker Gunship|Edge of Eternities|339|R|{2}{R}|Artifact - Spacecraft|||When this Spacecraft enters, it deals damage equal to the number of artifacts you control to target creature an opponent controls.$Station$STATION 6+$Flying$4/3| +Bioengineered Future|Edge of Eternities|340|R|{1}{G}{G}|Enchantment|||When this enchantment enters, create a Lander token.$Each creature you control enters with an additional +1/+1 counter on it for each land that entered the battlefield under your control this turn.| +Famished Worldsire|Edge of Eternities|341|M|{5}{G}{G}{G}|Creature - Leviathan|0|0|Ward {3}$Devour land 3$When this creature enters, look at the top X cards of your library, where X is this creature's power. Put any number of land cards from among them onto the battlefield tapped, then shuffle.| +Frenzied Baloth|Edge of Eternities|342|R|{G}{G}|Creature - Beast|3|2|This spell can't be countered.$Trample, haste$Creature spells you control can't be countered.$Combat damage can't be prevented.| +Icetill Explorer|Edge of Eternities|343|R|{2}{G}{G}|Creature - Insect Scout|2|4|You may play an additional land on each of your turns.$You may play lands from your graveyard.$Landfall -- Whenever a land you control enters, mill a card.| +Loading Zone|Edge of Eternities|344|R|{3}{G}|Enchantment|||If one or more counters would be put on a creature, Spacecraft, or Planet you control, twice that many of each of those kinds of counters are put on it instead.$Warp {G}| +Ouroboroid|Edge of Eternities|345|M|{2}{G}{G}|Creature - Plant Wurm|1|3|At the beginning of combat on your turn, put X +1/+1 counters on each creature you control, where X is this creature's power.| +Sledge-Class Seedship|Edge of Eternities|346|R|{2}{G}|Artifact - Spacecraft|||Station$STATION 7+$Flying$Whenever this Spacecraft attacks, you may put a creature from your hand onto the battlefield.$4/5| +Biotech Specialist|Edge of Eternities|347|R|{R}{G}|Creature - Insect Scientist|1|3|When this creature enters, create a Lander token.$Whenever you sacrifice an artifact, this creature deals 2 damage to target opponent.| +Infinite Guideline Station|Edge of Eternities|348|R|{W}{U}{B}{R}{G}|Legendary Artifact - Spacecraft|||When Infinite Guideline Station enters, create a tapped 2/2 colorless Robot artifact creature token for each multicolored permanent you control.$Station$STATION 12+$Flying$Whenever Infinite Guideline Station attacks, draw a card for each multicolored permanent you control.$7/15| +Pinnacle Emissary|Edge of Eternities|349|R|{1}{U}{R}|Artifact Creature - Robot|3|3|Whenever you cast an artifact spell, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying."$Warp {U/R}| +Singularity Rupture|Edge of Eternities|350|R|{3}{U}{B}{B}|Sorcery|||Destroy all creatures, then any number of target players each mill half their library, rounded down.| +Dawnsire, Sunstar Dreadnought|Edge of Eternities|351|M|{5}|Legendary Artifact - Spacecraft|||Station$STATION 10+$Whenever you attack, Dawnsire deals 100 damage to up to one target creature or planeswalker.$STATION 20+$Flying$20/20| +The Dominion Bracelet|Edge of Eternities|352|M|{2}|Legendary Artifact - Equipment|||Equipped creature gets +1/+1 and has "{15}, Exile The Dominion Bracelet: You control target opponent during their next turn. This ability costs {X} less to activate, where X is this creature's power. Activate only as a sorcery."$Equip {1}| +The Endstone|Edge of Eternities|353|M|{7}|Legendary Artifact|||Whenever you play a land or cast a spell, draw a card.$At the beginning of your end step, your life total becomes half your starting life total, rounded up.| +The Eternity Elevator|Edge of Eternities|354|R|{5}|Legendary Artifact - Spacecraft|||{T}: Add {C}{C}{C}.$Station$STATION 20+${T}: Add X mana of any one color, where X is the number of charge counters on The Eternity Elevator.| +Extinguisher Battleship|Edge of Eternities|355|R|{8}|Artifact - Spacecraft|||When this Spacecraft enters, destroy target noncreature permanent. Then this Spacecraft deals 4 damage to each creature.$Station$STATION 5+$Flying, trample$10/10| +Thrumming Hivepool|Edge of Eternities|356|R|{6}|Artifact|||Affinity for Slivers$Slivers you control have double strike and haste.$At the beginning of your upkeep, create two 1/1 colorless Sliver creature tokens.| +Anticausal Vestige|Edge of Eternities|357|M|{6}|Creature - Eldrazi|7|5|When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped.$Warp {4}| +Exalted Sunborn|Edge of Eternities|358|M|{3}{W}{W}|Creature - Angel Wizard|4|5|Flying, lifelink$If one or more tokens would be created under your control, twice that many of those tokens are created instead.$Warp {1}{W}| +Starfield Vocalist|Edge of Eternities|359|M|{3}{U}|Creature - Human Bard|3|4|If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.$Warp {1}{U}| Sothera, the Supervoid|Edge of Eternities|360|M|{2}{B}{B}|Legendary Enchantment|||Whenever a creature you control dies, each opponent chooses a creature they control and exiles it.$At the beginning of your end step, if a player controls no creatures, sacrifice Sothera, then put a creature card exiled with it onto the battlefield under your control with two additional +1/+1 counters on it.| +Devastating Onslaught|Edge of Eternities|361|M|{X}{X}{R}|Sorcery|||Create X tokens that are copies of target artifact or creature you control. Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step.| +Icetill Explorer|Edge of Eternities|362|M|{2}{G}{G}|Creature - Insect Scout|2|4|You may play an additional land on each of your turns.$You may play lands from your graveyard.$Landfall -- Whenever a land you control enters, mill a card.| +Mutinous Massacre|Edge of Eternities|363|M|{3}{B}{B}{R}{R}|Sorcery|||Choose odd or even. Destroy each creature with mana value of the chosen quality. Then gain control of all creatures until end of turn. Untap them. They gain haste until end of turn.| +The Dominion Bracelet|Edge of Eternities|364|M|{2}|Legendary Artifact - Equipment|||Equipped creature gets +1/+1 and has "{15}, Exile The Dominion Bracelet: You control target opponent during their next turn. This ability costs {X} less to activate, where X is this creature's power. Activate only as a sorcery."$Equip {1}| +The Endstone|Edge of Eternities|365|M|{7}|Legendary Artifact|||Whenever you play a land or cast a spell, draw a card.$At the beginning of your end step, your life total becomes half your starting life total, rounded up.| +Secluded Starforge|Edge of Eternities|366|M||Land|||{T}: Add {C}.${2}, {T}, Tap X untapped artifacts you control: Target creature gets +X/+0 until end of turn. Activate only as a sorcery.${5}, {T}: Create a 2/2 colorless Robot artifact creature token.| Plains|Edge of Eternities|367|C||Basic Land - Plains|||({T}: Add {W}.)| Island|Edge of Eternities|368|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Edge of Eternities|369|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|Edge of Eternities|370|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|Edge of Eternities|371|C||Basic Land - Forest|||({T}: Add {G}.)| +Adagia, Windswept Bastion|Edge of Eternities|372|M||Land - Planet|||This land enters tapped.${T}: Add {W}.$Station$STATION 12+${3}{W}, {T}: Create a token that's a copy of target artifact or enchantment you control, except it's legendary. Activate only as a sorcery.| +Breeding Pool|Edge of Eternities|373|R||Land - Forest Island|||({T}: Add {G} or {U}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Evendo, Waking Haven|Edge of Eternities|374|M||Land - Planet|||This land enters tapped.${T}: Add {G}.$Station$STATION 12+${G}, {T}: Add {G} for each creature you control.| +Godless Shrine|Edge of Eternities|375|R||Land - Plains Swamp|||({T}: Add {W} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Kavaron, Memorial World|Edge of Eternities|376|M||Land - Planet|||This land enters tapped.${T}: Add {R}.$Station$STATION 12+${1}{R}, {T}, Sacrifice a land: Create a 2/2 colorless Robot artifact creature token, then creatures you control get +1/+0 and gain haste until end of turn.| +Sacred Foundry|Edge of Eternities|377|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Stomping Ground|Edge of Eternities|378|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| +Susur Secundi, Void Altar|Edge of Eternities|379|M||Land - Planet|||This land enters tapped.${T}: Add {B}.$Station$STATION 12+${1}{B}, {T}, Pay 2 life, Sacrifice a creature: Draw cards equal to the sacrificed creature's power. Activate only as a sorcery.| +Uthros, Titanic Godcore|Edge of Eternities|380|M||Land - Planet|||This land enters tapped.${T}: Add {U}.$Station$STATION 12+${U}, {T}: Add {U} for each artifact you control.| +Watery Grave|Edge of Eternities|381|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.| Sothera, the Supervoid|Edge of Eternities|382|M|{2}{B}{B}|Legendary Enchantment|||Whenever a creature you control dies, each opponent chooses a creature they control and exiles it.$At the beginning of your end step, if a player controls no creatures, sacrifice Sothera, then put a creature card exiled with it onto the battlefield under your control with two additional +1/+1 counters on it.| +Anticausal Vestige|Edge of Eternities|383|M|{6}|Creature - Eldrazi|7|5|When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped.$Warp {4}| +Exalted Sunborn|Edge of Eternities|384|M|{3}{W}{W}|Creature - Angel Wizard|4|5|Flying, lifelink$If one or more tokens would be created under your control, twice that many of those tokens are created instead.$Warp {1}{W}| +Starfield Vocalist|Edge of Eternities|385|M|{3}{U}|Creature - Human Bard|3|4|If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.$Warp {1}{U}| Sothera, the Supervoid|Edge of Eternities|386|M|{2}{B}{B}|Legendary Enchantment|||Whenever a creature you control dies, each opponent chooses a creature they control and exiles it.$At the beginning of your end step, if a player controls no creatures, sacrifice Sothera, then put a creature card exiled with it onto the battlefield under your control with two additional +1/+1 counters on it.| +Devastating Onslaught|Edge of Eternities|387|M|{X}{X}{R}|Sorcery|||Create X tokens that are copies of target artifact or creature you control. Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step.| +Icetill Explorer|Edge of Eternities|388|M|{2}{G}{G}|Creature - Insect Scout|2|4|You may play an additional land on each of your turns.$You may play lands from your graveyard.$Landfall -- Whenever a land you control enters, mill a card.| +Mutinous Massacre|Edge of Eternities|389|M|{3}{B}{B}{R}{R}|Sorcery|||Choose odd or even. Destroy each creature with mana value of the chosen quality. Then gain control of all creatures until end of turn. Untap them. They gain haste until end of turn.| +The Dominion Bracelet|Edge of Eternities|390|M|{2}|Legendary Artifact - Equipment|||Equipped creature gets +1/+1 and has "{15}, Exile The Dominion Bracelet: You control target opponent during their next turn. This ability costs {X} less to activate, where X is this creature's power. Activate only as a sorcery."$Equip {1}| +The Endstone|Edge of Eternities|391|M|{7}|Legendary Artifact|||Whenever you play a land or cast a spell, draw a card.$At the beginning of your end step, your life total becomes half your starting life total, rounded up.| +Secluded Starforge|Edge of Eternities|392|M||Land|||{T}: Add {C}.${2}, {T}, Tap X untapped artifacts you control: Target creature gets +X/+0 until end of turn. Activate only as a sorcery.${5}, {T}: Create a 2/2 colorless Robot artifact creature token.| +Starfield Shepherd|Edge of Eternities|393|U|{3}{W}{W}|Creature - Angel|3|2|Flying$When this creature enters, search your library for a basic Plains card or a creature card with mana value 1 or less, reveal it, put it into your hand, then shuffle.$Warp {1}{W}| +Annul|Edge of Eternities|394|U|{U}|Instant|||Counter target artifact or enchantment spell.| +Umbral Collar Zealot|Edge of Eternities|395|U|{1}{B}|Creature - Human Cleric|3|2|Sacrifice another creature or artifact: Surveil 1.| +Kavaron Harrier|Edge of Eternities|396|U|{R}|Artifact Creature - Robot Soldier|2|1|Whenever this creature attacks, you may pay {2}. If you do, create a 2/2 colorless Robot artifact creature token that's tapped and attacking. Sacrifice that token at end of combat.| +Pull Through the Weft|Edge of Eternities|397|U|{3}{G}{G}|Sorcery|||Return up to two target nonland permanent cards from your graveyard to your hand, then return up to two target land cards from your graveyard to the battlefield tapped.| +Singularity Rupture|Edge of Eternities|398|R|{3}{U}{B}{B}|Sorcery|||Destroy all creatures, then any number of target players each mill half their library, rounded down.| +Emissary Escort|Edge of Eternities|399|R|{1}{U}|Artifact Creature - Robot Soldier|0|4|This creature gets +X/+0, where X is the greatest mana value among other artifacts you control.| +Hearthhull, the Worldseed|Edge of Eternities Commander|1|M|{1}{B}{R}{G}|Legendary Artifact - Spacecraft|||Station$STATION 2+${1}, {T}, Sacrifice a land: Draw two cards. You may play an additional land this turn.$STATION 8+$Flying, vigilance, haste$Whenever you sacrifice a land, each opponent loses 2 life.$6/7| +Inspirit, Flagship Vessel|Edge of Eternities Commander|2|M|{U}{R}{W}|Legendary Artifact - Spacecraft|||Station$STATION 1+$At the beginning of combat on your turn, put your choice of a +1/+1 counter or two charge counters on up to one other target artifact.$STATION 8+$Flying$Other artifacts you control have hexproof and indestructible.$5/5| Kilo, Apogee Mind|Edge of Eternities Commander|3|M|{U}{R}{W}|Legendary Artifact Creature - Robot Artificer|3|3|Haste$Whenever Kilo becomes tapped, proliferate.| Szarel, Genesis Shepherd|Edge of Eternities Commander|4|M|{2}{B}{R}{G}|Legendary Creature - Insect Druid|2|5|Flying$You may play lands from your graveyard.$Whenever you sacrifice another nontoken permanent during your turn, put a number of +1/+1 counters equal to Szarel's power on up to one other target creature.| +Patrolling Peacemaker|Edge of Eternities Commander|5|R|{2}{W}|Artifact Creature - Robot Soldier|0|0|This creature enters with two +1/+1 counters on it.$Whenever an opponent commits a crime, proliferate.| +Insight Engine|Edge of Eternities Commander|6|R|{2}{U}|Artifact|||{2}, {T}: Put a charge counter on this artifact, then draw a card for each charge counter on it.| +Uthros Research Craft|Edge of Eternities Commander|7|R|{2}{U}|Artifact - Spacecraft|||Station$STATION 3+$Whenever you cast an artifact spell, draw a card. Put a charge counter on this Spacecraft.$STATION 12+$Flying$This Spacecraft gets +1/+0 for each artifact you control.$0/8| +Eumidian Wastewaker|Edge of Eternities Commander|8|R|{2}{B}{B}|Creature - Insect Cleric|4|4|Whenever this creature attacks, you and defending player each discard a card or sacrifice a permanent. You draw a card for each land card put into a graveyard this way.$Encore {6}{B}{B}| +Depthshaker Titan|Edge of Eternities Commander|9|R|{5}{R}{R}|Artifact Creature - Robot|5|5|When this creature enters, any number of target noncreature artifacts you control become 3/3 artifact creatures. Sacrifice them at the beginning of the next end step.$Each artifact creature you control has melee, trample, and haste.| +Evendo Brushrazer|Edge of Eternities Commander|10|R|{2}{R}|Creature - Insect Warrior|2|2|Whenever you sacrifice a nontoken permanent, exile the top card of your library.$During your turn, as long as you've sacrificed a nontoken permanent this turn, you may play cards exiled with this creature.${T}, Sacrifice a land: Add {R}{R}.| +Long-Range Sensor|Edge of Eternities Commander|11|R|{2}{R}|Artifact|||Whenever you attack a player, put a charge counter on this artifact.${1}, Remove two charge counters from this artifact: Discover 4. Activate only as a sorcery.| +Planetary Annihilation|Edge of Eternities Commander|12|R|{3}{R}{R}|Sorcery|||Each player chooses six lands they control, then sacrifices the rest. Planetary Annihilation deals 6 damage to each creature.| +Baloth Prime|Edge of Eternities Commander|13|R|{3}{G}|Creature - Beast|10|10|This creature enters tapped with six stun counters on it.$Whenever you sacrifice a land, create a tapped 4/4 green Beast creature token and untap this creature.${4}, Sacrifice a land: You gain 2 life.| +Exploration Broodship|Edge of Eternities Commander|14|R|{G}|Artifact - Spacecraft|||Station$STATION 3+$You may play an additional land on each of your turns.$STATION 8+$Flying$Once during each of your turns, you may cast a permanent spell from your graveyard by sacrificing a land in addition to paying its other costs.$4/4| +Horizon Explorer|Edge of Eternities Commander|15|R|{2}{G}|Creature - Insect Scout|2|4|Lands you control enter untapped.$Whenever you attack a player, create a Lander token.| +Scouring Swarm|Edge of Eternities Commander|16|R|{1}{B}{G}|Creature - Insect|1|1|Flying$Whenever you sacrifice a land, create a tapped token that's a copy of this creature if seven or more land cards are in your graveyard. Otherwise, create a tapped 1/1 black Insect creature token with flying.| +Moxite Refinery|Edge of Eternities Commander|17|R|{2}|Artifact|||{2}, {T}, Remove X counters from an artifact or creature you control: Choose one. Activate only as a sorcery.$* Put X charge counters on target artifact.$* Put X +1/+1 counters on target creature.| +Solar Array|Edge of Eternities Commander|18|R|{3}|Artifact|||{T}: Add one mana of any color. When you next cast an artifact spell this turn, that spell gains sunburst.| +Surge Conductor|Edge of Eternities Commander|19|R|{3}|Artifact Creature - Robot|3|2|Whenever another nontoken artifact you control enters, proliferate.| +Eumidian Hatchery|Edge of Eternities Commander|20|R||Land|||{T}, Pay 1 life: Add {B}. Put a hatchling counter on this land.$When this land is put into a graveyard from the battlefield, for each hatchling counter on it, create a 1/1 black Insect creature token with flying.| +Festering Thicket|Edge of Eternities Commander|21|R||Land - Swamp Forest|||({T}: Add {B} or {G}.)$This land enters tapped.$Cycling {2}| +Glittering Massif|Edge of Eternities Commander|22|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$This land enters tapped.$Cycling {2}| +Radiant Summit|Edge of Eternities Commander|23|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$This land enters tapped unless you control two or more basic lands.| +Vernal Fen|Edge of Eternities Commander|24|R||Land - Swamp Forest|||({T}: Add {B} or {G}.)$This land enters tapped unless you control two or more basic lands.| +Patrolling Peacemaker|Edge of Eternities Commander|25|R|{2}{W}|Artifact Creature - Robot Soldier|0|0|This creature enters with two +1/+1 counters on it.$Whenever an opponent commits a crime, proliferate.| +Insight Engine|Edge of Eternities Commander|26|R|{2}{U}|Artifact|||{2}, {T}: Put a charge counter on this artifact, then draw a card for each charge counter on it.| +Uthros Research Craft|Edge of Eternities Commander|27|R|{2}{U}|Artifact - Spacecraft|||Station$STATION 3+$Whenever you cast an artifact spell, draw a card. Put a charge counter on this Spacecraft.$STATION 12+$Flying$This Spacecraft gets +1/+0 for each artifact you control.$0/8| +Eumidian Wastewaker|Edge of Eternities Commander|28|R|{2}{B}{B}|Creature - Insect Cleric|4|4|Whenever this creature attacks, you and defending player each discard a card or sacrifice a permanent. You draw a card for each land card put into a graveyard this way.$Encore {6}{B}{B}| +Depthshaker Titan|Edge of Eternities Commander|29|R|{5}{R}{R}|Artifact Creature - Robot|5|5|When this creature enters, any number of target noncreature artifacts you control become 3/3 artifact creatures. Sacrifice them at the beginning of the next end step.$Each artifact creature you control has melee, trample, and haste.| +Evendo Brushrazer|Edge of Eternities Commander|30|R|{2}{R}|Creature - Insect Warrior|2|2|Whenever you sacrifice a nontoken permanent, exile the top card of your library.$During your turn, as long as you've sacrificed a nontoken permanent this turn, you may play cards exiled with this creature.${T}, Sacrifice a land: Add {R}{R}.| +Long-Range Sensor|Edge of Eternities Commander|31|R|{2}{R}|Artifact|||Whenever you attack a player, put a charge counter on this artifact.${1}, Remove two charge counters from this artifact: Discover 4. Activate only as a sorcery.| +Planetary Annihilation|Edge of Eternities Commander|32|R|{3}{R}{R}|Sorcery|||Each player chooses six lands they control, then sacrifices the rest. Planetary Annihilation deals 6 damage to each creature.| +Baloth Prime|Edge of Eternities Commander|33|R|{3}{G}|Creature - Beast|10|10|This creature enters tapped with six stun counters on it.$Whenever you sacrifice a land, create a tapped 4/4 green Beast creature token and untap this creature.${4}, Sacrifice a land: You gain 2 life.| +Exploration Broodship|Edge of Eternities Commander|34|R|{G}|Artifact - Spacecraft|||Station$STATION 3+$You may play an additional land on each of your turns.$STATION 8+$Flying$Once during each of your turns, you may cast a permanent spell from your graveyard by sacrificing a land in addition to paying its other costs.$4/4| +Horizon Explorer|Edge of Eternities Commander|35|R|{2}{G}|Creature - Insect Scout|2|4|Lands you control enter untapped.$Whenever you attack a player, create a Lander token.| +Scouring Swarm|Edge of Eternities Commander|36|R|{1}{B}{G}|Creature - Insect|1|1|Flying$Whenever you sacrifice a land, create a tapped token that's a copy of this creature if seven or more land cards are in your graveyard. Otherwise, create a tapped 1/1 black Insect creature token with flying.| +Moxite Refinery|Edge of Eternities Commander|37|R|{2}|Artifact|||{2}, {T}, Remove X counters from an artifact or creature you control: Choose one. Activate only as a sorcery.$* Put X charge counters on target artifact.$* Put X +1/+1 counters on target creature.| +Solar Array|Edge of Eternities Commander|38|R|{3}|Artifact|||{T}: Add one mana of any color. When you next cast an artifact spell this turn, that spell gains sunburst.| +Surge Conductor|Edge of Eternities Commander|39|R|{3}|Artifact Creature - Robot|3|2|Whenever another nontoken artifact you control enters, proliferate.| +Eumidian Hatchery|Edge of Eternities Commander|40|R||Land|||{T}, Pay 1 life: Add {B}. Put a hatchling counter on this land.$When this land is put into a graveyard from the battlefield, for each hatchling counter on it, create a 1/1 black Insect creature token with flying.| +Festering Thicket|Edge of Eternities Commander|41|R||Land - Swamp Forest|||({T}: Add {B} or {G}.)$This land enters tapped.$Cycling {2}| +Glittering Massif|Edge of Eternities Commander|42|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$This land enters tapped.$Cycling {2}| +Radiant Summit|Edge of Eternities Commander|43|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$This land enters tapped unless you control two or more basic lands.| +Vernal Fen|Edge of Eternities Commander|44|R||Land - Swamp Forest|||({T}: Add {B} or {G}.)$This land enters tapped unless you control two or more basic lands.| +Swords to Plowshares|Edge of Eternities Commander|45|U|{W}|Instant|||Exile target creature. Its controller gains life equal to its power.| +Swan Song|Edge of Eternities Commander|46|R|{U}|Instant|||Counter target enchantment, instant, or sorcery spell. Its controller creates a 2/2 blue Bird creature token with flying.| +Tezzeret's Gambit|Edge of Eternities Commander|47|U|{3}{U/P}|Sorcery|||({U/P} can be paid with either {U} or 2 life.)$Draw two cards, then proliferate.| +Thirst for Knowledge|Edge of Eternities Commander|48|U|{2}{U}|Instant|||Draw three cards. Then discard two cards unless you discard an artifact card.| +Chaos Warp|Edge of Eternities Commander|49|R|{2}{R}|Instant|||The owner of target permanent shuffles it into their library, then reveals the top card of their library. If it's a permanent card, they put it onto the battlefield.| +Farseek|Edge of Eternities Commander|50|C|{1}{G}|Sorcery|||Search your library for a Plains, Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle.| +Springbloom Druid|Edge of Eternities Commander|51|C|{2}{G}|Creature - Elf Druid|1|1|When this creature enters, you may sacrifice a land. If you do, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.| +Binding the Old Gods|Edge of Eternities Commander|52|U|{2}{B}{G}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Destroy target nonland permanent an opponent controls.$II -- Search your library for a Forest card, put it onto the battlefield tapped, then shuffle.$III -- Creatures you control gain deathtouch until end of turn.| +Arcane Signet|Edge of Eternities Commander|53|C|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| +Cloud Key|Edge of Eternities Commander|54|R|{3}|Artifact|||As this artifact enters, choose artifact, creature, enchantment, instant, or sorcery.$Spells you cast of the chosen type cost {1} less to cast.| +Gavel of the Righteous|Edge of Eternities Commander|55|R|{2}|Artifact - Equipment|||At the beginning of combat on your turn, put a charge counter on this Equipment.$Equipped creature gets +1/+1 for each counter on this Equipment.$As long as this Equipment has four or more counters on it, equipped creature has double strike.$Equip--Pay {3} or remove a counter from this Equipment.| +Pentad Prism|Edge of Eternities Commander|56|U|{2}|Artifact|||Sunburst$Remove a charge counter from this artifact: Add one mana of any color.| +Sol Ring|Edge of Eternities Commander|57|U|{1}|Artifact|||{T}: Add {C}{C}.| +Battlefield Forge|Edge of Eternities Commander|58|R||Land|||{T}: Add {C}.${T}: Add {R} or {W}. This land deals 1 damage to you.| +Command Tower|Edge of Eternities Commander|59|C||Land|||{T}: Add one mana of any color in your commander's color identity.| +Fabled Passage|Edge of Eternities Commander|60|R||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Then if you control four or more lands, untap that land.| +Mountain Valley|Edge of Eternities Commander|61|U||Land|||This land enters tapped.${T}, Sacrifice this land: Search your library for a Mountain or Forest card, put it onto the battlefield, then shuffle.| +Terramorphic Expanse|Edge of Eternities Commander|62|C||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| +Angel of the Ruins|Edge of Eternities Commander|63|R|{5}{W}{W}|Artifact Creature - Angel|5|7|Flying$When this creature enters, exile up to two target artifacts and/or enchantments.$Plainscycling {2}| +Dispatch|Edge of Eternities Commander|64|U|{W}|Instant|||Tap target creature.$Metalcraft -- If you control three or more artifacts, exile that creature.| +Fumigate|Edge of Eternities Commander|65|R|{3}{W}{W}|Sorcery|||Destroy all creatures. You gain 1 life for each creature destroyed this way.| +Organic Extinction|Edge of Eternities Commander|66|R|{8}{W}{W}|Sorcery|||Improvise$Destroy all nonartifact creatures.| +Resourceful Defense|Edge of Eternities Commander|67|R|{2}{W}|Enchantment|||Whenever a permanent you control leaves the battlefield, if it had counters on it, put those counters on target permanent you control.${4}{W}: Move any number of counters from target permanent you control to another target permanent you control.| +Chrome Host Seedshark|Edge of Eternities Commander|68|R|{2}{U}|Creature - Phyrexian Shark|2|4|Flying$Whenever you cast a noncreature spell, incubate X, where X is that spell's mana value.| +Cyberdrive Awakener|Edge of Eternities Commander|69|R|{5}{U}|Artifact Creature - Construct|4|4|Flying$Other artifact creatures you control have flying.$When this creature enters, until end of turn, each noncreature artifact you control becomes an artifact creature with base power and toughness 4/4.| +Deepglow Skate|Edge of Eternities Commander|70|R|{4}{U}|Creature - Fish|3|3|When this creature enters, double the number of each kind of counter on any number of target permanents.| +Emry, Lurker of the Loch|Edge of Eternities Commander|71|R|{2}{U}|Legendary Creature - Merfolk Wizard|1|2|Affinity for artifacts$When Emry enters, mill four cards.${T}: Choose target artifact card in your graveyard. You may cast that card this turn.| +Etherium Sculptor|Edge of Eternities Commander|72|C|{1}{U}|Artifact Creature - Vedalken Artificer|1|2|Artifact spells you cast cost {1} less to cast.| +Experimental Augury|Edge of Eternities Commander|73|C|{1}{U}|Instant|||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. Proliferate.| +Kappa Cannoneer|Edge of Eternities Commander|74|R|{5}{U}|Artifact Creature - Turtle Warrior|4|4|Improvise$Ward {4}$Whenever this creature or another artifact you control enters, put a +1/+1 counter on this creature and it can't be blocked this turn.| +Phyrexian Metamorph|Edge of Eternities Commander|75|R|{3}{U/P}|Artifact Creature - Phyrexian Shapeshifter|0|0|({U/P} can be paid with either {U} or 2 life.)$You may have this creature enter as a copy of any artifact or creature on the battlefield, except it's an artifact in addition to its other types.| +Pull from Tomorrow|Edge of Eternities Commander|76|R|{X}{U}{U}|Instant|||Draw X cards, then discard a card.| +Ripples of Potential|Edge of Eternities Commander|77|R|{1}{U}|Instant|||Proliferate, then choose any number of permanents you control that had a counter put on them this way. Those permanents phase out.| +Tekuthal, Inquiry Dominus|Edge of Eternities Commander|78|M|{2}{U}{U}|Legendary Creature - Phyrexian Horror|3|5|Flying$If you would proliferate, proliferate twice instead.${1}{U/P}{U/P}, Remove three counters from among other artifacts, creatures, and planeswalkers you control: Put an indestructible counter on Tekuthal.| +Thought Monitor|Edge of Eternities Commander|79|R|{6}{U}|Artifact Creature - Construct|2|2|Affinity for artifacts$Flying$When this creature enters, draw two cards.| +Thrummingbird|Edge of Eternities Commander|80|U|{1}{U}|Creature - Phyrexian Bird Horror|1|1|Flying$Whenever this creature deals combat damage to a player, proliferate.| +Universal Surveillance|Edge of Eternities Commander|81|R|{X}{U}{U}{U}|Sorcery|||Improvise$Draw X cards.| +Braids, Arisen Nightmare|Edge of Eternities Commander|82|R|{1}{B}{B}|Legendary Creature - Nightmare|3|3|At the beginning of your end step, you may sacrifice an artifact, creature, enchantment, land, or planeswalker. If you do, each opponent may sacrifice a permanent of their choice that shares a card type with it. For each opponent who doesn't, that player loses 2 life and you draw a card.| +God-Eternal Bontu|Edge of Eternities Commander|83|M|{3}{B}{B}|Legendary Creature - Zombie God|5|6|Menace$When God-Eternal Bontu enters, sacrifice any number of other permanents, then draw that many cards.$When God-Eternal Bontu dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.| +Infernal Grasp|Edge of Eternities Commander|84|U|{1}{B}|Instant|||Destroy target creature. You lose 2 life.| +Night's Whisper|Edge of Eternities Commander|85|C|{1}{B}|Sorcery|||You draw two cards and you lose 2 life.| +Blasphemous Act|Edge of Eternities Commander|86|R|{8}{R}|Sorcery|||This spell costs {1} less to cast for each creature on the battlefield.$Blasphemous Act deals 13 damage to each creature.| +Chain Reaction|Edge of Eternities Commander|87|R|{2}{R}{R}|Sorcery|||Chain Reaction deals X damage to each creature, where X is the number of creatures on the battlefield.| +Hammer of Purphoros|Edge of Eternities Commander|88|R|{1}{R}{R}|Legendary Enchantment Artifact|||Creatures you control have haste.${2}{R}, {T}, Sacrifice a land: Create a 3/3 colorless Golem enchantment artifact creature token.| +Moraug, Fury of Akoum|Edge of Eternities Commander|89|M|{4}{R}{R}|Legendary Creature - Minotaur Warrior|6|6|Each creature you control gets +1/+0 for each time it has attacked this turn.$Landfall -- Whenever a land you control enters, if it's your main phase, there's an additional combat phase after this phase. At the beginning of that combat, untap all creatures you control.| +Sprouting Goblin|Edge of Eternities Commander|90|U|{1}{R}|Creature - Goblin Druid|2|2|Kicker {G}$When this creature enters, if it was kicked, search your library for a land card with a basic land type, reveal it, put it into your hand, then shuffle.${R}, {T}, Sacrifice a land: Draw a card.| +Aftermath Analyst|Edge of Eternities Commander|91|U|{1}{G}|Creature - Elf Detective|1|3|When this creature enters, mill three cards.${3}{G}, Sacrifice this creature: Return all land cards from your graveyard to the battlefield tapped.| +Augur of Autumn|Edge of Eternities Commander|92|R|{1}{G}{G}|Creature - Human Druid|2|3|You may look at the top card of your library any time.$You may play lands from the top of your library.$Coven -- As long as you control three or more creatures with different powers, you may cast creature spells from the top of your library.| +Beast Within|Edge of Eternities Commander|93|U|{2}{G}|Instant|||Destroy target permanent. Its controller creates a 3/3 green Beast creature token.| +Centaur Vinecrasher|Edge of Eternities Commander|94|R|{3}{G}|Creature - Plant Centaur|1|1|Trample$This creature enters with a number of +1/+1 counters on it equal to the number of land cards in all graveyards.$Whenever a land card is put into a graveyard from anywhere, you may pay {G}{G}. If you do, return this card from your graveyard to your hand.| +Cultivate|Edge of Eternities Commander|95|C|{2}{G}|Sorcery|||Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle.| +Formless Genesis|Edge of Eternities Commander|96|R|{2}{G}|Kindred Sorcery - Shapeshifter|||Changeling$Create an X/X colorless Shapeshifter creature token with changeling and deathtouch, where X is the number of land cards in your graveyard.$Retrace| +Groundskeeper|Edge of Eternities Commander|97|U|{G}|Creature - Human Druid|1|1|{1}{G}: Return target basic land card from your graveyard to your hand.| +Harrow|Edge of Eternities Commander|98|C|{2}{G}|Instant|||As an additional cost to cast this spell, sacrifice a land.$Search your library for up to two basic land cards, put them onto the battlefield, then shuffle.| +Loamcrafter Faun|Edge of Eternities Commander|99|R|{2}{G}|Creature - Satyr Druid|3|3|When this creature enters, you may discard one or more land cards. When you do, return up to that many target nonland permanent cards from your graveyard to your hand.| +Multani, Yavimaya's Avatar|Edge of Eternities Commander|100|M|{4}{G}{G}|Legendary Creature - Elemental Avatar|0|0|Reach, trample$Multani gets +1/+1 for each land you control and each land card in your graveyard.${1}{G}, Return two lands you control to their owner's hand: Return this card from your graveyard to your hand.| +Nature's Lore|Edge of Eternities Commander|101|U|{1}{G}|Sorcery|||Search your library for a Forest card, put that card onto the battlefield, then shuffle.| +Oracle of Mul Daya|Edge of Eternities Commander|102|R|{3}{G}|Creature - Elf Shaman|2|2|You may play an additional land on each of your turns.$Play with the top card of your library revealed.$You may play lands from the top of your library.| +Pest Infestation|Edge of Eternities Commander|103|R|{X}{X}{G}|Sorcery|||Destroy up to X target artifacts and/or enchantments. Create twice X 1/1 black and green Pest creature tokens with "When this token dies, you gain 1 life."| +Rampaging Baloths|Edge of Eternities Commander|104|R|{4}{G}{G}|Creature - Beast|6|6|Trample$Landfall -- Whenever a land you control enters, you may create a 4/4 green Beast creature token.| +Roiling Regrowth|Edge of Eternities Commander|105|U|{2}{G}|Instant|||Sacrifice a land. Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.| +Satyr Wayfinder|Edge of Eternities Commander|106|C|{1}{G}|Creature - Satyr|1|1|When this creature enters, reveal the top four cards of your library. You may put a land card from among them into your hand. Put the rest into your graveyard.| +Skyshroud Claim|Edge of Eternities Commander|107|C|{3}{G}|Sorcery|||Search your library for up to two Forest cards, put them onto the battlefield, then shuffle.| +Splendid Reclamation|Edge of Eternities Commander|108|R|{3}{G}|Sorcery|||Return all land cards from your graveyard to the battlefield tapped.| +Tear Asunder|Edge of Eternities Commander|109|U|{1}{G}|Instant|||Kicker {1}{B}$Exile target artifact or enchantment. If this spell was kicked, exile target nonland permanent instead.| +Tireless Tracker|Edge of Eternities Commander|110|R|{2}{G}|Creature - Human Scout|3|2|Landfall -- Whenever a land you control enters, investigate.$Whenever you sacrifice a Clue, put a +1/+1 counter on this creature.| +Titania, Protector of Argoth|Edge of Eternities Commander|111|M|{3}{G}{G}|Legendary Creature - Elemental|5|3|When Titania enters, return target land card from your graveyard to the battlefield.$Whenever a land you control is put into a graveyard from the battlefield, create a 5/3 green Elemental creature token.| +World Breaker|Edge of Eternities Commander|112|M|{6}{G}|Creature - Eldrazi|5|7|Devoid$When you cast this spell, exile target artifact, enchantment, or land.$Reach${2}{C}, Sacrifice a land: Return this card from your graveyard to your hand.| +Alibou, Ancient Witness|Edge of Eternities Commander|113|M|{3}{R}{W}|Legendary Artifact Creature - Golem|4|5|Other artifact creatures you control have haste.$Whenever one or more artifact creatures you control attack, Alibou deals X damage to any target and you scry X, where X is the number of tapped artifacts you control.| +Enthusiastic Mechanaut|Edge of Eternities Commander|114|U|{U}{R}|Artifact Creature - Goblin Artificer|2|2|Flying$Artifact spells you cast cost {1} less to cast.| +Escape to the Wilds|Edge of Eternities Commander|115|R|{3}{R}{G}|Sorcery|||Exile the top five cards of your library. You may play cards exiled this way until the end of your next turn.$You may play an additional land this turn.| +Gaze of Granite|Edge of Eternities Commander|116|R|{X}{B}{B}{G}|Sorcery|||Destroy each nonland permanent with mana value X or less.| +The Gitrog Monster|Edge of Eternities Commander|117|M|{3}{B}{G}|Legendary Creature - Frog Horror|6|6|Deathtouch$At the beginning of your upkeep, sacrifice The Gitrog Monster unless you sacrifice a land.$You may play an additional land on each of your turns.$Whenever one or more land cards are put into your graveyard from anywhere, draw a card.| +Jhoira, Weatherlight Captain|Edge of Eternities Commander|118|R|{2}{U}{R}|Legendary Creature - Human Artificer|3|3|Whenever you cast a historic spell, draw a card.| +Juri, Master of the Revue|Edge of Eternities Commander|119|U|{B}{R}|Legendary Creature - Human Shaman|1|1|Whenever you sacrifice a permanent, put a +1/+1 counter on Juri.$When Juri dies, it deals damage equal to its power to any target.| +Korvold, Fae-Cursed King|Edge of Eternities Commander|120|M|{2}{B}{R}{G}|Legendary Creature - Dragon Noble|4|4|Flying$Whenever Korvold enters or attacks, sacrifice another permanent.$Whenever you sacrifice a permanent, put a +1/+1 counter on Korvold and draw a card.| +Mayhem Devil|Edge of Eternities Commander|121|U|{1}{B}{R}|Creature - Devil|3|3|Whenever a player sacrifices a permanent, this creature deals 1 damage to any target.| +Mazirek, Kraul Death Priest|Edge of Eternities Commander|122|R|{3}{B}{G}|Legendary Creature - Insect Shaman|2|2|Flying$Whenever a player sacrifices another permanent, put a +1/+1 counter on each creature you control.| +Omnath, Locus of Rage|Edge of Eternities Commander|123|M|{3}{R}{R}{G}{G}|Legendary Creature - Elemental|5|5|Landfall -- Whenever a land you control enters, create a 5/5 red and green Elemental creature token.$Whenever Omnath or another Elemental you control dies, Omnath deals 3 damage to any target.| +Putrefy|Edge of Eternities Commander|124|U|{1}{B}{G}|Instant|||Destroy target artifact or creature. It can't be regenerated.| +Rakdos Charm|Edge of Eternities Commander|125|U|{B}{R}|Instant|||Choose one --$* Exile target player's graveyard.$* Destroy target artifact.$* Each creature deals 1 damage to its controller.| +Soul of Windgrace|Edge of Eternities Commander|126|M|{1}{B}{R}{G}|Legendary Creature - Cat Avatar|5|4|Whenever Soul of Windgrace enters or attacks, you may put a land card from a graveyard onto the battlefield tapped under your control.${G}, Discard a land card: You gain 3 life.${1}{R}, Discard a land card: Draw a card.${2}{B}, Discard a land card: Soul of Windgrace gains indestructible until end of turn. Tap it.| +Uurg, Spawn of Turg|Edge of Eternities Commander|127|U|{B}{B}{G}|Legendary Creature - Frog Beast|*|5|Uurg's power is equal to the number of land cards in your graveyard.$At the beginning of your upkeep, surveil 1.${B}{G}, Sacrifice a land: You gain 2 life.| +Wake the Past|Edge of Eternities Commander|128|R|{5}{R}{W}|Sorcery|||Return all artifact cards from your graveyard to the battlefield. They gain haste until end of turn.| +Windgrace's Judgment|Edge of Eternities Commander|129|R|{3}{B}{G}|Instant|||For any number of opponents, destroy target nonland permanent that player controls.| +Worldsoul's Rage|Edge of Eternities Commander|130|R|{X}{R}{G}|Sorcery|||Worldsoul's Rage deals X damage to any target. Put up to X land cards from your hand and/or graveyard onto the battlefield tapped.| +Astral Cornucopia|Edge of Eternities Commander|131|R|{X}{X}{X}|Artifact|||This artifact enters with X charge counters on it.${T}: Choose a color. Add one mana of that color for each charge counter on this artifact.| +Coretapper|Edge of Eternities Commander|132|U|{2}|Artifact Creature - Myr|1|1|{T}: Put a charge counter on target artifact.$Sacrifice this creature: Put two charge counters on target artifact.| +Crystalline Crawler|Edge of Eternities Commander|133|R|{4}|Artifact Creature - Construct|1|1|Converge -- This creature enters with a +1/+1 counter on it for each color of mana spent to cast it.$Remove a +1/+1 counter from this creature: Add one mana of any color.${T}: Put a +1/+1 counter on this creature.| +Darksteel Reactor|Edge of Eternities Commander|134|R|{4}|Artifact|||Indestructible$At the beginning of your upkeep, you may put a charge counter on this artifact.$When this artifact has twenty or more charge counters on it, you win the game.| +Empowered Autogenerator|Edge of Eternities Commander|135|R|{4}|Artifact|||This artifact enters tapped.${T}: Put a charge counter on this artifact. Add X mana of any one color, where X is the number of charge counters on this artifact.| +Etched Oracle|Edge of Eternities Commander|136|U|{4}|Artifact Creature - Wizard|0|0|Sunburst${1}, Remove four +1/+1 counters from this creature: Target player draws three cards.| +Everflowing Chalice|Edge of Eternities Commander|137|U|{0}|Artifact|||Multikicker {2}$This artifact enters with a charge counter on it for each time it was kicked.${T}: Add {C} for each charge counter on this artifact.| +Golem Foundry|Edge of Eternities Commander|138|C|{3}|Artifact|||Whenever you cast an artifact spell, you may put a charge counter on this artifact.$Remove three charge counters from this artifact: Create a 3/3 colorless Golem artifact creature token.| +Hangarback Walker|Edge of Eternities Commander|139|R|{X}{X}|Artifact Creature - Construct|0|0|This creature enters with X +1/+1 counters on it.$When this creature dies, create a 1/1 colorless Thopter artifact creature token with flying for each +1/+1 counter on this creature.${1}, {T}: Put a +1/+1 counter on this creature.| +Lux Artillery|Edge of Eternities Commander|140|R|{4}|Artifact|||Whenever you cast an artifact creature spell, it gains sunburst.$At the beginning of your end step, if there are thirty or more counters among artifacts and creatures you control, this artifact deals 10 damage to each opponent.| +Lux Cannon|Edge of Eternities Commander|141|R|{4}|Artifact|||{T}: Put a charge counter on this artifact.${T}, Remove three charge counters from this artifact: Destroy target permanent.| +Mindless Automaton|Edge of Eternities Commander|142|U|{4}|Artifact Creature - Construct|0|0|This creature enters with two +1/+1 counters on it.${1}, Discard a card: Put a +1/+1 counter on this creature.$Remove two +1/+1 counters from this creature: Draw a card.| +Soul-Guide Lantern|Edge of Eternities Commander|143|U|{1}|Artifact|||When this artifact enters, exile target card from a graveyard.${T}, Sacrifice this artifact: Exile each opponent's graveyard.${1}, {T}, Sacrifice this artifact: Draw a card.| +Steel Overseer|Edge of Eternities Commander|144|R|{2}|Artifact Creature - Construct|1|1|{T}: Put a +1/+1 counter on each artifact creature you control.| +Threefold Thunderhulk|Edge of Eternities Commander|145|R|{7}|Artifact Creature - Gnome|0|0|This creature enters with three +1/+1 counters on it.$Whenever this creature enters or attacks, create a number of 1/1 colorless Gnome artifact creature tokens equal to its power.${2}, Sacrifice another artifact: Put a +1/+1 counter on this creature.| +Titan Forge|Edge of Eternities Commander|146|R|{3}|Artifact|||{3}, {T}: Put a charge counter on this artifact.${T}, Remove three charge counters from this artifact: Create a 9/9 colorless Golem artifact creature token.| +Adarkar Wastes|Edge of Eternities Commander|147|R||Land|||{T}: Add {C}.${T}: Add {W} or {U}. This land deals 1 damage to you.| +Ancient Den|Edge of Eternities Commander|148|C||Artifact Land|||{T}: Add {W}.| +Bojuka Bog|Edge of Eternities Commander|149|C||Land|||This land enters tapped.$When this land enters, exile target player's graveyard.${T}: Add {B}.| +Buried Ruin|Edge of Eternities Commander|150|U||Land|||{T}: Add {C}.${2}, {T}, Sacrifice this land: Return target artifact card from your graveyard to your hand.| +Cabaretti Courtyard|Edge of Eternities Commander|151|C||Land|||When this land enters, sacrifice it. When you do, search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle and you gain 1 life.| +Canyon Slough|Edge of Eternities Commander|152|R||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$This land enters tapped.$Cycling {2}| +Cascade Bluffs|Edge of Eternities Commander|153|R||Land|||{T}: Add {C}.${U/R}, {T}: Add {U}{U}, {U}{R}, or {R}{R}.| +Cinder Glade|Edge of Eternities Commander|154|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$This land enters tapped unless you control two or more basic lands.| +Clifftop Retreat|Edge of Eternities Commander|155|R||Land|||This land enters tapped unless you control a Mountain or a Plains.${T}: Add {R} or {W}.| +Dakmor Salvage|Edge of Eternities Commander|156|U||Land|||This land enters tapped.${T}: Add {B}.$Dredge 2| +Escape Tunnel|Edge of Eternities Commander|157|C||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.${T}, Sacrifice this land: Target creature with power 2 or less can't be blocked this turn.| +Evolving Wilds|Edge of Eternities Commander|158|C||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| +Exotic Orchard|Edge of Eternities Commander|159|R||Land|||{T}: Add one mana of any color that a land an opponent controls could produce.| +Glacial Fortress|Edge of Eternities Commander|160|R||Land|||This land enters tapped unless you control a Plains or an Island.${T}: Add {W} or {U}.| +Great Furnace|Edge of Eternities Commander|161|C||Artifact Land|||{T}: Add {R}.| +Irrigated Farmland|Edge of Eternities Commander|162|R||Land - Plains Island|||({T}: Add {W} or {U}.)$This land enters tapped.$Cycling {2}| +Karn's Bastion|Edge of Eternities Commander|163|R||Land|||{T}: Add {C}.${4}, {T}: Proliferate.| +Karplusan Forest|Edge of Eternities Commander|164|R||Land|||{T}: Add {C}.${T}: Add {R} or {G}. This land deals 1 damage to you.| +Llanowar Wastes|Edge of Eternities Commander|165|R||Land|||{T}: Add {C}.${T}: Add {B} or {G}. This land deals 1 damage to you.| +Lonely Sandbar|Edge of Eternities Commander|166|C||Land|||This land enters tapped.${T}: Add {U}.$Cycling {U}| +Maestros Theater|Edge of Eternities Commander|167|C||Land|||When this land enters, sacrifice it. When you do, search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle and you gain 1 life.| +The Mycosynth Gardens|Edge of Eternities Commander|168|R||Land - Sphere|||{T}: Add {C}.${1}, {T}: Add one mana of any color.${X}, {T}: This land becomes a copy of target nontoken artifact you control with mana value X.| +Myriad Landscape|Edge of Eternities Commander|169|U||Land|||This land enters tapped.${T}: Add {C}.${2}, {T}, Sacrifice this land: Search your library for up to two basic land cards that share a land type, put them onto the battlefield tapped, then shuffle.| +Mystic Monastery|Edge of Eternities Commander|170|U||Land|||This land enters tapped.${T}: Add {U}, {R}, or {W}.| +Razortide Bridge|Edge of Eternities Commander|171|C||Artifact Land|||This land enters tapped.$Indestructible${T}: Add {W} or {U}.| +Riveteers Overlook|Edge of Eternities Commander|172|C||Land|||When this land enters, sacrifice it. When you do, search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle and you gain 1 life.| +Rocky Tar Pit|Edge of Eternities Commander|173|U||Land|||This land enters tapped.${T}, Sacrifice this land: Search your library for a Swamp or Mountain card, put it onto the battlefield, then shuffle.| +Rugged Prairie|Edge of Eternities Commander|174|R||Land|||{T}: Add {C}.${R/W}, {T}: Add {R}{R}, {R}{W}, or {W}{W}.| +Rustvale Bridge|Edge of Eternities Commander|175|C||Artifact Land|||This land enters tapped.$Indestructible${T}: Add {R} or {W}.| +Seat of the Synod|Edge of Eternities Commander|176|C||Artifact Land|||{T}: Add {U}.| +Secluded Steppe|Edge of Eternities Commander|177|C||Land|||This land enters tapped.${T}: Add {W}.$Cycling {W}| +Sheltered Thicket|Edge of Eternities Commander|178|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$This land enters tapped.$Cycling {2}| +Shivan Reef|Edge of Eternities Commander|179|R||Land|||{T}: Add {C}.${T}: Add {U} or {R}. This land deals 1 damage to you.| +Silverbluff Bridge|Edge of Eternities Commander|180|C||Artifact Land|||This land enters tapped.$Indestructible${T}: Add {U} or {R}.| +Skycloud Expanse|Edge of Eternities Commander|181|R||Land|||{1}, {T}: Add {W}{U}.| +Smoldering Marsh|Edge of Eternities Commander|182|R||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$This land enters tapped unless you control two or more basic lands.| +Spire of Industry|Edge of Eternities Commander|183|R||Land|||{T}: Add {C}.${T}, Pay 1 life: Add one mana of any color. Activate only if you control an artifact.| +Sulfur Falls|Edge of Eternities Commander|184|R||Land|||This land enters tapped unless you control an Island or a Mountain.${T}: Add {U} or {R}.| +Sulfurous Springs|Edge of Eternities Commander|185|R||Land|||{T}: Add {C}.${T}: Add {B} or {R}. This land deals 1 damage to you.| +Temple of Enlightenment|Edge of Eternities Commander|186|R||Land|||This land enters tapped.$When this land enters, scry 1.${T}: Add {W} or {U}.| +Temple of Epiphany|Edge of Eternities Commander|187|R||Land|||This land enters tapped.$When this land enters, scry 1.${T}: Add {U} or {R}.| +Temple of Triumph|Edge of Eternities Commander|188|R||Land|||This land enters tapped.$When this land enters, scry 1.${T}: Add {R} or {W}.| +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}.| 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.| diff --git a/pom.xml b/pom.xml index 329e9c45206..e597f31a0f8 100644 --- a/pom.xml +++ b/pom.xml @@ -353,7 +353,7 @@ com.google.guava guava - 30.1.1-jre + 33.4.8-jre @@ -377,7 +377,7 @@ org.jsoup jsoup - 1.14.3 + 1.21.1