diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java index 2488110b58a..6c5600abf33 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java @@ -27,9 +27,7 @@ */ package mage.client.deckeditor.collection.viewer; -import mage.cards.Card; -import mage.cards.CardDimensions; -import mage.cards.MageCard; +import mage.cards.*; import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; @@ -38,9 +36,7 @@ import mage.client.MageFrame; import mage.client.cards.BigCard; import mage.client.components.HoverButton; import mage.client.plugins.impl.Plugins; -import mage.client.util.Config; -import mage.client.util.ImageHelper; -import mage.client.util.NaturalOrderCardNumberComparator; +import mage.client.util.*; import mage.client.util.audio.AudioManager; import mage.client.util.sets.ConstructedFormats; import mage.components.ImagePanel; @@ -48,7 +44,6 @@ import mage.components.ImagePanelStyle; import mage.constants.Rarity; import mage.view.CardView; import org.apache.log4j.Logger; -import org.mage.card.arcane.GlowText; import org.mage.card.arcane.ManaSymbols; import javax.imageio.ImageIO; @@ -59,17 +54,17 @@ import java.io.FileNotFoundException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; +import java.util.*; import java.util.List; -import java.util.UUID; -import java.util.logging.Level; -import mage.client.util.CardsViewUtil; + import mage.game.command.Emblem; import mage.game.permanent.PermanentToken; import mage.game.permanent.token.Token; import mage.view.EmblemView; import mage.view.PermanentView; import org.mage.plugins.card.images.CardDownloadData; + +import static java.lang.Math.min; import static org.mage.plugins.card.images.DownloadPictures.getTokenCardUrls; /** @@ -115,6 +110,7 @@ public class MageBook extends JComponent { Image image = ImageHelper.loadImage(LEFT_PAGE_BUTTON_IMAGE_PATH); pageLeft = new HoverButton(null, image, image, image, new Rectangle(64, 64)); + //pageLeft.setBorder(BorderFactory.createLineBorder(new Color(180, 50, 0), 3, true)); //debug pageLeft.setBounds(0, 0, 64, 64); pageLeft.setVisible(false); pageLeft.setObserver(() -> { @@ -146,12 +142,46 @@ public class MageBook extends JComponent { add(jPanelCenter, BorderLayout.CENTER); add(jPanelRight, BorderLayout.LINE_END); - cardDimensions = new CardDimensions(0.45d); - } + int captionHeight = Math.max(30, pageLeft.getHeight()); // caption size = next-prev images - private void addLeftRightPageButtons() { - jLayeredPane.add(pageLeft, JLayeredPane.DEFAULT_LAYER, 0); - jLayeredPane.add(pageRight, JLayeredPane.DEFAULT_LAYER, 1); + + // Top Panel (left page + (caption / stats) + right page + jPanelTop = new JPanel(); + jPanelTop.setLayout(new BorderLayout()); + // jPanelTop.setBorder(BorderFactory.createLineBorder(new Color(180, 50, 150), 3, true)); // debug + jPanelTop.setPreferredSize(new Dimension(captionHeight, captionHeight)); + jPanelCenter.add(jPanelTop, BorderLayout.NORTH); + + // page left + pageRight.setPreferredSize(new Dimension(pageRight.getWidth(), pageRight.getHeight())); + jPanelTop.add(pageRight, BorderLayout.EAST); + // page right + pageLeft.setPreferredSize(new Dimension(pageLeft.getWidth(), pageLeft.getHeight())); + jPanelTop.add(pageLeft, BorderLayout.WEST); + + // Caption Panel + jPanelCaption = new JPanel(); + jPanelCaption.setLayout(new BorderLayout()); + jPanelCaption.setOpaque(false); + jPanelTop.add(jPanelCaption, BorderLayout.CENTER); + + // set's caption + setCaption = new JLabel(); + setCaption.setHorizontalAlignment(SwingConstants.CENTER); + //setCaption.setBorder(BorderFactory.createLineBorder(new Color(180, 50, 150), 3, true)); // debug + setCaption.setFont(jLayeredPane.getFont().deriveFont(25f)); + setCaption.setText("EMPTY CAPTION"); + jPanelCaption.add(setCaption, BorderLayout.NORTH); + + // set's info + setInfo = new JLabel(); + setInfo.setHorizontalAlignment(SwingConstants.CENTER); + //setCaption.setBorder(BorderFactory.createLineBorder(new Color(180, 50, 150), 3, true)); // debug + setInfo.setFont(jLayeredPane.getFont().deriveFont(17f)); + setInfo.setText("EMPTY STATS"); + jPanelCaption.add(setInfo, BorderLayout.SOUTH); + + cardDimensions = new CardDimensions(0.45d); } private void addSetTabs() { @@ -174,7 +204,7 @@ public class MageBook extends JComponent { if (setImage != null) { tab.setOverlayImage(setImage); } else { - System.out.println("Couldn't find: " + "/plugins/images/sets/" + set + "-C.jpg"); + System.out.println("Couldn't find symbol image: " + "/plugins/images/sets/" + set + "-C.jpg"); } tab.setSet(set); tab.setBounds(0, y, 39, 120); @@ -217,8 +247,10 @@ public class MageBook extends JComponent { private void showCardsOrTokens() { stateChanged = false; if (showCardsOrTokens) { + updateCardStats(currentSet, true); showCards(); } else { + updateCardStats(currentSet, false); int numTokens = showTokens(); showEmblems(numTokens); } @@ -226,14 +258,16 @@ public class MageBook extends JComponent { public void showCards() { jLayeredPane.removeAll(); - addLeftRightPageButtons(); + + // stats info + updateCardStats(currentSet, true); List cards = getCards(currentPage, currentSet); int size = cards.size(); Rectangle rectangle = new Rectangle(); rectangle.translate(OFFSET_X, OFFSET_Y); - for (int i = 0; i < Math.min(conf.CARDS_PER_PAGE / 2, size); i++) { + for (int i = 0; i < min(conf.CARDS_PER_PAGE / 2, size); i++) { Card card = cards.get(i).getMockCard(); addCard(new CardView(card), bigCard, null, rectangle); @@ -245,7 +279,7 @@ public class MageBook extends JComponent { - (cardDimensions.frameWidth + CardPosition.GAP_X) * conf.CARD_COLUMNS + CardPosition.GAP_X - OFFSET_X; rectangle.setLocation(second_page_x, OFFSET_Y); - for (int i = conf.CARDS_PER_PAGE / 2; i < Math.min(conf.CARDS_PER_PAGE, size); i++) { + for (int i = conf.CARDS_PER_PAGE / 2; i < min(conf.CARDS_PER_PAGE, size); i++) { Card card = cards.get(i).getMockCard(); addCard(new CardView(card), bigCard, null, rectangle); rectangle = CardPosition.translatePosition(i - conf.CARDS_PER_PAGE / 2, rectangle, conf); @@ -256,7 +290,6 @@ public class MageBook extends JComponent { public int showTokens() { jLayeredPane.removeAll(); - addLeftRightPageButtons(); List tokens = getTokens(currentPage, currentSet); int size = tokens.size(); @@ -264,7 +297,7 @@ public class MageBook extends JComponent { if (tokens != null && tokens.size() > 0) { Rectangle rectangle = new Rectangle(); rectangle.translate(OFFSET_X, OFFSET_Y); - for (int i = 0; i < Math.min(conf.CARDS_PER_PAGE / 2, size); i++) { + for (int i = 0; i < min(conf.CARDS_PER_PAGE / 2, size); i++) { Token token = tokens.get(i); addToken(token, bigCard, null, rectangle); rectangle = CardPosition.translatePosition(i, rectangle, conf); @@ -275,7 +308,7 @@ public class MageBook extends JComponent { - (cardDimensions.frameWidth + CardPosition.GAP_X) * conf.CARD_COLUMNS + CardPosition.GAP_X - OFFSET_X; rectangle.setLocation(second_page_x, OFFSET_Y); - for (int i = conf.CARDS_PER_PAGE / 2; i < Math.min(conf.CARDS_PER_PAGE, size); i++) { + for (int i = conf.CARDS_PER_PAGE / 2; i < min(conf.CARDS_PER_PAGE, size); i++) { Token token = tokens.get(i); addToken(token, bigCard, null, rectangle); rectangle = CardPosition.translatePosition(i - conf.CARDS_PER_PAGE / 2, rectangle, conf); @@ -346,12 +379,26 @@ public class MageBook extends JComponent { boolean implemented = card.getRarity() != Rarity.NA; + // implemented label + // old code, nowadays app load only implemented cards (JayDi85, 23.11.2017) + /* GlowText label = new GlowText(); label.setGlow(implemented ? Color.green : NOT_IMPLEMENTED, 12, 0.0f); label.setText(implemented ? "Implemented" : "Not implemented"); int dx = implemented ? 15 : 5; label.setBounds(rectangle.x + dx, rectangle.y + cardDimensions.frameHeight + 7, 110, 30); jLayeredPane.add(label); + */ + + // card number label + JLabel cardNumber = new JLabel(); + int dy = -5; // image panel have empty space in bottom (bug?), need to move label up + cardNumber.setBounds(rectangle.x, rectangle.y + cardImg.getHeight() + dy, cardDimensions.frameWidth, 20); + cardNumber.setHorizontalAlignment(SwingConstants.CENTER); + //cardNumber.setBorder(BorderFactory.createLineBorder(new Color(180, 50, 150), 3, true)); + cardNumber.setFont(jLayeredPane.getFont().deriveFont(jLayeredPane.getFont().getStyle() | Font.BOLD)); + cardNumber.setText(card.getCardNumber()); + jLayeredPane.add(cardNumber); } private void addToken(Token token, BigCard bigCard, UUID gameId, Rectangle rectangle) { @@ -389,6 +436,60 @@ public class MageBook extends JComponent { return cards.subList(start, end); } + private void updateCardStats(String setCode, boolean isCardsShow){ + // sets do not have total cards number, it's a workaround + + ExpansionSet set = Sets.findSet(setCode); + if (set != null){ + setCaption.setText(set.getCode() + " - " + set.getName()); + }else{ + setCaption.setText("ERROR"); + setInfo.setText("ERROR"); + return; + } + + if (!isCardsShow){ + // tokens or emblems, stats not need + setInfo.setText(""); + return; + } + + // cards stats + + int startNumber = 9999; + int endNumber = 0; + + List cards = set.getSetCardInfo(); + + // first run for numbers list + LinkedList haveNumbers = new LinkedList<>(); + for (ExpansionSet.SetCardInfo card: cards){ + int cardNumber = Integer.parseInt(card.getCardNumber()); + startNumber = min(startNumber, cardNumber); + endNumber = Math.max(endNumber, cardNumber); + haveNumbers.add(cardNumber); + } + + // second run for empty numbers + int countHave = haveNumbers.size(); + int countNotHave = 0; + if (cards.size() > 0){ + for(int i = startNumber; i <= endNumber; i++){ + if(!haveNumbers.contains(i)){ + countNotHave++; + } + } + } + + // result + setInfo.setText(String.format("Have %d cards of %d", countHave, countHave + countNotHave)); + if (countNotHave > 0) { + setInfo.setForeground(new Color(150, 0, 0)); + }else{ + setInfo.setForeground(jLayeredPane.getForeground()); + } + } + private List getTokens(int page, String set) { ArrayList allTokens = getTokenCardUrls(); ArrayList tokens = new ArrayList<>(); @@ -576,7 +677,6 @@ public class MageBook extends JComponent { setSize(conf.WIDTH, conf.HEIGHT); setPreferredSize(new Dimension(conf.WIDTH, conf.HEIGHT)); setMinimumSize(new Dimension(conf.WIDTH, conf.HEIGHT)); - pageRight.setBounds(conf.WIDTH - 2 * LEFT_RIGHT_PAGES_WIDTH - 64, 0, 64, 64); addSetTabs(); showCards(); } @@ -616,7 +716,7 @@ public class MageBook extends JComponent { _3x3Configuration() { this.WIDTH = 950; - this.HEIGHT = 650; + this.HEIGHT = 650 + 50; // + for caption CARD_ROWS = 3; CARD_COLUMNS = 3; this.CARDS_PER_PAGE = 18; @@ -629,7 +729,7 @@ public class MageBook extends JComponent { _4x4Configuration() { this.WIDTH = 1250; - this.HEIGHT = 850; + this.HEIGHT = 850 + 50; // + for caption CARD_ROWS = 4; CARD_COLUMNS = 4; this.CARDS_PER_PAGE = 32; @@ -638,6 +738,10 @@ public class MageBook extends JComponent { } } + private JPanel jPanelTop; + private JPanel jPanelCaption; + private JLabel setCaption; + private JLabel setInfo; private JPanel jPanelLeft; private ImagePanel jPanelCenter; private JPanel jPanelRight; @@ -648,6 +752,8 @@ public class MageBook extends JComponent { private int currentPage = 0; private String currentSet = "RTR"; + private int currentCardsInSet = 0; + private int currentCardsNotInSet = 0; private static CardDimensions cardDimensions = new CardDimensions(1.2d); private static final Logger log = Logger.getLogger(MageBook.class); 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 4c50b6c8b34..e473497f6d1 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -560,7 +560,7 @@ public class TablesPanel extends javax.swing.JPanel { formatFilterList.add(RowFilter.regexFilter("^Constructed - Vintage", TableTableModel.COLUMN_DECK_TYPE)); } if (btnFormatCommander.isSelected()) { - formatFilterList.add(RowFilter.regexFilter("^Commander|^Duel Commander|^Penny Dreadful Commander", TableTableModel.COLUMN_DECK_TYPE)); + formatFilterList.add(RowFilter.regexFilter("^Commander|^Duel Commander|^Penny Dreadful Commander|^Freeform Commander", TableTableModel.COLUMN_DECK_TYPE)); } if (btnFormatTinyLeader.isSelected()) { formatFilterList.add(RowFilter.regexFilter("^Tiny", TableTableModel.COLUMN_DECK_TYPE)); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java index 2c0f7cd44bf..35ec4fc2140 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java @@ -327,6 +327,10 @@ public abstract class CardRenderer { = faceArtImage.getSubimage( (int) (artRect.getX() * fullCardImgWidth), (int) (artRect.getY() * fullCardImgHeight), (int) artWidth, (int) artHeight);*/ + RenderingHints rh = new RenderingHints( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHints(rh); g.drawImage(faceArtImage, x, y, (int) targetWidth, (int) targetHeight, diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererUtils.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererUtils.java index 6e3921f099c..eca040234fb 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererUtils.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardRendererUtils.java @@ -16,8 +16,8 @@ import java.util.regex.Pattern; /** * @author stravant@gmail.com - *

- * Various static utilities for use in the card renderer + *

+ * Various static utilities for use in the card renderer */ public final class CardRendererUtils { @@ -51,6 +51,38 @@ public final class CardRendererUtils { // Return the buffered image return bimage; } + + private static Color abitbrighter(Color c) { + int r = c.getRed(); + int g = c.getGreen(); + int b = c.getBlue(); + int alpha = c.getAlpha(); + + int plus_r = (int) ((255 - r) / 2); + int plus_g = (int) ((255 - g) / 2); + int plus_b = (int) ((255 - b) / 2); + + return new Color(r + plus_r, + g + plus_g, + b + plus_b, + alpha); + } + + private static Color abitdarker(Color c) { + int r = c.getRed(); + int g = c.getGreen(); + int b = c.getBlue(); + int alpha = c.getAlpha(); + + int plus_r = (int) (Math.min (255 - r, r) / 2); + int plus_g = (int) (Math.min (255 - g, g) / 2); + int plus_b = (int) (Math.min (255 - b, b) / 2); + + return new Color(r - plus_r, + g - plus_g, + b - plus_b, + alpha); + } // Draw a rounded box with a 2-pixel border // Used on various card parts. @@ -68,6 +100,12 @@ public final class CardRendererUtils { g.fillOval(x + 2, y + 2, bevel * 2 - 4, h - 4); g.fillOval(x + 2 + w - bevel * 2, y + 2, bevel * 2 - 4, h - 4); g.fillRect(x + bevel, y + 2, w - 2 * bevel, h - 4); + g.setPaint(fill); + g.setColor(abitbrighter(g.getColor())); + g.drawLine(x + 1 + bevel, y + 1, x + 1 + bevel + w - 2 * bevel - 2, y + 1); + g.setPaint(fill); + g.setColor(abitdarker(g.getColor())); + g.drawLine(x + 1 + bevel, y + h - 2, x + 1 + bevel + w - 2 * bevel - 2, y + h - 2); } // Get the width of a mana cost rendered with ManaSymbols.draw diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java index e603ac6d1e2..312d232683c 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java @@ -106,7 +106,7 @@ public class ModernCardRenderer extends CardRenderer { public static final Color BORDER_RED = new Color(201, 71, 58); public static final Color BORDER_GREEN = new Color(4, 136, 69); public static final Color BORDER_GOLD = new Color(255, 228, 124); - public static final Color BORDER_COLORLESS = new Color(238, 242, 242); + public static final Color BORDER_COLORLESS = new Color(208, 212, 212); public static final Color BORDER_LAND = new Color(190, 173, 115); public static final Color BOX_WHITE = new Color(244, 245, 239); @@ -115,7 +115,7 @@ public class ModernCardRenderer extends CardRenderer { public static final Color BOX_RED = new Color(246, 208, 185); public static final Color BOX_GREEN = new Color(205, 221, 213); public static final Color BOX_GOLD = new Color(223, 195, 136); - public static final Color BOX_COLORLESS = new Color(220, 228, 232); + public static final Color BOX_COLORLESS = new Color(200, 208, 212); public static final Color BOX_LAND = new Color(220, 215, 213); public static final Color BOX_INVENTION = new Color(209, 97, 33); public static final Color BOX_VEHICLE = new Color(155, 105, 60); @@ -128,21 +128,21 @@ public class ModernCardRenderer extends CardRenderer { public static final Color BOX_GOLD_NIGHT = new Color(171, 134, 70); public static final Color BOX_COLORLESS_NIGHT = new Color(118, 147, 158); - public static final Color LAND_TEXTBOX_WHITE = new Color(248, 232, 188, 244); - public static final Color LAND_TEXTBOX_BLUE = new Color(189, 212, 236, 244); - public static final Color LAND_TEXTBOX_BLACK = new Color(174, 164, 162, 244); - public static final Color LAND_TEXTBOX_RED = new Color(242, 168, 133, 244); - public static final Color LAND_TEXTBOX_GREEN = new Color(198, 220, 198, 244); - public static final Color LAND_TEXTBOX_GOLD = new Color(236, 229, 207, 244); + public static final Color LAND_TEXTBOX_WHITE = new Color(248, 232, 188, 234); + public static final Color LAND_TEXTBOX_BLUE = new Color(189, 212, 236, 234); + public static final Color LAND_TEXTBOX_BLACK = new Color(174, 164, 162, 234); + public static final Color LAND_TEXTBOX_RED = new Color(242, 168, 133, 234); + public static final Color LAND_TEXTBOX_GREEN = new Color(198, 220, 198, 234); + public static final Color LAND_TEXTBOX_GOLD = new Color(236, 229, 207, 234); - public static final Color TEXTBOX_WHITE = new Color(252, 249, 244, 244); - public static final Color TEXTBOX_BLUE = new Color(229, 238, 247, 244); - public static final Color TEXTBOX_BLACK = new Color(241, 241, 240, 244); - public static final Color TEXTBOX_RED = new Color(243, 224, 217, 244); - public static final Color TEXTBOX_GREEN = new Color(217, 232, 223, 244); - public static final Color TEXTBOX_GOLD = new Color(240, 234, 209, 244); - public static final Color TEXTBOX_COLORLESS = new Color(219, 229, 233, 244); - public static final Color TEXTBOX_LAND = new Color(218, 214, 212, 244); + public static final Color TEXTBOX_WHITE = new Color(252, 249, 244, 234); + public static final Color TEXTBOX_BLUE = new Color(229, 238, 247, 234); + public static final Color TEXTBOX_BLACK = new Color(241, 241, 240, 234); + public static final Color TEXTBOX_RED = new Color(243, 224, 217, 234); + public static final Color TEXTBOX_GREEN = new Color(217, 232, 223, 234); + public static final Color TEXTBOX_GOLD = new Color(240, 234, 209, 234); + public static final Color TEXTBOX_COLORLESS = new Color(199, 209, 213, 234); + public static final Color TEXTBOX_LAND = new Color(218, 214, 212, 234); public static final Color ERROR_COLOR = new Color(255, 0, 255); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java index 938e45aa77a..3b1c9eda8dc 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java @@ -124,7 +124,6 @@ public final class ImageCache { if (exists) { LOGGER.debug("loading thumbnail for " + key + ", path=" + thumbnailPath); BufferedImage thumbnailImage = loadImage(thumbnailFile); - if (thumbnailImage == null) { // thumbnail exists but broken for some reason LOGGER.warn("failed loading thumbnail for " + key + ", path=" + thumbnailPath + ", thumbnail file is probably broken, attempting to recreate it..."); @@ -361,6 +360,23 @@ public final class ImageCache { } } + public static boolean isFaceImagePresent(CardView card) { + String path; + path = CardImageUtils.generateFaceImagePath(card.getName(), card.getExpansionSetCode()); + + if (path == null) { + return false; + } + TFile file = getTFile(path); + if (file == null) { + return false; + } + if (file.exists()) { + return true; + } + return false; + } + public static BufferedImage getThumbnail(CardView card) { return getImage(getKey(card, card.getName(), "#thumb")); } @@ -464,7 +480,6 @@ public final class ImageCache { // return alternateName + "#" + card.getExpansionSetCode() + "#" +card.getType()+ "#" + card.getCardNumber() + "#" // + (card.getTokenSetCode() == null ? "":card.getTokenSetCode()); // } - /** * Load image from file * diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java new file mode 100644 index 00000000000..570be2a80e4 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/FreeformCommander.java @@ -0,0 +1,152 @@ +/* + * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.deck; + +import java.util.*; +import java.util.Map.Entry; +import mage.abilities.common.CanBeYourCommanderAbility; +import mage.abilities.keyword.PartnerAbility; +import mage.cards.Card; +import mage.cards.ExpansionSet; +import mage.cards.Sets; +import mage.cards.decks.Constructed; +import mage.cards.decks.Deck; +import mage.constants.SetType; +import mage.filter.FilterMana; + +/** + * + * @author spjspj + */ +public class FreeformCommander extends Constructed { + + protected List bannedCommander = new ArrayList<>(); + private static final Map pdAllowed = new HashMap<>(); + private static boolean setupAllowed = false; + + public FreeformCommander() { + this("Freeform Commander"); + for (ExpansionSet set : Sets.getInstance().values()) { + setCodes.add(set.getCode()); + } + } + + public FreeformCommander(String name) { + super(name); + } + + @Override + public boolean validate(Deck deck) { + boolean valid = true; + FilterMana colorIdentity = new FilterMana(); + + if (deck.getCards().size() + deck.getSideboard().size() != 100) { + invalid.put("Deck", "Must contain 100 cards: has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards"); + valid = false; + } + + List basicLandNames = new ArrayList<>(Arrays.asList("Forest", "Island", "Mountain", "Swamp", "Plains", "Wastes")); + Map counts = new HashMap<>(); + countCards(counts, deck.getCards()); + countCards(counts, deck.getSideboard()); + + for (Map.Entry entry : counts.entrySet()) { + if (entry.getValue() > 1) { + if (!basicLandNames.contains(entry.getKey())) { + invalid.put(entry.getKey(), "Too many: " + entry.getValue()); + valid = false; + } + } + } + + generateFreeformHash(); + + if (deck.getSideboard().size() < 1 || deck.getSideboard().size() > 2) { + invalid.put("Commander", "Sideboard must contain only the commander(s)"); + valid = false; + } else { + for (Card commander : deck.getSideboard()) { + if (!(commander.isCreature() || + commander.isLegendary())) { + invalid.put("Commander", "For Freeform Commander, the commander must be a creature or be legendary. Yours was: " + commander.getName()); + valid = false; + } + if (deck.getSideboard().size() == 2 && !commander.getAbilities().contains(PartnerAbility.getInstance())) { + invalid.put("Commander", "Commander without Partner (" + commander.getName() + ')'); + valid = false; + } + FilterMana commanderColor = commander.getColorIdentity(); + if (commanderColor.isWhite()) { + colorIdentity.setWhite(true); + } + if (commanderColor.isBlue()) { + colorIdentity.setBlue(true); + } + if (commanderColor.isBlack()) { + colorIdentity.setBlack(true); + } + if (commanderColor.isRed()) { + colorIdentity.setRed(true); + } + if (commanderColor.isGreen()) { + colorIdentity.setGreen(true); + } + } + } + + for (Card card : deck.getCards()) { + if (!cardHasValidColor(colorIdentity, card)) { + invalid.put(card.getName(), "Invalid color (" + colorIdentity.toString() + ')'); + valid = false; + } + } + + for (Card card : deck.getSideboard()) { + if (!isSetAllowed(card.getExpansionSetCode())) { + if (!legalSets(card)) { + invalid.put(card.getName(), "Not allowed Set: " + card.getExpansionSetCode()); + valid = false; + } + } + } + return valid; + } + + public boolean cardHasValidColor(FilterMana commander, Card card) { + FilterMana cardColor = card.getColorIdentity(); + return !(cardColor.isBlack() && !commander.isBlack() + || cardColor.isBlue() && !commander.isBlue() + || cardColor.isGreen() && !commander.isGreen() + || cardColor.isRed() && !commander.isRed() + || cardColor.isWhite() && !commander.isWhite()); + } + + public void generateFreeformHash() { + return; + } +} diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml new file mode 100644 index 00000000000..8dc87f89fbf --- /dev/null +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + org.mage + mage-server-plugins + 1.4.26 + + + mage-game-freeformcommanderfreeforall + jar + Mage Game Freeform Commander Free For All + + + + ${project.groupId} + mage + ${project.version} + + + + + src + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.7 + 1.7 + + + + maven-resources-plugin + + UTF-8 + + + + + + mage-game-freeforall + + + + + diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAll.java b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAll.java new file mode 100644 index 00000000000..e6e1aa0607a --- /dev/null +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAll.java @@ -0,0 +1,77 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.game; + +import java.util.UUID; +import mage.constants.MultiplayerAttackOption; +import mage.constants.RangeOfInfluence; +import mage.game.match.MatchType; + +/** + * + * @author spjspj + */ +public class FreeformCommanderFreeForAll extends GameCommanderImpl { + + private int numPlayers; + + public FreeformCommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { + super(attackOption, range, freeMulligans, startLife); + } + + public FreeformCommanderFreeForAll(final FreeformCommanderFreeForAll game) { + super(game); + this.numPlayers = game.numPlayers; + } + + @Override + protected void init(UUID choosingPlayerId) { + startingPlayerSkipsDraw = false; + super.init(choosingPlayerId); + } + + @Override + public MatchType getGameType() { + return new FreeformCommanderFreeForAllType(); + } + + @Override + public int getNumPlayers() { + return numPlayers; + } + + public void setNumPlayers(int numPlayers) { + this.numPlayers = numPlayers; + } + + @Override + public FreeformCommanderFreeForAll copy() { + return new FreeformCommanderFreeForAll(this); + } +} diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllMatch.java b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllMatch.java new file mode 100644 index 00000000000..71f2e242432 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllMatch.java @@ -0,0 +1,55 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.game; + +import mage.game.match.MatchImpl; +import mage.game.match.MatchOptions; + +/** + * + * @author spjspj + */ +public class FreeformCommanderFreeForAllMatch extends MatchImpl { + + public FreeformCommanderFreeForAllMatch(MatchOptions options) { + super(options); + } + + @Override + public void startGame() throws GameException { + int startLife = 40; + boolean alsoHand = true; + FreeformCommanderFreeForAll game = new FreeformCommanderFreeForAll(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + game.setStartMessage(this.createGameStartMessage()); + game.setAlsoHand(alsoHand); + game.setAlsoLibrary(true); + initGame(game); + games.add(game); + } + +} diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllType.java b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllType.java new file mode 100644 index 00000000000..27967352def --- /dev/null +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllType.java @@ -0,0 +1,58 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.game; + +import mage.game.match.MatchType; + + +/** + * + * @author spjspj + */ +public class FreeformCommanderFreeForAllType extends MatchType { + + public FreeformCommanderFreeForAllType() { + this.name = "Freeform Commander Free For All"; + this.maxPlayers = 10; + this.minPlayers = 3; + this.numTeams = 0; + this.useAttackOption = true; + this.useRange = true; + this.sideboardingAllowed = false; + } + + protected FreeformCommanderFreeForAllType(final FreeformCommanderFreeForAllType matchType) { + super(matchType); + } + + @Override + public FreeformCommanderFreeForAllType copy() { + return new FreeformCommanderFreeForAllType(this); + } +} 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 84253863c6c..b8515e19bf6 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 @@ -2250,7 +2250,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { * @return */ private UUID getRandomOpponent(UUID abilityControllerId, Game game) { - UUID randomOpponentId = game.getOpponents(abilityControllerId).iterator().next(); + UUID randomOpponentId = null; Set opponents = game.getOpponents(abilityControllerId); if (opponents.size() > 1) { int rand = RandomUtil.nextInt(opponents.size()); diff --git a/Mage.Server.Plugins/pom.xml b/Mage.Server.Plugins/pom.xml index 6088bbe80d3..b244b900b5c 100644 --- a/Mage.Server.Plugins/pom.xml +++ b/Mage.Server.Plugins/pom.xml @@ -25,6 +25,7 @@ Mage.Game.TinyLeadersDuel Mage.Game.CanadianHighlanderDuel Mage.Game.PennyDreadfulCommanderFreeForAll + Mage.Game.FreeformCommanderFreeForAll Mage.Game.TwoPlayerDuel Mage.Player.AI Mage.Player.AIMinimax diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index ba16d20cc35..bfb4e4436c1 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -76,6 +76,7 @@ + @@ -151,6 +152,7 @@ + diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index 364875c0818..9a04035ad2c 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -154,6 +154,13 @@ ${project.version} runtime + + ${project.groupId} + mage-game-freeformcommanderfreeforall + ${project.version} + runtime + + ${project.groupId} mage-game-momirduel diff --git a/Mage.Server/release/config/config.xml b/Mage.Server/release/config/config.xml index 45b1ed1e2f0..b416ca297a9 100644 --- a/Mage.Server/release/config/config.xml +++ b/Mage.Server/release/config/config.xml @@ -73,6 +73,7 @@ + @@ -147,6 +148,7 @@ + diff --git a/Mage.Sets/src/mage/cards/a/AlexiZephyrMage.java b/Mage.Sets/src/mage/cards/a/AlexiZephyrMage.java index 3f55ec6cdb5..e7e48244581 100644 --- a/Mage.Sets/src/mage/cards/a/AlexiZephyrMage.java +++ b/Mage.Sets/src/mage/cards/a/AlexiZephyrMage.java @@ -40,6 +40,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; +import mage.constants.TargetAdjustment; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.StaticFilters; @@ -65,6 +66,7 @@ public class AlexiZephyrMage extends CardImpl { ability.addCost(new TapSourceCost()); ability.addCost(new DiscardTargetCost(new TargetCardInHand(2, new FilterCard("two cards")))); ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURES)); + ability.setTargetAdjustment(TargetAdjustment.X_TARGETS); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/c/CurtainOfLight.java b/Mage.Sets/src/mage/cards/c/CurtainOfLight.java index f7a762d49e8..bc3bcacfe8a 100644 --- a/Mage.Sets/src/mage/cards/c/CurtainOfLight.java +++ b/Mage.Sets/src/mage/cards/c/CurtainOfLight.java @@ -109,6 +109,7 @@ class CurtainOfLightEffect extends OneShotEffect { CombatGroup combatGroup = game.getCombat().findGroup(permanent.getId()); if (combatGroup != null) { combatGroup.setBlocked(true); + game.informPlayers(permanent.getLogName() + " has become blocked"); return true; } } diff --git a/Mage.Sets/src/mage/cards/d/DazzlingBeauty.java b/Mage.Sets/src/mage/cards/d/DazzlingBeauty.java index 02cbf8dcfda..0c82a483f8e 100644 --- a/Mage.Sets/src/mage/cards/d/DazzlingBeauty.java +++ b/Mage.Sets/src/mage/cards/d/DazzlingBeauty.java @@ -110,6 +110,7 @@ class DazzlingBeautyEffect extends OneShotEffect { CombatGroup combatGroup = game.getCombat().findGroup(permanent.getId()); if (combatGroup != null) { combatGroup.setBlocked(true); + game.informPlayers(permanent.getLogName() + " has become blocked"); return true; } } diff --git a/Mage.Sets/src/mage/cards/d/DeepwoodElder.java b/Mage.Sets/src/mage/cards/d/DeepwoodElder.java new file mode 100644 index 00000000000..14b5283995d --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeepwoodElder.java @@ -0,0 +1,117 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.d; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BecomesBasicLandTargetEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetAdjustment; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.Target; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author TheElk801 + */ +public class DeepwoodElder extends CardImpl { + + public DeepwoodElder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{G}"); + + this.subtype.add(SubType.DRYAD); + this.subtype.add(SubType.SPELLSHAPER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {X}{G}{G}, {tap}, Discard a card: X target lands become Forests until end of turn. + Ability ability = new SimpleActivatedAbility(new DeepwoodElderEffect(), new ManaCostsImpl("{X}{G}{G}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new DiscardCardCost()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_LANDS)); + ability.setTargetAdjustment(TargetAdjustment.X_TARGETS); + this.addAbility(ability); + } + + public DeepwoodElder(final DeepwoodElder card) { + super(card); + } + + @Override + public DeepwoodElder copy() { + return new DeepwoodElder(this); + } +} + +class DeepwoodElderEffect extends OneShotEffect { + + DeepwoodElderEffect() { + super(Outcome.LoseAbility); + this.staticText = "X target lands become Forests until end of turn"; + } + + DeepwoodElderEffect(final DeepwoodElderEffect effect) { + super(effect); + } + + @Override + public DeepwoodElderEffect copy() { + return new DeepwoodElderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Target target : source.getTargets()) { + for (UUID targetId : target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + ContinuousEffect effect = new BecomesBasicLandTargetEffect(Duration.EndOfTurn, SubType.FOREST); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + } + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FactOrFiction.java b/Mage.Sets/src/mage/cards/f/FactOrFiction.java index e90d554dd0a..0937996e9b8 100644 --- a/Mage.Sets/src/mage/cards/f/FactOrFiction.java +++ b/Mage.Sets/src/mage/cards/f/FactOrFiction.java @@ -41,7 +41,9 @@ import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; +import mage.target.Target; import mage.target.TargetCard; +import mage.target.common.TargetOpponent; /** * @author North @@ -96,6 +98,13 @@ class FactOrFictionEffect extends OneShotEffect { Set opponents = game.getOpponents(source.getControllerId()); if (!opponents.isEmpty()) { Player opponent = game.getPlayer(opponents.iterator().next()); + if (opponents.size() > 1) { + Target targetOpponent = new TargetOpponent(true); + if (controller.chooseTarget(Outcome.Neutral, targetOpponent, source, game)) { + opponent = game.getPlayer(targetOpponent.getFirstTarget()); + game.informPlayers(controller.getLogName() + " chose " + opponent.getLogName() + " to separate the revealed cards"); + } + } TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY, new FilterCard("cards to put in the first pile")); List pile1 = new ArrayList<>(); if (opponent.choose(Outcome.Neutral, cards, target, game)) { diff --git a/Mage.Sets/src/mage/cards/m/MiracleWorker.java b/Mage.Sets/src/mage/cards/m/MiracleWorker.java index 260ea0cb900..9c336e1c8cc 100644 --- a/Mage.Sets/src/mage/cards/m/MiracleWorker.java +++ b/Mage.Sets/src/mage/cards/m/MiracleWorker.java @@ -38,8 +38,9 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.AttachedToControlledPermanentPredicate; import mage.filter.predicate.permanent.AttachedToPredicate; import mage.target.TargetPermanent; @@ -52,7 +53,8 @@ public class MiracleWorker extends CardImpl { private static final FilterPermanent filter = new FilterPermanent("Aura attached to a creature you control"); static { - filter.add(new AttachedToPredicate(new FilterControlledCreaturePermanent())); + filter.add(new AttachedToControlledPermanentPredicate()); + filter.add(new AttachedToPredicate(new FilterCreaturePermanent())); filter.add(new SubtypePredicate(SubType.AURA)); } diff --git a/Mage.Sets/src/mage/cards/o/OrcishSpy.java b/Mage.Sets/src/mage/cards/o/OrcishSpy.java index 77d23fe9b9c..c61b09b45b2 100644 --- a/Mage.Sets/src/mage/cards/o/OrcishSpy.java +++ b/Mage.Sets/src/mage/cards/o/OrcishSpy.java @@ -38,6 +38,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; +import mage.target.TargetPlayer; /** * @@ -54,6 +55,7 @@ public class OrcishSpy extends CardImpl { // {T}: Look at the top three cards of target player's library. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new LookLibraryTopCardTargetPlayerEffect(3), new TapSourceCost()); + ability.addTarget(new TargetPlayer()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/r/RootSliver.java b/Mage.Sets/src/mage/cards/r/RootSliver.java index 92a181ed9e5..facf6a51ffe 100644 --- a/Mage.Sets/src/mage/cards/r/RootSliver.java +++ b/Mage.Sets/src/mage/cards/r/RootSliver.java @@ -29,34 +29,39 @@ package mage.cards.r; import java.util.UUID; import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.CantBeCounteredControlledEffect; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.CantBeCounteredSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterSpell; -import mage.filter.FilterStackObject; import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; /** * * @author cbt33, BetaSteward (Autumn's Veil, Combust) */ public class RootSliver extends CardImpl { - + private static final FilterSpell filter = new FilterSpell("Sliver spells"); - + static { filter.add(new SubtypePredicate(SubType.SLIVER)); } - public RootSliver(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.SLIVER); this.power = new MageInt(2); @@ -65,7 +70,7 @@ public class RootSliver extends CardImpl { // Root Sliver can't be countered. this.addAbility(new SimpleStaticAbility(Zone.STACK, new CantBeCounteredSourceEffect())); // Sliver spells can't be countered by spells or abilities. - this.addAbility(new SimpleStaticAbility(Zone.STACK, new CantBeCounteredControlledEffect(filter, new FilterStackObject(), Duration.WhileOnBattlefield))); + this.addAbility(new SimpleStaticAbility(Zone.STACK, new RootSliverEffect())); } @@ -79,3 +84,42 @@ public class RootSliver extends CardImpl { } } +class RootSliverEffect extends ContinuousRuleModifyingEffectImpl { + + public RootSliverEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "Sliver spells can't be countered by spells or abilities."; + } + + public RootSliverEffect(final RootSliverEffect effect) { + super(effect); + } + + @Override + public RootSliverEffect copy() { + return new RootSliverEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COUNTER; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell != null && spell.hasSubtype(SubType.SLIVER, game)) { + MageObject sourceObject = game.getObject(event.getSourceId()); + if (sourceObject != null && sourceObject instanceof StackObject) { + return true; + } + } + return false; + } + +} diff --git a/Mage.Sets/src/mage/cards/s/SimicManipulator.java b/Mage.Sets/src/mage/cards/s/SimicManipulator.java index decbf8fc60e..40675ce879e 100644 --- a/Mage.Sets/src/mage/cards/s/SimicManipulator.java +++ b/Mage.Sets/src/mage/cards/s/SimicManipulator.java @@ -30,26 +30,20 @@ package mage.cards.s; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.Mode; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.Cost; import mage.abilities.costs.common.RemoveVariableCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.abilities.keyword.EvolveAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; +import mage.constants.TargetAdjustment; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.common.FilterCreaturePermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; /** @@ -69,7 +63,7 @@ public class SimicManipulator extends CardImpl { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power less than or equal to the number of +1/+1 counters removed this way"); public SimicManipulator(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{U}"); this.subtype.add(SubType.MUTANT); this.subtype.add(SubType.WIZARD); @@ -80,11 +74,10 @@ public class SimicManipulator extends CardImpl { this.addAbility(new EvolveAbility()); // {T}, Remove one or more +1/+1 counters from Simic Manipulator: Gain control of target creature with power less than or equal to the number of +1/+1 counters removed this way. - // TODO: Improve targeting, that only valid targets (power <= removed counters) can be choosen - // Disadvantage now is, that a creature can be targeted that couldn't be targeted by rules. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new SimicManipulatorGainControlTargetEffect(Duration.Custom), new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainControlTargetEffect(Duration.Custom, true), new TapSourceCost()); ability.addTarget(new TargetCreaturePermanent(filter)); ability.addCost(new RemoveVariableCountersSourceCost(CounterType.P1P1.createInstance(), 1, "Remove one or more +1/+1 counters from {this}")); + ability.setTargetAdjustment(TargetAdjustment.SIMIC_MANIPULATOR); this.addAbility(ability); } @@ -97,53 +90,3 @@ public class SimicManipulator extends CardImpl { return new SimicManipulator(this); } } - -class SimicManipulatorGainControlTargetEffect extends ContinuousEffectImpl { - - private boolean valid; - - public SimicManipulatorGainControlTargetEffect(Duration duration) { - super(duration, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl); - } - - public SimicManipulatorGainControlTargetEffect(final SimicManipulatorGainControlTargetEffect effect) { - super(effect); - this.valid = effect.valid; - } - - @Override - public void init(Ability source, Game game) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null) { - int maxPower = 0; - for (Cost cost : source.getCosts()) { - if (cost instanceof RemoveVariableCountersSourceCost) { - maxPower = ((RemoveVariableCountersSourceCost) cost).getAmount(); - break; - } - } - if (permanent.getPower().getValue() <= maxPower) { - valid = true; - } - } - } - - @Override - public SimicManipulatorGainControlTargetEffect copy() { - return new SimicManipulatorGainControlTargetEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null && valid) { - return permanent.changeControllerId(source.getControllerId(), game); - } - return false; - } - - @Override - public String getText(Mode mode) { - return "Gain control of target " + mode.getTargets().get(0).getTargetName() + ' ' + duration.toString(); - } -} diff --git a/Mage.Sets/src/mage/cards/s/SkyshroudWarBeast.java b/Mage.Sets/src/mage/cards/s/SkyshroudWarBeast.java new file mode 100644 index 00000000000..25705180cda --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SkyshroudWarBeast.java @@ -0,0 +1,120 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.ChooseOpponentEffect; +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.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.constants.Zone; +import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author TheElk801 + */ +public class SkyshroudWarBeast extends CardImpl { + + public SkyshroudWarBeast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // As Skyshroud War Beast enters the battlefield, choose an opponent. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseOpponentEffect(Outcome.BoostCreature))); + + // Skyshroud War Beast's power and toughness are each equal to the number of nonbasic lands the chosen player controls. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SkyshroudWarBeastEffect())); + } + + public SkyshroudWarBeast(final SkyshroudWarBeast card) { + super(card); + } + + @Override + public SkyshroudWarBeast copy() { + return new SkyshroudWarBeast(this); + } +} + +class SkyshroudWarBeastEffect extends ContinuousEffectImpl { + + public SkyshroudWarBeastEffect() { + super(Duration.EndOfGame, Layer.PTChangingEffects_7, SubLayer.CharacteristicDefining_7a, Outcome.BoostCreature); + staticText = "{this}'s power and toughness are each equal to the number of nonbasic lands the chosen player controls"; + } + + public SkyshroudWarBeastEffect(final SkyshroudWarBeastEffect effect) { + super(effect); + } + + @Override + public SkyshroudWarBeastEffect copy() { + return new SkyshroudWarBeastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + MageObject target = game.getObject(source.getSourceId()); + if (target != null) { + UUID playerId = (UUID) game.getState().getValue(source.getSourceId().toString() + ChooseOpponentEffect.VALUE_KEY); + FilterLandPermanent filter = new FilterLandPermanent(); + filter.add(new ControllerIdPredicate(playerId)); + int number = new PermanentsOnBattlefieldCount(filter).calculate(game, source, this); + target.getPower().setValue(number); + target.getToughness().setValue(number); + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/u/UmbraMystic.java b/Mage.Sets/src/mage/cards/u/UmbraMystic.java index 69f8b71562d..406702ef13f 100644 --- a/Mage.Sets/src/mage/cards/u/UmbraMystic.java +++ b/Mage.Sets/src/mage/cards/u/UmbraMystic.java @@ -39,15 +39,12 @@ import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterEnchantmentPermanent; -import mage.filter.predicate.ObjectPlayer; -import mage.filter.predicate.ObjectPlayerPredicate; import mage.filter.predicate.mageobject.SubtypePredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.filter.predicate.permanent.AttachedToControlledPermanentPredicate; /** * - * @author North + * @author North & L_J */ public class UmbraMystic extends CardImpl { @@ -55,6 +52,7 @@ public class UmbraMystic extends CardImpl { static { filter.add(new SubtypePredicate(SubType.AURA)); + filter.add(new AttachedToControlledPermanentPredicate()); } public UmbraMystic(UUID ownerId, CardSetInfo setInfo) { @@ -78,24 +76,3 @@ public class UmbraMystic extends CardImpl { return new UmbraMystic(this); } } - -class UmbraMysticPredicate implements ObjectPlayerPredicate> { - - @Override - public boolean apply(ObjectPlayer input, Game game) { - Permanent attachement = input.getObject(); - if (attachement != null) { - Permanent permanent = game.getPermanent(attachement.getAttachedTo()); - if (permanent != null && permanent.getControllerId().equals(input.getPlayerId())) { - return true; - } - } - - return false; - } - - @Override - public String toString() { - return "Attached to permanents you control"; - } -} diff --git a/Mage.Sets/src/mage/cards/v/Voidwalk.java b/Mage.Sets/src/mage/cards/v/Voidwalk.java index 4398705271d..5f8e83cd60a 100644 --- a/Mage.Sets/src/mage/cards/v/Voidwalk.java +++ b/Mage.Sets/src/mage/cards/v/Voidwalk.java @@ -28,19 +28,23 @@ package mage.cards.v; import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CipherEffect; -import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlSourceEffect; +import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; /** * @@ -49,7 +53,7 @@ import mage.target.common.TargetCreaturePermanent; public class Voidwalk extends CardImpl { public Voidwalk(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}"); // Exile target creature. Return it to the battlefield under its owner's control at the beginning of the next end step. this.getSpellAbility().addEffect(new VoidwalkEffect()); @@ -71,32 +75,29 @@ public class Voidwalk extends CardImpl { class VoidwalkEffect extends OneShotEffect { - private static final String effectText = "Exile target creature. Return it to the battlefield under its owner's control at the beginning of the next end step"; - - VoidwalkEffect() { - super(Outcome.Benefit); - staticText = effectText; + public VoidwalkEffect() { + super(Outcome.Detriment); + staticText = "Exile target creature. Return it to the battlefield under its owner's control at the beginning of the next end step"; } - VoidwalkEffect(VoidwalkEffect effect) { + public VoidwalkEffect(final VoidwalkEffect effect) { super(effect); } @Override public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - if (getTargetPointer().getFirst(game, source) != null) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (permanent != null) { - int zcc = game.getState().getZoneChangeCounter(permanent.getId()); - if (permanent.moveToExile(null, "", source.getSourceId(), game)) { - game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( - new ReturnToBattlefieldUnderOwnerControlSourceEffect(false, zcc + 1)), source); - } - } + MageObject sourceObject = game.getObject(source.getSourceId()); + if (controller != null && permanent != null && sourceObject != null) { + if (controller.moveCardToExileWithInfo(permanent, source.getSourceId(), sourceObject.getIdName(), source.getSourceId(), game, Zone.BATTLEFIELD, true)) { + //create delayed triggered ability + Effect effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(); + effect.setText("Return that card to the battlefield under its owner's control at the beginning of the next end step"); + effect.setTargetPointer(new FixedTarget(source.getFirstTarget(), game)); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); + return true; } - return true; } return false; } diff --git a/Mage.Sets/src/mage/cards/w/WanderwineProphets.java b/Mage.Sets/src/mage/cards/w/WanderwineProphets.java index ec1dc319225..0eaf95a4161 100644 --- a/Mage.Sets/src/mage/cards/w/WanderwineProphets.java +++ b/Mage.Sets/src/mage/cards/w/WanderwineProphets.java @@ -32,6 +32,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.turn.AddExtraTurnControllerEffect; import mage.abilities.keyword.ChampionAbility; import mage.cards.CardImpl; @@ -48,7 +49,7 @@ import mage.target.common.TargetControlledPermanent; */ public class WanderwineProphets extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent("Merfolk"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent("a Merfolk"); static { filter.add(new SubtypePredicate(SubType.MERFOLK)); @@ -64,8 +65,10 @@ public class WanderwineProphets extends CardImpl { // Champion a Merfolk this.addAbility(new ChampionAbility(this, SubType.MERFOLK, false)); // Whenever Wanderwine Prophets deals combat damage to a player, you may sacrifice a Merfolk. If you do, take an extra turn after this one. - Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new AddExtraTurnControllerEffect(), true); - ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(1, 1, filter, true))); + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new DoIfCostPaid( + new AddExtraTurnControllerEffect(), + new SacrificeTargetCost(new TargetControlledPermanent(1, 1, filter, true)) + ), true); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/Exodus.java b/Mage.Sets/src/mage/sets/Exodus.java index 59c6cd1644f..2f2ad4e4fdf 100644 --- a/Mage.Sets/src/mage/sets/Exodus.java +++ b/Mage.Sets/src/mage/sets/Exodus.java @@ -150,6 +150,7 @@ public class Exodus extends ExpansionSet { cards.add(new SetCardInfo("Shield Mate", 19, Rarity.COMMON, mage.cards.s.ShieldMate.class)); cards.add(new SetCardInfo("Skyshaper", 137, Rarity.UNCOMMON, mage.cards.s.Skyshaper.class)); cards.add(new SetCardInfo("Skyshroud Elite", 123, Rarity.UNCOMMON, mage.cards.s.SkyshroudElite.class)); + cards.add(new SetCardInfo("Skyshroud War Beast", 124, Rarity.RARE, mage.cards.s.SkyshroudWarBeast.class)); cards.add(new SetCardInfo("Slaughter", 74, Rarity.UNCOMMON, mage.cards.s.Slaughter.class)); cards.add(new SetCardInfo("Soltari Visionary", 20, Rarity.COMMON, mage.cards.s.SoltariVisionary.class)); cards.add(new SetCardInfo("Song of Serenity", 125, Rarity.UNCOMMON, mage.cards.s.SongOfSerenity.class)); diff --git a/Mage.Sets/src/mage/sets/MercadianMasques.java b/Mage.Sets/src/mage/sets/MercadianMasques.java index ad0e9aa438a..1a4b56768b4 100644 --- a/Mage.Sets/src/mage/sets/MercadianMasques.java +++ b/Mage.Sets/src/mage/sets/MercadianMasques.java @@ -123,6 +123,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("Deadly Insect", 238, Rarity.COMMON, mage.cards.d.DeadlyInsect.class)); cards.add(new SetCardInfo("Deathgazer", 130, Rarity.UNCOMMON, mage.cards.d.Deathgazer.class)); cards.add(new SetCardInfo("Deepwood Drummer", 239, Rarity.COMMON, mage.cards.d.DeepwoodDrummer.class)); + cards.add(new SetCardInfo("Deepwood Elder", 240, Rarity.RARE, mage.cards.d.DeepwoodElder.class)); cards.add(new SetCardInfo("Deepwood Ghoul", 131, Rarity.COMMON, mage.cards.d.DeepwoodGhoul.class)); cards.add(new SetCardInfo("Deepwood Legate", 132, Rarity.UNCOMMON, mage.cards.d.DeepwoodLegate.class)); cards.add(new SetCardInfo("Deepwood Tantiv", 241, Rarity.UNCOMMON, mage.cards.d.DeepwoodTantiv.class)); diff --git a/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java b/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java index 0aab88b1f3c..dd4cd886e02 100644 --- a/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java @@ -190,8 +190,10 @@ class BestowEntersBattlefieldEffect extends ReplacementEffectImpl { if (bestowPermanent != null) { if (bestowPermanent.hasSubtype(SubType.AURA, game)) { MageObject basicObject = bestowPermanent.getBasicMageObject(game); - basicObject.getSubtype(null).add(SubType.AURA); - basicObject.getCardType().remove(CardType.CREATURE); + if (basicObject != null && !basicObject.getSubtype(null).contains(SubType.AURA)) { + basicObject.getSubtype(null).add(SubType.AURA); + basicObject.getCardType().remove(CardType.CREATURE); + } } } return false; diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 0e045b21538..22361e38d8d 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -37,6 +37,8 @@ import mage.MageObjectImpl; import mage.Mana; import mage.ObjectColor; import mage.abilities.*; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.RemoveVariableCountersTargetCost; import mage.abilities.effects.common.NameACardEffect; import mage.abilities.mana.ActivatedManaAbilityImpl; import mage.cards.repository.PluginClassloaderRegistery; @@ -417,6 +419,19 @@ public abstract class CardImpl extends MageObjectImpl implements Card { ability.getTargets().add(new TargetCreaturePermanent(filter2)); } break; + case SIMIC_MANIPULATOR: //Simic Manipulator only + xValue = 0; + for (Cost cost : ability.getCosts()) { + if (cost instanceof RemoveVariableCountersTargetCost) { + xValue = ((RemoveVariableCountersTargetCost) cost).getAmount(); + break; + } + } + ability.getTargets().clear(); + FilterCreaturePermanent newFilter = new FilterCreaturePermanent("creature with power less than or equal to " + xValue); + newFilter.add(new PowerPredicate(ComparisonType.FEWER_THAN, xValue + 1)); + ability.addTarget(new TargetCreaturePermanent(newFilter)); + break; } } diff --git a/Mage/src/main/java/mage/constants/TargetAdjustment.java b/Mage/src/main/java/mage/constants/TargetAdjustment.java index 0eb07153af5..d6f3ab0469d 100644 --- a/Mage/src/main/java/mage/constants/TargetAdjustment.java +++ b/Mage/src/main/java/mage/constants/TargetAdjustment.java @@ -12,5 +12,6 @@ public enum TargetAdjustment { X_POWER_LEQ, CHOSEN_NAME, CHOSEN_COLOR, VERSE_COUNTER_TARGETS, - TREASURE_COUNTER_POWER + TREASURE_COUNTER_POWER, + SIMIC_MANIPULATOR } diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToControlledPermanentPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToControlledPermanentPredicate.java new file mode 100644 index 00000000000..897118e11c3 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/AttachedToControlledPermanentPredicate.java @@ -0,0 +1,58 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.filter.predicate.permanent; + +import mage.filter.predicate.ObjectPlayer; +import mage.filter.predicate.ObjectPlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author North & L_J + */ +public class AttachedToControlledPermanentPredicate implements ObjectPlayerPredicate> { + + @Override + public boolean apply(ObjectPlayer input, Game game) { + Permanent attachement = input.getObject(); + if (attachement != null) { + Permanent permanent = game.getPermanent(attachement.getAttachedTo()); + if (permanent != null && permanent.getControllerId().equals(input.getPlayerId())) { + return true; + } + } + + return false; + } + + @Override + public String toString() { + return "Attached to permanents you control"; + } +} diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 38d664896e3..0b76841eab2 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -27,6 +27,10 @@ */ package mage.game.stack; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.Mana; @@ -60,11 +64,6 @@ import mage.players.Player; import mage.util.GameLog; import mage.util.SubTypeList; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; - /** * * @author BetaSteward_at_googlemail.com @@ -254,7 +253,9 @@ public class Spell extends StackObjImpl implements Card { // Must be removed first time, after that will be removed by continous effect // Otherwise effects like evolve trigger from creature comes into play event card.getCardType().remove(CardType.CREATURE); - card.getSubtype(game).add(SubType.AURA); + if (!card.getSubtype(game).contains(SubType.AURA)) { + card.getSubtype(game).add(SubType.AURA); + } } if (controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null)) { if (bestow) { @@ -263,7 +264,9 @@ public class Spell extends StackObjImpl implements Card { Permanent permanent = game.getPermanent(card.getId()); if (permanent != null && permanent instanceof PermanentCard) { permanent.setSpellAbility(ability); // otherwise spell ability without bestow will be set - card.addCardType(CardType.CREATURE); + if (!card.getCardType().contains(CardType.CREATURE)) { + card.addCardType(CardType.CREATURE); + } card.getSubtype(game).remove(SubType.AURA); } } @@ -483,7 +486,9 @@ public class Spell extends StackObjImpl implements Card { public SubTypeList getSubtype(Game game) { if (this.getSpellAbility() instanceof BestowAbility) { SubTypeList subtypes = card.getSubtype(game); - subtypes.add(SubType.AURA); + if (!subtypes.contains(SubType.AURA)) { // do it only once + subtypes.add(SubType.AURA); + } return subtypes; } return card.getSubtype(game); @@ -493,7 +498,9 @@ public class Spell extends StackObjImpl implements Card { public boolean hasSubtype(SubType subtype, Game game) { if (this.getSpellAbility() instanceof BestowAbility) { // workaround for Bestow (don't like it) SubTypeList subtypes = card.getSubtype(game); - subtypes.add(SubType.AURA); + if (!subtypes.contains(SubType.AURA)) { // do it only once + subtypes.add(SubType.AURA); + } if (subtypes.contains(subtype)) { return true; } diff --git a/Utils/get_modo_artids.pl b/Utils/get_modo_artids.pl index b55a732b3ca..287ce363dd0 100644 --- a/Utils/get_modo_artids.pl +++ b/Utils/get_modo_artids.pl @@ -84,7 +84,7 @@ $fix_set_codes {"WL"} = "WTH"; print ("Finished reading $count names\n"); - #$vals = `find /I "<" *lient*`; + #$vals = `find /I "<" *client*`; $vals = `findstr /I "CARDNAME_STRING DIGITALOBJECT ARTID CLONE FRAMESTYLE " *lient* | find /I /V "_DO.xml"`; my $current_artid = ""; @@ -177,12 +177,67 @@ $fix_set_codes {"WL"} = "WTH"; my $current_framestyle = ""; my %framestyles; - $framestyles {1} = "001"; - $framestyles {3} = "010"; - $framestyles {31} = "010"; - $framestyles {11} = "010"; - $framestyles {14} = "010"; - $framestyles {15} = "010"; + $framestyles {1} = "001"; # Pre-modern cards + $framestyles {2} = "010"; + $framestyles {3} = "010"; # M15 cards + $framestyles {4} = "013"; + $framestyles {5} = "020"; + $framestyles {6} = "010"; + $framestyles {7} = "020"; + $framestyles {8} = "010"; + $framestyles {9} = "010"; + $framestyles {10} = "030"; # "040"; + $framestyles {11} = "010"; # Avatars + $framestyles {12} = "010"; + $framestyles {13} = "050"; + $framestyles {14} = "010"; # Tokens + $framestyles {15} = "010"; # Tokens as well + $framestyles {18} = "010"; + $framestyles {19} = "030"; # "040"; + $framestyles {19} = "010"; + $framestyles {20} = "030"; + $framestyles {22} = "010"; + $framestyles {23} = "010"; + $framestyles {24} = "010"; + $framestyles {25} = "050"; + $framestyles {26} = "010"; + $framestyles {27} = "010"; + $framestyles {28} = "010"; + $framestyles {30} = "050"; + $framestyles {31} = "010"; # New cards + $framestyles {34} = "030"; # "040"; + $framestyles {35} = "030"; # "040"; + $framestyles {36} = "050"; + $framestyles {37} = "010"; + $framestyles {38} = "010"; + $framestyles {39} = "050"; + $framestyles {42} = "010"; + $framestyles {43} = "010"; + $framestyles {45} = "030"; + $framestyles {45} = "030"; # "040"; + $framestyles {46} = "050"; + $framestyles {47} = "020"; # Expedition lands + $framestyles {48} = "020"; + $framestyles {49} = "030"; # "040"; + $framestyles {50} = "030"; + $framestyles {51} = "050"; + $framestyles {52} = "010"; + $framestyles {53} = "050"; + $framestyles {54} = "010"; + + + my %types; + my $current_type = "reg"; + $types {4} = "flip"; + $types {10} = "planeswk"; + $types {19} = "planeswk"; + $types {20} = "planeswk"; + $types {34} = "planeswk"; + $types {35} = "planeswk"; + $types {45} = "planeswk"; + $types {49} = "planeswk"; + $types {50} = "planeswk"; + while ($vals =~ s/^(.*)\n//im) { @@ -217,8 +272,19 @@ $fix_set_codes {"WL"} = "WTH"; } if ($line =~ m/FRAMESTYLE value='([^']+)'/) { - $current_framestyle = "$1"; - $current_framestyle = $framestyles {$current_framestyle}; + my $val = $1; + $current_framestyle = $framestyles {$val}; + $current_type = $types {$val}; + + if ($current_framestyle =~ m/^$/) + { + print (" ERROR: $current_framestyle not known: ($line) --- "); + print (" http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/0000$current_artid" . "_typ_$current_type" . "_sty_$current_framestyle.jpg\n"); + } + if ($current_type =~ m/^$/) + { + $current_type = "reg"; + } } if ($line =~ m/<\/DigitalObject/) @@ -246,23 +312,23 @@ $fix_set_codes {"WL"} = "WTH"; $seen_artids {$current_artid} = "$current_set\\$current_name.jpg"; if ($current_artid < 10) { - print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/0000$current_artid" . "_typ_reg_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); + print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/0000$current_artid" . "_typ_$current_type" . "_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); } if ($current_artid < 100) { - print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/000$current_artid" . "_typ_reg_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); + print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/000$current_artid" . "_typ_$current_type" . "_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); } elsif ($current_artid < 1000) { - print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/00$current_artid" . "_typ_reg_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); + print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/00$current_artid" . "_typ_$current_type" . "_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); } elsif ($current_artid < 10000) { - print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/0$current_artid" . "_typ_reg_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); + print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/0$current_artid" . "_typ_$current_type" . "_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); } else { - print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/$current_artid" . "_typ_reg_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); + print (" echo \"1\" | cut.pl stdin \"http://mtgoclientdepot.onlinegaming.wizards.com/Graphics/Cards/Pics/$current_artid" . "_typ_$current_type" . "_sty_$current_framestyle.jpg\" \"$current_set\\$current_name.jpg\" wget_image\n"); } } else @@ -276,6 +342,8 @@ $fix_set_codes {"WL"} = "WTH"; $current_doc_id = ""; $current_line = ""; $current_name = ""; + $current_type = ""; + $current_framestyle = ""; } } }