diff --git a/Mage.Client/plugins/sounds/OnPlayerJoinedTable.wav b/Mage.Client/plugins/sounds/OnPlayerJoinedTable.wav new file mode 100644 index 00000000000..a8c11bb354e Binary files /dev/null and b/Mage.Client/plugins/sounds/OnPlayerJoinedTable.wav differ diff --git a/Mage.Client/release/backgrounds/Blue-Abstract.jpg b/Mage.Client/release/backgrounds/Blue-Abstract.jpg new file mode 100644 index 00000000000..dc16db47092 Binary files /dev/null and b/Mage.Client/release/backgrounds/Blue-Abstract.jpg differ diff --git a/Mage.Client/release/backgrounds/Distant-Galaxy.jpg b/Mage.Client/release/backgrounds/Distant-Galaxy.jpg new file mode 100644 index 00000000000..a8e1c1c0153 Binary files /dev/null and b/Mage.Client/release/backgrounds/Distant-Galaxy.jpg differ diff --git a/Mage.Client/release/backgrounds/Galaxy.jpg b/Mage.Client/release/backgrounds/Galaxy.jpg new file mode 100644 index 00000000000..5de98c77430 Binary files /dev/null and b/Mage.Client/release/backgrounds/Galaxy.jpg differ diff --git a/Mage.Client/release/backgrounds/Gray.jpg b/Mage.Client/release/backgrounds/Gray.jpg new file mode 100644 index 00000000000..284a678ea93 Binary files /dev/null and b/Mage.Client/release/backgrounds/Gray.jpg differ diff --git a/Mage.Client/release/sample-decks/2013/Modern/Modern Romance/Gadin's Robots.dck b/Mage.Client/release/sample-decks/2013/Modern/Modern Romance/Gadin's Robots.dck new file mode 100644 index 00000000000..7847a4cf292 --- /dev/null +++ b/Mage.Client/release/sample-decks/2013/Modern/Modern Romance/Gadin's Robots.dck @@ -0,0 +1,28 @@ +NAME:Gadin's Robots +4 [DDF:72] Darksteel Citadel +3 [MMA:220] Blinkmoth Nexus +3 [MBS:145] Inkmoth Nexus +4 [NPH:76] Vault Skirge +4 [HOP:110] Cranial Plating +4 [MMA:223] Glimmervoid +3 [LRW:261] Springleaf Drum +2 [MMA:198] Arcbound Ravager +1 [RTR:265] Mountain +3 [SOM:154] Etched Champion +4 [DDF:48] Master of Etherium +4 [M11:211] Ornithopter +3 [SOM:179] Mox Opal +4 [SOM:91] Galvanic Blast +4 [DDF:71] Thoughtcast +1 [RTR:255] Island +3 [SOM:174] Memnite +3 [MBS:131] Signal Pest +3 [DDF:44] Steel Overseer +SB: 1 [ROE:92] Unified Will +SB: 3 [NPH:159] Spellskite +SB: 1 [NPH:102] Whipflare +SB: 3 [MMA:213] Relic of Progenitus +SB: 1 [M13:62] Negate +SB: 3 [MMA:106] Blood Moon +SB: 2 [PC2:7] Ghostly Prison +SB: 1 [ISD:127] Ancient Grudge diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 55569236281..ba37acad848 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -37,7 +37,6 @@ package mage.client; import de.schlichtherle.truezip.file.TArchiveDetector; import de.schlichtherle.truezip.file.TConfig; import de.schlichtherle.truezip.fs.FsOutputOption; -import mage.cards.Card; import mage.cards.decks.Deck; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; @@ -479,19 +478,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { menu.show(component, 0, component.getHeight()); } - private List getAllCards() { - List cards = new ArrayList(); - List allCards = CardRepository.instance.getAllCards(); - for (CardInfo cardInfo : allCards) { - cards.add(cardInfo.getCard()); - } - - return cards; - } - private void checkForNewImages() { long beforeCall = System.currentTimeMillis(); - List cards = getAllCards(); + List cards = CardRepository.instance.getAllCards(); logger.info("Card pool load time: " + ((System.currentTimeMillis() - beforeCall) / 1000 + " seconds")); String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); @@ -507,7 +496,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } public void btnImagesActionPerformed(java.awt.event.ActionEvent evt) { - List cards = getAllCards(); + List cards = CardRepository.instance.getAllCards(); String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); diff --git a/Mage.Client/src/main/java/mage/client/components/HoverButton.java b/Mage.Client/src/main/java/mage/client/components/HoverButton.java index 4d78bbc3c11..fc8ac09de2c 100644 --- a/Mage.Client/src/main/java/mage/client/components/HoverButton.java +++ b/Mage.Client/src/main/java/mage/client/components/HoverButton.java @@ -26,8 +26,11 @@ public class HoverButton extends JPanel implements MouseListener { private int textOffsetY = 0; private int textOffsetButtonY = 2; private int textOffsetX = -1; + private int topTextOffsetX = -1; private Dimension overlayImageSize; + private String topText; + private boolean isHovered = false; private boolean isSelected = false; private boolean drawSet = false; @@ -36,6 +39,7 @@ public class HoverButton extends JPanel implements MouseListener { private Command observer = null; private Command onHover = null; private Color textColor = Color.white; + private Color textBGColor = Color.black; static final Font textFont = new Font("Arial", Font.PLAIN, 12); static final Font textFontMini = new Font("Arial", Font.PLAIN, 11); @@ -100,6 +104,18 @@ public class HoverButton extends JPanel implements MouseListener { } else { g.drawImage(disabledImage, 0, 0, imageSize.width, imageSize.height, this); } + if (topText != null) { + if (useMiniFont) { + g2d.setFont(textFontMini); + } else { + g2d.setFont(textFont); + } + topTextOffsetX = calculateOffsetForTop(g2d, topText); + g2d.setColor(textBGColor); + g2d.drawString(topText, topTextOffsetX+1, 13); + g2d.setColor(textColor); + g2d.drawString(topText, topTextOffsetX, 12); + } if (overlayImage != null) { g.drawImage(overlayImage, (imageSize.width - overlayImageSize.width) / 2, 10, this); } else if (set != null) { @@ -136,6 +152,15 @@ public class HoverButton extends JPanel implements MouseListener { return textOffsetX; } + private int calculateOffsetForTop(Graphics2D g2d, String text) { + if (topTextOffsetX == -1) { // calculate once + FontRenderContext frc = g2d.getFontRenderContext(); + int textWidth = (int) textFont.getStringBounds(text, frc).getWidth(); + topTextOffsetX = (imageSize.width - textWidth) / 2; + } + return topTextOffsetX; + } + public void setTextColor(Color textColor) { this.textColor = textColor; } @@ -242,4 +267,8 @@ public class HoverButton extends JPanel implements MouseListener { observer.execute(); } } + + public void setTopText(String topText) { + this.topText = topText; + } } diff --git a/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java index 99a5dd0c6b7..6d1a0666b1c 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java @@ -49,6 +49,7 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import mage.client.util.AudioManager; /** * @@ -392,6 +393,7 @@ class UpdateSeatsTask extends SwingWorker { if (count > 0) { if (current > count) { MageTray.getInstance().displayMessage("New player joined your game."); + AudioManager.playPlayerJoinedTable(); } else { MageTray.getInstance().displayMessage("A player left your game."); } diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java index 24d97a1b964..0890cde733e 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java @@ -34,6 +34,7 @@ package mage.client.game; +import mage.MageException; import mage.cards.MageCard; import mage.cards.action.ActionCallback; import mage.cards.decks.importer.DckDeckImporter; @@ -51,7 +52,9 @@ import mage.client.util.Command; import mage.client.util.ImageHelper; import mage.client.util.gui.BufferedImageBuilder; import mage.components.ImagePanel; +import mage.constants.Constants; import mage.remote.Session; +import mage.utils.timer.PriorityTimer; import mage.view.CardView; import mage.view.ManaPoolView; import mage.view.PlayerView; @@ -101,6 +104,8 @@ public class PlayerPanelExt extends javax.swing.JPanel { private int avatarId = -1; + private PriorityTimer timer; + /** Creates new form PlayerPanel */ public PlayerPanelExt() { setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT)); @@ -114,6 +119,25 @@ public class PlayerPanelExt extends javax.swing.JPanel { session = MageFrame.getSession(); cheat.setVisible(session.isTestMode()); cheat.setFocusable(false); + + long delay = 1000L; + timer = new PriorityTimer(Constants.PRIORITY_TIME_SEC, delay, new mage.interfaces.Action() { + @Override + public void execute() throws MageException { + // do nothing + } + }); + final PriorityTimer pt = timer; + timer.setTaskOnTick(new mage.interfaces.Action() { + @Override + public void execute() throws MageException { + int priorityTimeValue = pt.getCount(); + String text = getPriorityTimeLeftString(priorityTimeValue); + PlayerPanelExt.this.avatar.setTopText(text); + PlayerPanelExt.this.avatar.repaint(); + } + }); + timer.init(); } public void update(PlayerView player) { @@ -182,16 +206,22 @@ public class PlayerPanelExt extends javax.swing.JPanel { } } this.avatar.setText(player.getName()); + String priorityTimeValue = getPriorityTimeLeftString(player); + this.timer.setCount(player.getPriorityTimeLeft()); + this.avatar.setTopText(priorityTimeValue); this.btnPlayer.setText(player.getName()); if (player.isActive()) { this.avatar.setBorder(greenBorder); this.btnPlayer.setBorder(greenBorder); + this.timer.resume(); } else if (player.hasLeft()) { this.avatar.setBorder(redBorder); this.btnPlayer.setBorder(redBorder); + this.timer.pause(); } else { this.avatar.setBorder(emptyBorder); this.btnPlayer.setBorder(emptyBorder); + this.timer.pause(); } synchronized (this) { @@ -216,6 +246,18 @@ public class PlayerPanelExt extends javax.swing.JPanel { update(player.getManaPool()); } + private String getPriorityTimeLeftString(PlayerView player) { + int priorityTimeLeft = player.getPriorityTimeLeft(); + return getPriorityTimeLeftString(priorityTimeLeft); + } + + private String getPriorityTimeLeftString(int priorityTimeLeft) { + int h = priorityTimeLeft / 3600; + int m = (priorityTimeLeft % 3600) / 60; + int s = priorityTimeLeft % 60; + return (h < 10 ? "0" : "") + h + ":" + (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s; + } + protected void update(ManaPoolView pool) { manaLabels.get("B").setText(Integer.toString(pool.getBlack())); manaLabels.get("R").setText(Integer.toString(pool.getRed())); diff --git a/Mage.Client/src/main/java/mage/client/util/AudioManager.java b/Mage.Client/src/main/java/mage/client/util/AudioManager.java index 252ab22a300..cfed0bd6b4f 100644 --- a/Mage.Client/src/main/java/mage/client/util/AudioManager.java +++ b/Mage.Client/src/main/java/mage/client/util/AudioManager.java @@ -1,12 +1,15 @@ package mage.client.util; +import java.io.File; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.DataLine; import mage.client.constants.Constants; import mage.client.dialog.PreferencesDialog; import org.apache.log4j.Logger; -import javax.sound.sampled.*; -import java.io.File; - /** * Manager class for playing audio files. * @@ -42,6 +45,8 @@ public class AudioManager { audioManager.addArtifactClip = audioManager.loadClip(Constants.BASE_SOUND_PATH + "OnAddArtifact.wav"); audioManager.updateStackClip = audioManager.loadClip(Constants.BASE_SOUND_PATH + "OnStackNew.wav"); audioManager.onHover = audioManager.loadClip(Constants.BASE_SOUND_PATH + "OnHover.wav"); + + audioManager.playerJoinedTable = audioManager.loadClip(Constants.BASE_SOUND_PATH + "OnPlayerJoinedTable.wav"); } return audioManager; } @@ -114,6 +119,10 @@ public class AudioManager { checkAndPlayClip(getManager().onHover); } + public static void playPlayerJoinedTable() { + checkAndPlayClip(getManager().playerJoinedTable); + } + private static void checkAndPlayClip(Clip clip) { try { if (clip != null) { @@ -175,4 +184,6 @@ public class AudioManager { private Clip addArtifactClip = null; private Clip updateStackClip = null; private Clip onHover = null; + + private Clip playerJoinedTable = null; } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java index dac76f4ed1a..fa6c54aa947 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java @@ -1,12 +1,14 @@ package org.mage.plugins.card.dl.sources; +import org.mage.plugins.card.images.CardDownloadData; + /** * * @author North */ public interface CardImageSource { - String generateURL(Integer collectorId, String cardName, String cardSet, boolean twoFacedCard, boolean secondFace, boolean isFlipCard, boolean isSplitCard, boolean flippedView) throws Exception; - String generateTokenUrl(String name, String set); + String generateURL(CardDownloadData card) throws Exception; + String generateTokenUrl(CardDownloadData card); Float getAverageSize(); } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java index 192e8c0fcd1..5f340657f90 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java @@ -1,5 +1,6 @@ package org.mage.plugins.card.dl.sources; +import org.mage.plugins.card.images.CardDownloadData; import org.mage.plugins.card.utils.CardImageUtils; import java.util.HashMap; @@ -64,7 +65,9 @@ public class MagicCardsImageSource implements CardImageSource { } @Override - public String generateURL(Integer collectorId, String cardName, String cardSet, boolean twoFacedCard, boolean secondSide, boolean isFlipCard, boolean isSplitCard, boolean flippedView) throws Exception { + public String generateURL(CardDownloadData card) throws Exception { + Integer collectorId = card.getCollectorId(); + String cardSet = card.getSet(); if (collectorId == null || cardSet == null) { throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet); } @@ -72,14 +75,14 @@ public class MagicCardsImageSource implements CardImageSource { StringBuilder url = new StringBuilder("http://magiccards.info/scans/en/"); url.append(set.toLowerCase()).append("/").append(collectorId); - if (twoFacedCard) { - url.append(secondSide ? "b" : "a"); + if (card.isTwoFacedCard()) { + url.append(card.isSecondSide() ? "b" : "a"); } - if (isSplitCard) { + if (card.isSplitCard()) { url.append("a"); } - if (isFlipCard) { - if (flippedView) { // download rotated by 180 degree image + if (card.isFlipCard()) { + if (card.isFlippedSide()) { // download rotated by 180 degree image url.append("b"); } else { url.append("a"); @@ -91,17 +94,15 @@ public class MagicCardsImageSource implements CardImageSource { } @Override - public String generateTokenUrl(String name, String set) { - String _name = name.replaceAll(" ", "-").replace(",", "").toLowerCase(); - String _set = "not-supported-set"; - if (setNameReplacement.containsKey(set)) { - _set = setNameReplacement.get(set); + public String generateTokenUrl(CardDownloadData card) { + String name = card.getName().replaceAll(" ", "-").replace(",", "").toLowerCase(); + String set = "not-supported-set"; + if (setNameReplacement.containsKey(card.getSet())) { + set = setNameReplacement.get(card.getSet()); } else { - _set += "-" + set; + set += "-" + card.getSet(); } - String url = "http://magiccards.info/extras/token/" + _set + "/" + _name + ".jpg"; - return url; - + return "http://magiccards.info/extras/token/" + set + "/" + name + ".jpg"; } @Override diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java index d849cbc8ae1..aa597c8deeb 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java @@ -3,6 +3,7 @@ package org.mage.plugins.card.dl.sources; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; +import org.mage.plugins.card.images.CardDownloadData; import java.io.IOException; import java.util.HashMap; @@ -15,8 +16,8 @@ import java.util.Map; public class WizardCardsImageSource implements CardImageSource { private static CardImageSource instance; - private static Map setsAliases; - private Map sets; + private static Map setsAliases; + private Map> sets; public static CardImageSource getInstance() { if (instance == null) { @@ -26,8 +27,8 @@ public class WizardCardsImageSource implements CardImageSource { } public WizardCardsImageSource() { - sets = new HashMap(); - setsAliases = new HashMap(); + sets = new HashMap>(); + setsAliases = new HashMap(); setsAliases.put("MMA", "modernmasters/cig"); setsAliases.put("DGM", "dragonsmaze/cig"); setsAliases.put("GTC", "gatecrash/cig"); @@ -56,7 +57,7 @@ public class WizardCardsImageSource implements CardImageSource { private Map getSetLinks(String cardSet) { Map setLinks = new HashMap(); try { - Document doc = Jsoup.connect("http://www.wizards.com/magic/tcg/article.aspx?x=mtg/tcg/" + (String) setsAliases.get(cardSet)).get(); + Document doc = Jsoup.connect("http://www.wizards.com/magic/tcg/article.aspx?x=mtg/tcg/" + setsAliases.get(cardSet)).get(); Elements cardsImages = doc.select("img[height$=370]"); for (int i = 0; i < cardsImages.size(); i++) { String cardName = cardsImages.get(i).attr("title").replace("\u00C6", "AE").replace("\u2019", "'"); @@ -94,20 +95,22 @@ public class WizardCardsImageSource implements CardImageSource { } @Override - public String generateURL(Integer collectorId, String cardName, String cardSet, boolean twoFacedCard, boolean secondSide, boolean isFlipCard, boolean isSplitCard, boolean flippedView) throws Exception { + public String generateURL(CardDownloadData card) throws Exception { + Integer collectorId = card.getCollectorId(); + String cardSet = card.getSet(); if (collectorId == null || cardSet == null) { throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet); } - if (flippedView) { //doesn't support rotated images + if (card.isFlippedSide()) { //doesn't support rotated images return null; } if (setsAliases.get(cardSet) != null) { - Map setLinks = (Map) sets.get(cardSet); + Map setLinks = sets.get(cardSet); if (setLinks == null) { setLinks = getSetLinks(cardSet); sets.put(cardSet, setLinks); } - String link = setLinks.get(cardName); + String link = setLinks.get(card.getDownloadName()); if (link == null) { if (setLinks.size() >= collectorId) { link = setLinks.get(Integer.toString(collectorId - 1)); @@ -127,7 +130,7 @@ public class WizardCardsImageSource implements CardImageSource { } @Override - public String generateTokenUrl(String name, String set) { + public String generateTokenUrl(CardDownloadData card) { return null; } @@ -135,4 +138,4 @@ public class WizardCardsImageSource implements CardImageSource { public Float getAverageSize() { return 60.0f; } -} \ No newline at end of file +} diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/CardInfo.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/CardDownloadData.java similarity index 82% rename from Mage.Client/src/main/java/org/mage/plugins/card/images/CardInfo.java rename to Mage.Client/src/main/java/org/mage/plugins/card/images/CardDownloadData.java index 4c76ccd8e6e..9bfb9ada0b3 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/CardInfo.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/CardDownloadData.java @@ -4,7 +4,7 @@ package org.mage.plugins.card.images; * * @author North */ -public class CardInfo { +public class CardDownloadData { private String name; private String downloadName; @@ -19,15 +19,15 @@ public class CardInfo { private boolean splitCard; private boolean usesVariousArt; - public CardInfo(String name, String set, Integer collectorId, boolean usesVariousArt, Integer type) { + public CardDownloadData(String name, String set, Integer collectorId, boolean usesVariousArt, Integer type) { this(name, set, collectorId, usesVariousArt, type, false); } - public CardInfo(String name, String set, Integer collectorId, boolean usesVariousArt, Integer type, boolean token) { + public CardDownloadData(String name, String set, Integer collectorId, boolean usesVariousArt, Integer type, boolean token) { this(name, set, collectorId, usesVariousArt, type, token, false, false); } - public CardInfo(String name, String set, Integer collectorId, boolean usesVariousArt, Integer type, boolean token, boolean twoFacedCard, boolean secondSide) { + public CardDownloadData(String name, String set, Integer collectorId, boolean usesVariousArt, Integer type, boolean token, boolean twoFacedCard, boolean secondSide) { this.name = name; this.set = set; this.collectorId = collectorId; @@ -38,7 +38,7 @@ public class CardInfo { this.secondSide = secondSide; } - public CardInfo(final CardInfo card) { + public CardDownloadData(final CardDownloadData card) { this.name = card.name; this.set = card.set; this.collectorId = card.collectorId; @@ -57,7 +57,7 @@ public class CardInfo { if (getClass() != obj.getClass()) { return false; } - final CardInfo other = (CardInfo) obj; + final CardDownloadData other = (CardDownloadData) obj; if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { return false; } @@ -96,10 +96,6 @@ public class CardInfo { return collectorId; } - public void setCollectorId(Integer collectorId) { - this.collectorId = collectorId; - } - public String getName() { return name; } @@ -160,10 +156,6 @@ public class CardInfo { return type; } - public void setType(Integer type) { - this.type = type; - } - public boolean getUsesVariousArt() { return usesVariousArt; } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java index 3b0c602ee08..bcf918db432 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java @@ -4,7 +4,7 @@ import de.schlichtherle.truezip.file.TFile; import de.schlichtherle.truezip.file.TFileOutputStream; import de.schlichtherle.truezip.file.TVFS; import de.schlichtherle.truezip.fs.FsSyncException; -import mage.cards.Card; +import mage.cards.repository.CardInfo; import mage.client.dialog.PreferencesDialog; import mage.remote.Connection; import org.apache.log4j.Logger; @@ -46,7 +46,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab private JButton closeButton; private JButton startDownloadButton; private int cardIndex; - private ArrayList cards; + private ArrayList cards; private JComboBox jComboBox1; private JLabel jLabel1; private static boolean offlineMode = false; @@ -60,14 +60,12 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab private ExecutorService executor = Executors.newFixedThreadPool(10); - public static final Proxy.Type[] types = Proxy.Type.values(); - public static void main(String[] args) { startDownload(null, null, null); } - public static void startDownload(JFrame frame, List allCards, String imagesPath) { - ArrayList cards = getNeededCards(allCards, imagesPath); + public static void startDownload(JFrame frame, List allCards, String imagesPath) { + ArrayList cards = getNeededCards(allCards, imagesPath); /* * if (cards == null || cards.size() == 0) { @@ -99,33 +97,15 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab this.cancel = cancel; } - public DownloadPictures(ArrayList cards, String imagesPath) { + public DownloadPictures(ArrayList cards, String imagesPath) { this.cards = cards; this.imagesPath = imagesPath; - //addr = new JTextField("Proxy Address"); - //port = new JTextField("Proxy Port"); bar = new JProgressBar(this); JPanel p0 = new JPanel(); p0.setLayout(new BoxLayout(p0, BoxLayout.Y_AXIS)); - // Proxy Choice - /*ButtonGroup bg = new ButtonGroup(); - String[] labels = { "No Proxy", "HTTP Proxy", "SOCKS Proxy" }; - for (int i = 0; i < types.length; i++) { - JRadioButton rb = new JRadioButton(labels[i]); - rb.addChangeListener(new ProxyHandler(i)); - bg.add(rb); - p0.add(rb); - if (i == 0) - rb.setSelected(true); - }*/ - - // Proxy config - //p0.add(addr); - //p0.add(port); - p0.add(Box.createVerticalStrut(5)); jLabel1 = new JLabel(); jLabel1.setText("Please select server:"); @@ -206,11 +186,11 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab dlg = new JOptionPane(p0, JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[1]); } - public static boolean checkForNewCards(List allCards, String imagesPath) { + public static boolean checkForNewCards(List allCards, String imagesPath) { TFile file; - for (Card card : allCards) { - if (card.getCardNumber() > 0 && !card.getExpansionSetCode().isEmpty()) { - CardInfo url = new CardInfo(card.getName(), card.getExpansionSetCode(), card.getCardNumber(),card.getUsesVariousArt(),0 , false, card.canTransform(), card.isNightCard()); + for (CardInfo card : allCards) { + if (card.getCardNumber() > 0 && !card.getSetCode().isEmpty()) { + CardDownloadData url = new CardDownloadData(card.getName(), card.getSetCode(), card.getCardNumber(), usesVariousArt(card), 0, false, card.isDoubleFaced(), card.isNightCard()); file = new TFile(CardImageUtils.getImagePath(url, imagesPath)); if (!file.exists()) { return true; @@ -220,47 +200,54 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab return false; } - private static ArrayList getNeededCards(List allCards, String imagesPath) { + private static boolean usesVariousArt(CardInfo card) { + String className = card.getClassName(); + return Character.isDigit(className.charAt(className.length() - 1)); + } - ArrayList cardsToDownload = new ArrayList(); + private static String createDownloadName(CardInfo card) { + String className = card.getClassName(); + return className.substring(className.lastIndexOf('.') + 1); + } + + private static ArrayList getNeededCards(List allCards, String imagesPath) { + + ArrayList cardsToDownload = new ArrayList(); /** * read all card names and urls */ - ArrayList allCardsUrls = new ArrayList(); + ArrayList allCardsUrls = new ArrayList(); HashSet ignoreUrls = SettingsManager.getIntance().getIgnoreUrls(); try { offlineMode = true; - for (Card card : allCards) { - if (card.getCardNumber() > 0 && !card.getExpansionSetCode().isEmpty() - && !ignoreUrls.contains(card.getExpansionSetCode())) { + for (CardInfo card : allCards) { + if (card.getCardNumber() > 0 && !card.getSetCode().isEmpty() + && !ignoreUrls.contains(card.getSetCode())) { String cardName = card.getName(); - CardInfo url = new CardInfo(cardName, card.getExpansionSetCode(), card.getCardNumber(), card.getUsesVariousArt(), 0, false, card.canTransform(), card.isNightCard()); + CardDownloadData url = new CardDownloadData(cardName, card.getSetCode(), card.getCardNumber(), usesVariousArt(card), 0, false, card.isDoubleFaced(), card.isNightCard()); if (url.getUsesVariousArt()) { - url.setDownloadName(card.getClass().getName().replace(card.getClass().getPackage().getName() + ".", "")); - } - if (card.isFlipCard()) { - url.setFlipCard(true); - } - if (card.isSplitCard()) { - url.setSplitCard(true); + url.setDownloadName(createDownloadName(card)); } + + url.setFlipCard(card.isFlipCard()); + url.setSplitCard(card.isSplitCard()); + allCardsUrls.add(url); - if (card.canTransform()) { + if (card.isDoubleFaced()) { // add second side for downloading // it has the same expansion set code and card number as original one // second side = true; - Card secondSide = card.getSecondCardFace(); - url = new CardInfo(secondSide.getName(), card.getExpansionSetCode(), card.getCardNumber(), card.getUsesVariousArt(), 0, false, card.canTransform(), true); + url = new CardDownloadData(card.getSecondSideName(), card.getSetCode(), card.getCardNumber(), usesVariousArt(card), 0, false, card.isDoubleFaced(), true); allCardsUrls.add(url); } if (card.isFlipCard()) { if (card.getFlipCardName() == null || card.getFlipCardName().trim().isEmpty()) { throw new IllegalStateException("Flipped card can't have empty name."); } - url = new CardInfo(card.getFlipCardName(), card.getExpansionSetCode(), card.getCardNumber(), card.getUsesVariousArt(), 0, false, card.canTransform(), card.isNightCard()); + url = new CardDownloadData(card.getFlipCardName(), card.getSetCode(), card.getCardNumber(), usesVariousArt(card), 0, false, card.isDoubleFaced(), card.isNightCard()); url.setFlipCard(true); url.setFlippedSide(true); allCardsUrls.add(url); @@ -269,7 +256,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab if (card.getCardNumber() < 1) { System.err.println("There was a critical error!"); log.error("Card has no collector ID and won't be sent to client: " + card); - } else if (card.getExpansionSetCode().isEmpty()) { + } else if (card.getSetCode().isEmpty()) { System.err.println("There was a critical error!"); log.error("Card has no set name and won't be sent to client:" + card); } @@ -286,14 +273,14 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab /** * check to see which cards we already have */ - for (CardInfo card : allCardsUrls) { + for (CardDownloadData card : allCardsUrls) { file = new TFile(CardImageUtils.getImagePath(card, imagesPath)); if (!file.exists()) { cardsToDownload.add(card); } } - for (CardInfo card : cardsToDownload) { + for (CardDownloadData card : cardsToDownload) { if (card.isToken()) { log.info("Card to download: " + card.getName() + " (Token) "); } else { @@ -308,8 +295,8 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab return cardsToDownload; } - private static ArrayList getTokenCardUrls() throws RuntimeException { - ArrayList list = new ArrayList(); + private static ArrayList getTokenCardUrls() throws RuntimeException { + ArrayList list = new ArrayList(); InputStream in = DownloadPictures.class.getClassLoader().getResourceAsStream("card-pictures-tok.txt"); if (in == null) { @@ -332,15 +319,15 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab if (params.length >= 4) { if (params[1].toLowerCase().equals("generate") && params[2].startsWith("TOK:")) { String set = params[2].substring(4); - CardInfo card = new CardInfo(params[3], set, 0, false, 0, true); + CardDownloadData card = new CardDownloadData(params[3], set, 0, false, 0, true); list.add(card); } else if (params[1].toLowerCase().equals("generate") && params[2].startsWith("EMBLEM:")) { String set = params[2].substring(7); - CardInfo card = new CardInfo("Emblem " + params[3], set, 0, false,0, true); + CardDownloadData card = new CardDownloadData("Emblem " + params[3], set, 0, false,0, true); list.add(card); } else if (params[1].toLowerCase().equals("generate") && params[2].startsWith("EMBLEM-:")) { String set = params[2].substring(8); - CardInfo card = new CardInfo(params[3] + " Emblem", set, 0, false, 0, true); + CardDownloadData card = new CardDownloadData(params[3] + " Emblem", set, 0, false, 0, true); list.add(card); } } else { @@ -408,7 +395,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab for (int i = 0; i < cards.size() && !cancel; i++) { try { - CardInfo card = cards.get(i); + CardDownloadData card = cards.get(i); log.info("Downloading card: " + card.getName() + " (" + card.getSet() + ")"); @@ -417,10 +404,9 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab if (card.getCollectorId() != 0) { continue; } - url = cardImageSource.generateTokenUrl(card.getName(), card.getSet()); + url = cardImageSource.generateTokenUrl(card); } else { - url = cardImageSource.generateURL(card.getCollectorId(), card.getDownloadName(), card.getSet(), - card.isTwoFacedCard(), card.isSecondSide(), card.isFlipCard(), card.isSplitCard(), card.isFlippedSide()); + url = cardImageSource.generateURL(card); } if (url != null) { @@ -457,10 +443,10 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab private final class DownloadTask implements Runnable { - private CardInfo card; + private CardDownloadData card; private URL url; - public DownloadTask(CardInfo card, URL url) { + public DownloadTask(CardDownloadData card, URL url) { this.card = card; this.url = url; } @@ -573,10 +559,10 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab bar.setString(String.format("%d of %d cards finished! Please wait! [%.1f Mb]", card, count, mb)); } else { - Iterator cardsIterator = DownloadPictures.this.cards.iterator(); + Iterator cardsIterator = DownloadPictures.this.cards.iterator(); while (cardsIterator.hasNext()) { - CardInfo cardInfo = cardsIterator.next(); - TFile file = new TFile(CardImageUtils.getImagePath(cardInfo, imagesPath)); + CardDownloadData cardDownloadData = cardsIterator.next(); + TFile file = new TFile(CardImageUtils.getImagePath(cardDownloadData, imagesPath)); if (file.exists()) { cardsIterator.remove(); } @@ -584,7 +570,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab count = DownloadPictures.this.cards.size(); if (count == 0) { - bar.setString(String.format("0 cards remaining! Please close!", count)); + bar.setString("0 cards remaining! Please close!"); } else { bar.setString(String.format("%d cards remaining! Please choose another source!", count)); //executor = Executors.newFixedThreadPool(10); 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 e59c2deac5e..cb6d9e0dbeb 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 @@ -71,7 +71,7 @@ public class ImageCache { Integer type = Integer.parseInt(m.group(3)); Integer collectorId = Integer.parseInt(m.group(4)); - CardInfo info = new CardInfo(name, set, collectorId, usesVariousArt, type); + CardDownloadData info = new CardDownloadData(name, set, collectorId, usesVariousArt, type); if (collectorId == 0) { info.setToken(true); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java b/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java index 9634efb0770..75172f7c9d0 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/utils/CardImageUtils.java @@ -4,12 +4,12 @@ import de.schlichtherle.truezip.file.TFile; import java.util.HashMap; import mage.client.constants.Constants; import mage.client.dialog.PreferencesDialog; -import org.mage.plugins.card.images.CardInfo; +import org.mage.plugins.card.images.CardDownloadData; import org.mage.plugins.card.properties.SettingsManager; public class CardImageUtils { - private static HashMap pathCache = new HashMap(); + private static HashMap pathCache = new HashMap(); /** * Get path to image for specific card. @@ -18,7 +18,7 @@ public class CardImageUtils { * card to get path for * @return String if image exists, else null */ - public static String getImagePath(CardInfo card) { + public static String getImagePath(CardDownloadData card) { String filePath; TFile file; @@ -51,19 +51,19 @@ public class CardImageUtils { } } - private static String getTokenImagePath(CardInfo card) { + private static String getTokenImagePath(CardDownloadData card) { String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); String filename = getImagePath(card, path); TFile file = new TFile(filename); if (!file.exists()) { - CardInfo updated = new CardInfo(card); + CardDownloadData updated = new CardDownloadData(card); updated.setName(card.getName() + " 1"); filename = getImagePath(updated, path); file = new TFile(filename); if (!file.exists()) { - updated = new CardInfo(card); + updated = new CardDownloadData(card); updated.setName(card.getName() + " 2"); filename = getImagePath(updated, path); } @@ -72,10 +72,10 @@ public class CardImageUtils { return filename; } - private static String searchForCardImage(CardInfo card) { + private static String searchForCardImage(CardDownloadData card) { TFile file; String path; - CardInfo c = new CardInfo(card); + CardDownloadData c = new CardDownloadData(card); for (String set : SettingsManager.getIntance().getTokenLookupOrder()) { c.setSet(set); @@ -100,7 +100,7 @@ public class CardImageUtils { return set; } - private static String getImageDir(CardInfo card, String imagesPath) { + private static String getImageDir(CardDownloadData card, String imagesPath) { if (card.getSet() == null) { return ""; } @@ -129,7 +129,7 @@ public class CardImageUtils { } } - public static String getImagePath(CardInfo card, String imagesPath) { + public static String getImagePath(CardDownloadData card, String imagesPath) { String imageDir = getImageDir(card, imagesPath); String imageName; diff --git a/Mage.Common/src/mage/constants/Constants.java b/Mage.Common/src/mage/constants/Constants.java index a2e837dd0bf..c6b76fcb3b6 100644 --- a/Mage.Common/src/mage/constants/Constants.java +++ b/Mage.Common/src/mage/constants/Constants.java @@ -68,6 +68,11 @@ public final class Constants { public static final double SCALE_FACTOR = 0.5; + /** + * Time each player has during the game to play using his\her priority. + */ + public static final int PRIORITY_TIME_SEC = 1200; + public enum SessionState { DISCONNECTED, CONNECTED, CONNECTING, DISCONNECTING, SERVER_UNAVAILABLE, SERVER_STARTING; } diff --git a/Mage.Common/src/mage/utils/timer/PriorityTimer.java b/Mage.Common/src/mage/utils/timer/PriorityTimer.java new file mode 100644 index 00000000000..90911c8ddd2 --- /dev/null +++ b/Mage.Common/src/mage/utils/timer/PriorityTimer.java @@ -0,0 +1,127 @@ +package mage.utils.timer; + +import mage.MageException; +import mage.interfaces.Action; +import org.apache.log4j.Logger; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * @author noxx + */ +public class PriorityTimer extends TimerTask { + + private static final Logger logger = Logger.getLogger(PriorityTimer.class); + + private int count; + + private long delay; + + private Action taskOnTimeout; + + private Action taskOnTick; + + private States state = States.NONE; + + enum States { + NONE, + INIT, + RUNNING, + PAUSED, + FINISHED + } + + public PriorityTimer(int count, long delay, Action taskOnTimeout) { + this.count = count; + this.delay = delay; + this.taskOnTimeout = taskOnTimeout; + } + + public void init() { + state = States.INIT; + Timer timer = new Timer("Priority Timer", false); + long delayMs = delay * (int) (1000L / delay); + timer.scheduleAtFixedRate(this, delayMs, delayMs); + } + + public void start() { + if (state == States.NONE) { + throw new IllegalStateException("Timer should have been initialized first"); + } + if (state == States.FINISHED) { + throw new IllegalStateException("Timer has already finished its work"); + } + state = States.RUNNING; + } + + public void pause() { + state = States.PAUSED; + } + + public void stop() { + state = States.FINISHED; + count = 0; + } + + public void resume() { + if (state == States.FINISHED) { + throw new IllegalStateException("Timer has already finished its work"); + } + state = States.RUNNING; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public void setTaskOnTick(Action taskOnTick) { + this.taskOnTick = taskOnTick; + } + + @Override + public void run() { + if (state == States.RUNNING) { + count--; + if (taskOnTick != null) { + try { + taskOnTick.execute(); + } catch (MageException e) { + throw new RuntimeException(e); + } + } + } + if (logger.isDebugEnabled()) logger.debug("Count is: " + count); + //System.out.println("Count is: " + count); + if (count <= 0) { + cancel(); + try { + taskOnTimeout.execute(); + } catch (MageException e) { + throw new RuntimeException(e); + } + } + } + + public static void main(String[] args) throws Exception { + long delay = 250L; + int count = 5; + PriorityTimer timer = new PriorityTimer(count, delay, new Action() { + @Override + public void execute() throws MageException { + System.out.println("Exit"); + System.exit(0); + } + }); + timer.init(); + timer.start(); + Thread.sleep(2000); + timer.pause(); + Thread.sleep(3000); + timer.resume(); + } +} diff --git a/Mage.Common/src/mage/view/PlayerView.java b/Mage.Common/src/mage/view/PlayerView.java index a7cad2dfda0..f0e46419eaf 100644 --- a/Mage.Common/src/mage/view/PlayerView.java +++ b/Mage.Common/src/mage/view/PlayerView.java @@ -63,6 +63,7 @@ public class PlayerView implements Serializable { private List emblemList = new ArrayList(); private List attachments = new ArrayList(); private int statesSavedSize; + private int priorityTimeLeft; public PlayerView(Player player, GameState state, Game game) { this.playerId = player.getId(); @@ -108,6 +109,7 @@ public class PlayerView implements Serializable { } this.statesSavedSize = player.getStoredBookmark(); + this.priorityTimeLeft = player.getPriorityTimeLeft(); } private boolean showInBattlefield(Permanent permanent, GameState state) { @@ -191,4 +193,8 @@ public class PlayerView implements Serializable { public int getStatesSavedSize() { return statesSavedSize; } + + public int getPriorityTimeLeft() { + return priorityTimeLeft; + } } diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 79165bf6b9a..ce64108b878 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -28,11 +28,6 @@ package mage.player.human; -import java.io.Serializable; -import java.util.*; -import mage.constants.Outcome; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; import mage.MageObject; import mage.abilities.*; import mage.abilities.costs.common.SacrificeSourceCost; @@ -46,6 +41,9 @@ import mage.cards.Cards; import mage.cards.decks.Deck; import mage.choices.Choice; import mage.choices.ChoiceImpl; +import mage.constants.Outcome; +import mage.constants.RangeOfInfluence; +import mage.constants.Zone; import mage.filter.common.FilterAttackingCreature; import mage.filter.common.FilterBlockingCreature; import mage.filter.common.FilterCreatureForCombat; @@ -67,6 +65,9 @@ import mage.target.common.TargetDefender; import mage.util.ManaUtil; import org.apache.log4j.Logger; +import java.io.Serializable; +import java.util.*; + /** * @@ -98,40 +99,43 @@ public class HumanPlayer extends PlayerImpl { super(player); } - protected void waitForResponse() { + protected void waitForResponse(Game game) { response.clear(); log.debug("Waiting response from player: " + getId()); + game.resumeTimer(playerId); synchronized(response) { try { response.wait(); log.debug("Got response from player: " + getId()); } catch (InterruptedException ex) { ex.printStackTrace(); + } finally { + game.pauseTimer(playerId); } } } - protected void waitForBooleanResponse() { + protected void waitForBooleanResponse(Game game) { do { - waitForResponse(); + waitForResponse(game); } while (response.getBoolean() == null && !abort); } - protected void waitForUUIDResponse() { + protected void waitForUUIDResponse(Game game) { do { - waitForResponse(); + waitForResponse(game); } while (response.getUUID() == null && !abort); } - protected void waitForStringResponse() { + protected void waitForStringResponse(Game game) { do { - waitForResponse(); + waitForResponse(game); } while (response.getString() == null && !abort); } - protected void waitForIntegerResponse() { + protected void waitForIntegerResponse(Game game) { do { - waitForResponse(); + waitForResponse(game); } while (response.getInteger() == null && !abort); } @@ -143,7 +147,7 @@ public class HumanPlayer extends PlayerImpl { .append(getHand().size() > nextHandSize?"down to ":"for free, draw ") .append(nextHandSize) .append(nextHandSize == 1?" card?":" cards?").toString()); - waitForBooleanResponse(); + waitForBooleanResponse(game); if (!abort) { return response.getBoolean(); } @@ -154,7 +158,7 @@ public class HumanPlayer extends PlayerImpl { public boolean chooseUse(Outcome outcome, String message, Game game) { updateGameStatePriority("chooseUse", game); game.fireAskPlayerEvent(playerId, message); - waitForBooleanResponse(); + waitForBooleanResponse(game); if (!abort) { return response.getBoolean(); } @@ -175,7 +179,7 @@ public class HumanPlayer extends PlayerImpl { } while (!abort) { game.fireChooseEvent(playerId, replacementEffectChoice); - waitForResponse(); + waitForResponse(game); log.debug("Choose effect: " + response.getString()); if (response.getString() != null) { replacementEffectChoice.setChoice(response.getString()); @@ -196,7 +200,7 @@ public class HumanPlayer extends PlayerImpl { updateGameStatePriority("choose(3)", game); while (!abort) { game.fireChooseEvent(playerId, choice); - waitForResponse(); + waitForResponse(game); if (response.getString() != null) { choice.setChoice(response.getString()); return true; @@ -218,7 +222,7 @@ public class HumanPlayer extends PlayerImpl { while (!abort) { Set cards = target.possibleTargets(null, playerId, game); game.fireSelectTargetEvent(playerId, target.getMessage(), cards, target.isRequired(), options); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { if (target instanceof TargetPermanent) { if (((TargetPermanent)target).canTarget(playerId, response.getUUID(), sourceId, game, false)) { @@ -267,7 +271,7 @@ public class HumanPlayer extends PlayerImpl { Set possibleTargets = target.possibleTargets(source==null?null:source.getSourceId(), playerId, game); boolean required = possibleTargets.isEmpty() ? false : target.isRequired(); game.fireSelectTargetEvent(playerId, target.getMessage(), possibleTargets, required, getOptions(target)); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { if (possibleTargets.contains(response.getUUID())) { if (target instanceof TargetPermanent) { @@ -323,7 +327,7 @@ public class HumanPlayer extends PlayerImpl { options.put("chosen", (Serializable)chosen); } game.fireSelectTargetEvent(playerId, target.getMessage(), cards, required, options); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { if (target.canTarget(response.getUUID(), cards, game)) { target.add(response.getUUID(), game); @@ -358,7 +362,7 @@ public class HumanPlayer extends PlayerImpl { } } game.fireSelectTargetEvent(playerId, target.getMessage(), cards, target.isRequired(), null); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { if (target.canTarget(response.getUUID(), cards, game)) { target.addTarget(response.getUUID(), source, game); @@ -383,7 +387,7 @@ public class HumanPlayer extends PlayerImpl { updateGameStatePriority("chooseTargetAmount", game); while (!abort) { game.fireSelectTargetEvent(playerId, target.getMessage() + "\n Amount remaining:" + target.getAmountRemaining(), target.possibleTargets(source==null?null:source.getId(), playerId, game), target.isRequired(), null); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { if (target.canTarget(response.getUUID(), source, game)) { UUID targetId = response.getUUID(); @@ -412,7 +416,7 @@ public class HumanPlayer extends PlayerImpl { } updateGameStatePriority("priority", game); game.firePriorityEvent(playerId); - waitForResponse(); + waitForResponse(game); if (response.getBoolean() != null) { pass(game); return false; @@ -450,7 +454,7 @@ public class HumanPlayer extends PlayerImpl { updateGameStatePriority("chooseTriggeredAbility", game); while (!abort) { game.fireSelectTargetEvent(playerId, "Pick triggered ability (goes to the stack first)", abilities); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { for (TriggeredAbility ability: abilities) { if (ability.getId().equals(response.getUUID())) { @@ -466,7 +470,7 @@ public class HumanPlayer extends PlayerImpl { public boolean playMana(ManaCost unpaid, Game game) { updateGameStatePriority("playMana", game); game.firePlayManaEvent(playerId, "Pay " + unpaid.getText()); - waitForResponse(); + waitForResponse(game); if (response.getBoolean() != null) { return false; } else if (response.getUUID() != null) { @@ -500,7 +504,7 @@ public class HumanPlayer extends PlayerImpl { public int announceXMana(int min, int max, String message, Game game, Ability ability) { updateGameStatePriority("announceXMana", game); game.fireGetAmountEvent(playerId, message, min, max); - waitForIntegerResponse(); + waitForIntegerResponse(game); return response.getInteger(); } @@ -530,7 +534,7 @@ public class HumanPlayer extends PlayerImpl { return; } game.fireSelectEvent(playerId, "Select attackers"); - waitForResponse(); + waitForResponse(game); if (response.getBoolean() != null) { return; } else if (response.getInteger() != null) { @@ -593,7 +597,7 @@ public class HumanPlayer extends PlayerImpl { filter.add(new ControllerIdPredicate(defendingPlayerId)); while (!abort) { game.fireSelectEvent(playerId, "Select blockers"); - waitForResponse(); + waitForResponse(game); if (response.getBoolean() != null) { return; } else if (response.getInteger() != null) { @@ -622,7 +626,7 @@ public class HumanPlayer extends PlayerImpl { updateGameStatePriority("chooseAttackerOrder", game); while (!abort) { game.fireSelectTargetEvent(playerId, "Pick attacker", attackers, true); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { for (Permanent perm: attackers) { if (perm.getId().equals(response.getUUID())) { @@ -640,7 +644,7 @@ public class HumanPlayer extends PlayerImpl { updateGameStatePriority("chooseBlockerOrder", game); while (!abort) { game.fireSelectTargetEvent(playerId, "Pick blocker", blockers, true); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { for (Permanent perm: blockers) { if (perm.getId().equals(response.getUUID())) { @@ -656,7 +660,7 @@ public class HumanPlayer extends PlayerImpl { updateGameStatePriority("selectCombatGroup", game); TargetAttackingCreature target = new TargetAttackingCreature(); game.fireSelectTargetEvent(playerId, "Select attacker to block", target.possibleTargets(null, playerId, game), target.isRequired(), null); - waitForResponse(); + waitForResponse(game); if (response.getBoolean() != null) { // do nothing } else if (response.getUUID() != null) { @@ -696,7 +700,7 @@ public class HumanPlayer extends PlayerImpl { public int getAmount(int min, int max, String message, Game game) { updateGameStatePriority("getAmount", game); game.fireGetAmountEvent(playerId, message, min, max); - waitForIntegerResponse(); + waitForIntegerResponse(game); return response.getInteger(); } @@ -719,7 +723,7 @@ public class HumanPlayer extends PlayerImpl { updateGameStatePriority("specialAction", game); LinkedHashMap specialActions = game.getState().getSpecialActions().getControlledBy(playerId); game.fireGetChoiceEvent(playerId, name, new ArrayList(specialActions.values())); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { if (specialActions.containsKey(response.getUUID())) { activateAbility(specialActions.get(response.getUUID()), game); @@ -737,7 +741,7 @@ public class HumanPlayer extends PlayerImpl { } } game.fireGetChoiceEvent(playerId, name, new ArrayList(abilities.values())); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { if (abilities.containsKey(response.getUUID())) { activateAbility(abilities.get(response.getUUID()), game); @@ -757,7 +761,7 @@ public class HumanPlayer extends PlayerImpl { return (SpellAbility) useableAbilities.values().iterator().next(); } else if (useableAbilities != null && useableAbilities.size() > 0) { game.fireGetChoiceEvent(playerId, name, new ArrayList(useableAbilities.values())); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { if (useableAbilities.containsKey(response.getUUID())) { return (SpellAbility) useableAbilities.get(response.getUUID()); @@ -785,7 +789,7 @@ public class HumanPlayer extends PlayerImpl { modeMap.put(mode.getId(), modeText); } game.fireGetModeEvent(playerId, "Choose Mode", modeMap); - waitForResponse(); + waitForResponse(game); if (response.getUUID() != null) { for (Mode mode: modes.values()) { if (mode.getId().equals(response.getUUID())) { @@ -802,7 +806,7 @@ public class HumanPlayer extends PlayerImpl { public boolean choosePile(Outcome outcome, String message, List pile1, List pile2, Game game) { updateGameStatePriority("choosePile", game); game.fireChoosePileEvent(playerId, message, pile1, pile2); - waitForBooleanResponse(); + waitForBooleanResponse(game); if (!abort) { return response.getBoolean(); } diff --git a/Mage.Server/release/config/log4j.properties b/Mage.Server/release/config/log4j.properties index 932d8c71066..6a3d87231f1 100644 --- a/Mage.Server/release/config/log4j.properties +++ b/Mage.Server/release/config/log4j.properties @@ -1,5 +1,5 @@ #default levels -log4j.rootLogger=info, console, logfile +log4j.rootLogger=warn, console, logfile #console log log4j.appender.console=org.apache.log4j.ConsoleAppender 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 bd8c491074b..fe29d7fb06a 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -28,7 +28,6 @@ package mage.server.game; -import mage.constants.Zone; import mage.MageException; import mage.abilities.Ability; import mage.cards.Card; @@ -37,17 +36,21 @@ import mage.cards.decks.Deck; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; +import mage.constants.Constants; +import mage.constants.Zone; import mage.game.Game; import mage.game.GameException; import mage.game.events.Listener; import mage.game.events.PlayerQueryEvent; import mage.game.events.TableEvent; import mage.game.permanent.Permanent; +import mage.interfaces.Action; import mage.players.Player; import mage.server.*; import mage.server.util.Splitter; import mage.server.util.SystemUtil; import mage.server.util.ThreadExecutor; +import mage.utils.timer.PriorityTimer; import mage.view.AbilityPickerView; import mage.view.CardsView; import mage.view.ChatMessage.MessageColor; @@ -75,6 +78,8 @@ public class GameController implements GameCallback { private ConcurrentHashMap gameSessions = new ConcurrentHashMap(); private ConcurrentHashMap watchers = new ConcurrentHashMap(); + private ConcurrentHashMap timers = new ConcurrentHashMap(); + private ConcurrentHashMap userPlayerMap; private UUID gameSessionId; private Game game; @@ -106,6 +111,8 @@ public class GameController implements GameCallback { @Override public void event(TableEvent event) { try { + PriorityTimer timer; + UUID playerId; switch (event.getEventType()) { case UPDATE: updateGame(); @@ -124,6 +131,44 @@ public class GameController implements GameCallback { case ERROR: error(event.getMessage(), event.getException()); break; + case INIT_TIMER: + final UUID initPlayerId = event.getPlayerId(); + if (initPlayerId == null) { + throw new IllegalStateException("INIT_TIMER: playerId can't be null"); + } + long delay = 250L; // run each 250 ms + timer = new PriorityTimer(Constants.PRIORITY_TIME_SEC, delay, new Action() { + @Override + public void execute() throws MageException { + game.concede(initPlayerId); + logger.info("Game timeout for player: " + initPlayerId + ". Conceding."); + } + }); + timers.put(initPlayerId, timer); + timer.init(); + break; + case RESUME_TIMER: + playerId = event.getPlayerId(); + if (playerId == null) { + throw new IllegalStateException("RESUME_TIMER: playerId can't be null"); + } + timer = timers.get(playerId); + if (timer == null) { + throw new IllegalStateException("RESUME_TIMER: couldn't find timer for player: " + playerId); + } + timer.resume(); + break; + case PAUSE_TIMER: + playerId = event.getPlayerId(); + if (playerId == null) { + throw new IllegalStateException("PAUSE_TIMER: playerId can't be null"); + } + timer = timers.get(playerId); + if (timer == null) { + throw new IllegalStateException("PAUSE_TIMER: couldn't find timer for player: " + playerId); + } + timer.pause(); + break; } } catch (MageException ex) { logger.fatal("Table event listener error ", ex); @@ -388,6 +433,12 @@ public class GameController implements GameCallback { } private synchronized void updateGame() { + for (Player player: game.getState().getPlayers().values()) { + PriorityTimer timer = timers.get(player.getId()); + if (timer != null) { + player.setPriorityTimeLeft(timer.getCount()); + } + } for (final GameSession gameSession: gameSessions.values()) { gameSession.update(); } diff --git a/Mage.Sets/src/mage/sets/alarareborn/KathariBomber.java b/Mage.Sets/src/mage/sets/alarareborn/KathariBomber.java new file mode 100644 index 00000000000..274527612db --- /dev/null +++ b/Mage.Sets/src/mage/sets/alarareborn/KathariBomber.java @@ -0,0 +1,80 @@ +/* + * 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.sets.alarareborn; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.UnearthAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.game.permanent.token.GoblinToken; + +/** + * + * @author LevelX2 + */ +public class KathariBomber extends CardImpl { + + public KathariBomber(UUID ownerId) { + super(ownerId, 41, "Kathari Bomber", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{1}{B}{R}"); + this.expansionSetCode = "ARB"; + this.subtype.add("Bird"); + this.subtype.add("Shaman"); + + this.color.setRed(true); + this.color.setBlack(true); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // When Kathari Bomber deals combat damage to a player, put two 1/1 red Goblin creature tokens onto the battlefield and sacrifice Kathari Bomber. + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new CreateTokenEffect(new GoblinToken(), 2), false); + ability.addEffect(new SacrificeSourceEffect()); + this.addAbility(ability); + + // Unearth {3}{B}{R} + this.addAbility(new UnearthAbility(new ManaCostsImpl("{3}{B}{R}"))); + } + + public KathariBomber(final KathariBomber card) { + super(card); + } + + @Override + public KathariBomber copy() { + return new KathariBomber(this); + } +} diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/Earthshaker.java b/Mage.Sets/src/mage/sets/championsofkamigawa/Earthshaker.java index e7920ce8105..f658d9eb202 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/Earthshaker.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/Earthshaker.java @@ -62,6 +62,7 @@ public class Earthshaker extends CardImpl { this.color.setRed(true); this.power = new MageInt(4); this.toughness = new MageInt(5); + // Whenever you cast a Spirit or Arcane spell, Earthshaker deals 2 damage to each creature without flying. this.addAbility(new SpellCastTriggeredAbility(new DamageAllEffect(new StaticValue(2) , creatureFilter), filter, false)); } diff --git a/Mage.Sets/src/mage/sets/conflux/BrackwaterElemental.java b/Mage.Sets/src/mage/sets/conflux/BrackwaterElemental.java new file mode 100644 index 00000000000..b6b5c4ec8df --- /dev/null +++ b/Mage.Sets/src/mage/sets/conflux/BrackwaterElemental.java @@ -0,0 +1,108 @@ +/* + * 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.sets.conflux; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.AttacksOrBlocksTriggeredAbility; +import mage.abilities.common.delayed.AtEndOfTurnDelayedTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.abilities.keyword.UnearthAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author LevelX2 + */ +public class BrackwaterElemental extends CardImpl { + + public BrackwaterElemental(UUID ownerId) { + super(ownerId, 21, "Brackwater Elemental", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{2}{U}"); + this.expansionSetCode = "CON"; + this.subtype.add("Elemental"); + + this.color.setBlue(true); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Brackwater Elemental attacks or blocks, sacrifice it at the beginning of the next end step. + this.addAbility(new AttacksOrBlocksTriggeredAbility(new BrackwaterElementalSacrificeEffect(), false)); + // Unearth {2}{U} + this.addAbility(new UnearthAbility(new ManaCostsImpl("{2}{U}"))); + } + + public BrackwaterElemental(final BrackwaterElemental card) { + super(card); + } + + @Override + public BrackwaterElemental copy() { + return new BrackwaterElemental(this); + } +} + +class BrackwaterElementalSacrificeEffect extends OneShotEffect { + + public BrackwaterElementalSacrificeEffect() { + super(Outcome.Sacrifice); + this.staticText = "sacrifice it at the beginning of the next end step"; + } + + public BrackwaterElementalSacrificeEffect(final BrackwaterElementalSacrificeEffect effect) { + super(effect); + } + + @Override + public BrackwaterElementalSacrificeEffect copy() { + return new BrackwaterElementalSacrificeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + if (sourcePermanent != null) { + SacrificeTargetEffect sacrificeEffect = new SacrificeTargetEffect("sacrifice {this}"); + sacrificeEffect.setTargetPointer(new FixedTarget(sourcePermanent.getId())); + DelayedTriggeredAbility delayedAbility = new AtEndOfTurnDelayedTriggeredAbility(sacrificeEffect); + delayedAbility.setSourceId(source.getSourceId()); + delayedAbility.setControllerId(source.getControllerId()); + game.addDelayedTriggeredAbility(delayedAbility); + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/darkascension/FaithlessLooting.java b/Mage.Sets/src/mage/sets/darkascension/FaithlessLooting.java index 010a6220169..1e690ccfa48 100644 --- a/Mage.Sets/src/mage/sets/darkascension/FaithlessLooting.java +++ b/Mage.Sets/src/mage/sets/darkascension/FaithlessLooting.java @@ -32,8 +32,7 @@ import java.util.UUID; import mage.constants.CardType; import mage.constants.Rarity; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.common.DiscardControllerEffect; -import mage.abilities.effects.common.DrawCardControllerEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.abilities.keyword.FlashbackAbility; import mage.cards.CardImpl; import mage.constants.TimingRule; @@ -51,8 +50,7 @@ public class FaithlessLooting extends CardImpl { this.color.setRed(true); // Draw two cards, then discard two cards. - this.getSpellAbility().addEffect(new DrawCardControllerEffect(2)); - this.getSpellAbility().addEffect(new DiscardControllerEffect(2)); + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(2,2)); // Flashback {2}{R} this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{R}"), TimingRule.SORCERY)); } diff --git a/Mage.Sets/src/mage/sets/dissension/TrygonPredator.java b/Mage.Sets/src/mage/sets/dissension/TrygonPredator.java new file mode 100644 index 00000000000..e1417dabe0b --- /dev/null +++ b/Mage.Sets/src/mage/sets/dissension/TrygonPredator.java @@ -0,0 +1,52 @@ +/* + * 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.sets.dissension; + +import java.util.UUID; + +/** + * + * @author LevelX2 + */ +public class TrygonPredator extends mage.sets.modernmasters.TrygonPredator { + + public TrygonPredator(UUID ownerId) { + super(ownerId); + this.cardNumber = 133; + this.expansionSetCode = "DIS"; + } + + public TrygonPredator(final TrygonPredator card) { + super(card); + } + + @Override + public TrygonPredator copy() { + return new TrygonPredator(this); + } +} diff --git a/Mage.Sets/src/mage/sets/fallenempires/Seasinger.java b/Mage.Sets/src/mage/sets/fallenempires/Seasinger.java index 71d027b96f9..650f0c59849 100644 --- a/Mage.Sets/src/mage/sets/fallenempires/Seasinger.java +++ b/Mage.Sets/src/mage/sets/fallenempires/Seasinger.java @@ -35,8 +35,8 @@ import mage.abilities.Ability; import mage.abilities.StateTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SkipUntapOptionalAbility; -import mage.abilities.condition.Condition; import mage.abilities.condition.common.ControlsPermanentCondition; +import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinousEffect; import mage.abilities.effects.common.SacrificeSourceEffect; @@ -89,7 +89,7 @@ public class Seasinger extends CardImpl { this.addAbility(new SkipUntapOptionalAbility()); // {tap}: Gain control of target creature whose controller controls an Island for as long as you control Seasinger and Seasinger remains tapped. - ConditionalContinousEffect effect = new ConditionalContinousEffect(new GainControlTargetEffect(Duration.Custom), new ControlsPermanentCondition(seasinger, ControlsPermanentCondition.CountType.EQUAL_TO, 1, TappedCondition.getInstance()), rule); + ConditionalContinousEffect effect = new ConditionalContinousEffect(new GainControlTargetEffect(Duration.Custom), new ControlsPermanentCondition(seasinger, ControlsPermanentCondition.CountType.EQUAL_TO, 1, SourceTappedCondition.getInstance()), rule); Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapSourceCost()); creatureWhoseControllerControlsIsland.add(new ControllerControlsIslandPredicate()); ability.addTarget(new TargetCreaturePermanent(creatureWhoseControllerControlsIsland)); @@ -148,21 +148,3 @@ class SeasingerTriggeredAbility extends StateTriggeredAbility { if (player.choose(Outcome.Exile, targetPlayer.getHand(), target, game)) { Card card = targetPlayer.getHand().get(target.getFirstTarget(), game); if (card != null) { - card.moveToExile(null, "", source.getId(), game); + card.moveToExile(null, "", source.getSourceId(), game); } } diff --git a/Mage.Sets/src/mage/sets/lorwyn/RunedStalactite.java b/Mage.Sets/src/mage/sets/lorwyn/RunedStalactite.java new file mode 100644 index 00000000000..9a3757e93db --- /dev/null +++ b/Mage.Sets/src/mage/sets/lorwyn/RunedStalactite.java @@ -0,0 +1,52 @@ +/* + * 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.sets.lorwyn; + +import java.util.UUID; + +/** + * + * @author LevelX2 + */ +public class RunedStalactite extends mage.sets.modernmasters.RunedStalactite { + + public RunedStalactite(UUID ownerId) { + super(ownerId); + this.cardNumber = 260; + this.expansionSetCode = "LRW"; + } + + public RunedStalactite(final RunedStalactite card) { + super(card); + } + + @Override + public RunedStalactite copy() { + return new RunedStalactite(this); + } +} diff --git a/Mage.Sets/src/mage/sets/lorwyn/ThundercloudShaman.java b/Mage.Sets/src/mage/sets/lorwyn/ThundercloudShaman.java new file mode 100644 index 00000000000..b6617e88a6e --- /dev/null +++ b/Mage.Sets/src/mage/sets/lorwyn/ThundercloudShaman.java @@ -0,0 +1,52 @@ +/* + * 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.sets.lorwyn; + +import java.util.UUID; + +/** + * + * @author LevelX2 + */ +public class ThundercloudShaman extends mage.sets.modernmasters.ThundercloudShaman { + + public ThundercloudShaman(UUID ownerId) { + super(ownerId); + this.cardNumber = 195; + this.expansionSetCode = "LRW"; + } + + public ThundercloudShaman(final ThundercloudShaman card) { + super(card); + } + + @Override + public ThundercloudShaman copy() { + return new ThundercloudShaman(this); + } +} diff --git a/Mage.Sets/src/mage/sets/mirrodinbesieged/HellkiteIgniter.java b/Mage.Sets/src/mage/sets/mirrodinbesieged/HellkiteIgniter.java index e545d5724bd..2ff26c50a2f 100644 --- a/Mage.Sets/src/mage/sets/mirrodinbesieged/HellkiteIgniter.java +++ b/Mage.Sets/src/mage/sets/mirrodinbesieged/HellkiteIgniter.java @@ -29,9 +29,6 @@ package mage.sets.mirrodinbesieged; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -41,9 +38,13 @@ import mage.abilities.effects.common.continious.BoostSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; +import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.common.FilterArtifactPermanent; +import mage.filter.predicate.permanent.ControllerPredicate; /** * @@ -51,6 +52,11 @@ import mage.filter.common.FilterArtifactPermanent; */ public class HellkiteIgniter extends CardImpl { + private static final FilterArtifactPermanent filter = new FilterArtifactPermanent("artifact you control"); + static { + filter.add(new ControllerPredicate(TargetController.YOU)); + } + public HellkiteIgniter (UUID ownerId) { super(ownerId, 65, "Hellkite Igniter", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{5}{R}{R}"); this.expansionSetCode = "MBS"; @@ -58,11 +64,14 @@ public class HellkiteIgniter extends CardImpl { this.color.setRed(true); this.power = new MageInt(5); this.toughness = new MageInt(5); + + // Flying, haste this.addAbility(FlyingAbility.getInstance()); this.addAbility(HasteAbility.getInstance()); + // {1}{R}: Hellkite Igniter gets +X/+0 until end of turn, where X is the number of artifacts you control. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect( - new PermanentsOnBattlefieldCount(new FilterArtifactPermanent()), + new PermanentsOnBattlefieldCount(filter), new StaticValue(0), Duration.EndOfTurn), new ManaCostsImpl("{1}{R}"))); diff --git a/Mage.Sets/src/mage/sets/mirrodinbesieged/MirranSpy.java b/Mage.Sets/src/mage/sets/mirrodinbesieged/MirranSpy.java index d998f8a1912..57d3e28dd7a 100644 --- a/Mage.Sets/src/mage/sets/mirrodinbesieged/MirranSpy.java +++ b/Mage.Sets/src/mage/sets/mirrodinbesieged/MirranSpy.java @@ -28,12 +28,13 @@ package mage.sets.mirrodinbesieged; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Rarity; import mage.MageInt; import mage.abilities.common.SpellCastTriggeredAbility; import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.filter.FilterSpell; import mage.target.common.TargetCreaturePermanent; @@ -53,6 +54,11 @@ public class MirranSpy extends CardImpl { this.color.setBlue(true); this.power = new MageInt(1); this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast an artifact spell, you may untap target creature. SpellCastTriggeredAbility ability = new SpellCastTriggeredAbility(new UntapTargetEffect(), filter, true); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/sets/modernmasters/CarefulConsideration.java b/Mage.Sets/src/mage/sets/modernmasters/CarefulConsideration.java new file mode 100644 index 00000000000..d3b7e7fb8d8 --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/CarefulConsideration.java @@ -0,0 +1,52 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; + +/** + * + * @author LevelX2 + */ +public class CarefulConsideration extends mage.sets.timespiral.CarefulConsideration { + + public CarefulConsideration(UUID ownerId) { + super(ownerId); + this.cardNumber = 37; + this.expansionSetCode = "MMA"; + } + + public CarefulConsideration(final CarefulConsideration card) { + super(card); + } + + @Override + public CarefulConsideration copy() { + return new CarefulConsideration(this); + } +} diff --git a/Mage.Sets/src/mage/sets/modernmasters/DeathRattle.java b/Mage.Sets/src/mage/sets/modernmasters/DeathRattle.java new file mode 100644 index 00000000000..4d78df7ea27 --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/DeathRattle.java @@ -0,0 +1,76 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.DelveAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author LevelX2 + */ +public class DeathRattle extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + static { + filter.add(Predicates.not(new ColorPredicate(ObjectColor.GREEN))); + } + + public DeathRattle(UUID ownerId) { + super(ownerId, 78, "Death Rattle", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{5}{B}"); + this.expansionSetCode = "MMA"; + + this.color.setBlack(true); + + // Delve + this.addAbility(new DelveAbility()); + + // Destroy target nongreen creature. It can't be regenerated. + this.getSpellAbility().addEffect(new DestroyTargetEffect(true)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(true)); + + } + + public DeathRattle(final DeathRattle card) { + super(card); + } + + @Override + public DeathRattle copy() { + return new DeathRattle(this); + } +} diff --git a/Mage.Sets/src/mage/sets/modernmasters/DemigodOfRevenge.java b/Mage.Sets/src/mage/sets/modernmasters/DemigodOfRevenge.java new file mode 100644 index 00000000000..d80cbcd0f73 --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/DemigodOfRevenge.java @@ -0,0 +1,52 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; + +/** + * + * @author LevelX2 + */ +public class DemigodOfRevenge extends mage.sets.shadowmoor.DemigodOfRevenge { + + public DemigodOfRevenge(UUID ownerId) { + super(ownerId); + this.cardNumber = 187; + this.expansionSetCode = "MMA"; + } + + public DemigodOfRevenge(final DemigodOfRevenge card) { + super(card); + } + + @Override + public DemigodOfRevenge copy() { + return new DemigodOfRevenge(this); + } +} diff --git a/Mage.Sets/src/mage/sets/modernmasters/LogicKnot.java b/Mage.Sets/src/mage/sets/modernmasters/LogicKnot.java new file mode 100644 index 00000000000..1670de315dc --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/LogicKnot.java @@ -0,0 +1,67 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.common.CounterUnlessPaysEffect; +import mage.abilities.keyword.DelveAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.target.TargetSpell; + +/** + * + * @author LevelX2 + */ +public class LogicKnot extends CardImpl { + + public LogicKnot(UUID ownerId) { + super(ownerId, 51, "Logic Knot", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{X}{U}{U}"); + this.expansionSetCode = "MMA"; + + this.color.setBlue(true); + + // Delve + this.addAbility(new DelveAbility()); + + // Counter target spell unless its controller pays {X}. + this.getSpellAbility().addEffect(new CounterUnlessPaysEffect(new ManacostVariableValue())); + this.getSpellAbility().addTarget(new TargetSpell()); + } + + public LogicKnot(final LogicKnot card) { + super(card); + } + + @Override + public LogicKnot copy() { + return new LogicKnot(this); + } +} diff --git a/Mage.Sets/src/mage/sets/modernmasters/MoltenDisaster.java b/Mage.Sets/src/mage/sets/modernmasters/MoltenDisaster.java new file mode 100644 index 00000000000..715f485c371 --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/MoltenDisaster.java @@ -0,0 +1,152 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.condition.common.KickedCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continious.GainAbilitySourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.KickerAbility; +import mage.abilities.keyword.SplitSecondAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class MoltenDisaster extends CardImpl { + + public MoltenDisaster(UUID ownerId) { + super(ownerId, 123, "Molten Disaster", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{X}{R}{R}"); + this.expansionSetCode = "MMA"; + + this.color.setRed(true); + + // If Molten Disaster was kicked, it has split second. + Ability ability = new ConditionalTriggeredAbility(new MoltenDisasterTriggeredAbility(), KickedCondition.getInstance(), ""); + ability.setRuleAtTheTop(true); + this.addAbility(ability); + // Kicker {R} + this.addAbility(new KickerAbility("{R}")); + // Molten Disaster deals X damage to each creature without flying and each player. + this.getSpellAbility().addEffect(new MoltenDisasterEffect()); + } + + public MoltenDisaster(final MoltenDisaster card) { + super(card); + } + + @Override + public MoltenDisaster copy() { + return new MoltenDisaster(this); + } +} + +class MoltenDisasterTriggeredAbility extends TriggeredAbilityImpl { + + public MoltenDisasterTriggeredAbility() { + super(Zone.HAND, new GainAbilitySourceEffect(SplitSecondAbility.getInstance(), Duration.WhileOnStack), false); + } + + public MoltenDisasterTriggeredAbility(final MoltenDisasterTriggeredAbility ability) { + super(ability); + } + + @Override + public MoltenDisasterTriggeredAbility copy() { + return new MoltenDisasterTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType().equals(GameEvent.EventType.CAST_SPELL) && event.getSourceId().equals(this.getSourceId())) { + return true; + } + return false; + } + + @Override + public String getRule() { + return "If Molten Disaster was kicked, it has split second (As long as this spell is on the stack, players can't cast spells or activate abilities that aren't mana abilities.)"; + } +} + +class MoltenDisasterEffect extends OneShotEffect { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); + } + + public MoltenDisasterEffect() { + super(Outcome.Damage); + staticText = "{this} deals X damage to each creature without flying and each player"; + } + + public MoltenDisasterEffect(final MoltenDisasterEffect effect) { + super(effect); + } + + @Override + public MoltenDisasterEffect copy() { + return new MoltenDisasterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int amount = source.getManaCostsToPay().getX(); + for (Permanent permanent: game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { + permanent.damage(amount, source.getId(), game, true, false); + } + for (UUID playerId: game.getPlayer(source.getControllerId()).getInRange()) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.damage(amount, source.getSourceId(), game, false, true); + } + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/modernmasters/RunedStalactite.java b/Mage.Sets/src/mage/sets/modernmasters/RunedStalactite.java new file mode 100644 index 00000000000..6a08de7e6bf --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/RunedStalactite.java @@ -0,0 +1,74 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continious.BoostEquippedEffect; +import mage.abilities.effects.common.continious.GainAbilityAttachedEffect; +import mage.abilities.keyword.ChangelingAbility; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; + +/** + * + * @author LevelX2 + */ +public class RunedStalactite extends CardImpl { + + public RunedStalactite(UUID ownerId) { + super(ownerId, 214, "Runed Stalactite", Rarity.COMMON, new CardType[]{CardType.ARTIFACT}, "{1}"); + this.expansionSetCode = "MMA"; + this.subtype.add("Equipment"); + + // Equipped creature gets +1/+1 and is every creature type. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEquippedEffect(1,1))); + Effect effect = new GainAbilityAttachedEffect(ChangelingAbility.getInstance(), AttachmentType.EQUIPMENT, Duration.WhileOnBattlefield); + effect.setText("Equipped creature is every creature type (Changeling)"); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + // Equip {2} + this.addAbility(new EquipAbility(Outcome.BoostCreature, new ManaCostsImpl("{2}"))); + } + + public RunedStalactite(final RunedStalactite card) { + super(card); + } + + @Override + public RunedStalactite copy() { + return new RunedStalactite(this); + } +} diff --git a/Mage.Sets/src/mage/sets/modernmasters/ThundercloudShaman.java b/Mage.Sets/src/mage/sets/modernmasters/ThundercloudShaman.java new file mode 100644 index 00000000000..788a75f1ff5 --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/ThundercloudShaman.java @@ -0,0 +1,83 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageAllEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.ControllerPredicate; + +/** + * + * @author LevelX2 + */ +public class ThundercloudShaman extends CardImpl { + + private static final FilterCreaturePermanent filterGiants = new FilterCreaturePermanent("equal to the number of Giants you control"); + private static final FilterCreaturePermanent filterNonGiants = new FilterCreaturePermanent("non-Giant creature"); + static { + filterGiants.add(new ControllerPredicate(TargetController.YOU)); + filterGiants.add(new SubtypePredicate("Giant")); + filterNonGiants.add(Predicates.not(new SubtypePredicate("Giant"))); + } + + public ThundercloudShaman(UUID ownerId) { + super(ownerId, 135, "Thundercloud Shaman", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + this.expansionSetCode = "MMA"; + this.subtype.add("Giant"); + this.subtype.add("Shaman"); + + this.color.setRed(true); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Thundercloud Shaman enters the battlefield, it deals damage equal to the number of Giants you control to each non-Giant creature. + Effect effect = new DamageAllEffect(new PermanentsOnBattlefieldCount(filterGiants),filterNonGiants); + effect.setText("it deals damage equal to the number of Giants you control to each non-Giant creature"); + this.addAbility(new EntersBattlefieldTriggeredAbility(effect, false)); + } + + public ThundercloudShaman(final ThundercloudShaman card) { + super(card); + } + + @Override + public ThundercloudShaman copy() { + return new ThundercloudShaman(this); + } +} diff --git a/Mage.Sets/src/mage/sets/modernmasters/TidehollowSculler.java b/Mage.Sets/src/mage/sets/modernmasters/TidehollowSculler.java new file mode 100644 index 00000000000..0e0ad4d5055 --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/TidehollowSculler.java @@ -0,0 +1,52 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; + +/** + * + * @author LevelX2 + */ +public class TidehollowSculler extends mage.sets.shardsofalara.TidehollowSculler { + + public TidehollowSculler(UUID ownerId) { + super(ownerId); + this.cardNumber = 184; + this.expansionSetCode = "MMA"; + } + + public TidehollowSculler(final TidehollowSculler card) { + super(card); + } + + @Override + public TidehollowSculler copy() { + return new TidehollowSculler(this); + } +} diff --git a/Mage.Sets/src/mage/sets/modernmasters/Tombstalker.java b/Mage.Sets/src/mage/sets/modernmasters/Tombstalker.java new file mode 100644 index 00000000000..339b15d08c2 --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/Tombstalker.java @@ -0,0 +1,70 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.keyword.DelveAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; + +/** + * + * @author LevelX2 + */ +public class Tombstalker extends CardImpl { + + public Tombstalker(UUID ownerId) { + super(ownerId, 102, "Tombstalker", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{6}{B}{B}"); + this.expansionSetCode = "MMA"; + this.subtype.add("Demon"); + + this.color.setBlack(true); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // Delve + Ability ability = new DelveAbility(); + ability.setRuleAtTheTop(false); + this.addAbility(ability); + } + + public Tombstalker(final Tombstalker card) { + super(card); + } + + @Override + public Tombstalker copy() { + return new Tombstalker(this); + } +} diff --git a/Mage.Sets/src/mage/sets/modernmasters/TrygonPredator.java b/Mage.Sets/src/mage/sets/modernmasters/TrygonPredator.java new file mode 100644 index 00000000000..29ec4f62b1d --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/TrygonPredator.java @@ -0,0 +1,121 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.players.Player; +import mage.target.TargetPermanent; + +/** + * + * @author LevelX2 + */ +public class TrygonPredator extends CardImpl { + + public TrygonPredator(UUID ownerId) { + super(ownerId, 185, "Trygon Predator", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{1}{G}{U}"); + this.expansionSetCode = "MMA"; + this.subtype.add("Beast"); + + this.color.setBlue(true); + this.color.setGreen(true); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // Whenever Trygon Predator deals combat damage to a player, you may destroy target artifact or enchantment that player controls. + this.addAbility(new TrygonPredatorTriggeredAbility()); + } + + public TrygonPredator(final TrygonPredator card) { + super(card); + } + + @Override + public TrygonPredator copy() { + return new TrygonPredator(this); + } +} + +class TrygonPredatorTriggeredAbility extends TriggeredAbilityImpl { + + public TrygonPredatorTriggeredAbility() { + super(Zone.BATTLEFIELD, new DestroyTargetEffect(), true); + } + + public TrygonPredatorTriggeredAbility(final TrygonPredatorTriggeredAbility ability) { + super(ability); + } + + @Override + public TrygonPredatorTriggeredAbility copy() { + return new TrygonPredatorTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == EventType.DAMAGED_PLAYER && event.getSourceId().equals(this.sourceId) + && ((DamagedPlayerEvent) event).isCombatDamage()) { + Player player = game.getPlayer(event.getTargetId()); + if (player != null) { + FilterPermanent filter = new FilterPermanent("an artifact or enchantment controlled by " + player.getName()); + filter.add(Predicates.or( + new CardTypePredicate(CardType.ARTIFACT), + new CardTypePredicate(CardType.ENCHANTMENT))); + filter.add(new ControllerIdPredicate(event.getTargetId())); + + this.getTargets().clear(); + this.addTarget(new TargetPermanent(filter)); + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever {this} deals combat damage to a player, you may destroy target artifact or enchantment that player controls."; + } +} diff --git a/Mage.Sets/src/mage/sets/modernmasters/VedalkenShackles.java b/Mage.Sets/src/mage/sets/modernmasters/VedalkenShackles.java new file mode 100644 index 00000000000..7584f8d9eae --- /dev/null +++ b/Mage.Sets/src/mage/sets/modernmasters/VedalkenShackles.java @@ -0,0 +1,113 @@ +/* + * 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.sets.modernmasters; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SkipUntapOptionalAbility; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.decorator.ConditionalContinousEffect; +import mage.abilities.effects.common.continious.GainControlTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.common.FilterLandPermanent; +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.target.common.TargetCreaturePermanent; + +/** + * + * @author LevelX2 + */ +public class VedalkenShackles extends CardImpl { + + private static final FilterCreaturePermanent controllableCreatures = new FilterCreaturePermanent("creature with power less than or equal to the number of Islands you control"); + static { + controllableCreatures.add(new PowerIslandPredicate()); + } + + + public VedalkenShackles(UUID ownerId) { + super(ownerId, 218, "Vedalken Shackles", Rarity.MYTHIC, new CardType[]{CardType.ARTIFACT}, "{3}"); + this.expansionSetCode = "MMA"; + + // You may choose not to untap Vedalken Shackles during your untap step. + this.addAbility(new SkipUntapOptionalAbility()); + + // {2}, {tap}: Gain control of target creature with power less than or equal to the number of Islands you control for as long as Vedalken Shackles remains tapped. + ConditionalContinousEffect effect = new ConditionalContinousEffect( + new GainControlTargetEffect(Duration.Custom), SourceTappedCondition.getInstance(), + "Gain control of target creature with power less than or equal to the number of Islands you control for as long as {this} remains tapped"); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapSourceCost()); + ability.addTarget(new TargetCreaturePermanent(controllableCreatures)); + this.addAbility(ability); + } + + public VedalkenShackles(final VedalkenShackles card) { + super(card); + } + + @Override + public VedalkenShackles copy() { + return new VedalkenShackles(this); + } +} + +class PowerIslandPredicate implements ObjectPlayerPredicate> { + + public static final FilterLandPermanent filter = new FilterLandPermanent("Island"); + static { + filter.add(new SubtypePredicate("Island")); + } + + @Override + public boolean apply(ObjectPlayer input, Game game) { + Permanent permanent = input.getObject(); + if (permanent != null) { + int islands = game.getBattlefield().countAll(filter, input.getPlayerId(), game); + if (permanent.getPower().getValue() <= islands) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "creature with power less than or equal to the number of Islands you control"; + } +} diff --git a/Mage.Sets/src/mage/sets/nemesis/BlindingAngel.java b/Mage.Sets/src/mage/sets/nemesis/BlindingAngel.java index 039550ef59b..0d2309162e7 100644 --- a/Mage.Sets/src/mage/sets/nemesis/BlindingAngel.java +++ b/Mage.Sets/src/mage/sets/nemesis/BlindingAngel.java @@ -36,11 +36,13 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.constants.TurnPhase; import mage.game.Game; import mage.game.turn.TurnMod; +import mage.players.Player; /** * @@ -80,15 +82,15 @@ class SkipNextCombatEffect extends OneShotEffect { staticText = "that player skips his or her next combat phase"; } - public SkipNextCombatEffect(SkipNextCombatEffect effect) { + public SkipNextCombatEffect(final SkipNextCombatEffect effect) { super(effect); } @Override public boolean apply(Game game, Ability source) { - UUID targetId = source.getFirstTarget(); - if (targetId != null) { - game.getState().getTurnMods().add(new TurnMod(targetId, TurnPhase.COMBAT, null, true)); + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player != null) { + game.getState().getTurnMods().add(new TurnMod(player.getId(), TurnPhase.COMBAT, null, true)); return true; } return false; @@ -96,6 +98,6 @@ class SkipNextCombatEffect extends OneShotEffect { @Override public SkipNextCombatEffect copy() { - return new SkipNextCombatEffect(); + return new SkipNextCombatEffect(this); } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/newphyrexia/ConversionChamber.java b/Mage.Sets/src/mage/sets/newphyrexia/ConversionChamber.java index 409145b31e0..af1ad6abbca 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/ConversionChamber.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/ConversionChamber.java @@ -29,23 +29,22 @@ package mage.sets.newphyrexia; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.ExileFromGraveCost; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.common.FilterArtifactCard; import mage.game.permanent.token.GolemToken; -import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetCardInGraveyard; /** * @@ -56,10 +55,13 @@ public class ConversionChamber extends CardImpl { public ConversionChamber (UUID ownerId) { super(ownerId, 133, "Conversion Chamber", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{3}"); this.expansionSetCode = "NPH"; - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.CHARGE.createInstance()), new GenericManaCost(2)); + // {2}, {T}: Exile target artifact card from a graveyard. Put a charge counter on Conversion Chamber. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ExileTargetEffect(), new GenericManaCost(2)); + ability.addEffect(new AddCountersSourceEffect(CounterType.CHARGE.createInstance())); + ability.addTarget(new TargetCardInGraveyard(new FilterArtifactCard("artifact card from a graveyard"))); ability.addCost(new TapSourceCost()); - ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(new FilterArtifactCard("artifact card from a graveyard")))); this.addAbility(ability); + // {2}, {T}, Remove a charge counter from Conversion Chamber: Put a 3/3 colorless Golem artifact creature token onto the battlefield. ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new GolemToken()), new GenericManaCost(2)); ability.addCost(new TapSourceCost()); ability.addCost(new RemoveCountersSourceCost(CounterType.CHARGE.createInstance())); diff --git a/Mage.Sets/src/mage/sets/newphyrexia/OgreMenial.java b/Mage.Sets/src/mage/sets/newphyrexia/OgreMenial.java index 28d90a28670..dc9e5b7a8b7 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/OgreMenial.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/OgreMenial.java @@ -28,15 +28,16 @@ package mage.sets.newphyrexia; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Rarity; -import mage.constants.Zone; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.continious.BoostSourceEffect; +import mage.abilities.keyword.InfectAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; /** * @@ -52,7 +53,10 @@ public class OgreMenial extends CardImpl { this.color.setRed(true); this.power = new MageInt(0); this.toughness = new MageInt(4); + // Infect (This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters.) + this.addAbility(InfectAbility.getInstance()); + // {R}: Ogre Menial gets +1/+0 until end of turn. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(1, 0, Duration.EndOfTurn), new ManaCostsImpl("{R}"))); } diff --git a/Mage.Sets/src/mage/sets/odyssey/CarefulStudy.java b/Mage.Sets/src/mage/sets/odyssey/CarefulStudy.java index a4300e5440d..35a59312707 100644 --- a/Mage.Sets/src/mage/sets/odyssey/CarefulStudy.java +++ b/Mage.Sets/src/mage/sets/odyssey/CarefulStudy.java @@ -27,13 +27,11 @@ */ package mage.sets.odyssey; +import java.util.UUID; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.abilities.effects.common.DiscardControllerEffect; -import mage.abilities.effects.common.DrawCardControllerEffect; -import mage.cards.CardImpl; - -import java.util.UUID; /** * @author magenoxx_at_gmail.com @@ -47,8 +45,7 @@ public class CarefulStudy extends CardImpl { this.color.setBlue(true); // Draw two cards, then discard two cards. - this.getSpellAbility().addEffect(new DrawCardControllerEffect(2)); - this.getSpellAbility().addEffect(new DiscardControllerEffect(2)); + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(2,2)); } public CarefulStudy(final CarefulStudy card) { diff --git a/Mage.Sets/src/mage/sets/planechase/EtchedOracle.java b/Mage.Sets/src/mage/sets/planechase/EtchedOracle.java index c8344b5bf98..b62afa3c947 100644 --- a/Mage.Sets/src/mage/sets/planechase/EtchedOracle.java +++ b/Mage.Sets/src/mage/sets/planechase/EtchedOracle.java @@ -56,7 +56,7 @@ public class EtchedOracle extends CardImpl { this.power = new MageInt(0); this.toughness = new MageInt(0); - // Sunburst + // Sunburst (This enters the battlefield with a +1/+1 counter on it for each color of mana spent to cast it.) this.addAbility(new SunburstAbility()); // {1}, Remove four +1/+1 counters from Etched Oracle: Target player draws three cards. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawCardTargetEffect(3), new ManaCostsImpl("{1}")); diff --git a/Mage.Sets/src/mage/sets/returntoravnica/AngelOfSerenity.java b/Mage.Sets/src/mage/sets/returntoravnica/AngelOfSerenity.java index 0d65d73f69b..d1e7d5ad015 100644 --- a/Mage.Sets/src/mage/sets/returntoravnica/AngelOfSerenity.java +++ b/Mage.Sets/src/mage/sets/returntoravnica/AngelOfSerenity.java @@ -28,11 +28,6 @@ package mage.sets.returntoravnica; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; -import mage.constants.Zone; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.LeavesBattlefieldTriggeredAbility; @@ -41,6 +36,10 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.Card; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreaturePermanent; @@ -52,6 +51,7 @@ import mage.game.permanent.Permanent; import mage.target.Target; import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; /** * @@ -156,7 +156,7 @@ class AngelOfSerenityEnterEffect extends OneShotEffect { // or draw two cards, then discard two cards. mode = new Mode(); - mode.getEffects().add(new DrawCardControllerEffect(2)); - mode.getEffects().add(new DiscardControllerEffect(2)); + mode.getEffects().add(new DrawDiscardControllerEffect(2, 2)); this.getSpellAbility().addMode(mode); } diff --git a/Mage.Sets/src/mage/sets/returntoravnica/Thoughtflare.java b/Mage.Sets/src/mage/sets/returntoravnica/Thoughtflare.java index 54c4160b783..73e05f69a00 100644 --- a/Mage.Sets/src/mage/sets/returntoravnica/Thoughtflare.java +++ b/Mage.Sets/src/mage/sets/returntoravnica/Thoughtflare.java @@ -28,10 +28,9 @@ package mage.sets.returntoravnica; import java.util.UUID; +import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.constants.CardType; import mage.constants.Rarity; -import mage.abilities.effects.common.DiscardControllerEffect; -import mage.abilities.effects.common.DrawCardControllerEffect; import mage.cards.CardImpl; /** @@ -47,8 +46,7 @@ public class Thoughtflare extends CardImpl { this.color.setRed(true); // Draw four cards, then discard two cards. - this.getSpellAbility().addEffect(new DrawCardControllerEffect(4)); - this.getSpellAbility().addEffect(new DiscardControllerEffect(2)); + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(4, 2)); } public Thoughtflare(final Thoughtflare card) { diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/KothOfTheHammer.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/KothOfTheHammer.java index 16d2ed2d2fc..1192ed8a0b0 100644 --- a/Mage.Sets/src/mage/sets/scarsofmirrodin/KothOfTheHammer.java +++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/KothOfTheHammer.java @@ -28,12 +28,6 @@ package mage.sets.scarsofmirrodin; -import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.*; -import mage.constants.Rarity; -import mage.constants.Zone; import mage.MageInt; import mage.Mana; import mage.abilities.Ability; @@ -51,6 +45,7 @@ import mage.abilities.effects.common.UntapTargetEffect; import mage.abilities.effects.common.continious.BecomesCreatureTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; +import mage.constants.*; import mage.counters.CounterType; import mage.filter.common.FilterLandPermanent; import mage.filter.predicate.mageobject.SubtypePredicate; @@ -62,6 +57,8 @@ import mage.game.permanent.token.Token; import mage.target.common.TargetCreatureOrPlayer; import mage.target.common.TargetLandPermanent; +import java.util.UUID; + /** * * @author Loki, North @@ -95,7 +92,7 @@ public class KothOfTheHammer extends CardImpl { // -2: Add {R} to your mana pool for each Mountain you control. this.addAbility(new LoyaltyAbility(new DynamicManaEffect(Mana.RedMana, new PermanentsOnBattlefieldCount(filterCount)), -2)); - // -5: You get an emblem with "Mountains you control have ‘{T}: This land deals 1 damage to target creature or player.' + // -5: You get an emblem with "Mountains you control have '{T}: This land deals 1 damage to target creature or player.' this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new KothOfTheHammerEmblem()), -5)); } diff --git a/Mage.Sets/src/mage/sets/shadowmoor/DemigodOfRevenge.java b/Mage.Sets/src/mage/sets/shadowmoor/DemigodOfRevenge.java new file mode 100644 index 00000000000..4f24697e34f --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowmoor/DemigodOfRevenge.java @@ -0,0 +1,145 @@ +/* + * 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.sets.shadowmoor; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class DemigodOfRevenge extends CardImpl { + + public DemigodOfRevenge(UUID ownerId) { + super(ownerId, 183, "Demigod of Revenge", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{B/R}{B/R}{B/R}{B/R}{B/R}"); + this.expansionSetCode = "SHM"; + this.subtype.add("Spirit"); + this.subtype.add("Avatar"); + + this.color.setRed(true); + this.color.setBlack(true); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // Haste + this.addAbility(HasteAbility.getInstance()); + // When you cast Demigod of Revenge, return all cards named Demigod of Revenge from your graveyard to the battlefield. + this.addAbility(new DemigodOfRevengeTriggeredAbility()); + } + + public DemigodOfRevenge(final DemigodOfRevenge card) { + super(card); + } + + @Override + public DemigodOfRevenge copy() { + return new DemigodOfRevenge(this); + } +} + +class DemigodOfRevengeTriggeredAbility extends TriggeredAbilityImpl { + + public DemigodOfRevengeTriggeredAbility() { + super(Zone.STACK, new DemigodOfRevengeReturnEffect(), false); + } + + public DemigodOfRevengeTriggeredAbility(final DemigodOfRevengeTriggeredAbility ability) { + super(ability); + } + + @Override + public DemigodOfRevengeTriggeredAbility copy() { + return new DemigodOfRevengeTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType().equals(GameEvent.EventType.SPELL_CAST) && event.getSourceId().equals(this.getSourceId())) { + return true; + } + return false; + } + + @Override + public String getRule() { + return "When you cast Demigod of Revenge, " + super.getRule(); + } +} + +class DemigodOfRevengeReturnEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard(); + static { + filter.add(new NamePredicate("Demigod of Revenge")); + } + + public DemigodOfRevengeReturnEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "return all cards named Demigod of Revenge from your graveyard to the battlefield"; + } + + public DemigodOfRevengeReturnEffect(final DemigodOfRevengeReturnEffect effect) { + super(effect); + } + + @Override + public DemigodOfRevengeReturnEffect copy() { + return new DemigodOfRevengeReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + for (Card creature : player.getGraveyard().getCards(filter, game)) { + creature.putOntoBattlefield(game, Zone.GRAVEYARD, source.getId(), source.getControllerId()); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/shardsofalara/TidehollowSculler.java b/Mage.Sets/src/mage/sets/shardsofalara/TidehollowSculler.java new file mode 100644 index 00000000000..a700a0d2f19 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shardsofalara/TidehollowSculler.java @@ -0,0 +1,155 @@ +/* + * 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.sets.shardsofalara; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterNonlandCard; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +/** + * + * @author LevelX2 + */ +public class TidehollowSculler extends CardImpl { + + public TidehollowSculler(UUID ownerId) { + super(ownerId, 202, "Tidehollow Sculler", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{W}{B}"); + this.expansionSetCode = "ALA"; + this.subtype.add("Zombie"); + + this.color.setBlack(true); + this.color.setWhite(true); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Tidehollow Sculler enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card. + Ability ability = new EntersBattlefieldTriggeredAbility(new TidehollowScullerExileEffect(), false); + ability.addTarget(new TargetOpponent(true)); + this.addAbility(ability); + + + // When Tidehollow Sculler leaves the battlefield, return the exiled card to its owner's hand. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new TidehollowScullerLeaveEffect(), false )); + } + + public TidehollowSculler(final TidehollowSculler card) { + super(card); + } + + @Override + public TidehollowSculler copy() { + return new TidehollowSculler(this); + } +} + +class TidehollowScullerExileEffect extends OneShotEffect { + + public TidehollowScullerExileEffect() { + super(Outcome.Exile); + this.staticText = "target opponent reveals his or her hand and you choose a nonland card from it. Exile that card"; + } + + public TidehollowScullerExileEffect(final TidehollowScullerExileEffect effect) { + super(effect); + } + + @Override + public TidehollowScullerExileEffect copy() { + return new TidehollowScullerExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + if (player != null && targetPlayer != null) { + targetPlayer.revealCards("Tidehollow Sculler", targetPlayer.getHand(), game); + + TargetCard target = new TargetCard(Zone.PICK, new FilterNonlandCard("nonland card to exile")); + target.setRequired(true); + if (player.choose(Outcome.Exile, targetPlayer.getHand(), target, game)) { + Card card = targetPlayer.getHand().get(target.getFirstTarget(), game); + if (card != null) { + card.moveToExile(CardUtil.getCardExileZoneId(game, source), "Tidehollow Sculler", source.getSourceId(), game); + } + } + + return true; + } + return false; + } + + +} + +class TidehollowScullerLeaveEffect extends OneShotEffect { + + public TidehollowScullerLeaveEffect() { + super(Outcome.ReturnToHand); + this.staticText = "return the exiled card to its owner's hand"; + } + + public TidehollowScullerLeaveEffect(final TidehollowScullerLeaveEffect effect) { + super(effect); + } + + @Override + public TidehollowScullerLeaveEffect copy() { + return new TidehollowScullerLeaveEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + ExileZone exZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source)); + if (exZone != null) { + for (Card card : exZone.getCards(game)) { + if (card != null) { + card.moveToZone(Zone.HAND, source.getId(), game, false); + } + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/tempest/EmmessiTome.java b/Mage.Sets/src/mage/sets/tempest/EmmessiTome.java new file mode 100644 index 00000000000..0584dbca8a9 --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempest/EmmessiTome.java @@ -0,0 +1,65 @@ +/* + * 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.sets.tempest; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; + +/** + * + * @author LevelX2 + */ +public class EmmessiTome extends CardImpl { + + public EmmessiTome(UUID ownerId) { + super(ownerId, 274, "Emmessi Tome", Rarity.RARE, new CardType[]{CardType.ARTIFACT}, "{4}"); + this.expansionSetCode = "TMP"; + + // {5}, {tap}: Draw two cards, then discard a card. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawDiscardControllerEffect(2,1), new ManaCostsImpl("{5}")); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + public EmmessiTome(final EmmessiTome card) { + super(card); + } + + @Override + public EmmessiTome copy() { + return new EmmessiTome(this); + } +} diff --git a/Mage.Sets/src/mage/sets/tenth/Sift.java b/Mage.Sets/src/mage/sets/tenth/Sift.java index 86655ccb4f3..5a6f4fa78fb 100644 --- a/Mage.Sets/src/mage/sets/tenth/Sift.java +++ b/Mage.Sets/src/mage/sets/tenth/Sift.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.constants.CardType; import mage.constants.Rarity; import mage.abilities.effects.common.DiscardControllerEffect; -import mage.abilities.effects.common.DrawCardControllerEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.cards.CardImpl; /** @@ -47,8 +47,7 @@ public class Sift extends CardImpl { this.color.setBlue(true); // Draw three cards, then discard a card. - this.getSpellAbility().addEffect(new DrawCardControllerEffect(3)); - this.getSpellAbility().addEffect(new DiscardControllerEffect(1)); + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(3,1)); } public Sift(final Sift card) { diff --git a/Mage.Sets/src/mage/sets/timespiral/CarefulConsideration.java b/Mage.Sets/src/mage/sets/timespiral/CarefulConsideration.java new file mode 100644 index 00000000000..9450964a645 --- /dev/null +++ b/Mage.Sets/src/mage/sets/timespiral/CarefulConsideration.java @@ -0,0 +1,68 @@ +/* + * 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.sets.timespiral; + +import java.util.UUID; +import mage.abilities.condition.common.MyMainPhaseCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DrawDiscardTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.target.TargetPlayer; + +/** + * + * @author LevelX2 + */ +public class CarefulConsideration extends CardImpl { + + public CarefulConsideration(UUID ownerId) { + super(ownerId, 52, "Careful Consideration", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{2}{U}{U}"); + this.expansionSetCode = "TSP"; + + this.color.setBlue(true); + + // Target player draws four cards, then discards three cards. If you cast this spell during your main phase, instead that player draws four cards, then discards two cards. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new DrawDiscardTargetEffect(4,2), + new DrawDiscardTargetEffect(4,3), + MyMainPhaseCondition.getInstance(), + "Target player draws four cards, then discards three cards. If you cast this spell during your main phase, instead that player draws four cards, then discards two cards")); + this.getSpellAbility().addTarget(new TargetPlayer(true)); + } + + public CarefulConsideration(final CarefulConsideration card) { + super(card); + } + + @Override + public CarefulConsideration copy() { + return new CarefulConsideration(this); + } +} diff --git a/Mage.Sets/src/mage/sets/urzaslegacy/FranticSearch.java b/Mage.Sets/src/mage/sets/urzaslegacy/FranticSearch.java index 78225e1f969..97705ef069e 100644 --- a/Mage.Sets/src/mage/sets/urzaslegacy/FranticSearch.java +++ b/Mage.Sets/src/mage/sets/urzaslegacy/FranticSearch.java @@ -28,10 +28,9 @@ package mage.sets.urzaslegacy; import java.util.UUID; +import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.constants.CardType; import mage.constants.Rarity; -import mage.abilities.effects.common.DiscardControllerEffect; -import mage.abilities.effects.common.DrawCardControllerEffect; import mage.abilities.effects.common.UntapLandsEffect; import mage.cards.CardImpl; @@ -48,8 +47,7 @@ public class FranticSearch extends CardImpl { this.color.setBlue(true); // Draw two cards, then discard two cards. Untap up to three lands. - this.getSpellAbility().addEffect(new DrawCardControllerEffect(2)); - this.getSpellAbility().addEffect(new DiscardControllerEffect(2)); + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(2, 2)); this.getSpellAbility().addEffect(new UntapLandsEffect(3)); } diff --git a/Mage.Sets/src/mage/sets/urzassaga/Catalog.java b/Mage.Sets/src/mage/sets/urzassaga/Catalog.java index 89e3830c460..2f1d132b2cd 100644 --- a/Mage.Sets/src/mage/sets/urzassaga/Catalog.java +++ b/Mage.Sets/src/mage/sets/urzassaga/Catalog.java @@ -33,6 +33,7 @@ import mage.constants.Outcome; import mage.constants.Rarity; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.cards.CardImpl; import mage.game.Game; import mage.players.Player; @@ -49,7 +50,7 @@ public class Catalog extends CardImpl { this.color.setBlue(true); // Draw two cards, then discard a card. - this.getSpellAbility().addEffect(new CatalogEffect()); + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(2,1)); } public Catalog(final Catalog card) { @@ -61,31 +62,3 @@ public class Catalog extends CardImpl { return new Catalog(this); } } - -class CatalogEffect extends OneShotEffect { - - public CatalogEffect() { - super(Outcome.DrawCard); - this.staticText = "Draw two cards, then discard a card"; - } - - public CatalogEffect(final CatalogEffect effect) { - super(effect); - } - - @Override - public CatalogEffect copy() { - return new CatalogEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.drawCards(2, game); - player.discard(1, source, game); - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/sets/worldwake/ChainReaction.java b/Mage.Sets/src/mage/sets/worldwake/ChainReaction.java index a205f8f5530..c9cdc58679b 100644 --- a/Mage.Sets/src/mage/sets/worldwake/ChainReaction.java +++ b/Mage.Sets/src/mage/sets/worldwake/ChainReaction.java @@ -47,6 +47,7 @@ public class ChainReaction extends CardImpl { this.color.setRed(true); + // Chain Reaction deals X damage to each creature, where X is the number of creatures on the battlefield. this.getSpellAbility().addEffect(new DamageAllEffect(new PermanentsOnBattlefieldCount(new FilterCreaturePermanent()), new FilterCreaturePermanent())); } diff --git a/Mage/src/mage/abilities/condition/common/SourceTappedCondition.java b/Mage/src/mage/abilities/condition/common/SourceTappedCondition.java new file mode 100644 index 00000000000..b8b0a8522fd --- /dev/null +++ b/Mage/src/mage/abilities/condition/common/SourceTappedCondition.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.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author LevelX2 + */ + + +public class SourceTappedCondition implements Condition { + + private static SourceTappedCondition fInstance = new SourceTappedCondition(); + + public static Condition getInstance() { + return fInstance; + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId()); + if (permanent != null) { + return permanent.isTapped(); + } + return false; + } +} diff --git a/Mage/src/mage/abilities/decorator/ConditionalContinousEffect.java b/Mage/src/mage/abilities/decorator/ConditionalContinousEffect.java index 0614935d669..b3379612178 100644 --- a/Mage/src/mage/abilities/decorator/ConditionalContinousEffect.java +++ b/Mage/src/mage/abilities/decorator/ConditionalContinousEffect.java @@ -1,12 +1,12 @@ package mage.abilities.decorator; -import mage.constants.Duration; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.condition.Condition; import mage.abilities.condition.common.FixedCondition; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; +import mage.constants.Duration; import mage.constants.Layer; import mage.constants.SubLayer; import mage.game.Game; @@ -84,6 +84,9 @@ public class ConditionalContinousEffect extends ContinuousEffectImpl abilities = layeredEffects.getAbility(effect.getId()); for (Ability ability: abilities) { - if (ability.isInUseableZone(game, null, false)) { + // If e.g. triggerd abilities (non static) created the effect, the ability must not be in usable zone (e.g. Unearth giving Haste effect) + if (!(ability instanceof StaticAbility) || ability.isInUseableZone(game, null, false)) { layerEffects.add(effect); break; } diff --git a/Mage/src/mage/abilities/effects/common/DrawDiscardControllerEffect.java b/Mage/src/mage/abilities/effects/common/DrawDiscardControllerEffect.java index c37d692d13e..251843ae5c3 100644 --- a/Mage/src/mage/abilities/effects/common/DrawDiscardControllerEffect.java +++ b/Mage/src/mage/abilities/effects/common/DrawDiscardControllerEffect.java @@ -28,11 +28,12 @@ package mage.abilities.effects.common; -import mage.constants.Outcome; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; /** * @@ -40,13 +41,29 @@ import mage.players.Player; */ public class DrawDiscardControllerEffect extends OneShotEffect { + private int cardsToDraw; + private int cardsToDiscard; + public DrawDiscardControllerEffect() { + this(1,1); + } + + public DrawDiscardControllerEffect(int cardsToDraw, int cardsToDiscard) { super(Outcome.DrawCard); - staticText = "Draw a card, then discard a card"; + this.cardsToDraw = cardsToDraw; + this.cardsToDiscard = cardsToDiscard; + staticText = new StringBuilder("Draw ") + .append(cardsToDraw == 1?"a": CardUtil.numberToText(cardsToDraw)) + .append(" card").append(cardsToDraw == 1?" ": "s") + .append(", then discard ") + .append(cardsToDiscard == 1?"a": CardUtil.numberToText(cardsToDiscard)) + .append(" card").append(cardsToDiscard == 1?" ": "s").toString(); } public DrawDiscardControllerEffect(final DrawDiscardControllerEffect effect) { super(effect); + this.cardsToDraw = effect.cardsToDraw; + this.cardsToDiscard = effect.cardsToDiscard; } @Override @@ -58,8 +75,8 @@ public class DrawDiscardControllerEffect extends OneShotEffect { + + private int cardsToDraw; + private int cardsToDiscard; + + public DrawDiscardTargetEffect() { + this(1,1); + } + + public DrawDiscardTargetEffect(int cardsToDraw, int cardsToDiscard) { + super(Outcome.DrawCard); + this.cardsToDraw = cardsToDraw; + this.cardsToDiscard = cardsToDiscard; + staticText = new StringBuilder("Target player draws ") + .append(cardsToDraw == 1?"a": CardUtil.numberToText(cardsToDraw)) + .append(" card").append(cardsToDraw == 1?" ": "s") + .append(", then discard ") + .append(cardsToDiscard == 1?"a": CardUtil.numberToText(cardsToDiscard)) + .append(" card").append(cardsToDiscard == 1?" ": "s").toString(); + } + + public DrawDiscardTargetEffect(final DrawDiscardTargetEffect effect) { + super(effect); + this.cardsToDraw = effect.cardsToDraw; + this.cardsToDiscard = effect.cardsToDiscard; + } + + @Override + public DrawDiscardTargetEffect copy() { + return new DrawDiscardTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player != null) { + player.drawCards(cardsToDraw, game); + player.discard(cardsToDiscard, source, game); + return true; + } + return false; + } + +} diff --git a/Mage/src/mage/abilities/effects/common/SacrificeTargetEffect.java b/Mage/src/mage/abilities/effects/common/SacrificeTargetEffect.java index 4611df4260f..353aeba46ae 100644 --- a/Mage/src/mage/abilities/effects/common/SacrificeTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/SacrificeTargetEffect.java @@ -75,7 +75,7 @@ public class SacrificeTargetEffect extends OneShotEffect @Override public String getText(Mode mode) { - if ("".equals(staticText) && !mode.getTargets().isEmpty()) { + if (staticText.isEmpty() && !mode.getTargets().isEmpty()) { if (mode.getTargets().get(0).getNumberOfTargets() == 1) { return "The controller of target " + mode.getTargets().get(0).getTargetName() + " sacrifices it"; } else { diff --git a/Mage/src/mage/abilities/effects/common/continious/GainAbilitySourceEffect.java b/Mage/src/mage/abilities/effects/common/continious/GainAbilitySourceEffect.java index a18465d7c56..978705f07ee 100644 --- a/Mage/src/mage/abilities/effects/common/continious/GainAbilitySourceEffect.java +++ b/Mage/src/mage/abilities/effects/common/continious/GainAbilitySourceEffect.java @@ -28,12 +28,12 @@ package mage.abilities.effects.common.continious; +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; import mage.constants.Duration; import mage.constants.Layer; import mage.constants.Outcome; import mage.constants.SubLayer; -import mage.abilities.Ability; -import mage.abilities.effects.ContinuousEffectImpl; import mage.game.Game; import mage.game.permanent.Permanent; @@ -73,7 +73,7 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl 0) { + int adjCost = 0; + for (UUID cardId: target.getTargets()) { + Card card = game.getCard(cardId); + if (card == null) { + continue; + } + card.moveToExile(null, null, this.getSourceId(), game); + ++adjCost; + } + game.informPlayers(new StringBuilder(player.getName()).append(" delved ") + .append(adjCost).append(" creature").append(adjCost != 1?"s":"").append(" from his or her graveyard").toString()); + CardUtil.adjustCost((SpellAbility)ability, adjCost); + } + } + } + + @Override + public String getRule() { + return "Delve (You may exile any number of cards from your graveyard as you cast this spell. It costs {1} less to cast for each card exiled this way.)"; + } +} diff --git a/Mage/src/mage/abilities/keyword/InfectAbility.java b/Mage/src/mage/abilities/keyword/InfectAbility.java index 9b60d0a33dc..05db0001047 100644 --- a/Mage/src/mage/abilities/keyword/InfectAbility.java +++ b/Mage/src/mage/abilities/keyword/InfectAbility.java @@ -75,7 +75,7 @@ public class InfectAbility extends StaticAbility implements MageS @Override public String getRule() { - return "Infect"; + return "Infect (This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters.)"; } @Override diff --git a/Mage/src/mage/abilities/keyword/UnearthAbility.java b/Mage/src/mage/abilities/keyword/UnearthAbility.java index 4d08f2ca605..59a84127fae 100644 --- a/Mage/src/mage/abilities/keyword/UnearthAbility.java +++ b/Mage/src/mage/abilities/keyword/UnearthAbility.java @@ -28,10 +28,6 @@ package mage.abilities.keyword; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.TimingRule; -import mage.constants.Zone; import mage.abilities.Ability; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.DelayedTriggeredAbility; @@ -39,8 +35,12 @@ import mage.abilities.costs.mana.ManaCosts; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.ExileSourceEffect; -import mage.abilities.effects.common.continious.GainAbilitySourceEffect; import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.abilities.effects.common.continious.GainAbilitySourceEffect; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TimingRule; +import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; @@ -49,11 +49,19 @@ import mage.game.events.ZoneChangeEvent; /** * * @author BetaSteward_at_googlemail.com + * + * + * 702.82. Unearth + * + * 702.82a Unearth is an activated ability that functions while the card with unearth + * is in a graveyard. "Unearth [cost]" means "[Cost]: Return this card from your graveyard + * to the battlefield. It gains haste. Exile it at the beginning of the next end step. + * If it would leave the battlefield, exile it instead of putting it anywhere else. + * Activate this ability only any time you could cast a sorcery." + * */ public class UnearthAbility extends ActivatedAbilityImpl { - protected boolean unearthed; - public UnearthAbility(ManaCosts costs) { super(Zone.GRAVEYARD, new ReturnSourceFromGraveyardToBattlefieldEffect(), costs); this.timing = TimingRule.SORCERY; @@ -64,7 +72,6 @@ public class UnearthAbility extends ActivatedAbilityImpl { public UnearthAbility(final UnearthAbility ability) { super(ability); - this.unearthed = ability.unearthed; } @Override @@ -72,13 +79,12 @@ public class UnearthAbility extends ActivatedAbilityImpl { return new UnearthAbility(this); } - public boolean isUnearthed() { - return unearthed; - } - @Override public String getRule() { - return "Unearth " + super.getRule(); + StringBuilder sb = new StringBuilder("Unearth ").append(this.getManaCosts().getText()); + sb.append(" (").append(this.getManaCosts().getText()); + sb.append(": Return this card from your graveyard to the battlefield. It gains haste. Exile it at the beginning of the next end step or if it would leave the battlefield. Unearth only as a sorcery.)"); + return sb.toString(); } } @@ -133,8 +139,9 @@ class UnearthLeavesBattlefieldEffect extends ReplacementEffectImpl cardDao; diff --git a/Mage/src/mage/game/Game.java b/Mage/src/mage/game/Game.java index c4f2b06bd81..df3a256a243 100644 --- a/Mage/src/mage/game/Game.java +++ b/Mage/src/mage/game/Game.java @@ -28,9 +28,6 @@ package mage.game; -import mage.constants.MultiplayerAttackOption; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; import mage.MageItem; import mage.MageObject; import mage.abilities.Ability; @@ -44,6 +41,10 @@ import mage.cards.Card; import mage.cards.Cards; import mage.cards.decks.Deck; import mage.choices.Choice; +import mage.constants.Duration; +import mage.constants.MultiplayerAttackOption; +import mage.constants.RangeOfInfluence; +import mage.constants.Zone; import mage.game.combat.Combat; import mage.game.command.Emblem; import mage.game.events.GameEvent; @@ -65,7 +66,6 @@ import mage.util.functions.ApplyToPermanent; import java.io.Serializable; import java.util.*; -import mage.constants.Duration; public interface Game extends MageItem, Serializable { @@ -225,4 +225,9 @@ public interface Game extends MageItem, Serializable { // controlling the behaviour of replacement effects void setScopeRelevant(boolean scopeRelevant); public boolean getScopeRelevant(); + + // players' timers + void initTimer(UUID playerId); + void resumeTimer(UUID playerId); + void pauseTimer(UUID playerId); } diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index f7fa2fc9758..1212595fe44 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -29,7 +29,6 @@ package mage.game; import mage.Constants; -import mage.constants.CardType; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; @@ -49,12 +48,14 @@ import mage.actions.impl.MageAction; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.cards.SplitCard; import mage.cards.decks.Deck; import mage.choices.Choice; import mage.constants.*; import mage.counters.CounterType; import mage.filter.Filter; import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterPlaneswalkerPermanent; import mage.filter.predicate.mageobject.CardTypePredicate; import mage.filter.predicate.mageobject.NamePredicate; @@ -89,8 +90,6 @@ import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.Map.Entry; -import mage.cards.SplitCard; -import mage.filter.common.FilterControlledCreaturePermanent; public abstract class GameImpl> implements Game, Serializable { @@ -590,6 +589,7 @@ public abstract class GameImpl> implements Game, Serializa protected void init(UUID choosingPlayerId, GameOptions gameOptions) { for (Player player: state.getPlayers().values()) { player.beginTurn(this); + initTimer(player.getId()); } if (startMessage == null || startMessage.isEmpty()) { startMessage = "Game has started"; @@ -1946,4 +1946,20 @@ public abstract class GameImpl> implements Game, Serializa public void setStartMessage(String startMessage) { this.startMessage = startMessage; } + + @Override + public void initTimer(UUID playerId) { + tableEventSource.fireTableEvent(EventType.INIT_TIMER, playerId, null, this); + } + + @Override + public void resumeTimer(UUID playerId) { + tableEventSource.fireTableEvent(EventType.RESUME_TIMER, playerId, null, this); + } + + @Override + public void pauseTimer(UUID playerId) { + tableEventSource.fireTableEvent(EventType.PAUSE_TIMER, playerId, null, this); + } + } diff --git a/Mage/src/mage/game/events/TableEvent.java b/Mage/src/mage/game/events/TableEvent.java index b9c9041cead..6d6637b4220 100644 --- a/Mage/src/mage/game/events/TableEvent.java +++ b/Mage/src/mage/game/events/TableEvent.java @@ -46,7 +46,8 @@ import java.util.UUID; public class TableEvent extends EventObject implements ExternalEvent, Serializable { public enum EventType { - UPDATE, INFO, STATUS, REVEAL, LOOK, START_DRAFT, START_MATCH, SIDEBOARD, CONSTRUCT, SUBMIT_DECK, END, ERROR + UPDATE, INFO, STATUS, REVEAL, LOOK, START_DRAFT, START_MATCH, SIDEBOARD, CONSTRUCT, SUBMIT_DECK, END, ERROR, + INIT_TIMER, RESUME_TIMER, PAUSE_TIMER } private Game game; diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index b755ad8cc69..45134926757 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -299,4 +299,18 @@ public interface Player extends MageItem, Copyable { * */ void revealFaceDownCard(Card card, Game game); + + /** + * Set seconds left to play the game. + * + * @return + */ + void setPriorityTimeLeft(int timeLeft); + + /** + * Returns seconds left to play the game. + * + * @return + */ + int getPriorityTimeLeft(); } diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 9ba6065ec4b..ded8742beb5 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -28,8 +28,6 @@ package mage.players; -import mage.constants.*; -import mage.constants.Zone; import mage.MageObject; import mage.Mana; import mage.abilities.*; @@ -46,10 +44,13 @@ import mage.actions.MageDrawAction; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.cards.SplitCard; import mage.cards.decks.Deck; +import mage.constants.*; import mage.counters.Counter; import mage.counters.CounterType; import mage.counters.Counters; +import mage.filter.FilterCard; import mage.filter.common.FilterCreatureForCombat; import mage.game.ExileZone; import mage.game.Game; @@ -59,11 +60,13 @@ import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.game.stack.StackAbility; import mage.game.stack.StackObject; import mage.players.net.UserData; import mage.target.Target; import mage.target.TargetAmount; +import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetDiscard; import mage.watchers.common.BloodthirstWatcher; @@ -72,11 +75,6 @@ import org.apache.log4j.Logger; import java.io.Serializable; import java.util.*; -import mage.cards.SplitCard; -import mage.filter.FilterCard; -import mage.game.stack.Spell; -import mage.target.TargetCard; - public abstract class PlayerImpl> implements Player, Serializable { @@ -108,6 +106,7 @@ public abstract class PlayerImpl> implements Player, Ser protected boolean passedTurn; protected int turns; protected int storedBookmark = -1; + protected int priorityTimeLeft; /** * This indicates that player passed all turns until his own turn starts. @@ -1793,4 +1792,14 @@ public abstract class PlayerImpl> implements Player, Ser this.revealCards(name, cards, game); } } + + @Override + public void setPriorityTimeLeft(int timeLeft) { + priorityTimeLeft = timeLeft; + } + + @Override + public int getPriorityTimeLeft() { + return priorityTimeLeft; + } } diff --git a/Mage/src/mage/util/CardUtil.java b/Mage/src/mage/util/CardUtil.java index 2a6c6468952..96f3cbca77c 100644 --- a/Mage/src/mage/util/CardUtil.java +++ b/Mage/src/mage/util/CardUtil.java @@ -29,6 +29,7 @@ package mage.util; import java.util.Iterator; +import java.util.UUID; import mage.Mana; import mage.abilities.Ability; @@ -43,6 +44,7 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.VariableManaCost; import mage.cards.Card; import mage.constants.CardType; +import mage.game.Game; import mage.game.permanent.token.Token; import mage.util.functions.CopyFunction; import mage.util.functions.CopyTokenFunction; @@ -365,4 +367,25 @@ public class CardUtil { } return true; } + + /** + * Creates and saves a (card + zoneChangeCounter) specific exileId. + * + * + * @param game + * @param source - source ability + * @return - the specific UUID + */ + public static UUID getCardExileZoneId(Game game, Ability source) { + UUID exileId = null; + Card card = game.getCard(source.getSourceId()); + if (card != null) { + exileId = (UUID) game.getState().getValue(new StringBuilder("SourceExileZone").append(source.getSourceId()).append(card.getZoneChangeCounter()).toString()); + if (exileId == null) { + exileId = UUID.randomUUID(); + game.getState().setValue(new StringBuilder("SourceExileZone").append(source.getSourceId()).append(card.getZoneChangeCounter()).toString(), exileId); + } + } + return exileId; + } }