Merge pull request #48 from magefree/master

Merge https://github.com/magefree/mage
This commit is contained in:
L_J 2018-04-03 22:37:58 +02:00 committed by GitHub
commit e92df28fbf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1461 additions and 304 deletions

View file

@ -80,7 +80,7 @@
1 [C16:104] Windfall
1 [C16:148] Far Wanderings
1 [C16:46] Thrasios, Triton Hero
1 [C16:346] Mountain
2 [C16:346] Mountain
2 [C16:347] Mountain
1 [C16:348] Mountain
1 [C16:105] Army of the Damned

View file

@ -66,6 +66,7 @@
1 [C17:158] Soul's Majesty
1 [C17:73] Spirit of the Hearth
1 [C17:224] Staff of Nin
1 [C17:7] Stalking Leonin
1 [C17:281] Stirring Wildwood
1 [C17:75] Sunspear Shikari
1 [C17:226] Swiftfoot Boots

View file

@ -1,23 +1,8 @@
NAME:Mormir Basic
3 [BFZ:259] Island
3 [BFZ:261] Swamp
3 [BFZ:250] Plains
3 [BFZ:272] Forest
3 [BFZ:260] Swamp
3 [BFZ:271] Forest
3 [BFZ:270] Forest
3 [BFZ:265] Mountain
2 [BFZ:254] Plains
3 [BFZ:264] Swamp
3 [BFZ:274] Forest
1 [BFZ:252] Plains
3 [BFZ:262] Swamp
3 [BFZ:251] Plains
2 [BFZ:273] Forest
3 [BFZ:258] Island
2 [BFZ:269] Mountain
3 [BFZ:268] Mountain
3 [BFZ:257] Island
3 [BFZ:267] Mountain
3 [BFZ:266] Mountain
2 [BFZ:255] Island
12 [BFZ:250a] Plains
12 [BFZ:260a] Swamp
12 [BFZ:270a] Forest
12 [BFZ:265a] Mountain
12 [BFZ:255a] Island
LAYOUT MAIN:(1,5)(COLOR_IDENTITY,true,5)|([BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a])([BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a])([BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a])([BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a])([BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a])
LAYOUT SIDEBOARD:(0,0)(COLOR,true,5)|

View file

@ -0,0 +1,28 @@
2 [XLN:31] Raptor Companion
1 [XLN:135] Burning Sun's Avatar
2 [XLN:38] Slash of Talons
2 [XLN:36] Shining Aerosaur
1 [XLN:13] Goring Ceratops
3 [XLN:274] Mountain
3 [XLN:273] Mountain
2 [XLN:133] Bonded Horncrest
2 [XLN:275] Mountain
2 [XLN:30] Rallying Roar
3 [XLN:272] Mountain
2 [XLN:28] Pterodon Knight
2 [XLN:149] Lightning Strike
2 [XLN:146] Frenzied Raptor
4 [XLN:289] Stone Quarry
2 [XLN:288] Sun-Blessed Mount
2 [XLN:169] Tilonalli's Knight
1 [XLN:285] Huatli, Dinosaur Knight
2 [XLN:263] Plains
3 [XLN:262] Plains
3 [XLN:287] Huatli's Spurring
4 [XLN:286] Huatli's Snubhorn
2 [XLN:41] Territorial Hammerskull
3 [XLN:261] Plains
3 [XLN:260] Plains
2 [XLN:18] Kinjalli's Caller
LAYOUT MAIN:(1,4)(CARD_TYPE,false,50)|([XLN:13],[XLN:288],[XLN:288],[XLN:135],[XLN:18],[XLN:18],[XLN:286],[XLN:286],[XLN:286],[XLN:286],[XLN:31],[XLN:31],[XLN:169],[XLN:169],[XLN:146],[XLN:146],[XLN:41],[XLN:41],[XLN:133],[XLN:133],[XLN:28],[XLN:28],[XLN:36],[XLN:36])([XLN:287],[XLN:287],[XLN:287],[XLN:38],[XLN:38],[XLN:149],[XLN:149],[XLN:30],[XLN:30])([XLN:289],[XLN:289],[XLN:289],[XLN:289],[XLN:260],[XLN:260],[XLN:260],[XLN:261],[XLN:261],[XLN:261],[XLN:262],[XLN:262],[XLN:262],[XLN:263],[XLN:263],[XLN:272],[XLN:272],[XLN:272],[XLN:273],[XLN:273],[XLN:273],[XLN:274],[XLN:274],[XLN:274],[XLN:275],[XLN:275])([XLN:285])
LAYOUT SIDEBOARD:(0,0)(NONE,false,50)|

View file

@ -0,0 +1,23 @@
4 [HOU:193] Island
4 [HOU:192] Island
2 [AKH:41] Angler Drake
2 [HOU:29] Aerial Guide
2 [AKH:209] Weaver of Currents
2 [HOU:30] Aven Reedstalker
2 [AKH:219] Spring // Mind
4 [HOU:204] Woodland Stream
3 [HOU:54] Unsummon
3 [HOU:201] Avid Reclaimer
3 [AKH:179] Pouncing Cheetah
1 [HOU:200] Nissa, Genesis Mage
2 [HOU:203] Nissa's Encouragement
2 [HOU:115] Feral Prowler
4 [HOU:202] Brambleweft Behemoth
1 [AKH:196] Bounty of the Luxa
7 [HOU:199] Forest
1 [HOU:154] Reason // Believe
2 [HOU:143] River Hoopoe
2 [HOU:110] Ambuscade
7 [HOU:198] Forest
LAYOUT MAIN:(1,7)(CARD_TYPE,false,50)|([HOU:115],[HOU:115],[HOU:143],[HOU:143],[HOU:201],[HOU:201],[HOU:201],[AKH:179],[AKH:179],[AKH:179],[HOU:29],[HOU:29],[AKH:209],[AKH:209],[HOU:30],[HOU:30],[HOU:202],[HOU:202],[HOU:202],[HOU:202],[AKH:41],[AKH:41])([AKH:196])([AKH:219],[AKH:219])([HOU:54],[HOU:54],[HOU:54],[HOU:110],[HOU:110])([HOU:204],[HOU:204],[HOU:204],[HOU:204],[HOU:192],[HOU:192],[HOU:192],[HOU:192],[HOU:193],[HOU:193],[HOU:193],[HOU:193],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:199],[HOU:199],[HOU:199],[HOU:199],[HOU:199],[HOU:199],[HOU:199])([HOU:200])([HOU:154],[HOU:203],[HOU:203])
LAYOUT SIDEBOARD:(0,0)(NONE,false,50)|

View file

@ -104,6 +104,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
private static final String LITE_MODE_ARG = "-lite";
private static final String GRAY_MODE_ARG = "-gray";
private static final String FILL_SCREEN_ARG = "-fullscreen";
private static final String SKIP_DONE_SYMBOLS = "-skipDoneSymbols";
private static final String NOT_CONNECTED_TEXT = "<not connected>";
private static MageFrame instance;
@ -121,6 +122,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
//TODO: make gray theme, implement theme selector in preferences dialog
private static boolean grayMode = false;
private static boolean fullscreenMode = false;
private static boolean skipSmallSymbolGenerationForExisting = false;
private static final Map<UUID, ChatPanelBasic> CHATS = new HashMap<>();
private static final Map<UUID, GamePanel> GAMES = new HashMap<>();
@ -153,6 +155,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
return grayMode;
}
public static boolean isSkipSmallSymbolGenerationForExisting() {
return skipSmallSymbolGenerationForExisting;
}
@Override
public MageVersion getVersion() {
return VERSION;
@ -1191,6 +1197,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
if (arg.startsWith(FILL_SCREEN_ARG)) {
fullscreenMode = true;
}
if (arg.startsWith(SKIP_DONE_SYMBOLS)) {
skipSmallSymbolGenerationForExisting = true;
}
}
if (!liteMode) {
final SplashScreen splash = SplashScreen.getSplashScreen();

View file

@ -1084,6 +1084,22 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
repaint();
}
private void chooseMatching() {
Collection<CardView> toMatch = dragCardList();
for (DragCardGridListener l : listeners) {
for (CardView card : allCards) {
for (CardView aMatch : toMatch) {
if (card.getName().equals(aMatch.getName())) {
card.setSelected(true);
cardViews.get(card.getId()).update(card);
}
}
}
}
repaint();
}
private void showAll() {
for (DragCardGridListener l : listeners) {
l.showAll();
@ -1705,6 +1721,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
invertSelection.addActionListener(e2 -> invertSelection());
menu.add(invertSelection);
JMenuItem chooseMatching = new JMenuItem("Choose Matching");
chooseMatching.addActionListener(e2 -> chooseMatching());
menu.add(chooseMatching);
// Show 'Duplicate Selection' for FREE_BUILDING
if (this.mode == Constants.DeckEditorMode.FREE_BUILDING) {
JMenuItem duplicateSelection = new JMenuItem("Duplicate Selection");

View file

@ -8,10 +8,15 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import javax.swing.JPanel;
import javax.swing.Timer;
import mage.client.util.Command;
/**
@ -43,6 +48,7 @@ public class HoverButton extends JPanel implements MouseListener {
private Image topTextImageRight;
private String centerText;
private boolean wasHovered = false;
private boolean isHovered = false;
private boolean isSelected = false;
private boolean drawSet = false;
@ -52,7 +58,8 @@ public class HoverButton extends JPanel implements MouseListener {
private Command onHover = null;
private Color textColor = Color.white;
private final Rectangle centerTextArea = new Rectangle(5, 18, 75, 40);
private final Color centerTextColor = Color.YELLOW;
private Color centerTextColor = new Color(200, 210, 0, 200);
private Color origCenterTextColor = new Color(200, 210, 0, 200);
private final Color textBGColor = Color.black;
static final Font textFont = new Font("Arial", Font.PLAIN, 12);
@ -64,6 +71,13 @@ public class HoverButton extends JPanel implements MouseListener {
private boolean alignTextLeft = false;
Timer faderGainLife = null;
Timer faderLoseLife = null;
private int loseX = 0;
private int gainX = 0;
private boolean doLoseFade = true;
private boolean doGainFade = true;
public HoverButton(String text, Image image, Rectangle size) {
this(text, image, image, null, image, size);
if (image == null) {
@ -95,6 +109,10 @@ public class HoverButton extends JPanel implements MouseListener {
Graphics2D g2d = (Graphics2D) g;
if (isEnabled()) {
if (isHovered || textAlwaysVisible) {
if (isHovered) {
wasHovered = true;
setCenterColor(Color.YELLOW);
}
g.drawImage(hoverImage, 0, 0, imageSize.width, imageSize.height, this);
if (text != null) {
if (textColor != null) {
@ -109,6 +127,10 @@ public class HoverButton extends JPanel implements MouseListener {
g2d.drawString(text, textOffsetX, textOffsetY);
}
} else {
if (wasHovered) {
wasHovered = false;
setCenterColor(origCenterTextColor);
}
g.drawImage(image, 0, 0, imageSize.width, imageSize.height, this);
}
if (isSelected) {
@ -151,7 +173,7 @@ public class HoverButton extends JPanel implements MouseListener {
} else if (val > 99) {
fontSize = 34;
}
drawCenteredString(g2d, centerText, centerTextArea, new Font("Arial", Font.BOLD, fontSize));
drawCenteredStringWOutline(g2d, centerText, centerTextArea, new Font("Arial", Font.BOLD, fontSize));
}
g2d.setColor(textColor);
if (overlayImage != null) {
@ -175,6 +197,10 @@ public class HoverButton extends JPanel implements MouseListener {
}
}
public void setCenterColor(Color c) {
centerTextColor = c;
}
private int calculateOffset(Graphics2D g2d) {
if (textOffsetX == -1) { // calculate once
FontRenderContext frc = g2d.getFontRenderContext();
@ -345,7 +371,7 @@ public class HoverButton extends JPanel implements MouseListener {
* @param rect The Rectangle to center the text in.
* @param font
*/
public void drawCenteredString(Graphics g, String text, Rectangle rect, Font font) {
public void drawCenteredStringWOutline(Graphics2D g, String text, Rectangle rect, Font font) {
// Get the FontMetrics
FontMetrics metrics = g.getFontMetrics(font);
// Determine the X coordinate for the text
@ -354,7 +380,91 @@ public class HoverButton extends JPanel implements MouseListener {
int y = rect.y + ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent();
// Set the font
g.setFont(font);
// Draw the String
g.drawString(text, x, y);
GlyphVector gv = font.createGlyphVector(g.getFontRenderContext(), text);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g.drawGlyphVector(gv, x, y);
g.translate(x - 1, y - 1);
for (int i = 0; i < text.length(); i++) {
g.setColor(Color.BLACK);
g.draw(gv.getGlyphOutline(i));
}
g.translate(-x + 1, -y + 1);
}
public void gainLifeDisplay() {
if (faderGainLife == null && doGainFade) {
doGainFade = false;
faderGainLife = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
gainX++;
int alpha = Math.max(250 - gainX, 200);
setCenterColor(new Color(2 * gainX, 210, 255, alpha));
repaint();
if (gainX >= 100) {
setCenterColor(new Color(200, 210, 0, 200));
gainX = 100;
if (faderGainLife != null) {
faderGainLife.stop();
faderGainLife.setRepeats(false);
faderGainLife.setDelay(50000);
}
}
}
});
gainX = 0;
faderGainLife.setInitialDelay(25);
faderGainLife.setRepeats(true);
faderGainLife.start();
}
}
public void loseLifeDisplay() {
if (faderLoseLife == null && doLoseFade) {
doLoseFade = false;
faderLoseLife = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
loseX++;
int alpha = Math.max(250 - loseX, 200);
setCenterColor(new Color(250 - loseX / 2, 130 + loseX, 0, alpha));
repaint();
if (loseX >= 100) {
setCenterColor(new Color(200, 210, 0, 200));
loseX = 100;
stopLifeDisplay();
if (faderLoseLife != null) {
faderLoseLife.stop();
faderLoseLife.setRepeats(false);
faderLoseLife.setDelay(50000);
}
}
}
});
loseX = 0;
faderLoseLife.setInitialDelay(25);
faderLoseLife.setRepeats(true);
faderLoseLife.start();
}
}
public void stopLifeDisplay() {
if (faderGainLife != null && gainX >= 100) {
faderGainLife.stop();
faderGainLife = null;
}
doGainFade = true;
if (faderLoseLife != null && loseX >= 100) {
faderLoseLife.stop();
faderLoseLife = null;
}
doLoseFade = true;
}
}

View file

@ -113,6 +113,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
private int avatarId = -1;
private String flagName;
private String basicTooltipText;
private static final Map<UUID, Integer> playerLives = new HashMap<>();
private PriorityTimer timer;
@ -175,9 +176,30 @@ public class PlayerPanelExt extends javax.swing.JPanel {
public void update(PlayerView player) {
this.player = player;
int pastLife = player.getLife();
if (playerLives != null) {
if (playerLives.containsKey(player.getPlayerId())) {
pastLife = playerLives.get(player.getPlayerId());
}
playerLives.put(player.getPlayerId(), player.getLife());
}
int playerLife = player.getLife();
avatar.setCenterText("true".equals(MageFrame.getPreferences().get(PreferencesDialog.KEY_DISPLAY_LIVE_ON_AVATAR, "true"))
? String.valueOf(playerLife) : null);
boolean displayLife = "true".equals(MageFrame.getPreferences().get(PreferencesDialog.KEY_DISPLAY_LIVE_ON_AVATAR, "true"));
avatar.setCenterText(displayLife ? String.valueOf(playerLife) : null);
if (displayLife) {
if (playerLife != pastLife) {
if (playerLife > pastLife) {
avatar.gainLifeDisplay();
} else if (playerLife < pastLife) {
avatar.loseLifeDisplay();
}
} else if (playerLife == pastLife) {
avatar.stopLifeDisplay();
}
}
updateAvatar();
if (playerLife > 99) {

View file

@ -60,6 +60,7 @@ import mage.client.util.ButtonColumn;
import mage.client.util.GUISizeHelper;
import mage.client.util.IgnoreList;
import mage.client.util.MageTableRowSorter;
import mage.client.util.URLHandler;
import mage.client.util.gui.GuiDisplayUtil;
import mage.client.util.gui.TableUtil;
import mage.constants.*;
@ -579,7 +580,7 @@ public class TablesPanel extends javax.swing.JPanel {
this.jPanelBottom.setVisible(false);
} else {
this.jPanelBottom.setVisible(true);
this.jLabelFooterText.setText(serverMessages.get(0));
URLHandler.handleMessage(serverMessages.get(0), this.jLabelFooterText);
this.jButtonFooterNext.setVisible(serverMessages.size() > 1);
}
}
@ -1283,7 +1284,9 @@ public class TablesPanel extends javax.swing.JPanel {
if (currentMessage >= messages.size()) {
currentMessage = 0;
}
this.jLabelFooterText.setText(messages.get(currentMessage));
URLHandler.RemoveMouseAdapter(jLabelFooterText);
URLHandler.handleMessage(messages.get(currentMessage), this.jLabelFooterText);
}
}
}//GEN-LAST:event_jButtonFooterNextActionPerformed

View file

@ -0,0 +1,117 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.client.util;
import java.awt.Desktop;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import javax.swing.JLabel;
/**
*
* @author Dahny
*/
public class URLHandler {
private static MouseAdapter currentMouseAdapter;
/**
* This method makes a URL in a message click-able and converts the message
* into HTML.
*
* @param message: The selected message
* @param label: The message of the day label
*/
public static void handleMessage(String message, JLabel label) {
String url = detectURL(message);
if (!url.equals("")) {
label.addMouseListener(createMouseAdapter(url));
}
label.setText(convertToHTML(message));
}
public static void RemoveMouseAdapter(JLabel label) {
label.removeMouseListener(currentMouseAdapter);
currentMouseAdapter = null;
}
private static MouseAdapter createMouseAdapter(String url) {
currentMouseAdapter = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() > 0) {
if (Desktop.isDesktopSupported()) {
Desktop desktop = Desktop.getDesktop();
try {
URI uri = new URI(url);
desktop.browse(uri);
} catch (IOException | URISyntaxException ex) {
// do nothing
}
}
}
}
};
return currentMouseAdapter;
}
public static String convertToHTML(String input) {
String s = input;
String output = "<html>";
// separate the input by spaces
String[] parts = s.split("\\s+");
for (String item : parts) {
try {
URL url = new URL(item);
// The item is already a valid URL
output = output + "<a href=\"" + url + "\">" + url + "</a> ";
} catch (MalformedURLException e) {
//The item might still be a URL
if (item.startsWith("www.")) {
output = output + "<a href=\"" + item + "\">" + item + "</a> ";
} else {
output = output + item + " ";
}
}
}
output = output + "</html>";
return output;
}
public static String detectURL(String input) {
String s = input;
String output = "";
// separate the input by spaces
String[] parts = s.split("\\s+");
for (String item : parts) {
try {
URL url = new URL(item);
// The item is already a valid URL
output = url.toString();
} catch (MalformedURLException e) {
//The item might still be a URL
if (item.startsWith("www.")) {
output = "http://" + item;
}
}
}
return output;
}
}

View file

@ -625,9 +625,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
if (gameCard.hideInfo()) {
return;
}
if (this.contains(e.getPoint())) {
return;
}
if (tooltipShowing) {
synchronized (this) {
if (tooltipShowing) {

View file

@ -31,10 +31,14 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import javax.imageio.ImageIO;
import javax.swing.*;
import mage.cards.repository.ExpansionRepository;
import mage.client.MageFrame;
import mage.client.constants.Constants;
import mage.client.constants.Constants.ResourceSetSize;
import mage.client.constants.Constants.ResourceSymbolSize;
@ -57,7 +61,7 @@ public final class ManaSymbols {
private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class);
private static final Map<Integer, Map<String, BufferedImage>> manaImages = new HashMap<>();
private static final Map<String, Map<String, Image>> setImages = new HashMap<>();
private static final Map<String, Map<String, Image>> setImages = new ConcurrentHashMap<>();
private static final HashSet<String> onlyMythics = new HashSet<>();
private static final HashSet<String> withoutSymbols = new HashSet<>();
@ -77,7 +81,7 @@ public final class ManaSymbols {
}
private static final Map<String, Dimension> setImagesExist = new HashMap<>();
private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}");
private static String cachedPath;
private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9",
"B", "BG", "BR", "BP", "2B",
"G", "GU", "GW", "GP", "2G",
@ -174,8 +178,10 @@ public final class ManaSymbols {
if (!file.exists()) {
file.mkdirs();
}
String pathRoot = getResourceSetsPath(ResourceSetSize.SMALL) + set;
for (String code : codes) {
File newFile = new File(pathRoot + '-' + code + ".png");
if(!(MageFrame.isSkipSmallSymbolGenerationForExisting() && newFile.exists())){// skip if option enabled and file already exists
file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".png");
if (file.exists()) {
continue;
@ -192,7 +198,6 @@ public final class ManaSymbols {
}
Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width));
BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r);
File newFile = new File(getResourceSetsPath(ResourceSetSize.SMALL) + set + '-' + code + ".png");
ImageIO.write(resized, "png", newFile);
}
} catch (Exception e) {
@ -201,11 +206,11 @@ public final class ManaSymbols {
}
}
}
}
} catch (Exception e) {
}
}
// mark loaded images
// TODO: delete that code, images draw-show must dynamicly
File file;
@ -226,7 +231,6 @@ public final class ManaSymbols {
}
public static BufferedImage loadSVG(File svgFile, int resizeToWidth, int resizeToHeight, boolean useShadow) throws IOException {
// debug: disable shadow gen, need to test it
useShadow = false;
@ -424,17 +428,17 @@ public final class ManaSymbols {
}
private static boolean loadSymbolImages(int size) {
// load all symbols to cash
// load all symbols to cache
// priority: SVG -> GIF
// gif remain for backward compatibility
boolean fileErrors = false;
HashMap<String, BufferedImage> sizedSymbols = new HashMap<>();
for (String symbol : symbols) {
//boolean fileErrors = false;
AtomicBoolean fileErrors = new AtomicBoolean(false);
Map<String, BufferedImage> sizedSymbols = new ConcurrentHashMap<>();
IntStream.range(0, symbols.length).parallel().forEach(i-> {
String symbol = symbols[i];
BufferedImage image = null;
File file = null;
File file;
// svg
file = getSymbolFileNameAsSVG(symbol);
@ -456,13 +460,13 @@ public final class ManaSymbols {
if (image != null) {
sizedSymbols.put(symbol, image);
} else {
fileErrors = true;
fileErrors.set(true);
LOGGER.warn("SVG or GIF symbol can't be load: " + symbol);
}
}
});
manaImages.put(size, sizedSymbols);
return !fileErrors;
return !fileErrors.get();
}
private static void renameSymbols(String path) {

View file

@ -1,5 +1,7 @@
package org.mage.card.arcane;
import mage.util.StreamUtils;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
@ -42,7 +44,7 @@ public final class Util {
socket = new DatagramSocket();
broadcast(socket, data, port, NetworkInterface.getNetworkInterfaces());
} finally {
socket.close();
StreamUtils.closeQuietly(socket);
}
}

View file

@ -28,38 +28,21 @@
package mage.game;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.LimitedTimesPerTurnActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.DiscardCardCost;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.cards.Card;
import mage.cards.ExpansionSet;
import mage.cards.Sets;
import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.constants.CardType;
import mage.constants.MultiplayerAttackOption;
import mage.constants.Outcome;
import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence;
import mage.constants.SetType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.command.Emblem;
import mage.game.command.emblems.MomirEmblem;
import mage.game.match.MatchType;
import mage.game.permanent.token.EmptyToken;
import mage.game.turn.TurnMod;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.RandomUtil;
/**
*

View file

@ -38,7 +38,7 @@ public class MomirFreeForAllType extends MatchType {
public MomirFreeForAllType() {
this.name = "Momir Basic Free For All";
this.maxPlayers = 10;
this.minPlayers = 2;
this.minPlayers = 3;
this.numTeams = 0;
this.useAttackOption = true;
this.useRange = true;

View file

@ -244,6 +244,27 @@ public enum ChatManager {
}
return true;
}
if (command.startsWith("FIX")) {
message += "<br/>" + GameManager.instance.getChatId(chatId);
ChatSession session = chatSessions.get(chatId);
if (session != null && session.getInfo() != null) {
String gameId = session.getInfo();
if (gameId.startsWith("Game ")) {
UUID id = java.util.UUID.fromString(gameId.substring(5, gameId.length()));
for (Entry<UUID, GameController> entry : GameManager.instance.getGameController().entrySet()) {
if (entry.getKey().equals(id)) {
GameController controller = entry.getValue();
if (controller != null) {
message += controller.attemptToFixGame();
chatSessions.get(chatId).broadcastInfoToUser(user, message);
}
}
}
}
}
return true;
}
if (command.startsWith("CARD ")) {
Matcher matchPattern = getCardTextPattern.matcher(message.toLowerCase(Locale.ENGLISH));
if (matchPattern.find()) {

View file

@ -90,8 +90,8 @@ public class ChatSession {
String userName = clients.get(userId);
if (reason != DisconnectReason.LostConnection) { // for lost connection the user will be reconnected or session expire so no removeUserFromAllTablesAndChat of chat yet
final Lock w = lock.writeLock();
try {
w.lock();
try {
clients.remove(userId);
} finally {
w.unlock();

View file

@ -231,8 +231,8 @@ public enum UserManager {
}
logger.debug("Users to remove " + toRemove.size());
final Lock w = lock.readLock();
try {
w.lock();
try {
for (User user : toRemove) {
users.remove(user.getId());
}

View file

@ -37,6 +37,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.zip.GZIPOutputStream;
import mage.MageException;
import mage.abilities.Ability;
import mage.abilities.common.PassAbility;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.decks.Deck;
@ -57,6 +58,7 @@ import mage.game.events.PlayerQueryEvent;
import mage.game.events.TableEvent;
import mage.game.match.MatchPlayer;
import mage.game.permanent.Permanent;
import mage.game.turn.Phase;
import mage.interfaces.Action;
import mage.players.Player;
import mage.server.*;
@ -1150,13 +1152,13 @@ public class GameController implements GameCallback {
sb.append(state.getPlayerList());
sb.append("<br>getPlayers: ");
sb.append(state.getPlayers());
sb.append("<br>Player with Priority is: ");
sb.append("<br><font color=orange>Player with Priority is: ");
if (state.getPriorityPlayerId() != null) {
sb.append(game.getPlayer(state.getPriorityPlayerId()).getName());
} else {
sb.append("noone!");
}
sb.append("<br>getRevealed: ");
sb.append("</font><br>getRevealed: ");
sb.append(state.getRevealed());
sb.append("<br>getSpecialActions: ");
sb.append(state.getSpecialActions());
@ -1187,4 +1189,80 @@ public class GameController implements GameCallback {
return sb.toString();
}
public String attemptToFixGame() {
if (game == null) {
return "";
}
GameState state = game.getState();
if (state == null) {
return "";
}
StringBuilder sb = new StringBuilder();
sb.append("<br/>Game State:<br/><font size=-2>");
sb.append(state);
boolean fixedAlready = false;
sb.append("<br>Active player is: ");
sb.append(game.getPlayer(state.getActivePlayerId()).getName());
PassAbility pass = new PassAbility();
if (game.getPlayer(state.getActivePlayerId()).hasLeft()) {
Phase currentPhase = game.getPhase();
if (currentPhase != null) {
currentPhase.getStep().skipStep(game, state.getActivePlayerId());
sb.append("<br>Forcibly passing the phase!");
fixedAlready = true;
} else {
sb.append("<br>Current phase null");
}
sb.append("<br>Active player has left");
}
sb.append("<br>getChoosingPlayerId: ");
if (state.getChoosingPlayerId() != null) {
if (game.getPlayer(state.getChoosingPlayerId()).hasLeft()) {
Phase currentPhase = game.getPhase();
if (currentPhase != null && !fixedAlready) {
currentPhase.getStep().endStep(game, state.getActivePlayerId());
fixedAlready = true;
sb.append("<br>Forcibly passing the phase!");
} else if (currentPhase == null) {
sb.append("<br>Current phase null");
}
sb.append("<br>Choosing player has left");
}
}
sb.append("<br><font color=orange>Player with Priority is: ");
if (state.getPriorityPlayerId() != null) {
if (game.getPlayer(state.getPriorityPlayerId()).hasLeft()) {
Phase currentPhase = game.getPhase();
if (currentPhase != null && !fixedAlready) {
currentPhase.getStep().skipStep(game, state.getActivePlayerId());
fixedAlready = true;
sb.append("<br>Forcibly passing the phase!");
}
}
sb.append(game.getPlayer(state.getPriorityPlayerId()).getName());
sb.append("</font>");
}
sb.append("<br>Future Timeout:");
if (futureTimeout != null) {
sb.append("Cancelled?=");
sb.append(futureTimeout.isCancelled());
sb.append(",,,Done?=");
sb.append(futureTimeout.isDone());
sb.append(",,,GetDelay?=");
sb.append((int) futureTimeout.getDelay(TimeUnit.SECONDS));
if ((int) futureTimeout.getDelay(TimeUnit.SECONDS) < 25) {
game.endTurn(pass);
sb.append("<br>Forcibly passing the turn!");
}
} else {
sb.append("Not using future Timeout!");
}
sb.append("</font>");
return sb.toString();
}
}

View file

@ -0,0 +1,122 @@
/*
* 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.common.SimpleStaticAbility;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.AbilityType;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.filter.predicate.permanent.TokenPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetControlledPermanent;
/**
*
* @author L_J
*/
public class BrutalSuppression extends CardImpl {
public BrutalSuppression(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}");
// Activated abilities of nontoken Rebels cost an additional "Sacrifice a land" to activate.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BrutalSuppressionAdditionalCostEffect()));
}
public BrutalSuppression(final BrutalSuppression card) {
super(card);
}
@Override
public BrutalSuppression copy() {
return new BrutalSuppression(this);
}
}
class BrutalSuppressionAdditionalCostEffect extends CostModificationEffectImpl {
private static final FilterControlledPermanent filter = new FilterControlledPermanent("a land");
static{
filter.add(new CardTypePredicate(CardType.LAND));
}
private static final FilterPermanent filter2 = new FilterPermanent("nontoken Rebels");
static{
filter2.add(new SubtypePredicate(SubType.REBEL));
filter.add(Predicates.not(new TokenPredicate()));
}
BrutalSuppressionAdditionalCostEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST);
this.staticText = "Activated abilities of nontoken Rebels cost an additional \"Sacrifice a land\" to activate";
}
BrutalSuppressionAdditionalCostEffect(BrutalSuppressionAdditionalCostEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, true);
target.setRequired(false);
abilityToModify.addCost(new SacrificeTargetCost(target));
return true;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
if (abilityToModify.getAbilityType() == AbilityType.ACTIVATED || abilityToModify.getAbilityType() == AbilityType.MANA) {
Permanent rebelPermanent = game.getPermanent(abilityToModify.getSourceId());
if (rebelPermanent != null) {
return filter2.match(rebelPermanent, game);
}
}
return false;
}
@Override
public BrutalSuppressionAdditionalCostEffect copy() {
return new BrutalSuppressionAdditionalCostEffect(this);
}
}

View file

@ -59,7 +59,7 @@ public class CabalSlaver extends CardImpl {
this.toughness = new MageInt(1);
// Whenever a Goblin deals combat damage to a player, that player discards a card.
this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility(new DiscardTargetEffect(1), filter, false, SetTargetPointer.PLAYER, true));
this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility(new DiscardTargetEffect(1), filter, false, SetTargetPointer.NONE, true, true));
}
public CabalSlaver(final CabalSlaver card) {

View file

@ -28,9 +28,6 @@
package mage.cards.d;
import java.util.UUID;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DrawCardTargetEffect;
@ -50,16 +47,14 @@ public class DeepAnalysis extends CardImpl {
public DeepAnalysis(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}");
// Target player draws two cards.
this.getSpellAbility().addEffect(new DrawCardTargetEffect(2));
this.getSpellAbility().addTarget(new TargetPlayer());
// Flashback-{1}{U}, Pay 3 life.
Costs<Cost> costs = new CostsImpl<>();
costs.add(new ManaCostsImpl("{1}{U}"));
costs.add(new PayLifeCost(3));
this.addAbility(new FlashbackAbility(costs, TimingRule.SORCERY));
FlashbackAbility ability = new FlashbackAbility(new ManaCostsImpl("{1}{U}"), TimingRule.SORCERY);
ability.addCost(new PayLifeCost(3));
this.addAbility(ability);
}
public DeepAnalysis(final DeepAnalysis card) {

View file

@ -0,0 +1,99 @@
/*
* 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.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author L_J
*/
public class ElvishImpersonators extends CardImpl {
public ElvishImpersonators(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}");
this.subtype.add(SubType.ELVES);
this.power = new MageInt(0);
this.toughness = new MageInt(0);
// As Elvish Impersonators enters the battlefield, roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result.
this.addAbility(new AsEntersBattlefieldAbility(new ElvishImpersonatorsEffect()));
}
public ElvishImpersonators(final ElvishImpersonators card) {
super(card);
}
@Override
public ElvishImpersonators copy() {
return new ElvishImpersonators(this);
}
}
class ElvishImpersonatorsEffect extends OneShotEffect {
public ElvishImpersonatorsEffect() {
super(Outcome.Neutral);
staticText = "roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result";
}
public ElvishImpersonatorsEffect(final ElvishImpersonatorsEffect effect) {
super(effect);
}
@Override
public ElvishImpersonatorsEffect copy() {
return new ElvishImpersonatorsEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
int firstRoll = controller.rollDice(game, 6);
int secondRoll = controller.rollDice(game, 6);
game.addEffect(new SetPowerToughnessSourceEffect(firstRoll, secondRoll, Duration.WhileOnBattlefield, SubLayer.SetPT_7b), source);
return true;
}
return false;
}
}

View file

@ -71,7 +71,7 @@ public class GrowthSpurt extends CardImpl {
class GrowthSpurtEffect extends OneShotEffect {
GrowthSpurtEffect() {
super(Outcome.BoostCreature);
this.staticText = "todo"; //TODO
this.staticText = "Roll a six-sided die. Target creature gets +X/+X until end of turn, where X is the result";
}
GrowthSpurtEffect(final GrowthSpurtEffect effect) {

View file

@ -0,0 +1,180 @@
/*
* 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.UUID;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.CantCastMoreThanOneSpellEffect;
import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect;
import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect.HandSizeModification;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
/**
*
* @author L_J
*/
public class MineMineMine extends CardImpl {
public MineMineMine(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{4}{G}{G}");
// When Mine, Mine, Mine enters the battlefield, each player puts his or her library into his or her hand.
this.addAbility(new EntersBattlefieldTriggeredAbility(new MineMineMineDrawEffect()));
// Players have no maximum hand size and don't lose the game for drawing from an empty library.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD,
new MaximumHandSizeControllerEffect(Integer.MAX_VALUE, Duration.WhileOnBattlefield, HandSizeModification.SET, TargetController.ANY)
.setText("Players have no maximum hand size and don't lose the game for drawing from an empty library")));
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new MineMineMineDontLoseEffect()));
// Each player can't cast more than one spell each turn.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantCastMoreThanOneSpellEffect(TargetController.ANY)));
// When Mine, Mine, Mine leaves the battlefield, each player shuffles his or her hand and graveyard into his or her library.
this.addAbility(new LeavesBattlefieldTriggeredAbility(new MineMineMineShuffleEffect(), false));
}
public MineMineMine(final MineMineMine card) {
super(card);
}
@Override
public MineMineMine copy() {
return new MineMineMine(this);
}
}
class MineMineMineDrawEffect extends OneShotEffect {
MineMineMineDrawEffect() {
super(Outcome.DrawCard);
this.staticText = "each player puts his or her library into his or her hand";
}
MineMineMineDrawEffect(final MineMineMineDrawEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
CardsImpl libraryCards = new CardsImpl();
libraryCards.addAll(player.getLibrary().getCards(game));
player.moveCards(libraryCards, Zone.HAND, source, game);
}
}
return true;
}
@Override
public MineMineMineDrawEffect copy() {
return new MineMineMineDrawEffect(this);
}
}
class MineMineMineDontLoseEffect extends ReplacementEffectImpl {
MineMineMineDontLoseEffect() {
super(Duration.WhileOnBattlefield, Outcome.Neutral);
}
MineMineMineDontLoseEffect(final MineMineMineDontLoseEffect effect) {
super(effect);
}
@Override
public MineMineMineDontLoseEffect copy() {
return new MineMineMineDontLoseEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
return true;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DRAW_CARD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Player player = game.getPlayer(event.getPlayerId());
if (player != null && player.getLibrary().getCards(game).isEmpty()) {
return true;
}
return false;
}
}
class MineMineMineShuffleEffect extends OneShotEffect {
public MineMineMineShuffleEffect() {
super(Outcome.Neutral);
staticText = "each player shuffles his or her hand and graveyard into his or her library";
}
public MineMineMineShuffleEffect(final MineMineMineShuffleEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
player.moveCards(player.getHand(), Zone.LIBRARY, source, game);
player.moveCards(player.getGraveyard(), Zone.LIBRARY, source, game);
player.shuffleLibrary(source, game);
}
}
return true;
}
@Override
public MineMineMineShuffleEffect copy() {
return new MineMineMineShuffleEffect(this);
}
}

View file

@ -41,7 +41,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.StaticFilters;
import mage.filter.common.FilterInstantOrSorcerySpell;
import mage.filter.predicate.Predicate;
import mage.game.Game;
@ -136,11 +136,11 @@ class MizzixOfTheIzmagnusCostReductionEffect extends CostModificationEffectImpl
if (abilityToModify instanceof SpellAbility && abilityToModify.getControllerId().equals(source.getControllerId())) {
Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId());
if (spell != null) {
return new FilterInstantOrSorceryCard().match(spell, source.getSourceId(), source.getControllerId(), game);
} else {
// used at least for flashback ability because Flashback ability doesn't use stack or for getPlayables where spell is not cast yet
return StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(spell, source.getSourceId(), source.getControllerId(), game);
} else if (((SpellAbility) abilityToModify).isCheckPlayableMode()) {
// Spell is not on the stack yet, but possible playable spells are determined
Card sourceCard = game.getCard(abilityToModify.getSourceId());
return sourceCard != null && new FilterInstantOrSorceryCard().match(sourceCard, source.getSourceId(), source.getControllerId(), game);
return sourceCard != null && StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(sourceCard, source.getSourceId(), source.getControllerId(), game);
}
}
return false;

View file

@ -93,6 +93,7 @@ class NaturesWillEffect extends OneShotEffect {
land.untap(game);
}
}
return true;
}
return false;
}

View file

@ -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.o;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardAllEffect;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.SetPlayerLifeAllEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
/**
*
* @author L_J
*/
public class OnceMoreWithFeeling extends CardImpl {
public OnceMoreWithFeeling(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}{W}{W}{W}");
// Exile all permanents and all cards from all graveyards. Each player shuffles his or her hand into his or her library, then draws seven cards. Each player's life total becomes 10. Exile Once More with Feeling.
this.getSpellAbility().addEffect(new OnceMoreWithFeelingEffect());
Effect effect = new DrawCardAllEffect(7);
effect.setText(", then draws seven cards");
this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addEffect(new SetPlayerLifeAllEffect(10));
this.getSpellAbility().addEffect(ExileSpellEffect.getInstance());
// DCI ruling A deck can have only one card named Once More with Feeling.
// (according to rule 112.6m, this shouldn't do anything)
this.getSpellAbility().addEffect(new InfoEffect("<br>DCI ruling &mdash; A deck can have only one card named {this}"));
}
public OnceMoreWithFeeling(final OnceMoreWithFeeling card) {
super(card);
}
@Override
public OnceMoreWithFeeling copy() {
return new OnceMoreWithFeeling(this);
}
}
class OnceMoreWithFeelingEffect extends OneShotEffect {
public OnceMoreWithFeelingEffect() {
super(Outcome.Detriment);
staticText = "Exile all permanents and all cards from all graveyards. Each player shuffles his or her hand into his or her library";
}
public OnceMoreWithFeelingEffect(final OnceMoreWithFeelingEffect effect) {
super(effect);
}
@Override
public OnceMoreWithFeelingEffect copy() {
return new OnceMoreWithFeelingEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) {
permanent.moveToExile(null, "", source.getSourceId(), game);
}
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
for (UUID cid : player.getGraveyard().copy()) {
Card c = game.getCard(cid);
if (c != null) {
c.moveToExile(null, null, source.getSourceId(), game);
}
}
player.moveCards(player.getHand(), Zone.LIBRARY, source, game);
player.shuffleLibrary(source, game);
}
}
return true;
}
}

View file

@ -0,0 +1,152 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.r;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
/**
*
* @author jeffwadsworth
*/
public class Retribution extends CardImpl {
public Retribution(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}{R}");
// Choose two target creatures an opponent controls. That player chooses and sacrifices one of those creatures. Put a -1/-1 counter on the other.
this.getSpellAbility().addEffect(new RetributionEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanentOpponentSameController(2, 2, StaticFilters.FILTER_PERMANENT_CREATURE, false));
}
public Retribution(final Retribution card) {
super(card);
}
@Override
public Retribution copy() {
return new Retribution(this);
}
}
class RetributionEffect extends OneShotEffect {
public RetributionEffect() {
super(Outcome.Detriment);
this.staticText = "Choose two target creatures an opponent controls. That player chooses and sacrifices one of those creatures. Put a -1/-1 counter on the other";
}
public RetributionEffect(final RetributionEffect effect) {
super(effect);
}
@Override
public RetributionEffect copy() {
return new RetributionEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
MageObject sourceObject = source.getSourceObject(game);
if (sourceObject != null) {
boolean sacrificeDone = false;
int count = 0;
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
Permanent creature = game.getPermanent(targetId);
if (creature != null) {
Player controllerOfCreature = game.getPlayer(creature.getControllerId());
if ((count == 0
&& controllerOfCreature.chooseUse(Outcome.Sacrifice, "Sacrifice " + creature.getLogName() + '?', source, game))
|| (count == 1
&& !sacrificeDone)) {
creature.sacrifice(source.getId(), game);
sacrificeDone = true;
} else {
creature.addCounters(CounterType.M1M1.createInstance(), source, game);
}
count++;
}
}
return true;
}
return false;
}
}
class TargetCreaturePermanentOpponentSameController extends TargetCreaturePermanent {
public TargetCreaturePermanentOpponentSameController(int minNumTargets, int maxNumTargets, FilterCreaturePermanent filter, boolean notTarget) {
super(minNumTargets, maxNumTargets, filter, notTarget);
}
public TargetCreaturePermanentOpponentSameController(final TargetCreaturePermanentOpponentSameController target) {
super(target);
}
@Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
if (super.canTarget(controllerId, id, source, game)) {
Permanent firstTargetPermanent = game.getPermanent(id);
if (firstTargetPermanent != null
&& game.getOpponents(controllerId).contains(firstTargetPermanent.getControllerId())) {
for (Object object : getTargets()) {
UUID targetId = (UUID) object;
Permanent targetPermanent = game.getPermanent(targetId);
if (targetPermanent != null) {
if (!firstTargetPermanent.getId().equals(targetPermanent.getId())) {
if (!firstTargetPermanent.getControllerId().equals(targetPermanent.getOwnerId())) {
return false;
}
}
}
}
return true;
}
}
return false;
}
@Override
public TargetCreaturePermanentOpponentSameController copy() {
return new TargetCreaturePermanentOpponentSameController(this);
}
}

View file

@ -156,6 +156,7 @@ public class Homelands extends ExpansionSet {
cards.add(new SetCardInfo("Reef Pirates", 45, Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Reef Pirates", 46, Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Renewal", 66, Rarity.COMMON, mage.cards.r.Renewal.class));
cards.add(new SetCardInfo("Retribution", 99, Rarity.UNCOMMON, mage.cards.r.Retribution.class));
cards.add(new SetCardInfo("Reveka, Wizard Savant", 47, Rarity.RARE, mage.cards.r.RevekaWizardSavant.class));
cards.add(new SetCardInfo("Roots", 68, Rarity.UNCOMMON, mage.cards.r.Roots.class));
cards.add(new SetCardInfo("Root Spider", 67, Rarity.UNCOMMON, mage.cards.r.RootSpider.class));

View file

@ -206,7 +206,7 @@ public class MastersEdition extends ExpansionSet {
cards.add(new SetCardInfo("Rabid Wombat", 126, Rarity.UNCOMMON, mage.cards.r.RabidWombat.class));
cards.add(new SetCardInfo("Rainbow Vale", 179, Rarity.RARE, mage.cards.r.RainbowVale.class));
cards.add(new SetCardInfo("Righteous Avengers", 25, Rarity.COMMON, mage.cards.r.RighteousAvengers.class));
cards.add(new SetCardInfo("Ring of Ma'rûf", 163, Rarity.RARE, mage.cards.r.RingOfMaruf.class));
cards.add(new SetCardInfo("Ring of Ma'ruf", 163, Rarity.RARE, mage.cards.r.RingOfMaruf.class));
cards.add(new SetCardInfo("River Merfolk", 47, Rarity.COMMON, mage.cards.r.RiverMerfolk.class));
cards.add(new SetCardInfo("Roots", 127, Rarity.COMMON, mage.cards.r.Roots.class));
cards.add(new SetCardInfo("Scryb Sprites", 128, Rarity.COMMON, mage.cards.s.ScrybSprites.class));

View file

@ -215,6 +215,7 @@ public class MastersEditionII extends ExpansionSet {
cards.add(new SetCardInfo("Red Cliffs Armada", 62, Rarity.COMMON, mage.cards.r.RedCliffsArmada.class));
cards.add(new SetCardInfo("Reinforcements", 28, Rarity.COMMON, mage.cards.r.Reinforcements.class));
cards.add(new SetCardInfo("Reprisal", 29, Rarity.COMMON, mage.cards.r.Reprisal.class));
cards.add(new SetCardInfo("Retribution", 148, Rarity.UNCOMMON, mage.cards.r.Retribution.class));
cards.add(new SetCardInfo("Righteous Fury", 30, Rarity.RARE, mage.cards.r.RighteousFury.class));
cards.add(new SetCardInfo("Ritual of Subdual", 174, Rarity.RARE, mage.cards.r.RitualOfSubdual.class));
cards.add(new SetCardInfo("Ritual of the Machine", 109, Rarity.RARE, mage.cards.r.RitualOfTheMachine.class));

View file

@ -69,6 +69,7 @@ public class Prophecy extends ExpansionSet {
cards.add(new SetCardInfo("Bog Elemental", 57, Rarity.RARE, mage.cards.b.BogElemental.class));
cards.add(new SetCardInfo("Bog Glider", 58, Rarity.COMMON, mage.cards.b.BogGlider.class));
cards.add(new SetCardInfo("Branded Brawlers", 84, Rarity.COMMON, mage.cards.b.BrandedBrawlers.class));
cards.add(new SetCardInfo("Brutal Suppression", 85, Rarity.UNCOMMON, mage.cards.b.BrutalSuppression.class));
cards.add(new SetCardInfo("Calming Verse", 110, Rarity.COMMON, mage.cards.c.CalmingVerse.class));
cards.add(new SetCardInfo("Celestial Convergence", 5, Rarity.RARE, mage.cards.c.CelestialConvergence.class));
cards.add(new SetCardInfo("Chilling Apparition", 59, Rarity.UNCOMMON, mage.cards.c.ChillingApparition.class));

View file

@ -23,6 +23,7 @@ public class Unglued extends ExpansionSet {
cards.add(new SetCardInfo("Chicken Egg", 41, Rarity.COMMON, mage.cards.c.ChickenEgg.class));
cards.add(new SetCardInfo("Chicken a la King", 17, Rarity.RARE, mage.cards.c.ChickenALaKing.class));
cards.add(new SetCardInfo("Elvish Impersonators", 56, Rarity.COMMON, mage.cards.e.ElvishImpersonators.class));
cards.add(new SetCardInfo("Forest", 88, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false)));
cards.add(new SetCardInfo("Fowl Play", 24, Rarity.COMMON, mage.cards.f.FowlPlay.class));
cards.add(new SetCardInfo("Goblin Tutor", 45, Rarity.UNCOMMON, mage.cards.g.GoblinTutor.class));
@ -33,7 +34,9 @@ public class Unglued extends ExpansionSet {
cards.add(new SetCardInfo("Jack-in-the-Mox", 75, Rarity.RARE, mage.cards.j.JackInTheMox.class));
cards.add(new SetCardInfo("Jumbo Imp", 34, Rarity.UNCOMMON, mage.cards.j.JumboImp.class));
cards.add(new SetCardInfo("Krazy Kow", 48, Rarity.COMMON, mage.cards.k.KrazyKow.class));
cards.add(new SetCardInfo("Mine, Mine, Mine!", 65, Rarity.RARE, mage.cards.m.MineMineMine.class));
cards.add(new SetCardInfo("Mountain", 87, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false)));
cards.add(new SetCardInfo("Once More With Feeling", 11, Rarity.RARE, mage.cards.o.OnceMoreWithFeeling.class));
cards.add(new SetCardInfo("Paper Tiger", 78, Rarity.COMMON, mage.cards.p.PaperTiger.class));
cards.add(new SetCardInfo("Plains", 84, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false)));
cards.add(new SetCardInfo("Poultrygeist", 37, Rarity.COMMON, mage.cards.p.Poultrygeist.class));

View file

@ -30,6 +30,7 @@ package org.mage.test.cards.abilities.keywords;
import mage.abilities.keyword.TrampleAbility;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -580,4 +581,35 @@ public class FlashbackTest extends CardTestPlayerBase {
assertLife(playerA, 20);
}
/**
* Test cost reduction with mixed flashback costs
*/
@Test
public void testReduceMixedFlashbackCosts() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
// Whenever you cast an instant or sorcery spell with converted mana cost greater than the number of experience counters you have, you get an experience counter.
// Instant and sorcery spells you cast cost {1} less to cast for each experience counter you have.
addCard(Zone.BATTLEFIELD, playerA, "Mizzix of the Izmagnus");// 2/2
// Target player draws two cards.
// Flashback-{1}{U}, Pay 3 life.
addCard(Zone.HAND, playerA, "Deep Analysis"); // {3}{U}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deep Analysis");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Deep Analysis", 0);
assertExileCount(playerA, "Deep Analysis", 1);
assertHandCount(playerA, 4);
assertCounterCount(playerA, CounterType.EXPERIENCE, 2);
assertLife(playerA, 17);
}
}

View file

@ -120,4 +120,36 @@ public class MizzixOfTheIzmagnusTest extends CardTestPlayerBase {
assertLife(playerB, 17);
}
/**
* Test to reduce Flashback costs
*/
@Test
public void testReduceFlashbackCosts() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
// Whenever you cast an instant or sorcery spell with converted mana cost greater than the number of experience counters you have, you get an experience counter.
// Instant and sorcery spells you cast cost {1} less to cast for each experience counter you have.
addCard(Zone.BATTLEFIELD, playerA, "Mizzix of the Izmagnus");// 2/2
// Engulfing Flames deals 1 damage to target creature. It can't be regenerated this turn.
// Flashback {3}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
addCard(Zone.HAND, playerA, "Engulfing Flames"); // {R}
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");// 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Engulfing Flames", "Silvercoat Lion");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback", "Silvercoat Lion");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Engulfing Flames", 0);
assertExileCount(playerA, "Engulfing Flames", 1);
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
assertCounterCount(playerA, CounterType.EXPERIENCE, 1);
}
}

View file

@ -47,12 +47,18 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp
private final FilterPermanent filter;
private final SetTargetPointer setTargetPointer;
private final boolean onlyCombat;
private final boolean affectsDefendingPlayer;
public DealsDamageToAPlayerAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyCombat) {
this(effect, filter, optional, setTargetPointer, onlyCombat, false);
}
public DealsDamageToAPlayerAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyCombat, boolean affectsDefendingPlayer) {
super(Zone.BATTLEFIELD, effect, optional);
this.setTargetPointer = setTargetPointer;
this.filter = filter;
this.onlyCombat = onlyCombat;
this.affectsDefendingPlayer = affectsDefendingPlayer;
}
public DealsDamageToAPlayerAllTriggeredAbility(final DealsDamageToAPlayerAllTriggeredAbility ability) {
@ -60,6 +66,7 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp
this.setTargetPointer = ability.setTargetPointer;
this.filter = ability.filter;
this.onlyCombat = ability.onlyCombat;
this.affectsDefendingPlayer = ability.affectsDefendingPlayer;
}
@Override
@ -81,6 +88,10 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp
for (Effect effect : this.getEffects()) {
effect.setValue("damage", event.getAmount());
effect.setValue("sourceId", event.getSourceId());
if (affectsDefendingPlayer) {
effect.setTargetPointer(new FixedTarget(event.getTargetId()));
continue;
}
switch (setTargetPointer) {
case PLAYER:
effect.setTargetPointer(new FixedTarget(permanent.getControllerId()));

View file

@ -123,6 +123,7 @@ public enum SubType {
ELEMENTAL("Elemental", SubTypeSet.CreatureType),
ELEPHANT("Elephant", SubTypeSet.CreatureType),
ELF("Elf", SubTypeSet.CreatureType),
ELVES("Elves", SubTypeSet.CreatureType),
ELK("Elk", SubTypeSet.CreatureType),
EYE("Eye", SubTypeSet.CreatureType),
EWOK("Ewok", SubTypeSet.CreatureType, true), // Star Wars

View file

@ -74,6 +74,12 @@ public final class StaticFilters {
FILTER_CARD_A_NON_LAND.setLockedFilter(true);
}
public static final FilterInstantOrSorceryCard FILTER_CARD_INSTANT_OR_SORCERY = new FilterInstantOrSorceryCard();
static {
FILTER_CARD_INSTANT_OR_SORCERY.setLockedFilter(true);
}
public static final FilterPermanent FILTER_PERMANENT = new FilterPermanent();
static {

View file

@ -2,7 +2,7 @@
[![Join the chat at https://gitter.im/magefree/mage](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magefree/mage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/magefree/mage.svg?branch=master)](https://travis-ci.org/magefree/mage)
XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **16950** unique cards (over 328000 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented.
XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **16950** unique cards (over 32800 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented.
There are public servers where you can play XMage against other players. You can also host your own server to play against the AI and/or your friends.