diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index b7ae78ced5c..3d0a2d5a660 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -691,8 +691,10 @@ public class DeckEditorPanel extends javax.swing.JPanel { break; case 1: btnImportFromClipboardActionPerformed(evt); + break; case 2: btnImportFromClipboardActionWAppendPerformed(evt); + break; } }); @@ -803,6 +805,14 @@ public class DeckEditorPanel extends javax.swing.JPanel { .addComponent(jSplitPane1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 615, Short.MAX_VALUE)); } + private void processAndShowImportErrors(StringBuilder errorMessages){ + // show up errors list + if (errorMessages.length() > 0){ + String mes = "Founded problems with deck: \n\n" + errorMessages.toString(); + JOptionPane.showMessageDialog(MageFrame.getDesktop(), mes.substring(0, Math.min(1000, mes.length())), "Errors while loading deck", JOptionPane.WARNING_MESSAGE); + } + } + /** * @param evt ActionEvent */ @@ -814,11 +824,23 @@ public class DeckEditorPanel extends javax.swing.JPanel { dialog.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { + Deck newDeck = null; + StringBuilder errorMessages = new StringBuilder(); + + MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR)); try { - deck = Deck.load(DeckImporterUtil.importDeck(dialog.getTmpPath()), true, true); - refreshDeck(); + newDeck = Deck.load(DeckImporterUtil.importDeck(dialog.getTmpPath(), errorMessages), true, true); + processAndShowImportErrors(errorMessages); + + if (newDeck != null) { + deck = newDeck; + refreshDeck(); + } + } catch (GameException e1) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE); + }finally { + MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } } }); @@ -836,16 +858,22 @@ public class DeckEditorPanel extends javax.swing.JPanel { @Override public void windowClosed(WindowEvent e) { Deck deckToAppend = null; + StringBuilder errorMessages = new StringBuilder(); + + MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR)); try { - deckToAppend = Deck.load(DeckImporterUtil.importDeck(dialog.getTmpPath()), true, true); + deckToAppend = Deck.load(DeckImporterUtil.importDeck(dialog.getTmpPath(), errorMessages), true, true); + processAndShowImportErrors(errorMessages); + if (deckToAppend != null) { deck = Deck.append(deckToAppend, deck); refreshDeck(); } } catch (GameException e1) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE); + }finally { + MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } - } }); } @@ -875,20 +903,31 @@ public class DeckEditorPanel extends javax.swing.JPanel { } } } + + MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR)); try { - setCursor(new Cursor(Cursor.WAIT_CURSOR)); - deck = Deck.load(DeckImporterUtil.importDeck(file.getPath()), true, true); + Deck newDeck = null; + StringBuilder errorMessages = new StringBuilder(); + + newDeck = Deck.load(DeckImporterUtil.importDeck(file.getPath(), errorMessages), true, true); + processAndShowImportErrors(errorMessages); + + if (newDeck != null) { + deck = newDeck; + refreshDeck(true); + } + + // save last deck history + try { + MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath()); + } catch (IOException ex) { + logger.error("Error on save last load deck folder: " + ex.getMessage()); + } + } catch (GameException ex) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE); } finally { - setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); - } - refreshDeck(true); - try { - if (file != null) { - MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath()); - } - } catch (IOException ex) { + MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } } fcSelectDeck.setSelectedFile(null); @@ -924,7 +963,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { if (!fileName.endsWith(".dck")) { fileName += ".dck"; } - setCursor(new Cursor(Cursor.WAIT_CURSOR)); + MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR)); DeckCardLists cardLists = deck.getDeckCardLists(); cardLists.setCardLayout(deckArea.getCardLayout()); cardLists.setSideboardLayout(deckArea.getSideboardLayout()); @@ -932,7 +971,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { } catch (FileNotFoundException ex) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage() + "\nTry ensuring that the selected directory is writable.", "Error saving deck", JOptionPane.ERROR_MESSAGE); } finally { - setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } try { MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath()); @@ -967,29 +1006,36 @@ public class DeckEditorPanel extends javax.swing.JPanel { int ret = fcImportDeck.showOpenDialog(this); if (ret == JFileChooser.APPROVE_OPTION) { File file = fcImportDeck.getSelectedFile(); + MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR)); try { - setCursor(new Cursor(Cursor.WAIT_CURSOR)); DeckImporter importer = DeckImporterUtil.getDeckImporter(file.getPath()); + if (importer != null) { - deck = Deck.load(importer.importDeck(file.getPath())); - String errors = importer.getErrors(); - if (!errors.isEmpty()) { - JOptionPane.showMessageDialog(MageFrame.getDesktop(), errors, "Error importing deck", JOptionPane.ERROR_MESSAGE); + StringBuilder errorMessages = new StringBuilder(); + Deck newDeck = null; + + newDeck = Deck.load(importer.importDeck(file.getPath(), errorMessages)); + processAndShowImportErrors(errorMessages); + + if (newDeck != null) { + deck = newDeck; + refreshDeck(); } + + // save last deck import folder + try { + MageFrame.getPreferences().put("lastImportFolder", file.getCanonicalPath()); + } catch (IOException ex) { + logger.error("Error on save last used import folder: " + ex.getMessage()); + } + } else { JOptionPane.showMessageDialog(MageFrame.getDesktop(), "Unknown deck format", "Error importing deck", JOptionPane.ERROR_MESSAGE); } } catch (Exception ex) { logger.fatal(ex); } finally { - setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); - } - refreshDeck(); - try { - if (file != null) { - MageFrame.getPreferences().put("lastImportFolder", file.getCanonicalPath()); - } - } catch (IOException ex) { + MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } } fcImportDeck.setSelectedFile(null); @@ -1031,7 +1077,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { private void btnGenDeckActionPerformed(ActionEvent evt) { try { - setCursor(new Cursor(Cursor.WAIT_CURSOR)); + MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR)); String path = DeckGenerator.generateDeck(); deck = Deck.load(DeckImporterUtil.importDeck(path), true, true); } catch (GameException ex) { @@ -1039,7 +1085,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { } catch (DeckGeneratorException ex) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Generator error", JOptionPane.ERROR_MESSAGE); } finally { - setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } refreshDeck(); } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java index 2dd577bd08b..623b5a434d5 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/collection/viewer/MageBook.java @@ -464,7 +464,7 @@ public class MageBook extends JComponent { // first run for numbers list LinkedList haveNumbers = new LinkedList<>(); for (ExpansionSet.SetCardInfo card: cards){ - int cardNumber = Integer.parseInt(card.getCardNumber()); + int cardNumber = card.getCardNumberAsInt(); startNumber = min(startNumber, cardNumber); endNumber = Math.max(endNumber, cardNumber); haveNumbers.add(cardNumber); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index fce453e0ef9..a6903e42a1f 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -291,11 +291,8 @@ public final class ManaSymbols { }; t.setTranscodingHints(transcoderHints); t.transcode(input, null); - } - catch (TranscoderException ex) { - // Requires Java 6 - ex.printStackTrace(); - throw new IOException("Couldn't convert " + svgFile); + } catch (Exception e) { + throw new IOException("Couldn't convert svg file: " + svgFile + " , reason: " + e.getMessage()); } finally { cssFile.delete(); @@ -348,7 +345,7 @@ public final class ManaSymbols { //imagePointer[0]; } - private static File getSymbolFileNameAsSVG(String symbol){ + public static File getSymbolFileNameAsSVG(String symbol){ return new File(getResourceSymbolsPath(ResourceSymbolSize.SVG) + symbol + ".svg"); } @@ -364,7 +361,7 @@ public final class ManaSymbols { return loadSVG(sourceFile, resizeToWidth, resizeToHeight, true); } catch (Exception e) { - LOGGER.error("Can't load svg symbol: " + sourceFile.getPath()); + LOGGER.error("Can't load svg symbol: " + sourceFile.getPath() + " , reason: " + e.getMessage()); return null; } } @@ -724,14 +721,18 @@ public final class ManaSymbols { // not need to add different images (width and height do the work) // use best png size (generated on startup) TODO: add reload images after update String htmlImagesPath = getResourceSymbolsPath(ResourceSymbolSize.PNG); + htmlImagesPath = htmlImagesPath + .replace("$", "@S@"); // paths with $ will rise error, need escape that replaced = REPLACE_SYMBOLS_PATTERN.matcher(replaced).replaceAll( "$1$2 it = new GathererSymbols(); + Iterable it; + + it = new GathererSymbols(); for (DownloadJob job : it) { g.getDownloader().add(job); } @@ -539,6 +542,11 @@ public class CardPluginImpl implements CardPlugin { g.getDownloader().add(job); } + it = new ScryfallSymbolsSource(); + for (DownloadJob job : it) { + g.getDownloader().add(job); + } + /* it = new CardFrames(imagesDir); // TODO: delete frames download (not need now) for (DownloadJob job : it) { @@ -551,7 +559,7 @@ public class CardPluginImpl implements CardPlugin { g.getDownloader().add(job); } - JDialog d = new JDialog((Frame) null, "Download pictures", false); + JDialog d = new JDialog((Frame) null, "Download symbols", false); d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); d.addWindowListener(new WindowAdapter() { @Override diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java index 8d189b5796b..6efcfd3eef4 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java @@ -28,7 +28,6 @@ import org.mage.plugins.card.utils.CardImageUtils; public class DownloadJob extends AbstractLaternaBean { public enum State { - NEW, WORKING, FINISHED, ABORTED } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java index 8406c4e5415..1d01f9f7707 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java @@ -220,9 +220,10 @@ public class GathererSets implements Iterable { continue; // can't do other checks } - // 2. missing rarity icon: - // WARNING, need too much time (60+ secs), only for debug mode if (logger.isDebugEnabled()) { + // 2. missing rarity icon: + // WARNING, need too much time (60+ secs), only for debug mode + ///* if ((set.getCardsByRarity(Rarity.COMMON).size() > 0) && !res.haveCommon) { logger.error(String.format("Symbols: set have common cards, but don't download icon: %s (%s)", set.getCode(), set.getName())); } @@ -235,6 +236,29 @@ public class GathererSets implements Iterable { if ((set.getCardsByRarity(Rarity.MYTHIC).size() > 0) && !res.haveMyth) { logger.error(String.format("Symbols: set have mythic cards, but don't download icon: %s (%s)", set.getCode(), set.getName())); } + //*/ + + // 3. info: sets with alternative numbers + for(ExpansionSet.SetCardInfo card: set.getSetCardInfo()){ + if (String.valueOf(card.getCardNumberAsInt()).length() != card.getCardNumber().length()){ + logger.info(String.format("Symbols: set have alternative card but do not config to it: %s (%s)", set.getCode(), set.getName())); + break; + } + } + + // 4. info: sets with missing cards for boosters (todo: what about +20 number for alternative land arts?) + if (set.getMaxCardNumberInBooster() != Integer.MAX_VALUE) + { + for(ExpansionSet.SetCardInfo card: set.getSetCardInfo()){ + if (card.getCardNumberAsInt() > set.getMaxCardNumberInBooster()){ + if (card.getRarity() == Rarity.LAND) { + logger.info(String.format("Symbols: set's booster have land above max card number: %s (%s), %s - %s", set.getCode(), set.getName(), card.getCardNumber(), card.getName())); + }else { + logger.info(String.format("Symbols: set's booster missing nonland card:: %s (%s), %s - %s", set.getCode(), set.getName(), card.getCardNumber(), card.getName())); + } + } + } + } } } @@ -313,4 +337,4 @@ public class GathererSets implements Iterable { String url = "http://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + urlRarity; return new DownloadJob(set + '-' + rarity, fromURL(url), toFile(dst)); } -} +} \ No newline at end of file diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MythicspoilerComSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MythicspoilerComSource.java index 03ba31aac40..d29a7d113d8 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MythicspoilerComSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MythicspoilerComSource.java @@ -253,11 +253,13 @@ public enum MythicspoilerComSource implements CardImageSource { supportedSets.add("C17"); supportedSets.add("IMA"); supportedSets.add("XLN"); + supportedSets.add("RIX"); sets = new LinkedHashMap<>(); setsAliases = new HashMap<>(); setsAliases.put("exp", "bfz"); setsAliases.put("xln", "ixa"); + setsAliases.put("nem", "nms"); cardNameAliases = new HashMap<>(); // set+wrong name from web side => correct card name cardNameAliases.put("MM2-otherwordlyjourney", "otherworldlyjourney"); @@ -294,6 +296,10 @@ public enum MythicspoilerComSource implements CardImageSource { links.put("spitfirebastion", "spitfirebastion"); manualLinks.put("XLN", links); + HashMap linksRix = new HashMap<>(); + linksRix.put("vaultofcatlacan", "vaultofcatlacan"); + manualLinks.put("RIX", linksRix); + cardNameAliasesStart = new HashMap<>(); HashSet names = new HashSet<>(); names.add("eldrazidevastator.jpg"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java index b95cc3fefd3..111e4480a3f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java @@ -204,8 +204,8 @@ public enum ScryfallImageSource implements CardImageSource { supportedSets.add("IMA"); // supportedSets.add("E02"); // supportedSets.add("V17"); -// supportedSets.add("UST"); -// supportedSets.add("RIX"); + supportedSets.add("UST"); + supportedSets.add("RIX"); // supportedSets.add("A25"); // supportedSets.add("DOM"); // supportedSets.add("M19"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java new file mode 100644 index 00000000000..caef7e5d211 --- /dev/null +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallSymbolsSource.java @@ -0,0 +1,171 @@ +package org.mage.plugins.card.dl.sources; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.mage.plugins.card.dl.DownloadJob; + +import static org.mage.card.arcane.ManaSymbols.getSymbolFileNameAsSVG; +import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; + +// TODO: add force to download symbols (rewrite exist files) + +/** + * + * @author jaydi85@gmail.com + * + */ +public class ScryfallSymbolsSource implements Iterable { + + + static final String SOURCE_URL = "https://assets.scryfall.com/assets/scryfall.css"; // search css-file on https://scryfall.com/docs/api/colors + static final String STATE_PROP_NAME = "state"; + static final String DOWNLOAD_TEMP_FILE = getImagesDir() + File.separator + "temp" + File.separator + "scryfall-symbols-source.txt"; + + // card-symbol-(.{1,10}){background-image.+base64,(.+)("\)}) + // see https://regex101.com/ + static final String REGEXP_MANA_PATTERN = "card-symbol-(.{1,10})\\{background-image.+base64,(.+)(\"\\)\\})"; + + + protected static final org.apache.log4j.Logger LOGGER = org.apache.log4j.Logger.getLogger(ScryfallSymbolsSource.class); + + private static final int SYMBOLS_NUMBER_START = 0; + private static final int SYMBOLS_NUMBER_END = 20; + // copy-past symbols list from gatherer download + private static final String[] SYMBOLS_LIST = {"W", "U", "B", "R", "G", + "W/U", "U/B", "B/R", "R/G", "G/W", "W/B", "U/R", "B/G", "R/W", "G/U", + "2/W", "2/U", "2/B", "2/R", "2/G", + "WP", "UP", "BP", "RP", "GP", + "X", "S", "T", "Q", "C", "E"}; + + @Override + public Iterator iterator() { + ArrayList jobs = new ArrayList<>(); + + // all symbols on one page + jobs.add(generateDownloadJob()); + + return jobs.iterator(); + } + + private void parseData(String sourcePath){ + + String sourceData = ""; + try { + sourceData = new String(Files.readAllBytes(Paths.get(sourcePath))); + }catch (IOException e) { + LOGGER.error("Can't open file to parse data: " + sourcePath + " , reason: " + e.getMessage()); + } + + // gen symbols list + ArrayList allMageSymbols = new ArrayList<>(); + for(int i = 0; i < SYMBOLS_LIST.length; i++){ + allMageSymbols.add(SYMBOLS_LIST[i]); + } + for(Integer i = SYMBOLS_NUMBER_START; i <= SYMBOLS_NUMBER_END; i++){ + allMageSymbols.add(String.valueOf(SYMBOLS_NUMBER_START + i)); + } + + Map foundedData = new HashMap<>(); + + // search raw data + sourceData = sourceData.replaceAll(".card-symbol", "\n.card-symbol"); // css as one line, but need multiline + Pattern regex = Pattern.compile(REGEXP_MANA_PATTERN); + Matcher regexMatcher = regex.matcher(sourceData); + while (regexMatcher.find()) { + String symbolCode = regexMatcher.group(1).trim(); + String symbolData = regexMatcher.group(2).trim().replace(" ", "").replaceAll("\n", ""); // decoder need only wrapped text as one line + + foundedData.put(symbolCode, symbolData); + } + + // dirs maker + File dir = getSymbolFileNameAsSVG("W").getParentFile(); + if(!dir.exists()){ + dir.mkdirs(); + } + + // decode and save data (only if not exist) + for(String needCode: allMageSymbols){ + + String searchCode = needCode.replace("/", ""); + + if(!foundedData.containsKey(searchCode)) + { + LOGGER.warn("Can't found symbol code from scryfall: " + searchCode); + continue; + } + + File destFile = getSymbolFileNameAsSVG(searchCode); + if (destFile.exists() && (destFile.length() > 0)){ + continue; + } + + try { + // base64 transform + String data64 = foundedData.get(searchCode); + Base64.Decoder dec = Base64.getDecoder(); + byte[] fileData = dec.decode(data64); + + FileOutputStream stream = new FileOutputStream(destFile); + stream.write(fileData); + stream.close(); + + LOGGER.info("New svg symbol downloaded: " + needCode); + } catch (Exception e) { + LOGGER.error("Can't decode svg icon and save to file: " + destFile.getPath() + ", reason: " + e.getMessage()); + } + } + } + + + private class ScryfallSymbolsDownloadJob extends DownloadJob{ + + // listener for data parse after download complete + private class ScryDownloadOnFinishedListener implements PropertyChangeListener { + private String downloadedFile; + + public ScryDownloadOnFinishedListener(String ADestFile){ + this.downloadedFile = ADestFile; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (!evt.getPropertyName().equals(STATE_PROP_NAME)){ + throw new IllegalArgumentException("Unknown download property " + evt.getPropertyName()); + } + + if (evt.getNewValue() != State.FINISHED){ + return; + } + + // parse data and save to dest + parseData(this.downloadedFile); + } + } + + private String destFile = ""; + + public ScryfallSymbolsDownloadJob() { + super("Scryfall symbols source", fromURL(SOURCE_URL), toFile(DOWNLOAD_TEMP_FILE)); + this.destFile = DOWNLOAD_TEMP_FILE; + this.addPropertyChangeListener(STATE_PROP_NAME, new ScryDownloadOnFinishedListener(this.destFile)); + + // clear dest file (always download new data) + File file = new File(this.destFile); + if (file.exists()){ + file.delete(); + } + } + } + + private DownloadJob generateDownloadJob() { + return new ScryfallSymbolsDownloadJob(); + } +} 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 59a6be5bc5a..02ba9bb2a01 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 @@ -43,6 +43,8 @@ import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; public class DownloadPictures extends DefaultBoundedRangeModel implements Runnable { + // don't forget to remove new sets from ignore.urls to download (propeties file in resources) + private static DownloadPictures instance; private static final Logger logger = Logger.getLogger(DownloadPictures.class); @@ -772,6 +774,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab // START to download cardImageSource.doPause(url.getPath()); URLConnection httpConn = url.openConnection(p); + httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); httpConn.connect(); int responseCode = ((HttpURLConnection) httpConn).getResponseCode(); 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 919ad545d3a..e89c4158800 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 @@ -237,7 +237,7 @@ public final class CardImageUtils { } public static String generateFaceImagePath(String cardname, String set) { - return getImagesDir() + File.separator + "FACE" + File.separator + set + File.separator + prepareCardNameForFile(cardname) + File.separator + ".jpg"; + return getImagesDir() + File.separator + "FACE" + File.separator + set + File.separator + prepareCardNameForFile(cardname) + ".jpg"; } public static String generateTokenDescriptorImagePath(CardDownloadData card) { diff --git a/Mage.Client/src/main/resources/card-pictures-tok.txt b/Mage.Client/src/main/resources/card-pictures-tok.txt index 2bad012c0a2..7cd0c76905e 100644 --- a/Mage.Client/src/main/resources/card-pictures-tok.txt +++ b/Mage.Client/src/main/resources/card-pictures-tok.txt @@ -1062,6 +1062,7 @@ |Generate|TOK:USG|Minion|||MinionToken| |Generate|TOK:USG|Saproling|||SaprolingToken| |Generate|TOK:UST|Dragon|||DragonTokenGold| +|Generate|TOK:UST|StormCrow|||StormCrowToken| |Generate|TOK:V10|Wolf|||WolfToken| |Generate|TOK:V11|Faerie Rogue|||OonaQueenFaerieToken| |Generate|TOK:V12|Spirit|||SpiritToken| diff --git a/Mage.Client/src/main/resources/image.url.properties b/Mage.Client/src/main/resources/image.url.properties index 0ded050d87f..620a028344e 100644 --- a/Mage.Client/src/main/resources/image.url.properties +++ b/Mage.Client/src/main/resources/image.url.properties @@ -29,7 +29,6 @@ ulg=ul 6ed=6e btd=bd sth=sh -nem=ne por=po s99=st lgn=le @@ -74,6 +73,6 @@ dd3evg=ddaevg dd3gvl=ddagvl dd3jvc=ddajvc # Remove setname as soon as the images can be downloaded -ignore.urls=TOK,DDT,V17,RIX,E02,M19,M25,DOM,UST,H17 +ignore.urls=TOK,DDT,V17,E02,M19,M25,DOM,H17 # sets ordered by release time (newest goes first) token.lookup.order=M19,M25,DOM,E02,RIX,UST,XLN,IMA,H17,C17,V17,E01,DDT,CMA,HOU,MM3,DDS,AKH,DD3DVD,DD3EVG,DD3GVL,DD3JVC,H09,AER,PCA,C16,V16,MPS,KLD,DDR,CN2,EMN,EMA,SOI,DDQ,CP,CMA,ARENA,SUS,APAC,EURO,UGIN,C15,OGW,EXP,DDP,BFZ,DRB,V09,V10,V11,V12,V13,V14,V15,TPR,MPRP,DD3,DDO,ORI,MM2,PTC,DTK,FRF,KTK,M15,VMA,CNS,JOU,BNG,THS,DDL,M14,MMA,DGM,GTC,RTR,M13,AVR,DDI,DKA,ISD,M12,NPH,MBS,SOM,M11,ROE,DDE,WWK,ZEN,M10,GVL,ARB,DVD,CFX,JVC,ALA,EVE,SHM,EVG,MOR,LRW,10E,CLS,CHK,GRC \ No newline at end of file diff --git a/Mage.Common/src/main/java/mage/utils/MageVersion.java b/Mage.Common/src/main/java/mage/utils/MageVersion.java index 4780e89977c..2ef3e3430b0 100644 --- a/Mage.Common/src/main/java/mage/utils/MageVersion.java +++ b/Mage.Common/src/main/java/mage/utils/MageVersion.java @@ -41,7 +41,7 @@ public class MageVersion implements Serializable, Comparable { public final static int MAGE_VERSION_MAJOR = 1; public final static int MAGE_VERSION_MINOR = 4; public final static int MAGE_VERSION_PATCH = 26; - public final static String MAGE_VERSION_MINOR_PATCH = "V9b"; + public final static String MAGE_VERSION_MINOR_PATCH = "V10b"; public final static String MAGE_VERSION_INFO = ""; private final int major; diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AusHighlander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AusHighlander.java index aad84f38392..caae0aed3fc 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AusHighlander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/AusHighlander.java @@ -114,7 +114,39 @@ public class AusHighlander extends Constructed { int totalPoints = 0; for (Map.Entry entry : counts.entrySet()) { String cn = entry.getKey(); - + if (cn.equals("Ancestral Recall") + || cn.equals("Black Lotus") + || cn.equals("Time Vault")) { + totalPoints += 4; + invalid.put(cn, "4 points"); + } + if (cn.equals("Demonic Tutor") + || cn.equals("Imperial Seal") + || cn.equals("Mox Emerald") + || cn.equals("Mox Jet") + || cn.equals("Mox Pearl") + || cn.equals("Mox Ruby") + || cn.equals("Sol Ring") + || cn.equals("Time Walk") + || cn.equals("Tinker") + || cn.equals("Vampiric Tutor") + || cn.equals("Yawgmoth's Will") + || cn.equals("Mox Sapphire.")) { + totalPoints += 3; + invalid.put(cn, "3 points"); + } + if (cn.equals("Channel") + || cn.equals("Dig Through Time") + || cn.equals("Library of Alexandria") + || cn.equals("Mana Crypt") + || cn.equals("Mystical Tutor") + || cn.equals("Protean Hulk") + || cn.equals("Skullclamp") + || cn.equals("Strip Mine") + || cn.equals("Tolarian Academy.")) { + totalPoints += 2; + invalid.put(cn, "2 points"); + } if (cn.equals("Back to Basics") || cn.equals("Balance") || cn.equals("Birthing Pod") @@ -139,8 +171,8 @@ public class AusHighlander extends Constructed { || cn.equals("Natural Order") || cn.equals("Oath of Druids") || cn.equals("Personal Tutor") + || cn.equals("Sensei's Divining Top") || cn.equals("Snapcaster Mage") - || cn.equals("Steelshaper's Gift") || cn.equals("Stoneforge Mystic") || cn.equals("Survival of the Fittest") || cn.equals("Tainted Pact") @@ -151,50 +183,15 @@ public class AusHighlander extends Constructed { || cn.equals("Umezawa's Jitte") || cn.equals("Wasteland") || cn.equals("Wheel of Fortune") - || cn.equals("Worldly Tutor") - || cn.equals("Yawgmoth's Bargain")) { + || cn.equals("Yawgmoth's Bargain") + || cn.equals("Worldly Tutor")) { totalPoints += 1; invalid.put(cn, "1 point"); } - - if (cn.equals("Channel") - || cn.equals("Dig Through Time") - || cn.equals("Library of Alexandria") - || cn.equals("Mana Crypt") - || cn.equals("Mox Emerald") - || cn.equals("Mox Jet") - || cn.equals("Mox Pearl") - || cn.equals("Mox Ruby") - || cn.equals("Mox Sapphire") - || cn.equals("Mystical Tutor") - || cn.equals("Protean Hulk") - || cn.equals("Skullclamp") - || cn.equals("Strip Mine") - || cn.equals("Tolarian Academy")) { - totalPoints += 2; - invalid.put(cn, "2 points"); - } - - if (cn.equals("Demonic Tutor") - || cn.equals("Imperial Seal") - || cn.equals("Sol Ring") - || cn.equals("Time Walk") - || cn.equals("Tinker") - || cn.equals("Vampiric Tutor") - || cn.equals("Yawgmoth's Will")) { - totalPoints += 3; - invalid.put(cn, "3 points"); - } - - if (cn.equals("Ancestral Recall") - || cn.equals("Black Lotus") - || cn.equals("Time Vault")) { - totalPoints += 4; - invalid.put(cn, "4 points"); - } } if (totalPoints > 7) { invalid.put("Total points too high", "Your calculated point total was " + totalPoints); + invalid.put("Only you can see this!", "Your opponents will not be able to see this message or what cards are in your deck!"); valid = false; } return valid; diff --git a/Mage.Sets/src/mage/cards/a/AmateurAuteur.java b/Mage.Sets/src/mage/cards/a/AmateurAuteur.java new file mode 100644 index 00000000000..12968ab7497 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AmateurAuteur.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.cards.a; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.target.common.TargetEnchantmentPermanent; + +/** + * + * @author TheElk801 + */ +public class AmateurAuteur extends CardImpl { + + public AmateurAuteur(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Sacrifice Amateur Auteur: Destroy target enchantment. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), new SacrificeSourceCost()); + ability.addTarget(new TargetEnchantmentPermanent()); + this.addAbility(ability); + } + + public AmateurAuteur(final AmateurAuteur card) { + super(card); + } + + @Override + public AmateurAuteur copy() { + return new AmateurAuteur(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ApocalypseDemon.java b/Mage.Sets/src/mage/cards/a/ApocalypseDemon.java index 889d8d735c4..119c3cba427 100644 --- a/Mage.Sets/src/mage/cards/a/ApocalypseDemon.java +++ b/Mage.Sets/src/mage/cards/a/ApocalypseDemon.java @@ -39,7 +39,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.mageobject.CardTypePredicate; import mage.filter.predicate.permanent.AnotherPredicate; import mage.target.common.TargetControlledPermanent; @@ -52,7 +51,6 @@ public class ApocalypseDemon extends CardImpl { private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another creature"); static { - filter.add(new CardTypePredicate(CardType.CREATURE)); filter.add(new AnotherPredicate()); } @@ -76,6 +74,7 @@ public class ApocalypseDemon extends CardImpl { super(card); } + @Override public ApocalypseDemon copy() { return new ApocalypseDemon(this); } diff --git a/Mage.Sets/src/mage/cards/a/AsLuckWouldHaveIt.java b/Mage.Sets/src/mage/cards/a/AsLuckWouldHaveIt.java new file mode 100644 index 00000000000..1a5c89a8c76 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AsLuckWouldHaveIt.java @@ -0,0 +1,150 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.a; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.Counter; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class AsLuckWouldHaveIt extends CardImpl { + + static final String rule = "put a number of luck counters on {this} equal to the result. Then if there are 100 or more luck counters on {this}, you win the game."; + + public AsLuckWouldHaveIt(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}"); + + // Hexproof + this.addAbility(HexproofAbility.getInstance()); + + // Whenever you roll a die, put a number of luck counters on As Luck Would Have It equal to the result. Then if there are 100 or more luck counters on As Luck Would Have It, you win the game. + this.addAbility(new AsLuckWouldHaveItTriggeredAbility()); + } + + public AsLuckWouldHaveIt(final AsLuckWouldHaveIt card) { + super(card); + } + + @Override + public AsLuckWouldHaveIt copy() { + return new AsLuckWouldHaveIt(this); + } +} + +class AsLuckWouldHaveItTriggeredAbility extends TriggeredAbilityImpl { + + public AsLuckWouldHaveItTriggeredAbility() { + super(Zone.BATTLEFIELD, new AsLuckWouldHaveItEffect(), false); + } + + public AsLuckWouldHaveItTriggeredAbility(final AsLuckWouldHaveItTriggeredAbility ability) { + super(ability); + } + + @Override + public AsLuckWouldHaveItTriggeredAbility copy() { + return new AsLuckWouldHaveItTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DICE_ROLLED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (this.getControllerId().equals(event.getPlayerId()) && event.getFlag()) { + for (Effect effect : this.getEffects()) { + effect.setValue("rolled", event.getAmount()); + } + return true; + } + return false; + } + + @Override + public String getRule() { + return "Whenever you roll a die, " + super.getRule(); + } +} + +class AsLuckWouldHaveItEffect extends OneShotEffect { + + public AsLuckWouldHaveItEffect() { + super(Outcome.Benefit); + this.staticText = "put a number of luck counters on {this} equal to the result. Then if there are 100 or more luck counters on {this}, you win the game."; + } + + public AsLuckWouldHaveItEffect(final AsLuckWouldHaveItEffect effect) { + super(effect); + } + + @Override + public AsLuckWouldHaveItEffect copy() { + return new AsLuckWouldHaveItEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (controller != null && permanent != null) { + if (getValue("rolled") != null) { + int amount = (Integer) getValue("rolled"); + permanent.addCounters(new Counter("luck", amount), source, game); + + if (permanent.getCounters(game).getCount("luck") >= 100) { + Player player = game.getPlayer(permanent.getControllerId()); + if (player != null) { + player.won(game); + } + } + + return true; + } + } + return false; + + } +} diff --git a/Mage.Sets/src/mage/cards/b/BoxOfFreerangeGoblins.java b/Mage.Sets/src/mage/cards/b/BoxOfFreerangeGoblins.java new file mode 100644 index 00000000000..16771460577 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BoxOfFreerangeGoblins.java @@ -0,0 +1,92 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.token.GoblinToken; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class BoxOfFreerangeGoblins extends CardImpl { + + public BoxOfFreerangeGoblins(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{R}{R}"); + + // Roll a six-sided die. Create a number of 1/1 red Goblin creature tokens equal to the result. + this.getSpellAbility().addEffect(new BoxOfFreerangeGoblinsEffect()); + } + + public BoxOfFreerangeGoblins(final BoxOfFreerangeGoblins card) { + super(card); + } + + @Override + public BoxOfFreerangeGoblins copy() { + return new BoxOfFreerangeGoblins(this); + } +} + +class BoxOfFreerangeGoblinsEffect extends OneShotEffect { + + public BoxOfFreerangeGoblinsEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "Roll a six-sided die. Create a number of 1/1 red Goblin creature tokens equal to the result"; + } + + public BoxOfFreerangeGoblinsEffect(final BoxOfFreerangeGoblinsEffect effect) { + super(effect); + } + + @Override + public BoxOfFreerangeGoblinsEffect copy() { + return new BoxOfFreerangeGoblinsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int amount = controller.rollDice(game, 6); + CreateTokenEffect effect = new CreateTokenEffect(new GoblinToken(), amount); + effect.apply(game, source); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BuzzingWhackADoodle.java b/Mage.Sets/src/mage/cards/b/BuzzingWhackADoodle.java new file mode 100644 index 00000000000..45263884e9b --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BuzzingWhackADoodle.java @@ -0,0 +1,217 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.Set; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.IntCompareCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPlayer; +import mage.target.common.TargetOpponent; + +/** + * + * @author spjspj + */ +public class BuzzingWhackADoodle extends CardImpl { + + public BuzzingWhackADoodle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + // As Buzzing Whack-a-Doodle enters the battlefield, you and an opponent each secretly choose Whack or Doodle. Then those choices are revealed. If the choices match, Buzzing Whack-a-Doodle has that ability. Otherwise it has Buzz. + this.addAbility(new EntersBattlefieldTriggeredAbility(new BuzzingWhackADoodleEffect(), false)); + + // *Whack - T: Target player loses 2 life. + Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new LoseLifeTargetEffect(2), new TapSourceCost(), new WhackCondition()); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + + // *Doodle - T: You gain 3 life. + Ability ability2 = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new GainLifeEffect(3), new TapSourceCost(), new DoodleCondition()); + this.addAbility(ability2); + + // *Buzz - 2, T: Draw a card. + Ability ability3 = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new ManaCostsImpl("{2}"), new BuzzCondition()); + ability3.addCost(new TapSourceCost()); + this.addAbility(ability3); + } + + public BuzzingWhackADoodle(final BuzzingWhackADoodle card) { + super(card); + } + + @Override + public BuzzingWhackADoodle copy() { + return new BuzzingWhackADoodle(this); + } +} + +class BuzzingWhackADoodleEffect extends OneShotEffect { + + BuzzingWhackADoodleEffect() { + super(Outcome.Benefit); + this.staticText = "You and an opponent each secretly choose Whack or Doodle. Then those choices are revealed. If the choices match, {this} has that ability. Otherwise it has Buzz"; + } + + BuzzingWhackADoodleEffect(final BuzzingWhackADoodleEffect effect) { + super(effect); + } + + @Override + public BuzzingWhackADoodleEffect copy() { + return new BuzzingWhackADoodleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int whackCount = 0; + int doodleCount = 0; + + if (controller.chooseUse(Outcome.Benefit, "Choose Whack (yes) or Doodle (no)?", source, game)) { + whackCount++; + } else { + doodleCount++; + } + + Set opponents = game.getOpponents(source.getControllerId()); + if (!opponents.isEmpty()) { + Player opponent = game.getPlayer(opponents.iterator().next()); + if (opponents.size() > 1) { + Target targetOpponent = new TargetOpponent(true); + if (controller.chooseTarget(Outcome.Neutral, targetOpponent, source, game)) { + opponent = game.getPlayer(targetOpponent.getFirstTarget()); + game.informPlayers(controller.getLogName() + " chose " + opponent.getLogName() + " to choose Whack or Doodle"); + } + } + + if (opponent != null) { + if (opponent.chooseUse(Outcome.Benefit, "Choose Whack (yes) or Doodle (no)?", source, game)) { + whackCount++; + } else { + doodleCount++; + } + } + } + + if (whackCount == 2) { + game.informPlayers("Whack was chosen"); + game.getState().setValue("whack" + source.getSourceId(), Boolean.TRUE); + } else if (doodleCount == 2) { + game.informPlayers("Doodle was chosen"); + game.getState().setValue("doodle" + source.getSourceId(), Boolean.TRUE); + } else { + game.informPlayers("Buzz was chosen"); + game.getState().setValue("buzz" + source.getSourceId(), Boolean.TRUE); + } + return true; + } + return false; + } +} + +class WhackCondition extends IntCompareCondition { + + WhackCondition() { + super(ComparisonType.MORE_THAN, 0); + } + + @Override + protected int getInputValue(Game game, Ability source) { + Object object = game.getState().getValue("whack" + source.getSourceId()); + if (object != null && object instanceof Boolean && (Boolean) object) { + return 1; + } + return 0; + } + + @Override + public String toString() { + return "if both players picked 'Whack'"; + } +} + +class DoodleCondition extends IntCompareCondition { + + DoodleCondition() { + super(ComparisonType.MORE_THAN, 0); + } + + @Override + protected int getInputValue(Game game, Ability source) { + Object object = game.getState().getValue("doodle" + source.getSourceId()); + if (object != null && object instanceof Boolean && (Boolean) object) { + return 1; + } + return 0; + } + + @Override + public String toString() { + return "if both players picked 'Doodle'"; + } +} + +class BuzzCondition extends IntCompareCondition { + + BuzzCondition() { + super(ComparisonType.MORE_THAN, 0); + } + + @Override + protected int getInputValue(Game game, Ability source) { + Object object = game.getState().getValue("buzz" + source.getSourceId()); + if (object != null && object instanceof Boolean && (Boolean) object) { + return 1; + } + return 0; + } + + @Override + public String toString() { + return "if both players picked differently"; + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChitteringDoom.java b/Mage.Sets/src/mage/cards/c/ChitteringDoom.java new file mode 100644 index 00000000000..7152a7bdee1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChitteringDoom.java @@ -0,0 +1,98 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.c; + +import java.util.UUID; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.SquirrelToken; + +/** + * + * @author spjspj + */ +public class ChitteringDoom extends CardImpl { + + public ChitteringDoom(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); + + // Whenever you roll a 4 or higher on a die, create a 1/1 green Squirrel creature token. + this.addAbility(new ChitteringDoomTriggeredAbility()); + } + + public ChitteringDoom(final ChitteringDoom card) { + super(card); + } + + @Override + public ChitteringDoom copy() { + return new ChitteringDoom(this); + } +} + +class ChitteringDoomTriggeredAbility extends TriggeredAbilityImpl { + + public ChitteringDoomTriggeredAbility() { + super(Zone.BATTLEFIELD, new CreateTokenEffect(new SquirrelToken()), false); + } + + public ChitteringDoomTriggeredAbility(final ChitteringDoomTriggeredAbility ability) { + super(ability); + } + + @Override + public ChitteringDoomTriggeredAbility copy() { + return new ChitteringDoomTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DICE_ROLLED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (this.getControllerId().equals(event.getPlayerId()) && event.getFlag()) { + if (event.getAmount() >= 4) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever you roll a 4 or higher on a die, create a 1/1 green Squirrel creature token"; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CrowStorm.java b/Mage.Sets/src/mage/cards/c/CrowStorm.java new file mode 100644 index 00000000000..8ba10dcf889 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrowStorm.java @@ -0,0 +1,62 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.c; + +import java.util.UUID; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.StormAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.StormCrowToken; + +/** + * + * @author spjspj + */ +public class CrowStorm extends CardImpl { + + public CrowStorm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}"); + + // Create a 1/2 blue Bird creature token with flying named Storm Crow. + this.getSpellAbility().addEffect(new CreateTokenEffect(new StormCrowToken(), 1)); + + // Storm (When you cast this spell, copy it for each spell cast before it this turn.) + this.addAbility(new StormAbility()); + } + + public CrowStorm(final CrowStorm card) { + super(card); + } + + @Override + public CrowStorm copy() { + return new CrowStorm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CuriousKillbot.java b/Mage.Sets/src/mage/cards/c/CuriousKillbot.java new file mode 100644 index 00000000000..692c666743c --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CuriousKillbot.java @@ -0,0 +1,59 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * + * @author spjspj + */ +public class CuriousKillbot extends CardImpl { + + public CuriousKillbot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.subtype.add(SubType.KILLBOT); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + } + + public CuriousKillbot(final CuriousKillbot card) { + super(card); + } + + @Override + public CuriousKillbot copy() { + return new CuriousKillbot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DefensiveFormation.java b/Mage.Sets/src/mage/cards/d/DefensiveFormation.java index babba4f36e6..c9b24d01302 100644 --- a/Mage.Sets/src/mage/cards/d/DefensiveFormation.java +++ b/Mage.Sets/src/mage/cards/d/DefensiveFormation.java @@ -32,7 +32,6 @@ import mage.abilities.common.ControllerAssignCombatDamageToBlockersAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; /** * diff --git a/Mage.Sets/src/mage/cards/d/DrakestownForgotten.java b/Mage.Sets/src/mage/cards/d/DrakestownForgotten.java index a74dc5d4fdf..60ecd71c8ae 100644 --- a/Mage.Sets/src/mage/cards/d/DrakestownForgotten.java +++ b/Mage.Sets/src/mage/cards/d/DrakestownForgotten.java @@ -64,8 +64,8 @@ public class DrakestownForgotten extends CardImpl { new AddCountersSourceEffect( CounterType.P1P1.createInstance(), new CardsInAllGraveyardsCount(new FilterCreatureCard()), - false), - "with X +1/+1 counters on it, where X is the number of other creatures on the battlefield")); + false), + "with X +1/+1 counters on it, where X is the number of creature cards in all graveyards")); // {2}{B}, Remove a +1/+1 counter from Drakestown Forgotten: Target creature gets -1/-1 until end of turn. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostTargetEffect(-1, -1, Duration.EndOfTurn), new ManaCostsImpl<>("{2}{B}")); diff --git a/Mage.Sets/src/mage/cards/e/EarlOfSquirrel.java b/Mage.Sets/src/mage/cards/e/EarlOfSquirrel.java new file mode 100644 index 00000000000..661c498d401 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EarlOfSquirrel.java @@ -0,0 +1,97 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.e; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.BecomesSubtypeAllEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.keyword.SquirrellinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.util.SubTypeList; + +/** + * + * @author spjspj + */ +public class EarlOfSquirrel extends CardImpl { + + private final static FilterCreaturePermanent filter = new FilterCreaturePermanent("Creature tokens you control"); + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("Other squirrels you control"); + + + static { + filter.add(new TokenPredicate()); + filter.add(new ControllerPredicate(TargetController.YOU)); + filter2.add(new SubtypePredicate(SubType.SQUIRREL)); + } + + public EarlOfSquirrel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + + this.subtype.add(SubType.SQUIRREL); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Squirrellink (Damage dealt by this creature also causes you to create that many 1/1 green Squirrel creature tokens.) + this.addAbility(SquirrellinkAbility.getInstance()); + + // Creature tokens you control are Squirrels in addition to their other creature types. + SubTypeList subTypes = new SubTypeList(); + subTypes.add(SubType.SQUIRREL); + Effect effect = new BecomesSubtypeAllEffect(Duration.WhileOnBattlefield, subTypes, filter, false); + effect.setText("Creature tokens you control are Squirrels in addition to their other creature types"); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + + // Other Squirrels you control get +1/+1. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, filter2, true))); + } + + public EarlOfSquirrel(final EarlOfSquirrel card) { + super(card); + } + + @Override + public EarlOfSquirrel copy() { + return new EarlOfSquirrel(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GOTOJAIL.java b/Mage.Sets/src/mage/cards/g/GOTOJAIL.java new file mode 100644 index 00000000000..24f03f27b61 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GOTOJAIL.java @@ -0,0 +1,193 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.g; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.OnLeaveReturnExiledToBattlefieldAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseOpponentEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +/** + * + * @author spjspj + */ +public class GOTOJAIL extends CardImpl { + + private final static FilterCreaturePermanent filter = new FilterCreaturePermanent("creature an opponent controls"); + + static { + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public GOTOJAIL(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}"); + + // When GO TO JAIL enters the battlefield, exile target creature an opponent controls until GO TO JAIL leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new GoToJailExileEffect()); + ability.addTarget(new TargetCreaturePermanent(filter)); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new OnLeaveReturnExiledToBattlefieldAbility())); + this.addAbility(ability); + + // At the beginning of the upkeep of the exiled card's owner, that player rolls two six-sided dice. If he or she rolls doubles, sacrifice GO TO JAIL. + this.addAbility(new GoToJailTriggeredAbility()); + } + + public GOTOJAIL(final GOTOJAIL card) { + super(card); + } + + @Override + public GOTOJAIL copy() { + return new GOTOJAIL(this); + } +} + +class GoToJailExileEffect extends OneShotEffect { + + public GoToJailExileEffect() { + super(Outcome.Benefit); + this.staticText = "exile target creature an opponent controls until {this} leaves the battlefield."; + } + + public GoToJailExileEffect(final GoToJailExileEffect effect) { + super(effect); + } + + @Override + public GoToJailExileEffect copy() { + return new GoToJailExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = (Permanent) source.getSourceObjectIfItStillExists(game); + Permanent targetPermanent = game.getPermanent(targetPointer.getFirst(game, source)); + + // If GO TO JAIL leaves the battlefield before its triggered ability resolves, + // the target creature won't be exiled. + if (permanent != null && targetPermanent != null) { + Player controller = game.getPlayer(targetPermanent.getControllerId()); + if (controller != null) { + game.getState().setValue(permanent.getId() + ChooseOpponentEffect.VALUE_KEY, controller.getId()); + return new ExileTargetEffect(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), permanent.getIdName()).apply(game, source); + } + } + return false; + } +} + +class GoToJailTriggeredAbility extends TriggeredAbilityImpl { + + public GoToJailTriggeredAbility() { + super(Zone.BATTLEFIELD, new GoToJailUpkeepEffect(), false); + } + + public GoToJailTriggeredAbility(final GoToJailTriggeredAbility ability) { + super(ability); + } + + @Override + public GoToJailTriggeredAbility copy() { + return new GoToJailTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.UPKEEP_STEP_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getPlayerId().equals((UUID) game.getState().getValue(this.getSourceId().toString() + ChooseOpponentEffect.VALUE_KEY)); + } + + @Override + public String getRule() { + return "At the beginning of the chosen player's upkeep, " + super.getRule(); + } +} + +class GoToJailUpkeepEffect extends OneShotEffect { + + public GoToJailUpkeepEffect() { + super(Outcome.Sacrifice); + this.staticText = "that player rolls two six-sided dice. If he or she rolls doubles, sacrifice {this}"; + } + + public GoToJailUpkeepEffect(final GoToJailUpkeepEffect effect) { + super(effect); + } + + @Override + public GoToJailUpkeepEffect copy() { + return new GoToJailUpkeepEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObjectIfItStillExists(game); + Permanent permanent = (Permanent) source.getSourceObjectIfItStillExists(game); + + + if (sourceObject != null && sourceObject instanceof Permanent && permanent != null) { + UUID opponentId = (UUID) game.getState().getValue(sourceObject.getId().toString() + ChooseOpponentEffect.VALUE_KEY); + Player opponent = game.getPlayer(opponentId); + + if (opponent != null) { + int thisRoll = opponent.rollDice(game, 6); + int thatRoll = opponent.rollDice(game, 6); + if (thisRoll == thatRoll) { + return permanent.sacrifice(source.getSourceId(), game); + } + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GarbageElemental.java b/Mage.Sets/src/mage/cards/g/GarbageElemental.java new file mode 100644 index 00000000000..dcbe7c4a4c9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GarbageElemental.java @@ -0,0 +1,115 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.BattleCryAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.predicate.permanent.CounterPredicate; +import mage.game.Game; +import mage.game.permanent.token.GoblinToken; +import mage.game.permanent.token.Token; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class GarbageElemental extends CardImpl { + + public GarbageElemental(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add("Elemental"); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Battle cry + this.addAbility(new BattleCryAbility()); + + // When Garbage Elemental enters the battlefield, roll two six-sided dice. Create a number of 1/1 red Goblin creature tokens equal to the difference between those results. + this.addAbility(new EntersBattlefieldAbility(new GarbageElementalEffect(), + null, + "When {this} enters the battlefield, roll two six-sided dice. Create a number of 1/1 red Goblin creature tokens equal to the difference between those results", + null)); + + } + + public GarbageElemental(final GarbageElemental card) { + super(card); + } + + @Override + public GarbageElemental copy() { + return new GarbageElemental(this); + } +} + +class GarbageElementalEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent("permanent with a counter"); + + static { + filter.add(new CounterPredicate(null)); + } + + GarbageElementalEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "roll two six-sided dice. Create a number of 1/1 red Goblin creature tokens equal to the difference between those results"; + } + + GarbageElementalEffect(final GarbageElementalEffect effect) { + super(effect); + } + + @Override + public GarbageElementalEffect copy() { + return new GarbageElementalEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int thisRoll = controller.rollDice(game, 6); + int thatRoll = controller.rollDice(game, 6); + + Token token = new GoblinToken(); + return token.putOntoBattlefield(Math.abs(thatRoll - thisRoll), game, source.getSourceId(), source.getControllerId()); + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GoblinRockSled.java b/Mage.Sets/src/mage/cards/g/GoblinRockSled.java index 8c2ad210f8b..3addc4904e9 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinRockSled.java +++ b/Mage.Sets/src/mage/cards/g/GoblinRockSled.java @@ -35,6 +35,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.combat.CantAttackUnlessDefenderControllsPermanent; +import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -69,6 +70,9 @@ public class GoblinRockSled extends CardImpl { this.power = new MageInt(3); this.toughness = new MageInt(1); + // Trample + this.addAbility(TrampleAbility.getInstance()); + // Goblin Rock Sled doesn't untap during your untap step if it attacked during your last turn. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DontUntapIfAttackedLastTurnSourceEffect()), new AttackedLastTurnWatcher()); diff --git a/Mage.Sets/src/mage/cards/g/GroundPounder.java b/Mage.Sets/src/mage/cards/g/GroundPounder.java new file mode 100644 index 00000000000..640ffcbee0f --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GroundPounder.java @@ -0,0 +1,147 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class GroundPounder extends CardImpl { + + public GroundPounder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // 3G: Roll a six-sided die. Ground Pounder gets +X/+X until end of turn, where X is the result. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GroundPounderEffect(), new ManaCostsImpl("{3}{G}"))); + + // Whenever you roll a 5 or higher on a die, Ground Pounder gains trample until end of turn. + this.addAbility(new GroundPounderTriggeredAbility()); + } + + public GroundPounder(final GroundPounder card) { + super(card); + } + + @Override + public GroundPounder copy() { + return new GroundPounder(this); + } +} + +class GroundPounderEffect extends OneShotEffect { + + public GroundPounderEffect() { + super(Outcome.Benefit); + this.staticText = "Roll a six-sided die. {this} gets +X/+X until end of turn, where X is the result"; + } + + public GroundPounderEffect(final GroundPounderEffect effect) { + super(effect); + } + + @Override + public GroundPounderEffect copy() { + return new GroundPounderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (controller != null && permanent != null) { + int amount = controller.rollDice(game, 6); + game.addEffect(new BoostSourceEffect(amount, amount, Duration.EndOfTurn), source); + return true; + } + return false; + } +} + +class GroundPounderTriggeredAbility extends TriggeredAbilityImpl { + + public GroundPounderTriggeredAbility() { + super(Zone.BATTLEFIELD, new GainAbilitySourceEffect(TrampleAbility.getInstance(), Duration.EndOfTurn), false); + } + + public GroundPounderTriggeredAbility(final GroundPounderTriggeredAbility ability) { + super(ability); + } + + @Override + public GroundPounderTriggeredAbility copy() { + return new GroundPounderTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DICE_ROLLED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (this.getControllerId().equals(event.getPlayerId()) && event.getFlag()) { + if (event.getAmount() >= 5) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever you roll a 5 or higher on a die, {this} gains trample until end of turn"; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HammerHelper.java b/Mage.Sets/src/mage/cards/h/HammerHelper.java new file mode 100644 index 00000000000..bf54276c572 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HammerHelper.java @@ -0,0 +1,103 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.h; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author spjspj + */ +public class HammerHelper extends CardImpl { + + public HammerHelper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}"); + + // Gain control of target creature until end of turn. Untap that creature and roll a six-sided die. Until end of turn, it gains haste and gets +X/+0, where X is the result. + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new HammerHelperEffect()); + } + + public HammerHelper(final HammerHelper card) { + super(card); + } + + @Override + public HammerHelper copy() { + return new HammerHelper(this); + } +} + +class HammerHelperEffect extends OneShotEffect { + + HammerHelperEffect() { + super(Outcome.Benefit); + staticText = "Gain control of target creature until end of turn. Untap that creature and roll a six-sided die. Until end of turn, it gains haste and gets +X/+0, where X is the result"; + } + + HammerHelperEffect(HammerHelperEffect effect) { + super(effect); + } + + @Override + public HammerHelperEffect copy() { + return new HammerHelperEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent targetCreature = game.getPermanent(source.getFirstTarget()); + if (controller != null && targetCreature != null) { + source.getEffects().get(0).setTargetPointer(new FixedTarget(targetCreature.getId())); + game.addEffect(new GainControlTargetEffect(Duration.EndOfTurn), source); + targetCreature.untap(game); + int amount = controller.rollDice(game, 6); + game.addEffect(new BoostTargetEffect(amount, 0, Duration.EndOfTurn), source); + game.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn), source); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HauntingWind.java b/Mage.Sets/src/mage/cards/h/HauntingWind.java index 4533661fbb0..dca80cded44 100644 --- a/Mage.Sets/src/mage/cards/h/HauntingWind.java +++ b/Mage.Sets/src/mage/cards/h/HauntingWind.java @@ -29,8 +29,6 @@ package mage.cards.h; import java.util.UUID; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; @@ -98,14 +96,8 @@ class HauntingWindTriggeredAbility extends TriggeredAbilityImpl { if (stackAbility == null) { return false; } - boolean triggerable = true; - for (Cost cost : stackAbility.getCosts()) { - if (cost instanceof TapSourceCost) { - triggerable = false; - break; - } - } - if (!triggerable) { + String abilityText = stackAbility.getRule(true); + if (abilityText.contains("{T}:") || abilityText.contains("{T},") || abilityText.contains("{T} or")) { return false; } for (Effect effect : this.getEffects()) { diff --git a/Mage.Sets/src/mage/cards/h/Hydradoodle.java b/Mage.Sets/src/mage/cards/h/Hydradoodle.java new file mode 100644 index 00000000000..5637b38610c --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/Hydradoodle.java @@ -0,0 +1,133 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.EntersBattlefieldEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.permanent.CounterPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class Hydradoodle extends CardImpl { + + public Hydradoodle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}{X}{G}{G}"); + + this.subtype.add(SubType.HYDRA); + this.subtype.add(SubType.HOUND); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // As Hydradoodle enters the battlefield, roll X six-sided dice. Hydradoodle enters the battlefield with a number of +1/+1 counters on it equal to the total of those results. + this.addAbility(new EntersBattlefieldAbility(new HydradoodleEffect(), + null, + "As {this} enters the battlefield, roll X six-sided dice. {this} enters the battlefield with a number of +1/+1 counters on it equal to the total of those results", + null)); + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + } + + public Hydradoodle(final Hydradoodle card) { + super(card); + } + + @Override + public Hydradoodle copy() { + return new Hydradoodle(this); + } +} + +class HydradoodleEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent("permanent with a counter"); + + static { + filter.add(new CounterPredicate(null)); + } + + HydradoodleEffect() { + super(Outcome.BoostCreature); + this.staticText = "roll X six-sided dice. {this} enters the battlefield with a number of +1/+1 counters on it equal to the total of those results"; + } + + HydradoodleEffect(final HydradoodleEffect effect) { + super(effect); + } + + @Override + public HydradoodleEffect copy() { + return new HydradoodleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanentEntering(source.getSourceId()); + Player controller = game.getPlayer(source.getControllerId()); + if (permanent != null) { + SpellAbility spellAbility = (SpellAbility) getValue(EntersBattlefieldEffect.SOURCE_CAST_SPELL_ABILITY); + if (spellAbility != null + && spellAbility.getSourceId().equals(source.getSourceId()) + && permanent.getZoneChangeCounter(game) == spellAbility.getSourceObjectZoneChangeCounter()) { + int amount = spellAbility.getManaCostsToPay().getX(); + if (amount > 0) { + int total = 0; + for (int roll = 0; roll < amount; roll++) { + int thisRoll = controller.rollDice(game, 6); + total += thisRoll; + } + + permanent.addCounters(CounterType.P1P1.createInstance(total), source, game); + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/i/Inhumaniac.java b/Mage.Sets/src/mage/cards/i/Inhumaniac.java new file mode 100644 index 00000000000..94b94efb5f2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/Inhumaniac.java @@ -0,0 +1,106 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.i; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class Inhumaniac extends CardImpl { + + public Inhumaniac(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.BRAINIAC); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // At the beginning of your upkeep, roll a six-sided die. On a 3 or 4, put a +1/+1 counter on Inhumaniac. On a 5 or higher, put two +1/+1 counters on it. On a 1, remove all +1/+1 counters from Inhumaniac. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new InhumaniacEffect(), TargetController.YOU, false)); + } + + public Inhumaniac(final Inhumaniac card) { + super(card); + } + + @Override + public Inhumaniac copy() { + return new Inhumaniac(this); + } +} + +class InhumaniacEffect extends OneShotEffect { + + public InhumaniacEffect() { + super(Outcome.Benefit); + this.staticText = "roll a six-sided die. On a 3 or 4, put a +1/+1 counter on {this}. On a 5 or higher, put two +1/+1 counters on it. On a 1, remove all +1/+1 counters from {this}"; + } + + public InhumaniacEffect(final InhumaniacEffect effect) { + super(effect); + } + + @Override + public InhumaniacEffect copy() { + return new InhumaniacEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (controller != null && permanent != null) { + int amount = controller.rollDice(game, 6); + if (amount >= 3 && amount <= 4) { + permanent.addCounters(CounterType.P1P1.createInstance(1), source, game); + } else if (amount >= 5) { + permanent.addCounters(CounterType.P1P1.createInstance(2), source, game); + } else if (amount == 1) { + permanent.getCounters(game).removeAllCounters(CounterType.P1P1); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KrarksOtherThumb.java b/Mage.Sets/src/mage/cards/k/KrarksOtherThumb.java new file mode 100644 index 00000000000..b37ef0e660e --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KrarksOtherThumb.java @@ -0,0 +1,123 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.k; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.util.RandomUtil; + +/** + * + * @author spjspj + */ +public class KrarksOtherThumb extends CardImpl { + + public KrarksOtherThumb(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + addSuperType(SuperType.LEGENDARY); + + // If you would roll a die, instead roll two of those dice and ignore one of those results. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new KrarksOtherThumbEffect())); + } + + public KrarksOtherThumb(final KrarksOtherThumb card) { + super(card); + } + + @Override + public KrarksOtherThumb copy() { + return new KrarksOtherThumb(this); + } +} + +class KrarksOtherThumbEffect extends ReplacementEffectImpl { + + KrarksOtherThumbEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "If you would roll a die, instead roll two die and ignore one"; + } + + KrarksOtherThumbEffect(final KrarksOtherThumbEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player player = game.getPlayer(event.getPlayerId()); + if (player != null) { + // event.getData holds the num of sides of the die to roll + String data = event.getData(); + int numSides = Integer.parseInt(data); + int secondDieRoll = RandomUtil.nextInt(numSides) + 1; + + if (!game.isSimulation()) { + game.informPlayers("[Roll a die] " + player.getLogName() + " rolled a " + secondDieRoll); + } + if (player.chooseUse(outcome, "Ignore the first die roll?", source, game)) { + event.setAmount(secondDieRoll); + game.informPlayers(player.getLogName() + " ignores the first die roll."); + } else { + game.informPlayers(player.getLogName() + " ignores the second die roll."); + } + } + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ROLL_DICE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.getControllerId().equals(event.getPlayerId()); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public KrarksOtherThumbEffect copy() { + return new KrarksOtherThumbEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LobeLobber.java b/Mage.Sets/src/mage/cards/l/LobeLobber.java new file mode 100644 index 00000000000..d720b96565b --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LobeLobber.java @@ -0,0 +1,116 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.l; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.UntapSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPlayer; + +/** + * + * @author spjspj + */ +public class LobeLobber extends CardImpl { + + public LobeLobber(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature has "T: This creature deals 1 damage to target player. Roll a six-sided die. On a 5 or higher, untap it." + Effect effect = new LobeLobberEffect(); + SimpleActivatedAbility ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapSourceCost()); + ability.addTarget(new TargetPlayer()); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(ability, AttachmentType.EQUIPMENT))); + + // Equip 2 + this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(2))); + } + + public LobeLobber(final LobeLobber card) { + super(card); + } + + @Override + public LobeLobber copy() { + return new LobeLobber(this); + } +} + +class LobeLobberEffect extends OneShotEffect { + + public LobeLobberEffect() { + super(Outcome.Benefit); + this.staticText = "This creature deals 1 damage to target player. Roll a six-sided die. On a 5 or higher, untap it"; + } + + public LobeLobberEffect(final LobeLobberEffect effect) { + super(effect); + } + + @Override + public LobeLobberEffect copy() { + return new LobeLobberEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent equipment = game.getPermanent(source.getSourceId()); + Player player = game.getPlayer(source.getFirstTarget()); + + if (controller != null && equipment != null && player != null) { + player.damage(1, source.getSourceId(), game, false, true); + int amount = controller.rollDice(game, 6); + if (amount >= 5) { + new UntapSourceEffect().apply(game, source); + } + return true; + } + + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MadScienceFairProject.java b/Mage.Sets/src/mage/cards/m/MadScienceFairProject.java new file mode 100644 index 00000000000..3b503600c35 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MadScienceFairProject.java @@ -0,0 +1,132 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.m; + +import java.util.List; +import java.util.UUID; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.ManaEffect; +import mage.abilities.mana.ActivatedManaAbilityImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.ChoiceColor; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class MadScienceFairProject extends CardImpl { + + public MadScienceFairProject(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // {tap}: Roll a six-sided die. On a 3 or lower, target player adds {C} to his or her mana pool. Otherwise, that player adds one mana of any color he or she chooses to his or her mana pool. + this.addAbility(new MadScienceFairProjectManaAbility()); + } + + public MadScienceFairProject(final MadScienceFairProject card) { + super(card); + } + + @Override + public MadScienceFairProject copy() { + return new MadScienceFairProject(this); + } +} + +class MadScienceFairProjectManaAbility extends ActivatedManaAbilityImpl { + + public MadScienceFairProjectManaAbility() { + super(Zone.BATTLEFIELD, new MadScienceFairManaEffect(), new TapSourceCost()); + } + + public MadScienceFairProjectManaAbility(final MadScienceFairProjectManaAbility ability) { + super(ability); + } + + @Override + public MadScienceFairProjectManaAbility copy() { + return new MadScienceFairProjectManaAbility(this); + } +} + +class MadScienceFairManaEffect extends ManaEffect { + + public MadScienceFairManaEffect() { + super(); + this.staticText = "Roll a six-sided die. On a 3 or lower, target player adds {C} to his or her mana pool. Otherwise, that player adds one mana of any color he or she chooses to his or her mana pool"; + } + + public MadScienceFairManaEffect(final MadScienceFairManaEffect effect) { + super(effect); + } + + @Override + public MadScienceFairManaEffect copy() { + return new MadScienceFairManaEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (controller != null && permanent != null) { + int amount = controller.rollDice(game, 6); + if (amount <= 3) { + controller.getManaPool().addMana(Mana.ColorlessMana(1), game, source); + } else { + ChoiceColor choice = new ChoiceColor(); + controller.choose(Outcome.PutManaInPool, choice, game); + if (choice.getColor() == null) { + return false; + } + Mana chosen = choice.getMana(1); + if (chosen != null) { + checkToFirePossibleEvents(chosen, game, source); + controller.getManaPool().addMana(chosen, game, source); + return true; + } + } + return true; + } + return false; + } + + @Override + public Mana getMana(Game game, Ability source) { + return null; + } +} diff --git a/Mage.Sets/src/mage/cards/p/Painiac.java b/Mage.Sets/src/mage/cards/p/Painiac.java new file mode 100644 index 00000000000..44d005f6784 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/Painiac.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.p; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class Painiac extends CardImpl { + + public Painiac(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + this.subtype.add(SubType.BRAINIAC); + + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // At the beginning of your upkeep, roll a six-sided die. Painiac gets +X/+0 until end of turn, where X is the result. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new PainiacEffect(), TargetController.YOU, false)); + } + + public Painiac(final Painiac card) { + super(card); + } + + @Override + public Painiac copy() { + return new Painiac(this); + } +} + +class PainiacEffect extends OneShotEffect { + + public PainiacEffect() { + super(Outcome.Benefit); + this.staticText = "Roll a six-sided die. {this} gets +X/+0 until end of turn, where X is the result"; + } + + public PainiacEffect(final PainiacEffect effect) { + super(effect); + } + + @Override + public PainiacEffect copy() { + return new PainiacEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (controller != null && permanent != null) { + int amount = controller.rollDice(game, 6); + game.addEffect(new BoostSourceEffect(amount, 0, Duration.EndOfTurn), source); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RootSliver.java b/Mage.Sets/src/mage/cards/r/RootSliver.java index facf6a51ffe..0190c80edf1 100644 --- a/Mage.Sets/src/mage/cards/r/RootSliver.java +++ b/Mage.Sets/src/mage/cards/r/RootSliver.java @@ -70,7 +70,7 @@ public class RootSliver extends CardImpl { // Root Sliver can't be countered. this.addAbility(new SimpleStaticAbility(Zone.STACK, new CantBeCounteredSourceEffect())); // Sliver spells can't be countered by spells or abilities. - this.addAbility(new SimpleStaticAbility(Zone.STACK, new RootSliverEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new RootSliverEffect())); } @@ -88,7 +88,7 @@ class RootSliverEffect extends ContinuousRuleModifyingEffectImpl { public RootSliverEffect() { super(Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "Sliver spells can't be countered by spells or abilities."; + staticText = "Sliver spells can't be countered by spells or abilities"; } public RootSliverEffect(final RootSliverEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/ScytheSpecter.java b/Mage.Sets/src/mage/cards/s/ScytheSpecter.java index 57d0405ef4d..c6854255185 100644 --- a/Mage.Sets/src/mage/cards/s/ScytheSpecter.java +++ b/Mage.Sets/src/mage/cards/s/ScytheSpecter.java @@ -129,7 +129,8 @@ class ScytheSpecterEffect extends OneShotEffect { } for (UUID playerId : game.getOpponents(controller.getId())) {//lose life equal to CMC - if (cardDiscarded.get(playerId).getConvertedManaCost() == highestCMC) { + Card card = cardDiscarded.get(playerId); + if ((card != null) && (card.getConvertedManaCost() == highestCMC)) { Player opponent = game.getPlayer(playerId); if (opponent != null && discardedCheck.get(playerId) == 1) {//check that card was discarded diff --git a/Mage.Sets/src/mage/cards/s/SnickeringSquirrel.java b/Mage.Sets/src/mage/cards/s/SnickeringSquirrel.java new file mode 100644 index 00000000000..7022dc9c0bb --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SnickeringSquirrel.java @@ -0,0 +1,120 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class SnickeringSquirrel extends CardImpl { + + public SnickeringSquirrel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.subtype.add("Squirrel"); + this.subtype.add("Advisor"); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // You may tap Snickering Squirrel to increase the result of a die any player rolled by 1. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SnickeringSquirrelEffect())); + } + + public SnickeringSquirrel(final SnickeringSquirrel card) { + super(card); + } + + @Override + public SnickeringSquirrel copy() { + return new SnickeringSquirrel(this); + } +} + +class SnickeringSquirrelEffect extends ReplacementEffectImpl { + + SnickeringSquirrelEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "You may tap {this} to increase the result of a die any player rolled by 1"; + } + + SnickeringSquirrelEffect(final SnickeringSquirrelEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + + if (controller != null) { + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null && permanent.canTap() && !permanent.isTapped()) { + if (controller.chooseUse(Outcome.AIDontUseIt, "Do you want to tap this to increase the result of a die any player rolled by 1?", null, "Yes", "No", source, game)) { + permanent.tap(game); + event.setAmount(event.getAmount() + 1); + } + } + } + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ROLL_DICE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return true; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public SnickeringSquirrelEffect copy() { + return new SnickeringSquirrelEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SquirrelPoweredScheme.java b/Mage.Sets/src/mage/cards/s/SquirrelPoweredScheme.java new file mode 100644 index 00000000000..26a4aefa594 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SquirrelPoweredScheme.java @@ -0,0 +1,102 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * + * @author spjspj + */ +public class SquirrelPoweredScheme extends CardImpl { + + public SquirrelPoweredScheme(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); + + // Increase the result of each die you roll by 2. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SquirrelPoweredSchemeEffect())); + } + + public SquirrelPoweredScheme(final SquirrelPoweredScheme card) { + super(card); + } + + @Override + public SquirrelPoweredScheme copy() { + return new SquirrelPoweredScheme(this); + } +} + +class SquirrelPoweredSchemeEffect extends ReplacementEffectImpl { + + SquirrelPoweredSchemeEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "Increase the result of each die you roll by 2"; + } + + SquirrelPoweredSchemeEffect(final SquirrelPoweredSchemeEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(event.getAmount() + 2); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ROLL_DICE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.getControllerId().equals(event.getPlayerId()); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public SquirrelPoweredSchemeEffect copy() { + return new SquirrelPoweredSchemeEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SteelSquirrel.java b/Mage.Sets/src/mage/cards/s/SteelSquirrel.java new file mode 100644 index 00000000000..9dd2a0e8be7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SteelSquirrel.java @@ -0,0 +1,151 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.RollDiceEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class SteelSquirrel extends CardImpl { + + public SteelSquirrel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.subtype.add(SubType.SQUIRREL); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever you roll a 5 or higher on a die, Steel Squirrel gets +X/+X until end of turn, where X is the result. + this.addAbility(new SteelSquirrelTriggeredAbility()); + + // 6: Roll a six-sided die. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RollDiceEffect(null, 6), new GenericManaCost(6)); + this.addAbility(ability); + } + + public SteelSquirrel(final SteelSquirrel card) { + super(card); + } + + @Override + public SteelSquirrel copy() { + return new SteelSquirrel(this); + } +} + +class SteelSquirrelTriggeredAbility extends TriggeredAbilityImpl { + + public SteelSquirrelTriggeredAbility() { + super(Zone.BATTLEFIELD, new SteelSquirrelEffect()); + } + + public SteelSquirrelTriggeredAbility(final SteelSquirrelTriggeredAbility ability) { + super(ability); + } + + @Override + public SteelSquirrelTriggeredAbility copy() { + return new SteelSquirrelTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DICE_ROLLED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (this.getControllerId().equals(event.getPlayerId()) && event.getFlag()) { + if (event.getAmount() >= 5) { + for (Effect effect : this.getEffects()) { + effect.setValue("rolled", event.getAmount()); + } + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever you roll a 5 or higher on a die, " + super.getRule(); + } +} + +class SteelSquirrelEffect extends OneShotEffect { + + public SteelSquirrelEffect() { + super(Outcome.Benefit); + this.staticText = "{this} gets +X/+X until end of turn, where X is the result"; + } + + public SteelSquirrelEffect(final SteelSquirrelEffect effect) { + super(effect); + } + + @Override + public SteelSquirrelEffect copy() { + return new SteelSquirrelEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (controller != null && permanent != null) { + if (this.getValue("rolled") != null) { + int rolled = (Integer) this.getValue("rolled"); + game.addEffect(new BoostSourceEffect(rolled, rolled, Duration.EndOfTurn), source); + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonThePack.java b/Mage.Sets/src/mage/cards/s/SummonThePack.java new file mode 100644 index 00000000000..445e3dc47d0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonThePack.java @@ -0,0 +1,139 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseExpansionSetEffect; +import mage.abilities.effects.common.continuous.BecomesBlackZombieAdditionEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.ExpansionSet; +import mage.cards.Sets; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author spjspj + */ +public class SummonThePack extends CardImpl { + + public SummonThePack(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{7}{B}"); + + // Open a sealed Magic booster pack, reveal the cards, and put all creature cards revealed this way onto the battlefield under your control. They're Zombies in addition to their other types. (Remove those cards from your deck before beginning a new game) + this.getSpellAbility().addEffect(new SummonThePackEffect()); + } + + public SummonThePack(final SummonThePack card) { + super(card); + } + + @Override + public SummonThePack copy() { + return new SummonThePack(this); + } +} + +class SummonThePackEffect extends OneShotEffect { + + public SummonThePackEffect() { + super(Outcome.DestroyPermanent); + this.staticText = "Open a sealed Magic booster pack, reveal the cards, and put all creature cards revealed this way onto the battlefield under your control. They're Zombies in addition to their other types"; + } + + public SummonThePackEffect(final SummonThePackEffect effect) { + super(effect); + } + + @Override + public SummonThePackEffect copy() { + return new SummonThePackEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + ChooseExpansionSetEffect effect = new ChooseExpansionSetEffect(Outcome.UnboostCreature); + effect.apply(game, source); + Player controller = game.getPlayer(source.getControllerId()); + + String setChosen = null; + if (effect.getValue("setchosen") != null) { + setChosen = (String) effect.getValue("setchosen"); + } else if (game.getState().getValue(this.getId() + "_set") != null) { + setChosen = (String) game.getState().getValue(this.getId() + "_set"); + } + + if (setChosen != null && controller != null) { + //ExpansionInfo set = ExpansionRepository.instance.getSetByName(setChosen); + ExpansionSet expansionSet = Sets.findSet(setChosen); + if (expansionSet != null) { + List boosterPack = expansionSet.create15CardBooster(); + List creatureCards = new ArrayList<>(); + + if (boosterPack != null) { + StringBuilder message = new StringBuilder(controller.getLogName()).append(" opened: "); + + for (Card c : boosterPack) { + message.append(c.getName()).append(" "); + if (c != null && c.isCreature()) { + message.append(" (creature card) "); + ContinuousEffect effect2 = new BecomesBlackZombieAdditionEffect(); + effect2.setTargetPointer(new FixedTarget(c.getId())); + game.addEffect(effect2, source); + creatureCards.add(c); + c.setZone(Zone.OUTSIDE, game); + } + } + + if (creatureCards.size() > 0) { + Set ccs = new HashSet(creatureCards); + game.loadCards(ccs, controller.getId()); + controller.moveCards(ccs, Zone.BATTLEFIELD, source, game); + } + + game.informPlayers(message.toString()); + } + } + } + + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SurveyorsScope.java b/Mage.Sets/src/mage/cards/s/SurveyorsScope.java index 7621dce50ba..988c0fc6940 100644 --- a/Mage.Sets/src/mage/cards/s/SurveyorsScope.java +++ b/Mage.Sets/src/mage/cards/s/SurveyorsScope.java @@ -101,10 +101,8 @@ class SurveyorsScopeEffect extends OneShotEffect { } } game.informPlayers(new StringBuilder("Surveyor's Scope: X = ").append(numberOfLands).toString()); - if (numberOfLands > 0) { - return new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, numberOfLands, StaticFilters.FILTER_BASIC_LAND_CARD)).apply(game, source); - } - return true; + // 10/17/2013 If no players control at least two more lands than you when the ability resolves, you’ll still search and shuffle your library. + return new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, numberOfLands, StaticFilters.FILTER_BASIC_LAND_CARD)).apply(game, source); } return false; } diff --git a/Mage.Sets/src/mage/cards/s/SwordOfDungeonsAndDragons.java b/Mage.Sets/src/mage/cards/s/SwordOfDungeonsAndDragons.java index ab50290a067..191aa8226fb 100644 --- a/Mage.Sets/src/mage/cards/s/SwordOfDungeonsAndDragons.java +++ b/Mage.Sets/src/mage/cards/s/SwordOfDungeonsAndDragons.java @@ -159,13 +159,14 @@ class SwordOfDungeonsAndDragonsEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { int count = 1; - int dice = (int)(Math.random()*20+1); - while (dice == 20) { + int amount = controller.rollDice(game, 20); + + while (amount == 20) { count += 1; - dice = (int)(Math.random()*20+1); + amount = controller.rollDice(game, 20); } return new CreateTokenEffect(new DragonTokenGold(), count).apply(game, source); } return false; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/t/TargetMinotaur.java b/Mage.Sets/src/mage/cards/t/TargetMinotaur.java new file mode 100644 index 00000000000..e8d4156be94 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TargetMinotaur.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.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.abilities.keyword.ProwessAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author TheElk801 + */ +public class TargetMinotaur extends CardImpl { + + public TargetMinotaur(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.MINOTAUR); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Prowess + this.addAbility(new ProwessAbility()); + + } + + public TargetMinotaur(final TargetMinotaur card) { + super(card); + } + + @Override + public TargetMinotaur copy() { + return new TargetMinotaur(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TimeOut.java b/Mage.Sets/src/mage/cards/t/TimeOut.java new file mode 100644 index 00000000000..2907d764fce --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TimeOut.java @@ -0,0 +1,134 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.t; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +/** + * + * @author spjspj + */ +public class TimeOut extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("nonland permanent"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.LAND))); + } + + public TimeOut(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{U}"); + + // Roll a six-sided die. Put target nonland permanent into its owner's library just beneath the top X cards of that library, where X is the result. + this.getSpellAbility().addEffect(new TimeOutEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + + } + + public TimeOut(final TimeOut card) { + super(card); + } + + @Override + public TimeOut copy() { + return new TimeOut(this); + } +} + +class TimeOutEffect extends OneShotEffect { + + public TimeOutEffect() { + super(Outcome.Benefit); + this.staticText = "Roll a six-sided die. Put target nonland permanent into its owner's library just beneath the top X cards of that library, where X is the result"; + } + + public TimeOutEffect(final TimeOutEffect effect) { + super(effect); + } + + @Override + public TimeOutEffect copy() { + return new TimeOutEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent permanent = game.getPermanent(this.getTargetPointer().getFirst(game, source)); + if (permanent != null) { + Player owner = game.getPlayer(permanent.getOwnerId()); + if (owner != null) { + int amount = controller.rollDice(game, 6); + Cards cards = new CardsImpl(); + Deque cardIds = new LinkedList<>(); + for (int i = 0; i < amount; i++) { + Card card = owner.getLibrary().removeFromTop(game); + cards.add(card); + cardIds.push(card.getId()); + } + // return cards back to library + game.informPlayers(new StringBuilder(controller.getLogName()) + .append(" puts ").append(permanent.getName()) + .append(" beneath the top ").append(amount) + .append(" cards of ").append(owner.getLogName()).append("'s library").toString()); + permanent.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); + while (!cardIds.isEmpty()) { + UUID cardId = cardIds.poll(); + Card card = cards.get(cardId, game); + if (card != null) { + card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); + } + } + return true; + } + } + } + + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/w/WillingTestSubject.java b/Mage.Sets/src/mage/cards/w/WillingTestSubject.java new file mode 100644 index 00000000000..ebafcff0a1a --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WillingTestSubject.java @@ -0,0 +1,120 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.w; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.RollDiceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * + * @author spjspj + */ +public class WillingTestSubject extends CardImpl { + + public WillingTestSubject(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.MONKEY); + this.subtype.add(SubType.SCIENTIST); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever you roll a 4 or higher on a die, put a +1/+1 counter on Willing Test Subject. + this.addAbility(new WillingTestSubjectTriggeredAbility()); + + // 6: Roll a six-sided die. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RollDiceEffect(null, 6), new GenericManaCost(6)); + this.addAbility(ability); + } + + public WillingTestSubject(final WillingTestSubject card) { + super(card); + } + + @Override + public WillingTestSubject copy() { + return new WillingTestSubject(this); + } +} + +class WillingTestSubjectTriggeredAbility extends TriggeredAbilityImpl { + + public WillingTestSubjectTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + + } + + public WillingTestSubjectTriggeredAbility(final WillingTestSubjectTriggeredAbility ability) { + super(ability); + } + + @Override + public WillingTestSubjectTriggeredAbility copy() { + return new WillingTestSubjectTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DICE_ROLLED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (this.getControllerId().equals(event.getPlayerId()) && event.getFlag()) { + if (event.getAmount() >= 4) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever you roll a 4 or higher on a die, put a +1/+1 counter on {this}"; + } +} diff --git a/Mage.Sets/src/mage/sets/Unstable.java b/Mage.Sets/src/mage/sets/Unstable.java index 338e9e4d380..d7ecad68d42 100644 --- a/Mage.Sets/src/mage/sets/Unstable.java +++ b/Mage.Sets/src/mage/sets/Unstable.java @@ -27,7 +27,9 @@ */ package mage.sets; +import mage.cards.CardGraphicInfo; import mage.cards.ExpansionSet; +import mage.cards.FrameStyle; import mage.constants.Rarity; import mage.constants.SetType; @@ -45,6 +47,37 @@ public class Unstable extends ExpansionSet { private Unstable() { super("Unstable", "UST", ExpansionSet.buildDate(2017, 12, 8), SetType.JOKESET); - cards.add(new SetCardInfo("Sword of Dungeons and Dragons", 1, Rarity.MYTHIC, mage.cards.s.SwordOfDungeonsAndDragons.class)); + + cards.add(new SetCardInfo("Amateur Auteur", 3, Rarity.COMMON, mage.cards.a.AmateurAuteur.class)); + cards.add(new SetCardInfo("As Luck Would Have It", 102, Rarity.RARE, mage.cards.a.AsLuckWouldHaveIt.class)); + cards.add(new SetCardInfo("Box of Free-Range Goblins", 77, Rarity.COMMON, mage.cards.b.BoxOfFreerangeGoblins.class)); + cards.add(new SetCardInfo("Buzzing Whack-a-Doodle", 141, Rarity.UNCOMMON, mage.cards.b.BuzzingWhackADoodle.class)); + cards.add(new SetCardInfo("Chittering Doom", 104, Rarity.UNCOMMON, mage.cards.c.ChitteringDoom.class)); + cards.add(new SetCardInfo("Crow Storm", 31, Rarity.UNCOMMON, mage.cards.c.CrowStorm.class)); + cards.add(new SetCardInfo("Curious Killbot", 145, Rarity.COMMON, mage.cards.c.CuriousKillbot.class)); + cards.add(new SetCardInfo("Earl of Squirrel", 108, Rarity.RARE, mage.cards.e.EarlOfSquirrel.class)); + cards.add(new SetCardInfo("Forest", 216, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); + cards.add(new SetCardInfo("GO TO JAIL", 8, Rarity.COMMON, mage.cards.g.GOTOJAIL.class)); + cards.add(new SetCardInfo("Garbage Elemental", 82, Rarity.UNCOMMON, mage.cards.g.GarbageElemental.class)); + cards.add(new SetCardInfo("Ground Pounder", 110, Rarity.COMMON, mage.cards.g.GroundPounder.class)); + cards.add(new SetCardInfo("Hammer Helper", 85, Rarity.COMMON, mage.cards.h.HammerHelper.class)); + cards.add(new SetCardInfo("Hydradoodle", 112, Rarity.RARE, mage.cards.h.Hydradoodle.class)); + cards.add(new SetCardInfo("Inhumaniac", 59, Rarity.UNCOMMON, mage.cards.i.Inhumaniac.class)); + cards.add(new SetCardInfo("Island", 213, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); + cards.add(new SetCardInfo("Krark's Other Thumb", 151, Rarity.UNCOMMON, mage.cards.k.KrarksOtherThumb.class)); + cards.add(new SetCardInfo("Lobe Lobber", 153, Rarity.UNCOMMON, mage.cards.l.LobeLobber.class)); + cards.add(new SetCardInfo("Mad Science Fair Project", 154, Rarity.COMMON, mage.cards.m.MadScienceFairProject.class)); + cards.add(new SetCardInfo("Mountain", 215, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); + cards.add(new SetCardInfo("Painiac", 91, Rarity.COMMON, mage.cards.p.Painiac.class)); + cards.add(new SetCardInfo("Plains", 212, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); + cards.add(new SetCardInfo("Snickering Squirrel", 68, Rarity.COMMON, mage.cards.s.SnickeringSquirrel.class)); + cards.add(new SetCardInfo("Squirrel-Powered Scheme", 70, Rarity.UNCOMMON, mage.cards.s.SquirrelPoweredScheme.class)); + cards.add(new SetCardInfo("Steel Squirrel", 162, Rarity.UNCOMMON, mage.cards.s.SteelSquirrel.class)); + cards.add(new SetCardInfo("Summon the Pack", 74, Rarity.MYTHIC, mage.cards.s.SummonThePack.class)); + cards.add(new SetCardInfo("Swamp", 214, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); + cards.add(new SetCardInfo("Sword of Dungeons & Dragons", 1, Rarity.MYTHIC, mage.cards.s.SwordOfDungeonsAndDragons.class)); + cards.add(new SetCardInfo("Target Minotaur", 98, Rarity.COMMON, mage.cards.t.TargetMinotaur.class)); + cards.add(new SetCardInfo("Time Out", 48, Rarity.COMMON, mage.cards.t.TimeOut.class)); + cards.add(new SetCardInfo("Willing Test Subject", 126, Rarity.COMMON, mage.cards.w.WillingTestSubject.class)); } } diff --git a/Mage.Sets/src/mage/sets/ZendikarVsEldrazi.java b/Mage.Sets/src/mage/sets/ZendikarVsEldrazi.java index a39362d81f0..c50645e3dd7 100644 --- a/Mage.Sets/src/mage/sets/ZendikarVsEldrazi.java +++ b/Mage.Sets/src/mage/sets/ZendikarVsEldrazi.java @@ -69,7 +69,7 @@ public class ZendikarVsEldrazi extends ExpansionSet { cards.add(new SetCardInfo("Forest", 38, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 39, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 40, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forked Bolt", 60, Rarity.UNCOMMON, mage.cards.f.ForkedBolt.class)); + cards.add(new SetCardInfo("Forked Bolt", 60, Rarity.COMMON, mage.cards.f.ForkedBolt.class)); cards.add(new SetCardInfo("Frontier Guide", 12, Rarity.UNCOMMON, mage.cards.f.FrontierGuide.class)); cards.add(new SetCardInfo("Graypelt Hunter", 13, Rarity.COMMON, mage.cards.g.GraypeltHunter.class)); cards.add(new SetCardInfo("Graypelt Refuge", 32, Rarity.UNCOMMON, mage.cards.g.GraypeltRefuge.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java index 52aef300aaf..5ea1d4310cb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java @@ -1,107 +1,107 @@ -/* - * 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 org.mage.test.cards.modal; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class OneOrBothTest extends CardTestPlayerBase { - - @Test - public void testSubtleStrikeFirstMode() { - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); - // Choose one or both — - // • Target creature gets -1/-1 until end of turn. - // • Put a +1/+1 counter on target creature. - addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} - - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); - addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); - setModeChoice(playerA, "1"); - setModeChoice(playerA, null); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertPowerToughness(playerA, "Silvercoat Lion", 2, 2); - assertPowerToughness(playerB, "Pillarfield Ox", 1, 3); - } - - @Test - public void testSubtleStrikeSecondMode() { - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); - // Choose one or both — - // • Target creature gets -1/-1 until end of turn. - // • Put a +1/+1 counter on target creature. - addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} - - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); - addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); - setModeChoice(playerA, "2"); - setModeChoice(playerA, null); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertPowerToughness(playerA, "Silvercoat Lion", 2, 2); - assertPowerToughness(playerB, "Pillarfield Ox", 3, 5); - } - - @Test - public void testSubtleStrikeBothModes() { - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); - // Choose one or both — - // • Target creature gets -1/-1 until end of turn. - // • Put a +1/+1 counter on target creature. - addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} - - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); - addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); - addTarget(playerA, "Silvercoat Lion"); - setModeChoice(playerA, "1"); - setModeChoice(playerA, "2"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertPowerToughness(playerA, "Silvercoat Lion", 3, 3); - assertPowerToughness(playerB, "Pillarfield Ox", 1, 3); - } -} +/* + * 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 org.mage.test.cards.modal; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class OneOrBothTest extends CardTestPlayerBase { + + @Test + public void testSubtleStrikeFirstMode() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Choose one or both — + // • Target creature gets -1/-1 until end of turn. + // • Put a +1/+1 counter on target creature. + addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, null); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 2, 2); + assertPowerToughness(playerB, "Pillarfield Ox", 1, 3); + } + + @Test + public void testSubtleStrikeSecondMode() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Choose one or both — + // • Target creature gets -1/-1 until end of turn. + // • Put a +1/+1 counter on target creature. + addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); + setModeChoice(playerA, "2"); + setModeChoice(playerA, null); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 2, 2); + assertPowerToughness(playerB, "Pillarfield Ox", 3, 5); + } + + @Test + public void testSubtleStrikeBothModes() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Choose one or both — + // • Target creature gets -1/-1 until end of turn. + // • Put a +1/+1 counter on target creature. + addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); + addTarget(playerA, "Silvercoat Lion"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "2"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerB, "Pillarfield Ox", 1, 3); + assertPowerToughness(playerA, "Silvercoat Lion", 3, 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/decks/importer/TxtDeckImporterTest.java b/Mage.Tests/src/test/java/org/mage/test/decks/importer/TxtDeckImporterTest.java index aa351e47f89..c4b1dbd4685 100644 --- a/Mage.Tests/src/test/java/org/mage/test/decks/importer/TxtDeckImporterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/decks/importer/TxtDeckImporterTest.java @@ -12,7 +12,7 @@ public class TxtDeckImporterTest { @Test public void testImportWithBlankLineAboveSideboard() { - TxtDeckImporter importer = new TxtDeckImporter(); + TxtDeckImporter importer = new TxtDeckImporter(false); CardInfo card; DeckCardLists deck = new DeckCardLists(); diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 595a54d6e60..68b35334ca3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1825,6 +1825,17 @@ public class TestPlayer implements Player { return computerPlayer.flipCoin(game, appliedEffects); } + @Override + public int rollDice(Game game, int numSides) { + return computerPlayer.rollDice(game, numSides); + } + + @Override + public int rollDice(Game game, ArrayList appliedEffects, int numSides) { + return computerPlayer.rollDice(game, appliedEffects, numSides); + } + + @Override public List getAvailableAttackers(Game game) { return computerPlayer.getAvailableAttackers(game); diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index e082f0a0b90..fd469eb8fc7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -646,6 +646,17 @@ public class PlayerStub implements Player { return false; } + @Override + public int rollDice(Game game, int numSides) { + return 1; + } + + @Override + public int rollDice(Game game, ArrayList appliedEffects, int numSides) { + return 1; + } + + @Override public void discard(int amount, Ability source, Game game) { diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index a0b1a113141..574e5b627fc 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -22,6 +22,35 @@ public class VerifyCardDataTest { // right now this is very noisy, and not useful enough to make any assertions on private static final boolean CHECK_SOURCE_TOKENS = false; + private static final HashMap> skipCheckLists = new HashMap<>(); + private static void skipListCreate(String listName){ skipCheckLists.put(listName, new LinkedHashSet<>()); } + private static void skipListAddName(String listName, String name){ skipCheckLists.get(listName).add(name); } + private static boolean skipListHaveName(String listName, String name){ return skipCheckLists.get(listName).contains(name); } + + static { + // skip lists for checks (example: ustable cards with same name may have different stats) + + // power-toughness + skipListCreate("PT"); + skipListAddName("PT", "Garbage Elemental"); // UST + + // color + skipListCreate("COLOR"); + //skipListAddName("COLOR", "Ulrich, Uncontested Alpha"); // gatherer is missing the color indicator on one card and json has wrong data (16.12.2017: not actual) + + // cost + skipListCreate("COST"); + + // supertype + skipListCreate("SUPERTYPE"); + + // type + skipListCreate("TYPE"); + + // subtype + skipListCreate("SUBTYPE"); + } + public static List allCards() { Collection sets = Sets.getInstance().values(); List cards = new ArrayList<>(); @@ -38,7 +67,7 @@ public class VerifyCardDataTest { } private void warn(Card card, String message) { - System.out.println("Warning: " + message + " for " + card.getName()); + System.out.println("Warning: " + message + " for " + card.getName() + " (" + card.getExpansionSetCode() + ")"); } private void fail(Card card, String category, String message) { @@ -129,10 +158,8 @@ public class VerifyCardDataTest { } private void checkColors(Card card, JsonCard ref) { - // gatherer is missing the color indicator on one card: - if ("Ulrich, Uncontested Alpha".equals(ref.name)) { - return; - } + if (skipListHaveName("COLOR", card.getName())){ return; } + Collection expected = ref.colors; ObjectColor color = card.getColor(null); if (expected == null) { @@ -149,7 +176,11 @@ public class VerifyCardDataTest { } private void checkSubtypes(Card card, JsonCard ref) { + if (skipListHaveName("SUBTYPE", card.getName())){ return; } + Collection expected = ref.subtypes; + + // fix names (e.g. Urza’s to Urza's) if (expected != null && expected.contains("Urza’s")) { expected = new ArrayList<>(expected); for (ListIterator it = ((List) expected).listIterator(); it.hasNext();) { @@ -158,12 +189,15 @@ public class VerifyCardDataTest { } } } + if (!eqSet(card.getSubtype(null).stream().map(p -> p.toString()).collect(Collectors.toSet()), expected)) { fail(card, "subtypes", card.getSubtype(null) + " != " + expected); } } private void checkSupertypes(Card card, JsonCard ref) { + if (skipListHaveName("SUPERTYPE", card.getName())){ return; } + Collection expected = ref.supertypes; if (!eqSet(card.getSuperType().stream().map(s -> s.toString()).collect(Collectors.toList()), expected)) { fail(card, "supertypes", card.getSuperType() + " != " + expected); @@ -171,6 +205,8 @@ public class VerifyCardDataTest { } private void checkTypes(Card card, JsonCard ref) { + if (skipListHaveName("TYPE", card.getName())){ return; } + Collection expected = ref.types; List type = new ArrayList<>(); for (CardType cardType : card.getCardType()) { @@ -189,6 +225,8 @@ public class VerifyCardDataTest { } private void checkPT(Card card, JsonCard ref) { + if (skipListHaveName("PT", card.getName())){ return; } + if (!eqPT(card.getPower().toString(), ref.power) || !eqPT(card.getToughness().toString(), ref.toughness)) { String pt = card.getPower() + "/" + card.getToughness(); String expected = ref.power + '/' + ref.toughness; @@ -205,6 +243,8 @@ public class VerifyCardDataTest { } private void checkCost(Card card, JsonCard ref) { + if (skipListHaveName("COST", card.getName())){ return; } + String expected = ref.manaCost; String cost = join(card.getManaCost().getSymbols()); if (cost != null && cost.isEmpty()) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChooseExpansionSetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChooseExpansionSetEffect.java new file mode 100644 index 00000000000..ba2a223a87a --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseExpansionSetEffect.java @@ -0,0 +1,102 @@ +/* + * 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.effects.common; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.repository.ExpansionRepository; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.util.CardUtil; + +/** + * @author LevelX2 (spjspj) + */ +public class ChooseExpansionSetEffect extends OneShotEffect { + + public ChooseExpansionSetEffect(Outcome outcome) { + super(outcome); + staticText = "choose an expansion set"; + } + + public ChooseExpansionSetEffect(final ChooseExpansionSetEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + + MageObject mageObject = game.getPermanentEntering(source.getSourceId()); + if (mageObject == null) { + mageObject = game.getObject(source.getSourceId()); + } + + if (controller != null) { + Choice setChoice = new ChoiceImpl(true); + setChoice.setMessage("Choose expansion set"); + List setCodes = ExpansionRepository.instance.getSetCodes(); + Set sets = new HashSet(setCodes); + + setChoice.setChoices(sets); + + while (!controller.choose(outcome, setChoice, game)) { + if (!controller.canRespond()) { + return false; + } + } + if (setChoice.getChoice() == null) { + return false; + } + if (!game.isSimulation()) { + game.informPlayers(controller.getLogName() + " has chosen set " + setChoice.getChoice()); + } + game.getState().setValue(mageObject.getId() + "_set", setChoice.getChoice()); + this.setValue("setchosen", setChoice.getChoice()); + if (mageObject instanceof Permanent) { + ((Permanent) mageObject).addInfo("chosen set", CardUtil.addToolTipMarkTags("Chosen set: " + setChoice.getChoice()), game); + } + } + return false; + } + + @Override + public ChooseExpansionSetEffect copy() { + return new ChooseExpansionSetEffect(this); + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/RollDiceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RollDiceEffect.java new file mode 100644 index 00000000000..8b3dfc5e738 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/RollDiceEffect.java @@ -0,0 +1,96 @@ +/* + * 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.effects.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.Effects; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author spjspj + */ +public class RollDiceEffect extends OneShotEffect { + + protected Effects executingEffects = new Effects(); + protected int numSides; + + public RollDiceEffect(Effect effect, int numSides) { + this(effect, Outcome.Neutral, numSides); + } + + public RollDiceEffect(Effect effect, Outcome outcome, int numSides) { + super(outcome); + addEffect(effect); + this.numSides = numSides; + } + + public RollDiceEffect(final RollDiceEffect effect) { + super(effect); + this.executingEffects = effect.executingEffects.copy(); + this.numSides = effect.numSides; + } + + public void addEffect(Effect effect) { + if (effect != null) { + executingEffects.add(effect); + } + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject mageObject = game.getObject(source.getSourceId()); + if (controller != null && mageObject != null) { + controller.rollDice(game, numSides); + return true; + } + return false; + } + + @Override + public String getText(Mode mode) { + if (!staticText.isEmpty()) { + return staticText; + } + StringBuilder sb = new StringBuilder("Roll a " + numSides + " sided dice"); + return sb.toString(); + } + + @Override + public RollDiceEffect copy() { + return new RollDiceEffect(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/SquirrellinkAbility.java b/Mage/src/main/java/mage/abilities/keyword/SquirrellinkAbility.java new file mode 100644 index 00000000000..a2c1f55e389 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/SquirrellinkAbility.java @@ -0,0 +1,66 @@ +/* +* 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.keyword; + +import mage.constants.Zone; +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; + +import java.io.ObjectStreamException; + +/** + * + * @author BetaSteward_at_googlemail.com (spjspj) + */ +public class SquirrellinkAbility extends StaticAbility implements MageSingleton { + + private static final SquirrellinkAbility instance = new SquirrellinkAbility(); + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static SquirrellinkAbility getInstance() { + return instance; + } + + private SquirrellinkAbility() { + super(Zone.ALL, null); + } + + @Override + public String getRule() { + return "Squirrellink (Damage dealt by this creature also causes you to create that many 1/1/ green Squirrel creature tokens.)"; + } + + @Override + public SquirrellinkAbility copy() { + return instance; + } +} diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 22361e38d8d..d2f8d1a2300 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -594,6 +594,9 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } else if (game.getPhase() == null) { // E.g. Commander of commander game removed = true; + } else { + // Unstable - Summon the Pack + removed = true; } break; case BATTLEFIELD: // for sacrificing permanents or putting to library diff --git a/Mage/src/main/java/mage/cards/ExpansionSet.java b/Mage/src/main/java/mage/cards/ExpansionSet.java index 0b312c96aa1..c14340386ed 100644 --- a/Mage/src/main/java/mage/cards/ExpansionSet.java +++ b/Mage/src/main/java/mage/cards/ExpansionSet.java @@ -32,6 +32,7 @@ import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.constants.Rarity; import mage.constants.SetType; +import mage.util.CardUtil; import mage.util.RandomUtil; import java.io.Serializable; @@ -82,6 +83,10 @@ public abstract class ExpansionSet implements Serializable { return this.cardNumber; } + public int getCardNumberAsInt(){ + return CardUtil.parseCardNumberAsInt(this.cardNumber); + } + public Rarity getRarity() { return this.rarity; } @@ -390,7 +395,7 @@ public abstract class ExpansionSet implements Serializable { savedCardsInfos = CardRepository.instance.findCards(criteria); // Workaround after card number is numeric if (maxCardNumberInBooster != Integer.MAX_VALUE) { - savedCardsInfos.removeIf(next -> Integer.valueOf(next.getCardNumber()) > maxCardNumberInBooster && rarity != Rarity.LAND); + savedCardsInfos.removeIf(next -> next.getCardNumberAsInt() > maxCardNumberInBooster && rarity != Rarity.LAND); } savedCards.put(rarity, savedCardsInfos); @@ -431,4 +436,6 @@ public abstract class ExpansionSet implements Serializable { savedCards.clear(); } + public int getMaxCardNumberInBooster() { return maxCardNumberInBooster; } + } diff --git a/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java index 45600162174..b5d87b6c358 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DeckImporter.java @@ -45,7 +45,14 @@ public abstract class DeckImporter { protected StringBuilder sbMessage = new StringBuilder(); //TODO we should stop using this not garbage collectable StringBuilder. It just bloats protected int lineCount; - public DeckCardLists importDeck(String file) { + + /** + * + * @param file file to import + * @param errorMessages you can setup output messages to showup to user (set null for fatal exception on messages.count > 0) + * @return decks list + */ + public DeckCardLists importDeck(String file, StringBuilder errorMessages) { File f = new File(file); DeckCardLists deckList = new DeckCardLists(); if (!f.exists()) { @@ -62,8 +69,15 @@ public abstract class DeckImporter { lineCount++; readLine(line, deckList); } + if (sbMessage.length() > 0) { - logger.fatal(sbMessage); + if(errorMessages != null) { + // normal output for user + errorMessages.append(sbMessage); + }else{ + // fatal error + logger.fatal(sbMessage); + } } } catch (Exception ex) { logger.fatal(null, ex); @@ -74,6 +88,10 @@ public abstract class DeckImporter { return deckList; } + public DeckCardLists importDeck(String file) { + return importDeck(file, null); + } + public String getErrors(){ return sbMessage.toString(); } diff --git a/Mage/src/main/java/mage/cards/decks/importer/DeckImporterUtil.java b/Mage/src/main/java/mage/cards/decks/importer/DeckImporterUtil.java index da6d4c207e6..b19a5850c07 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DeckImporterUtil.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DeckImporterUtil.java @@ -29,19 +29,48 @@ package mage.cards.decks.importer; import mage.cards.decks.DeckCardLists; +import java.io.File; +import java.util.Scanner; + /** * * @author North */ public final class DeckImporterUtil { + public static final String[] SIDEBOARD_MARKS = new String[]{"//sideboard", "sb: "}; + + public static boolean haveSideboardSection(String file){ + // search for sideboard section: + // or //sideboard + // or SB: 1 card name -- special deckstats.net + + File f = new File(file); + try (Scanner scanner = new Scanner(f)) { + while (scanner.hasNextLine()) { + String line = scanner.nextLine().trim().toLowerCase(); + + for(String mark: SIDEBOARD_MARKS){ + if (line.startsWith(mark)){ + return true; + } + } + } + } catch (Exception e) { + // ignore error, deckimporter will process it + } + + // not found + return false; + } + public static DeckImporter getDeckImporter(String file) { if (file.toLowerCase().endsWith("dec")) { return new DecDeckImporter(); } else if (file.toLowerCase().endsWith("mwdeck")) { return new MWSDeckImporter(); } else if (file.toLowerCase().endsWith("txt")) { - return new TxtDeckImporter(); + return new TxtDeckImporter(haveSideboardSection(file)); } else if (file.toLowerCase().endsWith("dck")) { return new DckDeckImporter(); } else if (file.toLowerCase().endsWith("dek")) { @@ -51,12 +80,16 @@ public final class DeckImporterUtil { } } - public static DeckCardLists importDeck(String file) { + public static DeckCardLists importDeck(String file, StringBuilder errorMessages) { DeckImporter deckImporter = getDeckImporter(file); if (deckImporter != null) { - return deckImporter.importDeck(file); + return deckImporter.importDeck(file, errorMessages); } else { return new DeckCardLists(); } } + + public static DeckCardLists importDeck(String file) { + return importDeck(file, null); + } } diff --git a/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java index 089fbb1a6db..de7904451ee 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/TxtDeckImporter.java @@ -47,25 +47,60 @@ public class TxtDeckImporter extends DeckImporter { public static final Set IGNORE_NAMES = new HashSet<>(Arrays.asList(SET_VALUES)); private boolean sideboard = false; + private boolean switchSideboardByEmptyLine = true; // all cards after first empty line will be sideboard (like mtgo format) private int nonEmptyLinesTotal = 0; + public TxtDeckImporter(boolean haveSideboardSection){ + if(haveSideboardSection){ + switchSideboardByEmptyLine = false; + } + } + @Override protected void readLine(String line, DeckCardLists deckList) { - if (line.toLowerCase().contains("sideboard")) { - sideboard = true; - return; - } - if (line.startsWith("//")) { + + line = line.trim(); + + // process comment: + // skip or force to sideboard + String commentString = line.toLowerCase(); + if (commentString.startsWith("//")){ + // use start, not contains (card names may contain commands like "Legerdemain") + + if (commentString.startsWith("//sideboard")) { + sideboard = true; + } + + // skip comment line return; } - // Start the sideboard on empty line that follows - // at least 1 non-empty line - if (line.isEmpty() && nonEmptyLinesTotal > 0) { - sideboard = true; + // remove inner card comments from text line: 2 Blinding Fog #some text (like deckstats format) + int commentDelim = line.indexOf('#'); + if(commentDelim >= 0){ + line = line.substring(0, commentDelim).trim(); + } + + // switch sideboard by empty line + if (switchSideboardByEmptyLine && line.isEmpty() && nonEmptyLinesTotal > 0) { + if(!sideboard){ + sideboard = true; + }else{ + sbMessage.append("Found empty line at ").append(lineCount).append(", but sideboard already used. Use //sideboard switcher OR one empty line to devide your cards.").append('\n'); + } + + // skip empty line return; - } else { - nonEmptyLinesTotal++; + } + + nonEmptyLinesTotal++; + + // single line sideboard card from deckstats.net + // SB: 3 Carnage Tyrant + boolean singleLineSideBoard = false; + if (line.startsWith("SB:")){ + line = line.replace("SB:", "").trim(); + singleLineSideBoard = true; } line = line.replace("\t", " "); // changing tabs to blanks as delimiter @@ -87,12 +122,17 @@ public class TxtDeckImporter extends DeckImporter { } try { int num = Integer.parseInt(lineNum.replaceAll("\\D+", "")); + if ((num < 0) || (num > 100)){ + sbMessage.append("Invalid number (too small or too big): ").append(lineNum).append(" at line ").append(lineCount).append('\n'); + return; + } + CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(lineName, true); if (cardInfo == null) { sbMessage.append("Could not find card: '").append(lineName).append("' at line ").append(lineCount).append('\n'); } else { for (int i = 0; i < num; i++) { - if (!sideboard) { + if (!sideboard && !singleLineSideBoard) { deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); } else { deckList.getSideboard().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); @@ -103,5 +143,4 @@ public class TxtDeckImporter extends DeckImporter { sbMessage.append("Invalid number: ").append(lineNum).append(" at line ").append(lineCount).append('\n'); } } - } diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index 1600f5b82ad..7a725647d13 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -40,6 +40,7 @@ import mage.cards.*; import mage.cards.mock.MockCard; import mage.cards.mock.MockSplitCard; import mage.constants.*; +import mage.util.CardUtil; import mage.util.SubTypeList; import org.apache.log4j.Logger; @@ -374,6 +375,10 @@ public class CardInfo { return cardNumber; } + public int getCardNumberAsInt() { + return CardUtil.parseCardNumberAsInt(cardNumber); + } + public boolean isSplitCard() { return splitCard; } diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 59517a91ff3..7f50dec4965 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -58,7 +58,7 @@ public enum CardRepository { // raise this if db structure was changed private static final long CARD_DB_VERSION = 51; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 95; + private static final long CARD_CONTENT_VERSION = 96; private Dao cardDao; private Set classNames; diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 755d1ea658f..4766d9114d4 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -72,6 +72,7 @@ public enum SubType { BITH("Bith", SubTypeSet.CreatureType, true), // Star Wars BLINKMOTH("Blinkmoth", SubTypeSet.CreatureType), BOAR("Boar", SubTypeSet.CreatureType), + BRAINIAC("Brainiac", SubTypeSet.CreatureType, true), // Unstable BRINGER("Bringer", SubTypeSet.CreatureType), BRUSHWAGG("Brushwagg", SubTypeSet.CreatureType), // C @@ -177,7 +178,8 @@ public enum SubType { KALEESH("Kaleesh", SubTypeSet.CreatureType, true), // Star Wars KAVU("Kavu", SubTypeSet.CreatureType), KELDOR("KelDor", SubTypeSet.CreatureType, true), - KIRIN("Kirin", SubTypeSet.CreatureType), + KILLBOT("Killbot", SubTypeSet.CreatureType, true), // Unstable + KIRIN("Kirin", SubTypeSet.CreatureType), KITHKIN("Kithkin", SubTypeSet.CreatureType), KNIGHT("Knight", SubTypeSet.CreatureType), KOBOLD("Kobold", SubTypeSet.CreatureType), @@ -268,6 +270,7 @@ public enum SubType { SAPROLING("Saproling", SubTypeSet.CreatureType), SATYR("Satyr", SubTypeSet.CreatureType), SCARECROW("Scarecrow", SubTypeSet.CreatureType), + SCIENTIST("Scientist", SubTypeSet.CreatureType, true), // Unstable SCION("Scion", SubTypeSet.CreatureType), SCORPION("Scorpion", SubTypeSet.CreatureType), SCOUT("Scout", SubTypeSet.CreatureType), diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 45b7894b720..d26bc9ec608 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -578,6 +578,9 @@ public class Combat implements Serializable, Copyable { * @param game */ private void retrieveMustBlockAttackerRequirements(Player attackingPlayer, Game game) { + if (attackingPlayer == null) { + return; + } if (!game.getContinuousEffects().existRequirementEffects()) { return; } diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index b6b65c48476..34c8357a0dc 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -229,6 +229,7 @@ public class GameEvent implements Serializable { ENCHANT_PLAYER, ENCHANTED_PLAYER, CAN_TAKE_MULLIGAN, FLIP_COIN, COIN_FLIPPED, SCRY, FATESEAL, + ROLL_DICE, DICE_ROLLED, PAID_CUMULATIVE_UPKEEP, DIDNT_PAY_CUMULATIVE_UPKEEP, //permanent events diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index c30051d825d..a5e5b9d59c1 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -52,6 +52,7 @@ import mage.game.combat.CombatGroup; import mage.game.command.CommandObject; import mage.game.events.*; import mage.game.events.GameEvent.EventType; +import mage.game.permanent.token.SquirrelToken; import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.players.Player; @@ -802,6 +803,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { if (dealtDamageByThisTurn == null) { dealtDamageByThisTurn = new HashSet<>(); } + // Unstable ability - Earl of Squirrel + if (sourceAbilities.containsKey(SquirrellinkAbility.getInstance().getId())) { + Player player = game.getPlayer(sourceControllerId); + new SquirrelToken().putOntoBattlefield(damageDone, game, sourceId, player.getId()); + } dealtDamageByThisTurn.add(new MageObjectReference(source, game)); } if (source == null) { diff --git a/Mage/src/main/java/mage/game/permanent/token/StormCrowToken.java b/Mage/src/main/java/mage/game/permanent/token/StormCrowToken.java new file mode 100644 index 00000000000..a06ffdb32eb --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/StormCrowToken.java @@ -0,0 +1,50 @@ +/* +* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this list of +* conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this list +* of conditions and the following disclaimer in the documentation and/or other materials +* provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The views and conclusions contained in the software and documentation are those of the +* authors and should not be interpreted as representing official policies, either expressed +* or implied, of BetaSteward_at_googlemail.com. + */ +package mage.game.permanent.token; + +import mage.constants.CardType; +import mage.constants.SubType; +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; + +/** + * + * @author spjspj + */ +public class StormCrowToken extends Token { + + public StormCrowToken() { + super("Storm Crow", "1/2 blue Bird creature token with flying named Storm Crow"); + cardType.add(CardType.CREATURE); + color.setBlue(true); + subtype.add(SubType.BIRD); + power = new MageInt(1); + toughness = new MageInt(2); + this.addAbility(FlyingAbility.getInstance()); + } +} diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 71478eb2f98..eb72ad1fac4 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -417,6 +417,10 @@ public interface Player extends MageItem, Copyable { boolean flipCoin(Game game, ArrayList appliedEffects); + int rollDice(Game game, int numSides); + + int rollDice(Game game, ArrayList appliedEffects, int numSides); + @Deprecated void discard(int amount, Ability source, Game game); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 7168fcbcd69..423aee4372d 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -80,6 +80,7 @@ import mage.game.events.ZoneChangeEvent; import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; +import mage.game.permanent.token.SquirrelToken; import mage.game.stack.Spell; import mage.game.stack.StackAbility; import mage.game.stack.StackObject; @@ -1841,6 +1842,11 @@ public abstract class PlayerImpl implements Player, Serializable { Player player = game.getPlayer(sourceControllerId); player.gainLife(actualDamage, game); } + // Unstable ability - Earl of Squirrel + if (sourceAbilities.containsKey(SquirrellinkAbility.getInstance().getId())) { + Player player = game.getPlayer(sourceControllerId); + new SquirrelToken().putOntoBattlefield(actualDamage, game, sourceId, player.getId()); + } game.fireEvent(new DamagedPlayerEvent(playerId, sourceId, playerId, actualDamage, combatDamage)); return actualDamage; } @@ -2332,6 +2338,34 @@ public abstract class PlayerImpl implements Player, Serializable { return event.getFlag(); } + @Override + public int rollDice(Game game, int numSides) { + return this.rollDice(game, null, numSides); + } + + /** + * @param game + * @param appliedEffects + * @return the number that the player rolled + */ + @Override + public int rollDice(Game game, ArrayList appliedEffects, int numSides) { + int result = RandomUtil.nextInt(numSides) + 1; + if (!game.isSimulation()) { + game.informPlayers("[Roll a die] " + getLogName() + " rolled a " + result + " on a " + numSides + " sided dice"); + } + GameEvent event = new GameEvent(GameEvent.EventType.ROLL_DICE, playerId, null, playerId, result, true); + event.setAppliedEffects(appliedEffects); + event.setAmount(result); + event.setData(numSides + ""); + if (!game.replaceEvent(event)) { + GameEvent ge = new GameEvent(GameEvent.EventType.DICE_ROLLED, playerId, null, playerId, event.getAmount(), event.getFlag()); + ge.setData(numSides + ""); + game.fireEvent(ge); + } + return event.getAmount(); + } + @Override public List getAvailableAttackers(Game game) { // TODO: get available opponents and their planeswalkers, check for each if permanent can attack one diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index a91dd87bd07..301a26e83f2 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -382,6 +382,25 @@ public final class CardUtil { return true; } + + /** + * Parse card number as int (support base [123] and alternative numbers [123b]). + * + * @param cardNumber origin card number + * @return int + */ + public static int parseCardNumberAsInt(String cardNumber){ + + if (cardNumber.isEmpty()){ throw new IllegalArgumentException("Card number is empty.");} + + if(Character.isDigit(cardNumber.charAt(cardNumber.length() - 1))) + { + return Integer.parseInt(cardNumber); + }else{ + return Integer.parseInt(cardNumber.substring(0, cardNumber.length() - 1)); + } + } + /** * Creates and saves a (card + zoneChangeCounter) specific exileId. * diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 73f297ad8f9..113a6e90685 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -32701,4 +32701,66 @@ Tetzimoc, Primal Death|Rivals of Ixalan|86|R|{4}{B}{B}|Legendary Creature - Elde Ghalta, Primal Hunger|Rivals of Ixalan|130|R|{10}{G}{G}|Legendary Creature - Elder Dinosaur|12|12|Ghalta, Primal Hunger costs {X} less to cast, where X is the total power of creatures you control.$Trample| Storm the Vault|Rivals of Ixalan|173|R|{2}{U}{R}|Legendary Enchantment|||Whenever one or more creatures you control deal combat damage to a player, create a colorless Treasure artifact token with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."$At the beginning of your end step, if you control five or more artifacts, transform Storm the Vault.| Vault of Catlacan|Rivals of Ixalan|173|R||Legendary Land|||(Transforms from Storm the Vault.)${T}: Add one mana of any color to your mana pool.${T}: Add {U} to your mana pool for each artifact you control.| -The Immortal Sun|Rivals of Ixalan|180|M|{6}|Legendary Artifact|||Players can't activate loyalty abilities of planeswalkers.$At the beginning of your draw step, draw an additional card.$Spells you cast cost {1} less to cast.$Creatures you control get +1/+1.| \ No newline at end of file +The Immortal Sun|Rivals of Ixalan|180|M|{6}|Legendary Artifact|||Players can't activate loyalty abilities of planeswalkers.$At the beginning of your draw step, draw an additional card.$Spells you cast cost {1} less to cast.$Creatures you control get +1/+1.| +Amateur Auteur|Unstable|3|C|{1}{W}|Creature - Human|2|2|Sacrifice Amateur Auteur: Destroy target enchantment.| +Angelic Rocket|Unstable|139|R|{8}|Host Creature - Angel|4|4|Flying$When this creature enters the battlefield, you may destroy target nonland permanent.| +As Luck Would Have It|Unstable|102|R|{G}|Enchantment|||Hexproof$Whenever you roll a die, put a number of luck counters on As Luck Would Have It equal to the result. Then is there are 100 or more luck counters on As Luck Would Have It, you win the game. (Count both rolls if you reroll a die.)| +Baron Von Count|Unstable|127|M|{1}{B}{R}|Legendary Creature - Human Villain|3|3|Baron Von Count enters the battlefield with a doom counter on "5."$Whenever you cast a spell with the indicated numeral in its mana cost, text box, power, or toughness, move the doom counter one numeral to the left.$When the doom counter moves from "1," destroy target player and put that doom counter on "5."| +Big Boa Constrictor|Unstable|51|C|{3}{B}|Host Creature - Snake|1|2|When this creature enters the battlefield, roll a six-sided die. Target opponent loses life equal to the result.| +Bumbling Pangolin|Unstable|78|C|{3}{R}|Host Creature - Beast|2|2|When this creature enters the battlefield, you may destroy target artifact.| +Buzzing Whack-a-Doodle|Unstable|141|U|{4}|Artifact|||As Buzzing Whack-a-Doodle enters the battlefield, you and an opponent each secretly choose Whack or Doodle. Then those choices are revealed. If the choices match, Buzzing Whack-a-Doodle has that ability. Otherwise it has Buzz.$*Whack - T: Target player loses 2 life. $*Doodle - T: You gain 3 life.$*Buzz - 2, T: Draw a card.| +Chittering Doom|Unstable|104|U|{3}{G}|Enchantment|||Whenever you roll a 4 or higher on a die, create a 1/1 green Squirrel creature token.| +Common Iguana|Unstable|79|C|{1}{R}|Host Creature - Lizard|1|3|When this creature enters the battlefield, you may discard a card. If you do, draw a card.| +Contraption Cannon|Unstable|144|U|{4}|Artifact|||2, Sacrifice Contraption Cannon: It deals damage to target creature or player equal to the number of Contraptions you control.| +Crow Storm|Unstable|31|U|{2}{U}|Sorcery|||Create a 1/2 blue Bird creature token with flying named Storm Crow.$Storm (When you cast this spell, copy it for each spell cast before it this turn.)| +Curious Killbot|Unstable|145|C|{2}|Artifact Creature - Killbot|2|1|| +Dirty Rat|Unstable|53|C|{1}{B}|Host Creature - Rat|1|1|When this creature enters the battlefield, target opponent discards a card.| +Eager Beaver|Unstable|107|C|{2}{G}|Host Creature - Beaver|3|2|When this creature enters the battlefield, you may untap target permanent.| +Earl of Squirrel|Unstable|108|R|{4}{G}{G}|Creature - Squirrel Advisor|4|4|Squirrellink (Damage dealt by this creature also causes you to create that many 1/1 green Squirrel creature tokens.)$Creature tokens you control are Squirrels in addition to their other creature types.$Other Squirrels you control get +1/+1.| +Feisty Stegosaurus|Unstable|81|C|{4}{R}|Host Creature - Dinosaur|2|1|When this creature enters the battlefield, roll a six-sided die. This creature deals damage equal to the result to target creature an opponent controls.| +Forest|Unstable|216|C||Basic Land - Forest|||G| +GO TO JAIL|Unstable|8|C|{W}|Enchantment|||When GO TO JAIL enters the battlefield, exile target creature an opponent controls until GO TO JAIL leaves the battlefield.$At the beginning of the upkeep of the exiled card's owner, that player rolls two six-sided dice. If he or she rolls doubles, sacrifice GO TO JAIL.| +Gnome-Made Engine|Unstable|148|C|{4}|Host Creature - Construct|2|2|When this creature enters the battlefield, create a 1/1 colorless Gnome artifact creature token.| +Ground Pounder|Unstable|110|C|{1}{G}|Creature - Goblin Warrior|2|2|3G: Roll a six-sided die. Ground Pounder gets +X/+X until end of turn, where X is the result.$Whenever you roll a 5 or higher on a die, Ground Pounder gains trample until end of turn.| +Hammer Helper|Unstable|85|C|{3}{R}|Sorcery|||Gain control of target creature until end of turn. Untap that creature and roll a six-sided die. Until end of turn, it gains haste and gets +X/+0, where X is the result.| +Hammer Jammer|Unstable|86|U|{3}{R}|Creature - Goblin Warrior|0|0|As Hammer Jammer enters the battlefield, roll a six-sided die. Hammer Jammer enters the battlefield with a number of +1/+1 counters on it equal to the result.$Whenever you roll a die, remove all +1/+1 counters from Hammer Jammer, then put a number of +1/+1 counters on it equal to the result.| +Hangman|Unstable|56|R|{B}|Creature - Human Villain|1|1|As Hangman enters the battlefield, secretly note a word with six to eight letters.$1: Target player who doesn't control Hangman guesses he noted word or an unguessed letter in that word. If he or she guesses wrong, put a +1/+1 counter on Hangman. Any player may activate this ability.$When a player guesses the noted word of all of its letters, sacrifice Hangman.| +Hazmat Suit (Used)|Unstable|57|C|{3}{B}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +2/+1 and has menace.$Whenever a player's skin or fingernail touches enchanted creature, that player loses 2 life.| +Hot Fix|Unstable|133|R|{4}{W}{U}|Sorcery||| You have ten seconds to look at and rearrange the cards in your library. At the end of those ten seconds, if you're touching one or more of those cards, shuffle your library.| +Hydradoodle|Unstable|112|R|{X}{X}{G}{G}|Creature - Hydra Hound|0|0|As Hydradoodle enters the battlefield, roll X six-sided dice. Hydradoodle enters the battlefield with s number of +1/+1 counters on it equal to the total of those results.$Reach, trample| +Incite Insight|Unstable|36|R|{X}{U}{U}|Sorcery|||Assemble X Contraptions.| +Inhumaniac|Unstable|59|U|{1}{B}|Creature - Brainiac|1|1|At the beginning of your upkeep, roll a six-sided die. On a 3 or 4, put a +1/+1 counter on Inhumaniac. On a 5 or higher, put two +1/+1 counters on it. On a 1, remove all +1/+1 counters from Inhumaniac.| +Island|Unstable|213|C||Basic Land - Island|||U| +Jackknight|Unstable|11|R|{1}{W}|Artifact Creature - Cyborg Knight|1|1|Whenever another artifact enters the battlefield under your control, put a +1/+1 counter on Jackknight. If that artifact is a Contraption, Jackknight gains lifelink until end of turn.| +Krark's Other Thumb|Unstable|151|U|{2}|Legendary Artifact|||If you would roll a die, instead roll two of those dice and ignore one of those results.| +Lobe Lobber|Unstable|153|U|{2}|Artifact - Equipment|||Equipped creature has "T: This creature deals 1 damage to target player. Roll a six-sided die. On a 5 or higher, untap it."$Equip 2| +Mad Science Fair Project|Unstable|154|C|{3}|Artifact|||T: Roll a six-sided die. On a 3 or lower, target player adds {C} to his or her mana pool. Otherwise, that player adds one mana of any color he or she chooses to his or her mana pool.| +Mer Man|Unstable|39|C|{4}{U}|Host Creature - Human Fish|3|3|When this creature enters the battlefield, you may draw a card.| +Mountain|Unstable|215|C||Basic Land - Mountain|||R| +Numbing Jellyfish|Unstable|42|C|{3}{U}|Host Creature - Jellyfish|2|3|When this creature enters the battlefield, roll a six-sided die. Target player puts the top X cards of his or her library into his or her graveyard, where X is the result.| +Ordinary Pony|Unstable|17|C|{2}{W}|Host Creature - Horse|2|3|When this creature enters the battlefield, you may exile target non-Horse creature you control, then return it to the battlefield under its owner's control.| +Painiac|Unstable|91|C|{2}{R}|Creature - Brainiac|0|3|At the beginning of your upkeep, roll a six-sided die. Painiac gets +X/+0 until end of turn, where X is the result.| +Plains|Unstable|212|C||Basic Land - Plains|||W| +Shaggy Camel|Unstable|22|C|{3}{W}|Host Creature - Camel|3|3|When this creature enters the battlefield, creatures you control get +1/+1 until end of turn.| +Skull Saucer|Unstable|66|U|{4}{B}{B}|Creature - Zombie Head|4|1|Flying$When Skull Saucer enters the battlefield, destroy target creature and put your head on the table. Sacrifice Skull Saucer when your head stops touching the table.| +Snickering Squirrel|Unstable|68|C|{B}|Creature - Squirrel Advisor|1|1|You may tap Snickering Squirrel to increase the result of a die any player rolled by 1.| +Split Screen|Unstable|158|R|{4}|Artifact|||When Split Screen enters the battlefield, shuffle your library and deal it into four libraries. If anything refers to your library, choose one of your libraries for it.$Play with your libraries' top cards revealed.$When Split Screen leaves the battlefield, shuffle your libraries together.| +Spy Eye|Unstable|46|U|{2}{U}{U}|Creature - Eye Spy|1|3|Flying$Whenever Spy Eye deals combat damage to a player, you may draw a card from that player's library.| +Squirrel-Powered Scheme|Unstable|70|U|{2}{B}|Enchantment|||Increase the result of each die you roll by 2.| +Box of Freerange Goblins|Unstable|77|C|{4}{R}{R}|Sorcery |||Roll a six-sided die. Create a number of 1/1 red Goblin creature tokens equal to the result.| +Staff of the Letter Magus|Unstable|159|U|{3}|Artifact|||As Staff of the Letter Magus enters the battlefield, choose a consonant other than N, R, S, or T.$Whenever a player casts a spell, you gain 1 life for each time the chosen letter appears in that spell's name.| +Steamflogger Boss|Unstable|93|R|{3}{R}|Creature - Goblin Rigger|3|3|Other Riggers you control get +1/+0 and have haste.$If a Rigger you control would assemble a Contraption, it assembles two Contraptions instead.| +Steamflogger Service Rep|Unstable|124|U|{2}{G}|Creature - Goblin Rigger|1|1|Whenever another Goblin enters the battlefield under your control, you may pay 1. If you do, Steamflogger Service Rep assembles a Contraption.| +Steel Squirrel|Unstable|162|U|{2}|Artifact Creature - Squirrel|1|1|Whenever you roll a 5 or higher on a die, Steel Squirrel gets +X/+X until end of turn, where X is the result.$6: Roll a six-sided die.| +Stinging Scorpion|Unstable|72|C|{4}{B}|Host Creature - Scorpion|3|2|When this creature enters the battlefield, target creature an opponent controls gets -1/-1 until end of turn.| +Summon the Pack|Unstable|74|M|{7}{B}|Sorcery|||Open a sealed Magic booster pack, reveal the cards, and put all creature cards revealed this way onto the battlefield under your control. They're Zombies in addition to their other types. (Remove those cards from your deck before beginning a new game)| +Swamp|Unstable|214|C||Basic Land - Swamp|||B| +Sword of Dungeons and Dragons|Unstable|1|M|{3}|Artifact - Equipment|||Equipped creature gets +2/+2 and has protection from Rogues and from Clerics.$Whenever equipped creature deals combat damage to a player, create a 4/4 gold Dragon creature token with flying and roll a d20. If you roll a 20, repeat this process.$Equip {2}| +Target Minotaur|Unstable|98|C|{1}{R}|Creature - Minotaur Warrior|2|1|Prowess| +The Big Idea|Unstable|76|R|{4}{R}{R}|Legendary Creature - Brainiac Villain|4|4|2{BR}{BR}, T: Roll a six-sided dice. Create a number of 1/1 red Brainiac creature tokens equal to the result. $Tap three untapped Brainiacs you control: The next time you would roll a six-sided die, instead roll two six-sided dice and use the total of those results.| +Time Out|Unstable|48|C|{4}{U}|Instant|||Roll a six-sided die. Put target nonland permanent into its owner's library just beneath the top X cards of that library, where X is the result.| +Voracious Vacuum|Unstable|164|C|{3}|Host Creature - Construct|1|1|When this creature enters the battlefield, put a +1/+1 counter on target creature.| +Wall of Fortune|Unstable|50|C|{1}{U}|Artifact Creature - Wall|0|4|Defender$You may tap an untapped Wall you control to have any player reroll a die that player rolled.| +Wild Crocodile|Unstable|125|C|{1}{G}|Host Creature - Crocodile|1|1|When this creature enters the battlefield, search your library for a basic land card, reveal it, put it into your hand, then shuffle your library.| +Willing Test Subject|Unstable|126|C|{2}{G}|Creature- Spider Monkey Scientist|2|2|Reach$Whenever you roll a 4 or higher on a die, put a +1/+1 counter on Willing Test Subject.$6: Roll a six-sided die.| +Garbage Elemental|Unstable|82|U|{4}{R}|Creature - Elemental|3|2|Battle cry (Whenever this creature attacks, each other attacking creature gets +1/+0 until end of turn.)$When Garbage Elemental enters the battlefield, roll two six-sided dice. Create a number of 1/1 red Goblin creature tokens equal to the difference between those results.|