diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 8bf5bd192cc..480249f9bf6 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -1,1268 +1,1268 @@ -package org.mage.card.arcane; - -import java.awt.AlphaComposite; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.StringTokenizer; -import java.util.UUID; -import javax.swing.BorderFactory; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import mage.cards.MagePermanent; -import mage.cards.TextPopup; -import mage.cards.action.ActionCallback; -import mage.cards.action.TransferData; -import mage.client.dialog.PreferencesDialog; -import mage.client.plugins.adapters.MageActionCallback; -import mage.client.plugins.impl.Plugins; -import mage.client.util.ImageHelper; -import mage.client.util.audio.AudioManager; -import mage.components.ImagePanel; -import mage.constants.AbilityType; -import mage.constants.CardType; -import mage.constants.EnlargeMode; -import mage.utils.CardUtil; -import mage.view.AbilityView; -import mage.view.CardView; -import mage.view.CounterView; -import mage.view.PermanentView; -import mage.view.StackAbilityView; -import net.java.truevfs.access.TFile; -import org.apache.log4j.Logger; -import org.mage.card.arcane.ScaledImagePanel.MultipassType; -import org.mage.card.arcane.ScaledImagePanel.ScalingType; -import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL; -import org.mage.plugins.card.dl.sources.DirectLinksForDownload; -import org.mage.plugins.card.images.ImageCache; -import org.mage.plugins.card.utils.impl.ImageManagerImpl; - -/** - * Main class for drawing Mage card object. - * - * @author arcane, nantuko, noxx - */ -@SuppressWarnings({"unchecked", "rawtypes"}) -public class CardPanel extends MagePermanent implements MouseListener, MouseMotionListener, MouseWheelListener, ComponentListener { - - private static final long serialVersionUID = -3272134219262184410L; - - private static final Logger LOGGER = Logger.getLogger(CardPanel.class); - - private static final int WIDTH_LIMIT = 90; // card width limit to create smaller counter - public static final double TAPPED_ANGLE = Math.PI / 2; - public static final double FLIPPED_ANGLE = Math.PI; - public static final float ASPECT_RATIO = 3.5f / 2.5f; - public static final int POPUP_X_GAP = 1; // prevent tooltip window from blinking - - public static CardPanel dragAnimationPanel; - - public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149); - - private static final float ROUNDED_CORNER_SIZE = 0.1f; - private static final float BLACK_BORDER_SIZE = 0.03f; - private static final int TEXT_GLOW_SIZE = 6; - private static final float TEXT_GLOW_INTENSITY = 3f; - private static final float ROT_CENTER_TO_TOP_CORNER = 1.0295630140987000315797369464196f; - private static final float ROT_CENTER_TO_BOTTOM_CORNER = 0.7071067811865475244008443621048f; - - public CardView gameCard; - public CardView updateCard; - - // for two faced cards - public CardView temporary; - private List links = new ArrayList<>(); - - public double tappedAngle = 0; - public double flippedAngle = 0; - public final ScaledImagePanel imagePanel; - public ImagePanel overlayPanel; - - public JPanel buttonPanel; - private JButton dayNightButton; - - public JPanel copyIconPanel; - private JButton showCopySourceButton; - - public JPanel iconPanel; - private JButton typeButton; - - public JPanel counterPanel; - private JLabel loyaltyCounterLabel; - private JLabel plusCounterLabel; - private JLabel otherCounterLabel; - private JLabel minusCounterLabel; - private int loyaltyCounter; - private int plusCounter; - private int otherCounter; - private int minusCounter; - private int lastCardWidth; - - private GlowText titleText; - private GlowText ptText; - private boolean displayEnabled = true; - private boolean isAnimationPanel; - public int cardXOffset, cardYOffset, cardWidth, cardHeight; - private int symbolWidth; - - private boolean isSelected; - private boolean isPlayable; - private boolean isChoosable; - private boolean canAttack; - private boolean showCastingCost; - private boolean hasImage = false; - private float alpha = 1.0f; - - private ActionCallback callback; - - protected boolean tooltipShowing; - protected TextPopup tooltipText = new TextPopup(); - protected UUID gameId; - private TransferData data = new TransferData(); - - private boolean isPermanent; - private boolean hasSickness; - private String zone; - - public double transformAngle = 1; - - private boolean transformed; - private boolean animationInProgress = false; - - private boolean displayTitleAnyway; - - private JPanel cardArea; - - private int yTextOffset = 10; - - // if this is set, it's opened if the user right clicks on the card panel - private JPopupMenu popupMenu; - - public CardPanel(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension) { - this.gameCard = newGameCard; - this.callback = callback; - this.gameId = gameId; - - this.isPermanent = this.gameCard instanceof PermanentView; - if (isPermanent) { - this.hasSickness = ((PermanentView) this.gameCard).hasSummoningSickness(); - } - - this.setCardBounds(0, 0, dimension.width, dimension.height); - - //for container debug (don't remove) - //setBorder(BorderFactory.createLineBorder(Color.green)); - if (this.gameCard.canTransform()) { - buttonPanel = new JPanel(); - buttonPanel.setLayout(null); - buttonPanel.setOpaque(false); - add(buttonPanel); - - dayNightButton = new JButton(""); - dayNightButton.setLocation(2, 2); - dayNightButton.setSize(25, 25); - - buttonPanel.setVisible(true); - - BufferedImage day = ImageManagerImpl.getInstance().getDayImage(); - dayNightButton.setIcon(new ImageIcon(day)); - - buttonPanel.add(dayNightButton); - - dayNightButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - // if card is being rotated, ignore action performed - // if card is tapped, no visual transforming is possible (implementation limitation) - // if card is permanent, it will be rotated by Mage, so manual rotate should be possible - if (animationInProgress || isTapped() || isPermanent) { - return; - } - Animation.transformCard(CardPanel.this, CardPanel.this, true); - } - }); - } - if (!newGameCard.isAbility()) { - // panel to show counters on the card - counterPanel = new JPanel(); - counterPanel.setLayout(null); - counterPanel.setOpaque(false); - add(counterPanel); - - plusCounterLabel = new JLabel(""); - plusCounterLabel.setToolTipText("+1/+1"); - counterPanel.add(plusCounterLabel); - - minusCounterLabel = new JLabel(""); - minusCounterLabel.setToolTipText("-1/-1"); - counterPanel.add(minusCounterLabel); - - loyaltyCounterLabel = new JLabel(""); - loyaltyCounterLabel.setToolTipText("loyalty"); - counterPanel.add(loyaltyCounterLabel); - - otherCounterLabel = new JLabel(""); - counterPanel.add(otherCounterLabel); - - counterPanel.setVisible(false); - } - if (newGameCard.isAbility()) { - if (AbilityType.TRIGGERED.equals(newGameCard.getAbilityType())) { - setTypeIcon(ImageManagerImpl.getInstance().getTriggeredAbilityImage(), "Triggered Ability"); - } else if (AbilityType.ACTIVATED.equals(newGameCard.getAbilityType())) { - setTypeIcon(ImageManagerImpl.getInstance().getActivatedAbilityImage(), "Activated Ability"); - } - } - - if (this.gameCard.isToken()) { - setTypeIcon(ImageManagerImpl.getInstance().getTokenIconImage(), "Token Permanent"); - } - - // icon to inform about permanent is copying something - if (this.gameCard instanceof PermanentView) { - copyIconPanel = new JPanel(); - copyIconPanel.setLayout(null); - copyIconPanel.setOpaque(false); - add(copyIconPanel); - - showCopySourceButton = new JButton(""); - showCopySourceButton.setLocation(2, 2); - showCopySourceButton.setSize(25, 25); - showCopySourceButton.setToolTipText("This permanent is copying a target. To see original image, push this button or turn mouse wheel down while hovering with the mouse pointer over the permanent."); - copyIconPanel.setVisible(((PermanentView) this.gameCard).isCopy()); - - showCopySourceButton.setIcon(new ImageIcon(ImageManagerImpl.getInstance().getCopyInformIconImage())); - - copyIconPanel.add(showCopySourceButton); - - showCopySourceButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ActionCallback callback = Plugins.getInstance().getActionCallback(); - ((MageActionCallback) callback).enlargeCard(EnlargeMode.COPY); - } - }); - } - - setBackground(Color.black); - setOpaque(false); - - addMouseListener(this); - addMouseMotionListener(this); - addMouseWheelListener(this); - addComponentListener(this); - - displayTitleAnyway = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_CARD_NAMES, "true").equals("true"); - - titleText = new GlowText(); - setText(gameCard); -// int fontSize = (int) cardHeight / 11; -// titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - titleText.setForeground(Color.white); - titleText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); - titleText.setWrap(true); - add(titleText); - - ptText = new GlowText(); - if (CardUtil.isCreature(gameCard)) { - ptText.setText(gameCard.getPower() + "/" + gameCard.getToughness()); - } else if (CardUtil.isPlaneswalker(gameCard)) { - ptText.setText(gameCard.getLoyalty()); - } -// ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - ptText.setForeground(Color.white); - ptText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); - add(ptText); - - BufferedImage sickness = ImageManagerImpl.getInstance().getSicknessImage(); - overlayPanel = new ImagePanel(sickness, ImagePanel.SCALED); - overlayPanel.setOpaque(false); - add(overlayPanel); - - imagePanel = new ScaledImagePanel(); - imagePanel.setBorder(BorderFactory.createLineBorder(Color.white)); - add(imagePanel); - imagePanel.setScaleLarger(true); - imagePanel.setScalingType(ScalingType.nearestNeighbor); - imagePanel.setScalingMultiPassType(MultipassType.none); - - String cardType = getType(newGameCard); - tooltipText.setText(getText(cardType, newGameCard)); - - Util.threadPool.submit(new Runnable() { - @Override - public void run() { - try { - tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; - flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; - if (!loadImage) { - return; - } - BufferedImage srcImage; - if (gameCard.isFaceDown()) { - srcImage = getFaceDownImage(); - } else { - srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); - } - if (srcImage != null) { - hasImage = true; - setText(gameCard); - setImage(srcImage); - } - if (gameCard.isTransformed()) { - toggleTransformed(); - } - setText(gameCard); - } catch (Exception e) { - LOGGER.fatal("Problem during image animation", e); - } catch (Error err) { - LOGGER.error("Problem during image animation", err); - } - } - }); - } - - private void setTypeIcon(BufferedImage bufferedImage, String toolTipText) { - iconPanel = new JPanel(); - iconPanel.setLayout(null); - iconPanel.setOpaque(false); - add(iconPanel); - - typeButton = new JButton(""); - typeButton.setLocation(2, 2); - typeButton.setSize(25, 25); - - iconPanel.setVisible(true); - typeButton.setIcon(new ImageIcon(bufferedImage)); - if (toolTipText != null) { - typeButton.setToolTipText(toolTipText); - } - iconPanel.add(typeButton); - } - - public void cleanUp() { - if (dayNightButton != null) { - for (ActionListener al : dayNightButton.getActionListeners()) { - dayNightButton.removeActionListener(al); - } - } - for (MouseListener ml : this.getMouseListeners()) { - this.removeMouseListener(ml); - } - for (MouseMotionListener ml : this.getMouseMotionListeners()) { - this.removeMouseMotionListener(ml); - } - for (MouseWheelListener ml : this.getMouseWheelListeners()) { - this.removeMouseWheelListener(ml); - } - // this holds reference to ActionCallback forever so set it to null to prevent - this.callback = null; - this.data = null; - this.counterPanel = null; - } - - private void setText(CardView card) { - titleText.setText(!displayTitleAnyway && hasImage ? "" : card.getName()); - } - - private void setImage(Image srcImage) { - synchronized (imagePanel) { - imagePanel.setImage(srcImage); - repaint(); - } - doLayout(); - } - - public void setImage(final CardPanel panel) { - synchronized (panel.imagePanel) { - if (panel.imagePanel.hasImage()) { - setImage(panel.imagePanel.getSrcImage()); - } - } - } - - @Override - public void setZone(String zone) { - this.zone = zone; - } - - @Override - public String getZone() { - return zone; - } - - public void setScalingType(ScalingType scalingType) { - imagePanel.setScalingType(scalingType); - } - - public void setDisplayEnabled(boolean displayEnabled) { - this.displayEnabled = displayEnabled; - } - - public boolean isDisplayEnabled() { - return displayEnabled; - } - - public void setAnimationPanel(boolean isAnimationPanel) { - this.isAnimationPanel = isAnimationPanel; - } - - @Override - public void setSelected(boolean isSelected) { - this.isSelected = isSelected; - if (isSelected) { - this.titleText.setGlowColor(Color.green); - } else { - this.titleText.setGlowColor(Color.black); - } - // noxx: bad idea is to call repaint in setter method - ////repaint(); - } - - @Override - public void setChoosable(boolean isChoosable) { - this.isChoosable = isChoosable; - } - - @Override - public void setCardAreaRef(JPanel cardArea) { - this.cardArea = cardArea; - } - - public boolean getSelected() { - return this.isSelected; - } - - public void setShowCastingCost(boolean showCastingCost) { - this.showCastingCost = showCastingCost; - } - - @Override - public void paint(Graphics g) { - if (!displayEnabled) { - return; - } - if (!isValid()) { - super.validate(); - } - Graphics2D g2d = (Graphics2D) g; - if (transformAngle < 1) { - float edgeOffset = (cardWidth + cardXOffset) / 2f; - g2d.translate(edgeOffset * (1 - transformAngle), 0); - g2d.scale(transformAngle, 1); - } - if (tappedAngle + flippedAngle > 0) { - g2d = (Graphics2D) g2d.create(); - float edgeOffset = cardWidth / 2f; - double angle = tappedAngle + (Math.abs(flippedAngle - FLIPPED_ANGLE) < 0.001 ? 0 : flippedAngle); - g2d.rotate(angle, cardXOffset + edgeOffset, cardYOffset + cardHeight - edgeOffset); - } - super.paint(g2d); - } - - @Override - protected void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D) g; - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - if (alpha != 1.0f) { - AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha); - g2d.setComposite(composite); - } - - if (!hasImage) { - g2d.setColor(new Color(30, 200, 200, 120)); - } else { - g2d.setColor(new Color(0, 0, 0, 0)); - } - - int cornerSize = Math.max(4, Math.round(cardWidth * ROUNDED_CORNER_SIZE)); - g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); - - if (isSelected) { - g2d.setColor(Color.green); - g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); - } else if (isChoosable) { - g2d.setColor(new Color(250, 250, 0, 230)); - g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); - } else if (isPlayable) { - g2d.setColor(new Color(153, 102, 204, 200)); - //g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); - g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); - } - - if (canAttack) { - g2d.setColor(new Color(0, 0, 255, 230)); - g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); - } - - //TODO:uncomment - /* - if (gameCard.isAttacking()) { - g2d.setColor(new Color(200,10,10,200)); - g2d.fillRoundRect(cardXOffset+1, cardYOffset+1, cardWidth-2, cardHeight-2, cornerSize, cornerSize); - }*/ - } - - @Override - protected void paintChildren(Graphics g) { - super.paintChildren(g); - - if (showCastingCost && !isAnimationPanel && cardWidth < 200 && cardWidth > 60) { - String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost()); - int width = getWidth(manaCost); - if (hasImage) { - ManaSymbols.draw(g, manaCost, cardXOffset + cardWidth - width - 5, cardYOffset + 5, symbolWidth); - } else { - ManaSymbols.draw(g, manaCost, cardXOffset + 8, cardHeight - 9, symbolWidth); - } - } - } - - private int getWidth(String manaCost) { - int width = 0; - manaCost = manaCost.replace("\\", ""); - StringTokenizer tok = new StringTokenizer(manaCost, " "); - while (tok.hasMoreTokens()) { - tok.nextToken(); - width += symbolWidth; - } - return width; - } - - @Override - public void doLayout() { - int borderSize = Math.round(cardWidth * BLACK_BORDER_SIZE); - imagePanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - imagePanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - - if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) { - overlayPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - overlayPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - } else { - overlayPanel.setVisible(false); - } - - if (buttonPanel != null) { - buttonPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - buttonPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - dayNightButton.setLocation(0, cardHeight - 30); - } - if (iconPanel != null) { - iconPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - iconPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - } - if (copyIconPanel != null) { - copyIconPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - copyIconPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - } - if (counterPanel != null) { - counterPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - counterPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - int size = cardWidth > WIDTH_LIMIT ? 40 : 20; - - minusCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size * 2); - minusCounterLabel.setSize(size, size); - - plusCounterLabel.setLocation(5, counterPanel.getHeight() - size * 2); - plusCounterLabel.setSize(size, size); - - loyaltyCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size); - loyaltyCounterLabel.setSize(size, size); - - otherCounterLabel.setLocation(5, counterPanel.getHeight() - size); - otherCounterLabel.setSize(size, size); - - } - int fontHeight = Math.round(cardHeight * (27f / 680)); - boolean showText = (!isAnimationPanel && fontHeight < 12); - titleText.setVisible(showText); - ptText.setVisible(showText); - - if (showText) { - int fontSize = (int) cardHeight / 11; - titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - - int titleX = Math.round(cardWidth * (20f / 480)); - int titleY = Math.round(cardHeight * (9f / 680)) + yTextOffset; - titleText.setBounds(cardXOffset + titleX, cardYOffset + titleY, cardWidth - titleX, cardHeight - titleY); - - ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - Dimension ptSize = ptText.getPreferredSize(); - ptText.setSize(ptSize.width, ptSize.height); - int ptX = Math.round(cardWidth * (420f / 480)) - ptSize.width / 2; - int ptY = Math.round(cardHeight * (675f / 680)) - ptSize.height; - - int offsetX = Math.round((CARD_SIZE_FULL.width - cardWidth) / 10.0f); - - ptText.setLocation(cardXOffset + ptX - TEXT_GLOW_SIZE / 2 - offsetX, cardYOffset + ptY - TEXT_GLOW_SIZE / 2); - } - - if (isAnimationPanel || cardWidth < 200) { - imagePanel.setScalingType(ScalingType.nearestNeighbor); - } else { - imagePanel.setScalingType(ScalingType.bilinear); - } - } - - @Override - public String toString() { - return gameCard.toString(); - } - - @Override - public final void setCardBounds(int x, int y, int cardWidth, int cardHeight) { - this.cardWidth = cardWidth; - this.symbolWidth = cardWidth / 7; - this.cardHeight = cardHeight; - if (this.isPermanent) { - int rotCenterX = Math.round(cardWidth / 2f); - int rotCenterY = cardHeight - rotCenterX; - int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER); - int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER); - int xOffset = getXOffset(cardWidth); - int yOffset = getYOffset(cardWidth, cardHeight); - cardXOffset = -xOffset; - cardYOffset = -yOffset; - int width = -xOffset + rotCenterX + rotCenterToTopCorner; - int height = -yOffset + rotCenterY + rotCenterToBottomCorner; - setBounds(x + xOffset, y + yOffset, width, height); - } else { - cardXOffset = 5; - cardYOffset = 5; - int width = cardXOffset * 2 + cardWidth; - int height = cardYOffset * 2 + cardHeight; - setBounds(x - cardXOffset, y - cardYOffset, width, height); - } - } - - public int getXOffset(int cardWidth) { - if (this.isPermanent) { - int rotCenterX = Math.round(cardWidth / 2f); - int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER); - int xOffset = rotCenterX - rotCenterToBottomCorner; - return xOffset; - } else { - return cardXOffset; - } - } - - public int getYOffset(int cardWidth, int cardHeight) { - if (this.isPermanent) { - int rotCenterX = Math.round(cardWidth / 2f); - int rotCenterY = cardHeight - rotCenterX; - int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER); - int yOffset = rotCenterY - rotCenterToTopCorner; - return yOffset; - } else { - return cardYOffset; - } - - } - - public int getCardX() { - return getX() + cardXOffset; - } - - public int getCardY() { - return getY() + cardYOffset; - } - - public int getCardWidth() { - return cardWidth; - } - - public int getCardHeight() { - return cardHeight; - } - - public Point getCardLocation() { - Point p = getLocation(); - p.x += cardXOffset; - p.y += cardYOffset; - return p; - } - - public CardView getCard() { - return this.gameCard; - } - - @Override - public void setAlpha(float alpha) { - this.alpha = alpha; - if (alpha == 0) { - this.ptText.setVisible(false); - this.titleText.setVisible(false); - } else if (alpha == 1.0f) { - this.ptText.setVisible(true); - this.titleText.setVisible(true); - } - } - - @Override - public float getAlpha() { - return alpha; - } - - public int getCardXOffset() { - return cardXOffset; - } - - public int getCardYOffset() { - return cardYOffset; - } - - @Override - public void updateImage() { - Util.threadPool.submit(new Runnable() { - @Override - public void run() { - try { - tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; - flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; - BufferedImage srcImage; - if (gameCard.isFaceDown()) { - srcImage = getFaceDownImage(); - } else if (cardWidth > THUMBNAIL_SIZE_FULL.width) { - srcImage = ImageCache.getImage(gameCard, cardWidth, cardHeight); - } else { - srcImage = ImageCache.getThumbnail(gameCard); - } - if (srcImage != null) { - hasImage = true; - setText(gameCard); - setImage(srcImage); - } - } catch (Exception e) { - e.printStackTrace(); - } catch (Error err) { - err.printStackTrace(); - } - } - }); - } - - private BufferedImage getFaceDownImage() { - if (isPermanent) { - if (((PermanentView) gameCard).isMorphed()) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.getManifestImage(); - } - } else if (this.gameCard instanceof StackAbilityView) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); - } - } - - @Override - public List getLinks() { - return links; - } - - @Override - public boolean isTapped() { - if (isPermanent) { - return ((PermanentView) gameCard).isTapped(); - } - return false; - } - - @Override - public boolean isFlipped() { - if (isPermanent) { - return ((PermanentView) gameCard).isFlipped(); - } - return false; - } - - @Override - public boolean isTransformed() { - if (isPermanent) { - return gameCard.isTransformed(); - } - return false; - } - - @Override - public void showCardTitle() { - displayTitleAnyway = true; - setText(gameCard); - } - - @Override - public void onBeginAnimation() { - animationInProgress = true; - } - - @Override - public void onEndAnimation() { - animationInProgress = false; - } - - @Override - public void update(CardView card) { - this.updateCard = card; - if (isPermanent && (card instanceof PermanentView)) { - boolean needsTapping = isTapped() != ((PermanentView) card).isTapped(); - boolean needsFlipping = isFlipped() != ((PermanentView) card).isFlipped(); - if (needsTapping || needsFlipping) { - Animation.tapCardToggle(this, this, needsTapping, needsFlipping); - } - if (needsTapping && ((PermanentView) card).isTapped()) { - AudioManager.playTapPermanent(); - } - boolean needsTranforming = isTransformed() != card.isTransformed(); - if (needsTranforming) { - Animation.transformCard(this, this, card.isTransformed()); - } - } - if (card.canTransform()) { - dayNightButton.setVisible(!isPermanent); - } - - if (CardUtil.isCreature(card) && CardUtil.isPlaneswalker(card)) { - ptText.setText(card.getPower() + "/" + card.getToughness() + " (" + card.getLoyalty() + ")"); - } else if (CardUtil.isCreature(card)) { - ptText.setText(card.getPower() + "/" + card.getToughness()); - } else if (CardUtil.isPlaneswalker(card)) { - ptText.setText(card.getLoyalty()); - } else { - ptText.setText(""); - } - setText(card); - - this.isPlayable = card.isPlayable(); - this.isChoosable = card.isChoosable(); - this.canAttack = card.isCanAttack(); - this.isSelected = card.isSelected(); - - boolean updateImage = !gameCard.getName().equals(card.getName()) || gameCard.isFaceDown() != card.isFaceDown(); // update after e.g. turning a night/day card - if (updateImage && gameCard.canTransform() && card.canTransform() && transformed) { - if (card.getSecondCardFace() != null && card.getSecondCardFace().getName().equals(gameCard.getName())) { - transformed = false; - } - } - this.gameCard = card; - - String cardType = getType(card); - tooltipText.setText(getText(cardType, card)); - - if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) { - overlayPanel.setVisible(true); - } else { - overlayPanel.setVisible(false); - } - if (updateImage) { - updateImage(); - if (card.canTransform()) { - BufferedImage transformIcon; - if (transformed) { - transformIcon = ImageManagerImpl.getInstance().getNightImage(); - } else { - transformIcon = ImageManagerImpl.getInstance().getDayImage(); - } - dayNightButton.setIcon(new ImageIcon(transformIcon)); - } - } - - if (counterPanel != null) { - updateCounters(card); - } - - repaint(); - } - - private void updateCounters(CardView card) { - if (card.getCounters() != null && !card.getCounters().isEmpty()) { - String name = ""; - if (lastCardWidth != cardWidth) { - lastCardWidth = cardWidth; - plusCounter = 0; - minusCounter = 0; - otherCounter = 0; - loyaltyCounter = 0; - } - plusCounterLabel.setVisible(false); - minusCounterLabel.setVisible(false); - loyaltyCounterLabel.setVisible(false); - otherCounterLabel.setVisible(false); - for (CounterView counterView : card.getCounters()) { - if (counterView.getCount() == 0) { - continue; - } - switch (counterView.getName()) { - case "+1/+1": - if (counterView.getCount() != plusCounter) { - plusCounter = counterView.getCount(); - plusCounterLabel.setIcon(getCounterImageWithAmount(plusCounter, ImageManagerImpl.getInstance().getCounterImageGreen(), cardWidth)); - } - plusCounterLabel.setVisible(true); - break; - case "-1/-1": - if (counterView.getCount() != minusCounter) { - minusCounter = counterView.getCount(); - minusCounterLabel.setIcon(getCounterImageWithAmount(minusCounter, ImageManagerImpl.getInstance().getCounterImageRed(), cardWidth)); - } - minusCounterLabel.setVisible(true); - break; - case "loyalty": - if (counterView.getCount() != loyaltyCounter) { - loyaltyCounter = counterView.getCount(); - loyaltyCounterLabel.setIcon(getCounterImageWithAmount(loyaltyCounter, ImageManagerImpl.getInstance().getCounterImageViolet(), cardWidth)); - } - loyaltyCounterLabel.setVisible(true); - break; - default: - if (name.isEmpty()) { // only first other counter is shown - name = counterView.getName(); - otherCounter = counterView.getCount(); - otherCounterLabel.setToolTipText(name); - otherCounterLabel.setIcon(getCounterImageWithAmount(otherCounter, ImageManagerImpl.getInstance().getCounterImageGrey(), cardWidth)); - otherCounterLabel.setVisible(true); - } - } - } - - counterPanel.setVisible(true); - } else { - plusCounterLabel.setVisible(false); - minusCounterLabel.setVisible(false); - loyaltyCounterLabel.setVisible(false); - otherCounterLabel.setVisible(false); - counterPanel.setVisible(false); - } - - } - - private static ImageIcon getCounterImageWithAmount(int amount, BufferedImage image, int cardWidth) { - int factor = cardWidth > WIDTH_LIMIT ? 2 : 1; - int xOffset = amount > 9 ? 2 : 5; - int fontSize = factor == 1 ? amount < 10 ? 12 : amount < 100 ? 10 : amount < 1000 ? 7 : 6 - : amount < 10 ? 19 : amount < 100 ? 15 : amount < 1000 ? 12 : amount < 10000 ? 9 : 8; - BufferedImage newImage; - if (cardWidth > WIDTH_LIMIT) { - newImage = ImageManagerImpl.deepCopy(image); - } else { - newImage = ImageHelper.getResizedImage(image, 20, 20); - } - Graphics graphics = newImage.getGraphics(); - graphics.setColor(Color.BLACK); - graphics.setFont(new Font("Arial Black", amount > 100 ? Font.PLAIN : Font.BOLD, fontSize)); - graphics.drawString(Integer.toString(amount), xOffset * factor, 11 * factor); - return new ImageIcon(newImage); - } - - @Override - public boolean contains(int x, int y) { - return containsThis(x, y, true); - } - - public boolean containsThis(int x, int y, boolean root) { - Point component = getLocation(); - - int cx = getCardX() - component.x; - int cy = getCardY() - component.y; - int cw = getCardWidth(); - int ch = getCardHeight(); - if (isTapped()) { - cy = ch - cw + cx; - ch = cw; - cw = getCardHeight(); - } - - return x >= cx && x <= cx + cw && y >= cy && y <= cy + ch; - } - - @Override - public CardView getOriginal() { - return this.gameCard; - } - - @Override - public Image getImage() { - if (this.hasImage) { - if (gameCard.isFaceDown()) { - return getFaceDownImage(); - } else { - return ImageCache.getImageOriginal(gameCard); - } - } - return null; - } - - @Override - public void mouseClicked(MouseEvent e) { - } - - @Override - public void mouseEntered(MouseEvent e) { - if (gameCard.hideInfo()) { - return; - } - if (!tooltipShowing) { - synchronized (this) { - if (!tooltipShowing) { - TransferData transferData = getTransferDataForMouseEntered(); - if (this.isShowing()) { - tooltipShowing = true; - callback.mouseEntered(e, transferData); - } - } - } - } - } - - @Override - public void mouseDragged(MouseEvent e) { - data.component = this; - callback.mouseDragged(e, data); - } - - @Override - public void mouseMoved(MouseEvent e) { - if (gameCard.hideInfo()) { - return; - } - data.component = this; - callback.mouseMoved(e, data); - } - - @Override - public void mouseExited(MouseEvent e) { - if (gameCard.hideInfo()) { - return; - } - if (getMousePosition(true) != null) { - return; - } - if (tooltipShowing) { - synchronized (this) { - if (tooltipShowing) { - tooltipShowing = false; - data.component = this; - data.card = this.gameCard; - data.popupText = tooltipText; - callback.mouseExited(e, data); - } - } - } - } - - @Override - public void mousePressed(MouseEvent e) { - data.component = this; - data.card = this.gameCard; - data.gameId = this.gameId; - callback.mousePressed(e, data); - } - - @Override - public void mouseReleased(MouseEvent e) { - callback.mouseReleased(e, data); - } - - /** - * Prepares data to be sent to action callback on client side. - * - * @return - */ - private TransferData getTransferDataForMouseEntered() { - data.component = this; - data.card = this.gameCard; - data.popupText = tooltipText; - data.gameId = this.gameId; - data.locationOnScreen = data.component.getLocationOnScreen(); // we need this for popup - data.popupOffsetX = isTapped() ? cardHeight + cardXOffset + POPUP_X_GAP : cardWidth + cardXOffset + POPUP_X_GAP; - data.popupOffsetY = 40; - return data; - } - - protected final String getType(CardView card) { - StringBuilder sbType = new StringBuilder(); - - for (String superType : card.getSuperTypes()) { - sbType.append(superType).append(" "); - } - - for (CardType cardType : card.getCardTypes()) { - sbType.append(cardType.toString()).append(" "); - } - - if (card.getSubTypes().size() > 0) { - sbType.append("- "); - for (String subType : card.getSubTypes()) { - sbType.append(subType).append(" "); - } - } - - return sbType.toString().trim(); - } - - protected final String getText(String cardType, CardView card) { - StringBuilder sb = new StringBuilder(); - if (card instanceof StackAbilityView || card instanceof AbilityView) { - for (String rule : card.getRules()) { - sb.append("\n").append(rule); - } - } else { - sb.append(card.getName()); - if (card.getManaCost().size() > 0) { - sb.append("\n").append(card.getManaCost()); - } - sb.append("\n").append(cardType); - if (card.getColor().hasColor()) { - sb.append("\n").append(card.getColor().toString()); - } - if (card.getCardTypes().contains(CardType.CREATURE)) { - sb.append("\n").append(card.getPower()).append("/").append(card.getToughness()); - } else if (card.getCardTypes().contains(CardType.PLANESWALKER)) { - sb.append("\n").append(card.getLoyalty()); - } - if (card.getRules() == null) { - card.overrideRules(new ArrayList()); - } - for (String rule : card.getRules()) { - sb.append("\n").append(rule); - } - if (card.getExpansionSetCode() != null && card.getExpansionSetCode().length() > 0) { - sb.append("\n").append(card.getCardNumber()).append(" - "); - sb.append(card.getExpansionSetCode()).append(" - "); - sb.append(card.getRarity().toString()); - } - } - return sb.toString(); - } - - @Override - public void update(PermanentView card) { - this.hasSickness = card.hasSummoningSickness(); - this.copyIconPanel.setVisible(card.isCopy()); - update((CardView) card); - } - - @Override - public PermanentView getOriginalPermanent() { - if (isPermanent) { - return (PermanentView) this.gameCard; - } - throw new IllegalStateException("Is not permanent."); - } - - @Override - public void updateCallback(ActionCallback callback, UUID gameId) { - this.callback = callback; - this.gameId = gameId; - } - - public void setTransformed(boolean transformed) { - this.transformed = transformed; - } - - @Override - public void toggleTransformed() { - this.transformed = !this.transformed; - if (transformed) { - if (dayNightButton != null) { // if transformbable card is copied, button can be null - BufferedImage night = ImageManagerImpl.getInstance().getNightImage(); - dayNightButton.setIcon(new ImageIcon(night)); - } - if (this.gameCard.getSecondCardFace() == null) { - LOGGER.error("no second side for card to transform!"); - return; - } - if (!isPermanent) { // use only for custom transformation (when pressing day-night button) - this.temporary = this.gameCard; - update(this.gameCard.getSecondCardFace()); - } - } else { - if (dayNightButton != null) { // if transformbable card is copied, button can be null - BufferedImage day = ImageManagerImpl.getInstance().getDayImage(); - dayNightButton.setIcon(new ImageIcon(day)); - } - if (!isPermanent) { // use only for custom transformation (when pressing day-night button) - update(this.temporary); - this.temporary = null; - } - } - String temp = this.gameCard.getAlternateName(); - this.gameCard.setAlternateName(this.gameCard.getOriginalName()); - this.gameCard.setOriginalName(temp); - updateImage(); - } - - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - if (gameCard.hideInfo()) { - return; - } - data.component = this; - callback.mouseWheelMoved(e, data); - } - - public JPanel getCardArea() { - return cardArea; - } - - @Override - public void componentResized(ComponentEvent ce) { - doLayout(); - // this update removes the isChoosable mark from targetCardsInLibrary - // so only done for permanents because it's needed to redraw counters in different size, if window size was changed - // no perfect solution yet (maybe also other not wanted effects for PermanentView objects) - if (updateCard != null && (updateCard instanceof PermanentView)) { - update(updateCard); - } - } - - @Override - public void componentMoved(ComponentEvent ce) { - } - - @Override - public void componentShown(ComponentEvent ce) { - } - - @Override - public void componentHidden(ComponentEvent ce) { - } - - @Override - public void setTextOffset(int yOffset) { - yTextOffset = yOffset; - } - - @Override - public JPopupMenu getPopupMenu() { - return popupMenu; - } - - @Override - public void setPopupMenu(JPopupMenu popupMenu) { - this.popupMenu = popupMenu; - } - -} +package org.mage.card.arcane; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.UUID; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import mage.cards.MagePermanent; +import mage.cards.TextPopup; +import mage.cards.action.ActionCallback; +import mage.cards.action.TransferData; +import mage.client.dialog.PreferencesDialog; +import mage.client.plugins.adapters.MageActionCallback; +import mage.client.plugins.impl.Plugins; +import mage.client.util.ImageHelper; +import mage.client.util.audio.AudioManager; +import mage.components.ImagePanel; +import mage.constants.AbilityType; +import mage.constants.CardType; +import mage.constants.EnlargeMode; +import mage.utils.CardUtil; +import mage.view.AbilityView; +import mage.view.CardView; +import mage.view.CounterView; +import mage.view.PermanentView; +import mage.view.StackAbilityView; +import net.java.truevfs.access.TFile; +import org.apache.log4j.Logger; +import org.mage.card.arcane.ScaledImagePanel.MultipassType; +import org.mage.card.arcane.ScaledImagePanel.ScalingType; +import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL; +import org.mage.plugins.card.dl.sources.DirectLinksForDownload; +import org.mage.plugins.card.images.ImageCache; +import org.mage.plugins.card.utils.impl.ImageManagerImpl; + +/** + * Main class for drawing Mage card object. + * + * @author arcane, nantuko, noxx + */ +@SuppressWarnings({"unchecked", "rawtypes"}) +public class CardPanel extends MagePermanent implements MouseListener, MouseMotionListener, MouseWheelListener, ComponentListener { + + private static final long serialVersionUID = -3272134219262184410L; + + private static final Logger LOGGER = Logger.getLogger(CardPanel.class); + + private static final int WIDTH_LIMIT = 90; // card width limit to create smaller counter + public static final double TAPPED_ANGLE = Math.PI / 2; + public static final double FLIPPED_ANGLE = Math.PI; + public static final float ASPECT_RATIO = 3.5f / 2.5f; + public static final int POPUP_X_GAP = 1; // prevent tooltip window from blinking + + public static CardPanel dragAnimationPanel; + + public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149); + + private static final float ROUNDED_CORNER_SIZE = 0.1f; + private static final float BLACK_BORDER_SIZE = 0.03f; + private static final int TEXT_GLOW_SIZE = 6; + private static final float TEXT_GLOW_INTENSITY = 3f; + private static final float ROT_CENTER_TO_TOP_CORNER = 1.0295630140987000315797369464196f; + private static final float ROT_CENTER_TO_BOTTOM_CORNER = 0.7071067811865475244008443621048f; + + public CardView gameCard; + public CardView updateCard; + + // for two faced cards + public CardView temporary; + private List links = new ArrayList<>(); + + public double tappedAngle = 0; + public double flippedAngle = 0; + public final ScaledImagePanel imagePanel; + public ImagePanel overlayPanel; + + public JPanel buttonPanel; + private JButton dayNightButton; + + public JPanel copyIconPanel; + private JButton showCopySourceButton; + + public JPanel iconPanel; + private JButton typeButton; + + public JPanel counterPanel; + private JLabel loyaltyCounterLabel; + private JLabel plusCounterLabel; + private JLabel otherCounterLabel; + private JLabel minusCounterLabel; + private int loyaltyCounter; + private int plusCounter; + private int otherCounter; + private int minusCounter; + private int lastCardWidth; + + private GlowText titleText; + private GlowText ptText; + private boolean displayEnabled = true; + private boolean isAnimationPanel; + public int cardXOffset, cardYOffset, cardWidth, cardHeight; + private int symbolWidth; + + private boolean isSelected; + private boolean isPlayable; + private boolean isChoosable; + private boolean canAttack; + private boolean showCastingCost; + private boolean hasImage = false; + private float alpha = 1.0f; + + private ActionCallback callback; + + protected boolean tooltipShowing; + protected TextPopup tooltipText = new TextPopup(); + protected UUID gameId; + private TransferData data = new TransferData(); + + private boolean isPermanent; + private boolean hasSickness; + private String zone; + + public double transformAngle = 1; + + private boolean transformed; + private boolean animationInProgress = false; + + private boolean displayTitleAnyway; + + private JPanel cardArea; + + private int yTextOffset = 10; + + // if this is set, it's opened if the user right clicks on the card panel + private JPopupMenu popupMenu; + + public CardPanel(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension) { + this.gameCard = newGameCard; + this.callback = callback; + this.gameId = gameId; + + this.isPermanent = this.gameCard instanceof PermanentView; + if (isPermanent) { + this.hasSickness = ((PermanentView) this.gameCard).hasSummoningSickness(); + } + + this.setCardBounds(0, 0, dimension.width, dimension.height); + + //for container debug (don't remove) + //setBorder(BorderFactory.createLineBorder(Color.green)); + if (this.gameCard.canTransform()) { + buttonPanel = new JPanel(); + buttonPanel.setLayout(null); + buttonPanel.setOpaque(false); + add(buttonPanel); + + dayNightButton = new JButton(""); + dayNightButton.setLocation(2, 2); + dayNightButton.setSize(25, 25); + + buttonPanel.setVisible(true); + + BufferedImage day = ImageManagerImpl.getInstance().getDayImage(); + dayNightButton.setIcon(new ImageIcon(day)); + + buttonPanel.add(dayNightButton); + + dayNightButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // if card is being rotated, ignore action performed + // if card is tapped, no visual transforming is possible (implementation limitation) + // if card is permanent, it will be rotated by Mage, so manual rotate should be possible + if (animationInProgress || isTapped() || isPermanent) { + return; + } + Animation.transformCard(CardPanel.this, CardPanel.this, true); + } + }); + } + if (!newGameCard.isAbility()) { + // panel to show counters on the card + counterPanel = new JPanel(); + counterPanel.setLayout(null); + counterPanel.setOpaque(false); + add(counterPanel); + + plusCounterLabel = new JLabel(""); + plusCounterLabel.setToolTipText("+1/+1"); + counterPanel.add(plusCounterLabel); + + minusCounterLabel = new JLabel(""); + minusCounterLabel.setToolTipText("-1/-1"); + counterPanel.add(minusCounterLabel); + + loyaltyCounterLabel = new JLabel(""); + loyaltyCounterLabel.setToolTipText("loyalty"); + counterPanel.add(loyaltyCounterLabel); + + otherCounterLabel = new JLabel(""); + counterPanel.add(otherCounterLabel); + + counterPanel.setVisible(false); + } + if (newGameCard.isAbility()) { + if (AbilityType.TRIGGERED.equals(newGameCard.getAbilityType())) { + setTypeIcon(ImageManagerImpl.getInstance().getTriggeredAbilityImage(), "Triggered Ability"); + } else if (AbilityType.ACTIVATED.equals(newGameCard.getAbilityType())) { + setTypeIcon(ImageManagerImpl.getInstance().getActivatedAbilityImage(), "Activated Ability"); + } + } + + if (this.gameCard.isToken()) { + setTypeIcon(ImageManagerImpl.getInstance().getTokenIconImage(), "Token Permanent"); + } + + // icon to inform about permanent is copying something + if (this.gameCard instanceof PermanentView) { + copyIconPanel = new JPanel(); + copyIconPanel.setLayout(null); + copyIconPanel.setOpaque(false); + add(copyIconPanel); + + showCopySourceButton = new JButton(""); + showCopySourceButton.setLocation(2, 2); + showCopySourceButton.setSize(25, 25); + showCopySourceButton.setToolTipText("This permanent is copying a target. To see original image, push this button or turn mouse wheel down while hovering with the mouse pointer over the permanent."); + copyIconPanel.setVisible(((PermanentView) this.gameCard).isCopy()); + + showCopySourceButton.setIcon(new ImageIcon(ImageManagerImpl.getInstance().getCopyInformIconImage())); + + copyIconPanel.add(showCopySourceButton); + + showCopySourceButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ActionCallback callback = Plugins.getInstance().getActionCallback(); + ((MageActionCallback) callback).enlargeCard(EnlargeMode.COPY); + } + }); + } + + setBackground(Color.black); + setOpaque(false); + + addMouseListener(this); + addMouseMotionListener(this); + addMouseWheelListener(this); + addComponentListener(this); + + displayTitleAnyway = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_CARD_NAMES, "true").equals("true"); + + titleText = new GlowText(); + setText(gameCard); +// int fontSize = (int) cardHeight / 11; +// titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + titleText.setForeground(Color.white); + titleText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); + titleText.setWrap(true); + add(titleText); + + ptText = new GlowText(); + if (CardUtil.isCreature(gameCard)) { + ptText.setText(gameCard.getPower() + "/" + gameCard.getToughness()); + } else if (CardUtil.isPlaneswalker(gameCard)) { + ptText.setText(gameCard.getLoyalty()); + } +// ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + ptText.setForeground(Color.white); + ptText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); + add(ptText); + + BufferedImage sickness = ImageManagerImpl.getInstance().getSicknessImage(); + overlayPanel = new ImagePanel(sickness, ImagePanel.SCALED); + overlayPanel.setOpaque(false); + add(overlayPanel); + + imagePanel = new ScaledImagePanel(); + imagePanel.setBorder(BorderFactory.createLineBorder(Color.white)); + add(imagePanel); + imagePanel.setScaleLarger(true); + imagePanel.setScalingType(ScalingType.nearestNeighbor); + imagePanel.setScalingMultiPassType(MultipassType.none); + + String cardType = getType(newGameCard); + tooltipText.setText(getText(cardType, newGameCard)); + + Util.threadPool.submit(new Runnable() { + @Override + public void run() { + try { + tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; + flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; + if (!loadImage) { + return; + } + BufferedImage srcImage; + if (gameCard.isFaceDown()) { + srcImage = getFaceDownImage(); + } else { + srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); + } + if (srcImage != null) { + hasImage = true; + setText(gameCard); + setImage(srcImage); + } + if (gameCard.isTransformed()) { + toggleTransformed(); + } + setText(gameCard); + } catch (Exception e) { + LOGGER.fatal("Problem during image animation", e); + } catch (Error err) { + LOGGER.error("Problem during image animation", err); + } + } + }); + } + + private void setTypeIcon(BufferedImage bufferedImage, String toolTipText) { + iconPanel = new JPanel(); + iconPanel.setLayout(null); + iconPanel.setOpaque(false); + add(iconPanel); + + typeButton = new JButton(""); + typeButton.setLocation(2, 2); + typeButton.setSize(25, 25); + + iconPanel.setVisible(true); + typeButton.setIcon(new ImageIcon(bufferedImage)); + if (toolTipText != null) { + typeButton.setToolTipText(toolTipText); + } + iconPanel.add(typeButton); + } + + public void cleanUp() { + if (dayNightButton != null) { + for (ActionListener al : dayNightButton.getActionListeners()) { + dayNightButton.removeActionListener(al); + } + } + for (MouseListener ml : this.getMouseListeners()) { + this.removeMouseListener(ml); + } + for (MouseMotionListener ml : this.getMouseMotionListeners()) { + this.removeMouseMotionListener(ml); + } + for (MouseWheelListener ml : this.getMouseWheelListeners()) { + this.removeMouseWheelListener(ml); + } + // this holds reference to ActionCallback forever so set it to null to prevent + this.callback = null; + this.data = null; + this.counterPanel = null; + } + + private void setText(CardView card) { + titleText.setText(!displayTitleAnyway && hasImage ? "" : card.getName()); + } + + private void setImage(Image srcImage) { + synchronized (imagePanel) { + imagePanel.setImage(srcImage); + repaint(); + } + doLayout(); + } + + public void setImage(final CardPanel panel) { + synchronized (panel.imagePanel) { + if (panel.imagePanel.hasImage()) { + setImage(panel.imagePanel.getSrcImage()); + } + } + } + + @Override + public void setZone(String zone) { + this.zone = zone; + } + + @Override + public String getZone() { + return zone; + } + + public void setScalingType(ScalingType scalingType) { + imagePanel.setScalingType(scalingType); + } + + public void setDisplayEnabled(boolean displayEnabled) { + this.displayEnabled = displayEnabled; + } + + public boolean isDisplayEnabled() { + return displayEnabled; + } + + public void setAnimationPanel(boolean isAnimationPanel) { + this.isAnimationPanel = isAnimationPanel; + } + + @Override + public void setSelected(boolean isSelected) { + this.isSelected = isSelected; + if (isSelected) { + this.titleText.setGlowColor(Color.green); + } else { + this.titleText.setGlowColor(Color.black); + } + // noxx: bad idea is to call repaint in setter method + ////repaint(); + } + + @Override + public void setChoosable(boolean isChoosable) { + this.isChoosable = isChoosable; + } + + @Override + public void setCardAreaRef(JPanel cardArea) { + this.cardArea = cardArea; + } + + public boolean getSelected() { + return this.isSelected; + } + + public void setShowCastingCost(boolean showCastingCost) { + this.showCastingCost = showCastingCost; + } + + @Override + public void paint(Graphics g) { + if (!displayEnabled) { + return; + } + if (!isValid()) { + super.validate(); + } + Graphics2D g2d = (Graphics2D) g; + if (transformAngle < 1) { + float edgeOffset = (cardWidth + cardXOffset) / 2f; + g2d.translate(edgeOffset * (1 - transformAngle), 0); + g2d.scale(transformAngle, 1); + } + if (tappedAngle + flippedAngle > 0) { + g2d = (Graphics2D) g2d.create(); + float edgeOffset = cardWidth / 2f; + double angle = tappedAngle + (Math.abs(flippedAngle - FLIPPED_ANGLE) < 0.001 ? 0 : flippedAngle); + g2d.rotate(angle, cardXOffset + edgeOffset, cardYOffset + cardHeight - edgeOffset); + } + super.paint(g2d); + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + if (alpha != 1.0f) { + AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha); + g2d.setComposite(composite); + } + + if (!hasImage) { + g2d.setColor(new Color(30, 200, 200, 120)); + } else { + g2d.setColor(new Color(0, 0, 0, 0)); + } + + int cornerSize = Math.max(4, Math.round(cardWidth * ROUNDED_CORNER_SIZE)); + g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); + + if (isSelected) { + g2d.setColor(Color.green); + g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + } else if (isChoosable) { + g2d.setColor(new Color(250, 250, 0, 230)); + g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + } else if (isPlayable) { + g2d.setColor(new Color(153, 102, 204, 200)); + //g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); + } + + if (canAttack) { + g2d.setColor(new Color(0, 0, 255, 230)); + g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + } + + //TODO:uncomment + /* + if (gameCard.isAttacking()) { + g2d.setColor(new Color(200,10,10,200)); + g2d.fillRoundRect(cardXOffset+1, cardYOffset+1, cardWidth-2, cardHeight-2, cornerSize, cornerSize); + }*/ + } + + @Override + protected void paintChildren(Graphics g) { + super.paintChildren(g); + + if (showCastingCost && !isAnimationPanel && cardWidth < 200 && cardWidth > 60) { + String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost()); + int width = getWidth(manaCost); + if (hasImage) { + ManaSymbols.draw(g, manaCost, cardXOffset + cardWidth - width - 5, cardYOffset + 5, symbolWidth); + } else { + ManaSymbols.draw(g, manaCost, cardXOffset + 8, cardHeight - 9, symbolWidth); + } + } + } + + private int getWidth(String manaCost) { + int width = 0; + manaCost = manaCost.replace("\\", ""); + StringTokenizer tok = new StringTokenizer(manaCost, " "); + while (tok.hasMoreTokens()) { + tok.nextToken(); + width += symbolWidth; + } + return width; + } + + @Override + public void doLayout() { + int borderSize = Math.round(cardWidth * BLACK_BORDER_SIZE); + imagePanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + imagePanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + + if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) { + overlayPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + overlayPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + } else { + overlayPanel.setVisible(false); + } + + if (buttonPanel != null) { + buttonPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + buttonPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + dayNightButton.setLocation(0, cardHeight - 30); + } + if (iconPanel != null) { + iconPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + iconPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + } + if (copyIconPanel != null) { + copyIconPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + copyIconPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + } + if (counterPanel != null) { + counterPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + counterPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + int size = cardWidth > WIDTH_LIMIT ? 40 : 20; + + minusCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size * 2); + minusCounterLabel.setSize(size, size); + + plusCounterLabel.setLocation(5, counterPanel.getHeight() - size * 2); + plusCounterLabel.setSize(size, size); + + loyaltyCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size); + loyaltyCounterLabel.setSize(size, size); + + otherCounterLabel.setLocation(5, counterPanel.getHeight() - size); + otherCounterLabel.setSize(size, size); + + } + int fontHeight = Math.round(cardHeight * (27f / 680)); + boolean showText = (!isAnimationPanel && fontHeight < 12); + titleText.setVisible(showText); + ptText.setVisible(showText); + + if (showText) { + int fontSize = (int) cardHeight / 11; + titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + + int titleX = Math.round(cardWidth * (20f / 480)); + int titleY = Math.round(cardHeight * (9f / 680)) + yTextOffset; + titleText.setBounds(cardXOffset + titleX, cardYOffset + titleY, cardWidth - titleX, cardHeight - titleY); + + ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + Dimension ptSize = ptText.getPreferredSize(); + ptText.setSize(ptSize.width, ptSize.height); + int ptX = Math.round(cardWidth * (420f / 480)) - ptSize.width / 2; + int ptY = Math.round(cardHeight * (675f / 680)) - ptSize.height; + + int offsetX = Math.round((CARD_SIZE_FULL.width - cardWidth) / 10.0f); + + ptText.setLocation(cardXOffset + ptX - TEXT_GLOW_SIZE / 2 - offsetX, cardYOffset + ptY - TEXT_GLOW_SIZE / 2); + } + + if (isAnimationPanel || cardWidth < 200) { + imagePanel.setScalingType(ScalingType.nearestNeighbor); + } else { + imagePanel.setScalingType(ScalingType.bilinear); + } + } + + @Override + public String toString() { + return gameCard.toString(); + } + + @Override + public final void setCardBounds(int x, int y, int cardWidth, int cardHeight) { + this.cardWidth = cardWidth; + this.symbolWidth = cardWidth / 7; + this.cardHeight = cardHeight; + if (this.isPermanent) { + int rotCenterX = Math.round(cardWidth / 2f); + int rotCenterY = cardHeight - rotCenterX; + int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER); + int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER); + int xOffset = getXOffset(cardWidth); + int yOffset = getYOffset(cardWidth, cardHeight); + cardXOffset = -xOffset; + cardYOffset = -yOffset; + int width = -xOffset + rotCenterX + rotCenterToTopCorner; + int height = -yOffset + rotCenterY + rotCenterToBottomCorner; + setBounds(x + xOffset, y + yOffset, width, height); + } else { + cardXOffset = 5; + cardYOffset = 5; + int width = cardXOffset * 2 + cardWidth; + int height = cardYOffset * 2 + cardHeight; + setBounds(x - cardXOffset, y - cardYOffset, width, height); + } + } + + public int getXOffset(int cardWidth) { + if (this.isPermanent) { + int rotCenterX = Math.round(cardWidth / 2f); + int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER); + int xOffset = rotCenterX - rotCenterToBottomCorner; + return xOffset; + } else { + return cardXOffset; + } + } + + public int getYOffset(int cardWidth, int cardHeight) { + if (this.isPermanent) { + int rotCenterX = Math.round(cardWidth / 2f); + int rotCenterY = cardHeight - rotCenterX; + int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER); + int yOffset = rotCenterY - rotCenterToTopCorner; + return yOffset; + } else { + return cardYOffset; + } + + } + + public int getCardX() { + return getX() + cardXOffset; + } + + public int getCardY() { + return getY() + cardYOffset; + } + + public int getCardWidth() { + return cardWidth; + } + + public int getCardHeight() { + return cardHeight; + } + + public Point getCardLocation() { + Point p = getLocation(); + p.x += cardXOffset; + p.y += cardYOffset; + return p; + } + + public CardView getCard() { + return this.gameCard; + } + + @Override + public void setAlpha(float alpha) { + this.alpha = alpha; + if (alpha == 0) { + this.ptText.setVisible(false); + this.titleText.setVisible(false); + } else if (alpha == 1.0f) { + this.ptText.setVisible(true); + this.titleText.setVisible(true); + } + } + + @Override + public float getAlpha() { + return alpha; + } + + public int getCardXOffset() { + return cardXOffset; + } + + public int getCardYOffset() { + return cardYOffset; + } + + @Override + public void updateImage() { + Util.threadPool.submit(new Runnable() { + @Override + public void run() { + try { + tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; + flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; + BufferedImage srcImage; + if (gameCard.isFaceDown()) { + srcImage = getFaceDownImage(); + } else if (cardWidth > THUMBNAIL_SIZE_FULL.width) { + srcImage = ImageCache.getImage(gameCard, cardWidth, cardHeight); + } else { + srcImage = ImageCache.getThumbnail(gameCard); + } + if (srcImage != null) { + hasImage = true; + setText(gameCard); + setImage(srcImage); + } + } catch (Exception e) { + e.printStackTrace(); + } catch (Error err) { + err.printStackTrace(); + } + } + }); + } + + private BufferedImage getFaceDownImage() { + if (isPermanent) { + if (((PermanentView) gameCard).isMorphed()) { + return ImageCache.getMorphImage(); + } else { + return ImageCache.getManifestImage(); + } + } else if (this.gameCard instanceof StackAbilityView) { + return ImageCache.getMorphImage(); + } else { + return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); + } + } + + @Override + public List getLinks() { + return links; + } + + @Override + public boolean isTapped() { + if (isPermanent) { + return ((PermanentView) gameCard).isTapped(); + } + return false; + } + + @Override + public boolean isFlipped() { + if (isPermanent) { + return ((PermanentView) gameCard).isFlipped(); + } + return false; + } + + @Override + public boolean isTransformed() { + if (isPermanent) { + return gameCard.isTransformed(); + } + return false; + } + + @Override + public void showCardTitle() { + displayTitleAnyway = true; + setText(gameCard); + } + + @Override + public void onBeginAnimation() { + animationInProgress = true; + } + + @Override + public void onEndAnimation() { + animationInProgress = false; + } + + @Override + public void update(CardView card) { + this.updateCard = card; + if (isPermanent && (card instanceof PermanentView)) { + boolean needsTapping = isTapped() != ((PermanentView) card).isTapped(); + boolean needsFlipping = isFlipped() != ((PermanentView) card).isFlipped(); + if (needsTapping || needsFlipping) { + Animation.tapCardToggle(this, this, needsTapping, needsFlipping); + } + if (needsTapping && ((PermanentView) card).isTapped()) { + AudioManager.playTapPermanent(); + } + boolean needsTranforming = isTransformed() != card.isTransformed(); + if (needsTranforming) { + Animation.transformCard(this, this, card.isTransformed()); + } + } + if (card.canTransform()) { + dayNightButton.setVisible(!isPermanent); + } + + if (CardUtil.isCreature(card) && CardUtil.isPlaneswalker(card)) { + ptText.setText(card.getPower() + "/" + card.getToughness() + " (" + card.getLoyalty() + ")"); + } else if (CardUtil.isCreature(card)) { + ptText.setText(card.getPower() + "/" + card.getToughness()); + } else if (CardUtil.isPlaneswalker(card)) { + ptText.setText(card.getLoyalty()); + } else { + ptText.setText(""); + } + setText(card); + + this.isPlayable = card.isPlayable(); + this.isChoosable = card.isChoosable(); + this.canAttack = card.isCanAttack(); + this.isSelected = card.isSelected(); + + boolean updateImage = !gameCard.getName().equals(card.getName()) || gameCard.isFaceDown() != card.isFaceDown(); // update after e.g. turning a night/day card + if (updateImage && gameCard.canTransform() && card.canTransform() && transformed) { + if (card.getSecondCardFace() != null && card.getSecondCardFace().getName().equals(gameCard.getName())) { + transformed = false; + } + } + this.gameCard = card; + + String cardType = getType(card); + tooltipText.setText(getText(cardType, card)); + + if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) { + overlayPanel.setVisible(true); + } else { + overlayPanel.setVisible(false); + } + if (updateImage) { + updateImage(); + if (card.canTransform()) { + BufferedImage transformIcon; + if (transformed || card.isTransformed()) { + transformIcon = ImageManagerImpl.getInstance().getNightImage(); + } else { + transformIcon = ImageManagerImpl.getInstance().getDayImage(); + } + dayNightButton.setIcon(new ImageIcon(transformIcon)); + } + } + + if (counterPanel != null) { + updateCounters(card); + } + + repaint(); + } + + private void updateCounters(CardView card) { + if (card.getCounters() != null && !card.getCounters().isEmpty()) { + String name = ""; + if (lastCardWidth != cardWidth) { + lastCardWidth = cardWidth; + plusCounter = 0; + minusCounter = 0; + otherCounter = 0; + loyaltyCounter = 0; + } + plusCounterLabel.setVisible(false); + minusCounterLabel.setVisible(false); + loyaltyCounterLabel.setVisible(false); + otherCounterLabel.setVisible(false); + for (CounterView counterView : card.getCounters()) { + if (counterView.getCount() == 0) { + continue; + } + switch (counterView.getName()) { + case "+1/+1": + if (counterView.getCount() != plusCounter) { + plusCounter = counterView.getCount(); + plusCounterLabel.setIcon(getCounterImageWithAmount(plusCounter, ImageManagerImpl.getInstance().getCounterImageGreen(), cardWidth)); + } + plusCounterLabel.setVisible(true); + break; + case "-1/-1": + if (counterView.getCount() != minusCounter) { + minusCounter = counterView.getCount(); + minusCounterLabel.setIcon(getCounterImageWithAmount(minusCounter, ImageManagerImpl.getInstance().getCounterImageRed(), cardWidth)); + } + minusCounterLabel.setVisible(true); + break; + case "loyalty": + if (counterView.getCount() != loyaltyCounter) { + loyaltyCounter = counterView.getCount(); + loyaltyCounterLabel.setIcon(getCounterImageWithAmount(loyaltyCounter, ImageManagerImpl.getInstance().getCounterImageViolet(), cardWidth)); + } + loyaltyCounterLabel.setVisible(true); + break; + default: + if (name.isEmpty()) { // only first other counter is shown + name = counterView.getName(); + otherCounter = counterView.getCount(); + otherCounterLabel.setToolTipText(name); + otherCounterLabel.setIcon(getCounterImageWithAmount(otherCounter, ImageManagerImpl.getInstance().getCounterImageGrey(), cardWidth)); + otherCounterLabel.setVisible(true); + } + } + } + + counterPanel.setVisible(true); + } else { + plusCounterLabel.setVisible(false); + minusCounterLabel.setVisible(false); + loyaltyCounterLabel.setVisible(false); + otherCounterLabel.setVisible(false); + counterPanel.setVisible(false); + } + + } + + private static ImageIcon getCounterImageWithAmount(int amount, BufferedImage image, int cardWidth) { + int factor = cardWidth > WIDTH_LIMIT ? 2 : 1; + int xOffset = amount > 9 ? 2 : 5; + int fontSize = factor == 1 ? amount < 10 ? 12 : amount < 100 ? 10 : amount < 1000 ? 7 : 6 + : amount < 10 ? 19 : amount < 100 ? 15 : amount < 1000 ? 12 : amount < 10000 ? 9 : 8; + BufferedImage newImage; + if (cardWidth > WIDTH_LIMIT) { + newImage = ImageManagerImpl.deepCopy(image); + } else { + newImage = ImageHelper.getResizedImage(image, 20, 20); + } + Graphics graphics = newImage.getGraphics(); + graphics.setColor(Color.BLACK); + graphics.setFont(new Font("Arial Black", amount > 100 ? Font.PLAIN : Font.BOLD, fontSize)); + graphics.drawString(Integer.toString(amount), xOffset * factor, 11 * factor); + return new ImageIcon(newImage); + } + + @Override + public boolean contains(int x, int y) { + return containsThis(x, y, true); + } + + public boolean containsThis(int x, int y, boolean root) { + Point component = getLocation(); + + int cx = getCardX() - component.x; + int cy = getCardY() - component.y; + int cw = getCardWidth(); + int ch = getCardHeight(); + if (isTapped()) { + cy = ch - cw + cx; + ch = cw; + cw = getCardHeight(); + } + + return x >= cx && x <= cx + cw && y >= cy && y <= cy + ch; + } + + @Override + public CardView getOriginal() { + return this.gameCard; + } + + @Override + public Image getImage() { + if (this.hasImage) { + if (gameCard.isFaceDown()) { + return getFaceDownImage(); + } else { + return ImageCache.getImageOriginal(gameCard); + } + } + return null; + } + + @Override + public void mouseClicked(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + if (gameCard.hideInfo()) { + return; + } + if (!tooltipShowing) { + synchronized (this) { + if (!tooltipShowing) { + TransferData transferData = getTransferDataForMouseEntered(); + if (this.isShowing()) { + tooltipShowing = true; + callback.mouseEntered(e, transferData); + } + } + } + } + } + + @Override + public void mouseDragged(MouseEvent e) { + data.component = this; + callback.mouseDragged(e, data); + } + + @Override + public void mouseMoved(MouseEvent e) { + if (gameCard.hideInfo()) { + return; + } + data.component = this; + callback.mouseMoved(e, data); + } + + @Override + public void mouseExited(MouseEvent e) { + if (gameCard.hideInfo()) { + return; + } + if (getMousePosition(true) != null) { + return; + } + if (tooltipShowing) { + synchronized (this) { + if (tooltipShowing) { + tooltipShowing = false; + data.component = this; + data.card = this.gameCard; + data.popupText = tooltipText; + callback.mouseExited(e, data); + } + } + } + } + + @Override + public void mousePressed(MouseEvent e) { + data.component = this; + data.card = this.gameCard; + data.gameId = this.gameId; + callback.mousePressed(e, data); + } + + @Override + public void mouseReleased(MouseEvent e) { + callback.mouseReleased(e, data); + } + + /** + * Prepares data to be sent to action callback on client side. + * + * @return + */ + private TransferData getTransferDataForMouseEntered() { + data.component = this; + data.card = this.gameCard; + data.popupText = tooltipText; + data.gameId = this.gameId; + data.locationOnScreen = data.component.getLocationOnScreen(); // we need this for popup + data.popupOffsetX = isTapped() ? cardHeight + cardXOffset + POPUP_X_GAP : cardWidth + cardXOffset + POPUP_X_GAP; + data.popupOffsetY = 40; + return data; + } + + protected final String getType(CardView card) { + StringBuilder sbType = new StringBuilder(); + + for (String superType : card.getSuperTypes()) { + sbType.append(superType).append(" "); + } + + for (CardType cardType : card.getCardTypes()) { + sbType.append(cardType.toString()).append(" "); + } + + if (card.getSubTypes().size() > 0) { + sbType.append("- "); + for (String subType : card.getSubTypes()) { + sbType.append(subType).append(" "); + } + } + + return sbType.toString().trim(); + } + + protected final String getText(String cardType, CardView card) { + StringBuilder sb = new StringBuilder(); + if (card instanceof StackAbilityView || card instanceof AbilityView) { + for (String rule : card.getRules()) { + sb.append("\n").append(rule); + } + } else { + sb.append(card.getName()); + if (card.getManaCost().size() > 0) { + sb.append("\n").append(card.getManaCost()); + } + sb.append("\n").append(cardType); + if (card.getColor().hasColor()) { + sb.append("\n").append(card.getColor().toString()); + } + if (card.getCardTypes().contains(CardType.CREATURE)) { + sb.append("\n").append(card.getPower()).append("/").append(card.getToughness()); + } else if (card.getCardTypes().contains(CardType.PLANESWALKER)) { + sb.append("\n").append(card.getLoyalty()); + } + if (card.getRules() == null) { + card.overrideRules(new ArrayList()); + } + for (String rule : card.getRules()) { + sb.append("\n").append(rule); + } + if (card.getExpansionSetCode() != null && card.getExpansionSetCode().length() > 0) { + sb.append("\n").append(card.getCardNumber()).append(" - "); + sb.append(card.getExpansionSetCode()).append(" - "); + sb.append(card.getRarity().toString()); + } + } + return sb.toString(); + } + + @Override + public void update(PermanentView card) { + this.hasSickness = card.hasSummoningSickness(); + this.copyIconPanel.setVisible(card.isCopy()); + update((CardView) card); + } + + @Override + public PermanentView getOriginalPermanent() { + if (isPermanent) { + return (PermanentView) this.gameCard; + } + throw new IllegalStateException("Is not permanent."); + } + + @Override + public void updateCallback(ActionCallback callback, UUID gameId) { + this.callback = callback; + this.gameId = gameId; + } + + public void setTransformed(boolean transformed) { + this.transformed = transformed; + } + + @Override + public void toggleTransformed() { + this.transformed = !this.transformed; + if (transformed) { + if (dayNightButton != null) { // if transformbable card is copied, button can be null + BufferedImage night = ImageManagerImpl.getInstance().getNightImage(); + dayNightButton.setIcon(new ImageIcon(night)); + } + if (this.gameCard.getSecondCardFace() == null) { + LOGGER.error("no second side for card to transform!"); + return; + } + if (!isPermanent) { // use only for custom transformation (when pressing day-night button) + this.temporary = this.gameCard; + update(this.gameCard.getSecondCardFace()); + } + } else { + if (dayNightButton != null) { // if transformbable card is copied, button can be null + BufferedImage day = ImageManagerImpl.getInstance().getDayImage(); + dayNightButton.setIcon(new ImageIcon(day)); + } + if (!isPermanent) { // use only for custom transformation (when pressing day-night button) + update(this.temporary); + this.temporary = null; + } + } + String temp = this.gameCard.getAlternateName(); + this.gameCard.setAlternateName(this.gameCard.getOriginalName()); + this.gameCard.setOriginalName(temp); + updateImage(); + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + if (gameCard.hideInfo()) { + return; + } + data.component = this; + callback.mouseWheelMoved(e, data); + } + + public JPanel getCardArea() { + return cardArea; + } + + @Override + public void componentResized(ComponentEvent ce) { + doLayout(); + // this update removes the isChoosable mark from targetCardsInLibrary + // so only done for permanents because it's needed to redraw counters in different size, if window size was changed + // no perfect solution yet (maybe also other not wanted effects for PermanentView objects) + if (updateCard != null && (updateCard instanceof PermanentView)) { + update(updateCard); + } + } + + @Override + public void componentMoved(ComponentEvent ce) { + } + + @Override + public void componentShown(ComponentEvent ce) { + } + + @Override + public void componentHidden(ComponentEvent ce) { + } + + @Override + public void setTextOffset(int yOffset) { + yTextOffset = yOffset; + } + + @Override + public JPopupMenu getPopupMenu() { + return popupMenu; + } + + @Override + public void setPopupMenu(JPopupMenu popupMenu) { + this.popupMenu = popupMenu; + } + +} diff --git a/Mage.Sets/src/mage/sets/betrayersofkamigawa/FinalJudgment.java b/Mage.Sets/src/mage/sets/betrayersofkamigawa/FinalJudgment.java index 4089078c877..2808eaf96fd 100644 --- a/Mage.Sets/src/mage/sets/betrayersofkamigawa/FinalJudgment.java +++ b/Mage.Sets/src/mage/sets/betrayersofkamigawa/FinalJudgment.java @@ -1,98 +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.sets.betrayersofkamigawa; - -import mage.constants.CardType; -import mage.constants.Rarity; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.cards.CardImpl; -import mage.constants.Outcome; -import mage.filter.FilterPermanent; -import mage.filter.predicate.mageobject.CardTypePredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; - -import java.util.UUID; - -/** - * - * @author Loki - */ -public class FinalJudgment extends CardImpl { - - public FinalJudgment(UUID ownerId) { - super(ownerId, 4, "Final Judgment", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{4}{W}{W}"); - this.expansionSetCode = "BOK"; - - // Exile all creatures. - this.getSpellAbility().addEffect(new FinalJudgmentEffect()); - - } - - public FinalJudgment(final FinalJudgment card) { - super(card); - } - - @Override - public FinalJudgment copy() { - return new FinalJudgment(this); - } -} - -class FinalJudgmentEffect extends OneShotEffect { - - private static final FilterPermanent filter = new FilterPermanent(""); - - static { - filter.add(new CardTypePredicate(CardType.CREATURE)); - } - - public FinalJudgmentEffect() { - super(Outcome.Exile); - staticText = "Exile all creatures"; - } - - public FinalJudgmentEffect(final FinalJudgmentEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { - permanent.moveToExile(null, null,source.getSourceId(), game); - } - return true; - } - - @Override - public FinalJudgmentEffect copy() { - return new FinalJudgmentEffect(this); - } - -} \ No newline at end of file +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.betrayersofkamigawa; + +import java.util.UUID; +import mage.abilities.effects.common.ExileAllEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterCreaturePermanent; + +/** + * + * @author Loki + */ +public class FinalJudgment extends CardImpl { + + public FinalJudgment(UUID ownerId) { + super(ownerId, 4, "Final Judgment", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{4}{W}{W}"); + this.expansionSetCode = "BOK"; + + // Exile all creatures. + this.getSpellAbility().addEffect(new ExileAllEffect(new FilterCreaturePermanent())); + } + + public FinalJudgment(final FinalJudgment card) { + super(card); + } + + @Override + public FinalJudgment copy() { + return new FinalJudgment(this); + } +} diff --git a/Mage.Sets/src/mage/sets/gameday/AnguishedUnmaking.java b/Mage.Sets/src/mage/sets/gameday/AnguishedUnmaking.java new file mode 100644 index 00000000000..703e860e096 --- /dev/null +++ b/Mage.Sets/src/mage/sets/gameday/AnguishedUnmaking.java @@ -0,0 +1,52 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.gameday; + +import java.util.UUID; + +/** + * + * @author fireshoes + */ +public class AnguishedUnmaking extends mage.sets.shadowsoverinnistrad.AnguishedUnmaking { + + public AnguishedUnmaking(UUID ownerId) { + super(ownerId); + this.cardNumber = 52; + this.expansionSetCode = "MGDC"; + } + + public AnguishedUnmaking(final AnguishedUnmaking card) { + super(card); + } + + @Override + public AnguishedUnmaking copy() { + return new AnguishedUnmaking(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AngelOfDeliverance.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AngelOfDeliverance.java new file mode 100644 index 00000000000..144720c4481 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AngelOfDeliverance.java @@ -0,0 +1,126 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +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.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class AngelOfDeliverance extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature an opponent controls"); + + static { + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public AngelOfDeliverance(UUID ownerId) { + super(ownerId, 2, "Angel of Deliverance", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{6}{W}{W}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Angel"); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Delirium — Whenever Angel of Deliverance deals damage, if there are four or more card types among cards in your graveyard, + // exile target creature an opponent controls. + Ability ability = new ConditionalTriggeredAbility( + new AngelOfDeliveranceDealsDamageTriggeredAbility(), + new DeliriumCondition(), + "Delirium — Whenever {this} deals damage, if there are four or more card types among cards in your graveyard, exile target creature an opponent controls" + ); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + } + + public AngelOfDeliverance(final AngelOfDeliverance card) { + super(card); + } + + @Override + public AngelOfDeliverance copy() { + return new AngelOfDeliverance(this); + } +} + +class AngelOfDeliveranceDealsDamageTriggeredAbility extends TriggeredAbilityImpl { + + public AngelOfDeliveranceDealsDamageTriggeredAbility() { + super(Zone.BATTLEFIELD, new ExileTargetEffect(), false); + } + + public AngelOfDeliveranceDealsDamageTriggeredAbility(final AngelOfDeliveranceDealsDamageTriggeredAbility ability) { + super(ability); + } + + @Override + public AngelOfDeliveranceDealsDamageTriggeredAbility copy() { + return new AngelOfDeliveranceDealsDamageTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.DAMAGED_PLAYER + || event.getType() == EventType.DAMAGED_CREATURE + || event.getType() == EventType.DAMAGED_PLANESWALKER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getSourceId().equals(this.getSourceId())) { + for (Effect effect : this.getEffects()) { + effect.setValue("damage", event.getAmount()); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AnguishedUnmaking.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AnguishedUnmaking.java new file mode 100644 index 00000000000..f7b2ae24d52 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AnguishedUnmaking.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.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.target.common.TargetNonlandPermanent; + +/** + * + * @author fireshoes + */ +public class AnguishedUnmaking extends CardImpl { + + public AnguishedUnmaking(UUID ownerId) { + super(ownerId, 242, "Anguished Unmaking", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{1}{W}{B}"); + this.expansionSetCode = "SOI"; + + // Exile target nonland permanent. You lose 3 life. + getSpellAbility().addEffect(new ExileTargetEffect()); + getSpellAbility().addTarget(new TargetNonlandPermanent()); + getSpellAbility().addEffect(new LoseLifeSourceControllerEffect(3)); + } + + public AnguishedUnmaking(final AnguishedUnmaking card) { + super(card); + } + + @Override + public AnguishedUnmaking copy() { + return new AnguishedUnmaking(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DescendUponTheSinful.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DescendUponTheSinful.java new file mode 100644 index 00000000000..b960906818a --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DescendUponTheSinful.java @@ -0,0 +1,69 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileAllEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.permanent.token.AngelToken; + +/** + * + * @author fireshoes + */ +public class DescendUponTheSinful extends CardImpl { + + public DescendUponTheSinful(UUID ownerId) { + super(ownerId, 13, "Descend upon the Sinful", Rarity.MYTHIC, new CardType[]{CardType.SORCERY}, "{4}{W}{W}"); + this.expansionSetCode = "SOI"; + + // Exile all creatures + this.getSpellAbility().addEffect(new ExileAllEffect(new FilterCreaturePermanent())); + + // Delirium — Put a 4/4 white Angel creature token with flying onto the battlefield if there are four or more card types among cards in your graveyard. + Effect effect = new ConditionalOneShotEffect(new CreateTokenEffect(new AngelToken()), DeliriumCondition.getInstance()); + effect.setText("
Delirium — Put a 4/4 white Angel creature token with flying onto the battlefield if there are four or more card types among cards in your graveyard"); + this.getSpellAbility().addEffect(effect); + } + + public DescendUponTheSinful(final DescendUponTheSinful card) { + super(card); + } + + @Override + public DescendUponTheSinful copy() { + return new DescendUponTheSinful(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrogskolCavalry.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrogskolCavalry.java new file mode 100644 index 00000000000..e74d3536262 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrogskolCavalry.java @@ -0,0 +1,86 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.game.permanent.token.SpiritWhiteToken; + +/** + * + * @author fireshoes + */ +public class DrogskolCavalry extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("another Spirit"); + + static { + filter.add(new AnotherPredicate()); + filter.add(new SubtypePredicate("Spirit")); + } + + public DrogskolCavalry(UUID ownerId) { + super(ownerId, 15, "Drogskol Cavalry", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{5}{W}{W}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Spirit"); + this.subtype.add("Knight"); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever another Spirit enters the battlefield under your control, you gain 2 life. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new GainLifeEffect(2), filter)); + + // {3}{W}: Put a 1/1 white Spirit creature token with flying onto the battlefield. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken()), new ManaCostsImpl("{3}{W}"))); + } + + public DrogskolCavalry(final DrogskolCavalry card) { + super(card); + } + + @Override + public DrogskolCavalry copy() { + return new DrogskolCavalry(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NephaliaMoondrakes.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NephaliaMoondrakes.java new file mode 100644 index 00000000000..51bbb29ab4d --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NephaliaMoondrakes.java @@ -0,0 +1,85 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class NephaliaMoondrakes extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Creatures you control"); + + public NephaliaMoondrakes(UUID ownerId) { + super(ownerId, 75, "Nephalia Moondrakes", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Drake"); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Nephalia Moondrakes enters the battlefield, target creature gains flying until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), false); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + + // {4}{U}{U}, Exile Nephalia Moondrakes from your graveyard: Creatures you control gain flying until end of turn. + ability = new SimpleActivatedAbility(Zone.GRAVEYARD, new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.EndOfTurn, filter), new ManaCostsImpl("{4}{U}{U}")); + ability.addCost(new ExileSourceFromGraveCost()); + this.addAbility(ability); + } + + public NephaliaMoondrakes(final NephaliaMoondrakes card) { + super(card); + } + + @Override + public NephaliaMoondrakes copy() { + return new NephaliaMoondrakes(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PiousEvangel.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PiousEvangel.java index 4501a9419fe..de41e3d1a80 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PiousEvangel.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PiousEvangel.java @@ -41,13 +41,11 @@ import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.AnotherPredicate; -import mage.filter.predicate.permanent.ControllerPredicate; import mage.target.common.TargetControlledPermanent; /** @@ -60,7 +58,6 @@ public class PiousEvangel extends CardImpl { private static final FilterControlledPermanent filter2 = new FilterControlledPermanent("another permanent"); static { - filter.add(new ControllerPredicate(TargetController.YOU)); filter2.add(new AnotherPredicate()); } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SinisterConcoction.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SinisterConcoction.java new file mode 100644 index 00000000000..3b973761bc7 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SinisterConcoction.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.PutTopCardOfYourLibraryToGraveyardCost; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class SinisterConcoction extends CardImpl { + + public SinisterConcoction(UUID ownerId) { + super(ownerId, 135, "Sinister Concoction", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{B}"); + this.expansionSetCode = "SOI"; + + // {B}, Pay 1 life, Put the top card of your library into your graveyard, Discard a card, Sacrifice Sinister Concoction: Destroy target creature. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), new ManaCostsImpl("{B}")); + ability.addCost(new PayLifeCost(1)); + ability.addCost(new PutTopCardOfYourLibraryToGraveyardCost()); + ability.addCost(new DiscardCardCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + public SinisterConcoction(final SinisterConcoction card) { + super(card); + } + + @Override + public SinisterConcoction copy() { + return new SinisterConcoction(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/AngelToken.java b/Mage/src/main/java/mage/game/permanent/token/AngelToken.java index 95a43e34646..a879bb96a90 100644 --- a/Mage/src/main/java/mage/game/permanent/token/AngelToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/AngelToken.java @@ -1,34 +1,34 @@ -package mage.game.permanent.token; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import mage.MageInt; -import mage.abilities.keyword.FlyingAbility; -import mage.constants.CardType; - -public class AngelToken extends Token { - - final static private List tokenImageSets = new ArrayList<>(); - - static { - tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CFX", "GTC", "ISD", "M14", "ORI", "ZEN")); - } - - public AngelToken() { - this(null); - } - - public AngelToken(String setCode) { - super("Angel", "4/4 white Angel creature token with flying"); - availableImageSetCodes = tokenImageSets; - setOriginalExpansionSetCode(setCode); - - cardType.add(CardType.CREATURE); - color.setWhite(true); - subtype.add("Angel"); - power = new MageInt(4); - toughness = new MageInt(4); - addAbility(FlyingAbility.getInstance()); - } -} +package mage.game.permanent.token; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; + +public class AngelToken extends Token { + + final static private List tokenImageSets = new ArrayList<>(); + + static { + tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CFX", "GTC", "ISD", "M14", "ORI", "SOI", "ZEN")); + } + + public AngelToken() { + this(null); + } + + public AngelToken(String setCode) { + super("Angel", "4/4 white Angel creature token with flying"); + availableImageSetCodes = tokenImageSets; + setOriginalExpansionSetCode(setCode); + + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add("Angel"); + power = new MageInt(4); + toughness = new MageInt(4); + addAbility(FlyingAbility.getInstance()); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java b/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java index 75ef08c1855..cf5e5201187 100644 --- a/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java @@ -1,88 +1,88 @@ -/* - * 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 java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import mage.MageInt; -import mage.abilities.keyword.FlyingAbility; -import mage.constants.CardType; - -/** - * @author nantuko - */ -public class SpiritWhiteToken extends Token { - - final static private List tokenImageSets = new ArrayList<>(); - - static { - tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CNS", "DDC", "DDK", "FRF", "ISD", "KTK", "M15", "MM2", "SHM")); - } - - public SpiritWhiteToken() { - this(null, 0); - } - - public SpiritWhiteToken(String setCode) { - this(setCode, 0); - } - - public SpiritWhiteToken(String setCode, int tokenType) { - super("Spirit", "1/1 white Spirit creature token with flying"); - availableImageSetCodes = tokenImageSets; - setOriginalExpansionSetCode(setCode); - if (tokenType > 0) { - setTokenType(tokenType); - } - cardType.add(CardType.CREATURE); - subtype.add("Spirit"); - color.setWhite(true); - power = new MageInt(1); - toughness = new MageInt(1); - - addAbility(FlyingAbility.getInstance()); - } - - @Override - public void setExpansionSetCodeForImage(String code) { - super.setExpansionSetCodeForImage(code); - if (getOriginalExpansionSetCode() != null && getOriginalExpansionSetCode().equals("AVR")) { - setTokenType(1); - } - } - - public SpiritWhiteToken(final SpiritWhiteToken token) { - super(token); - } - - @Override - public SpiritWhiteToken copy() { - return new SpiritWhiteToken(this); - } -} +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; + +/** + * @author nantuko + */ +public class SpiritWhiteToken extends Token { + + final static private List tokenImageSets = new ArrayList<>(); + + static { + tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CNS", "DDC", "DDK", "FRF", "ISD", "KTK", "M15", "MM2", "SHM", "SOI")); + } + + public SpiritWhiteToken() { + this(null, 0); + } + + public SpiritWhiteToken(String setCode) { + this(setCode, 0); + } + + public SpiritWhiteToken(String setCode, int tokenType) { + super("Spirit", "1/1 white Spirit creature token with flying"); + availableImageSetCodes = tokenImageSets; + setOriginalExpansionSetCode(setCode); + if (tokenType > 0) { + setTokenType(tokenType); + } + cardType.add(CardType.CREATURE); + subtype.add("Spirit"); + color.setWhite(true); + power = new MageInt(1); + toughness = new MageInt(1); + + addAbility(FlyingAbility.getInstance()); + } + + @Override + public void setExpansionSetCodeForImage(String code) { + super.setExpansionSetCodeForImage(code); + if (getOriginalExpansionSetCode() != null && getOriginalExpansionSetCode().equals("AVR")) { + setTokenType(1); + } + } + + public SpiritWhiteToken(final SpiritWhiteToken token) { + super(token); + } + + @Override + public SpiritWhiteToken copy() { + return new SpiritWhiteToken(this); + } +} diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 75d0f3254f1..b34280cb21f 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -56948,6 +56948,7 @@ Descend upon the Sinful|Shadows over Innistrad|13|M|{4}{W}{W}|Sorcery|||Exile al Drogskol Cavalry|Shadows over Innistrad|15|R|{5}{W}{W}|Creature - Spirit Knight|4|4|Flying$Whenever another Spirit enters the battlefield under your control, you gain 2 life.${3}{W}: Put a 1/1 white Spirit creature token with flying onto the battlefield.| Eerie Interlude|Shadows over Innistrad|16|R|{2}{W}|Instant|||Exile any number of target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step.| Expose Evil|Shadows over Innistrad|19|C|{1}{W}|Instant|||Tap up to two target creatures.$Investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| +Odric, Lunarch Marshal|Shadows over Innistrad|31|R|{3}{W}|Legendary Creature - Human Soldier|3|3|At the beginning of each combat, creatures you control gain first strike until end of turn if a creature you control has first strike. The same is true for flying, deathtouch, double strike, haste, hexproof, indestructible, lifelink, menace, reach, skulk, trample, and vigilance.| Pious Evangel|Shadows over Innistrad|34a|U|{2}{W}|Creature - Human Cleric|2|2|Whenever Pious Evangel or another creature enters the battlefield under your control, you gain 1 life.${2}, {T}, Sacrifice another permanent: Transform Pious Evangel.| Wayward Disciple|Shadows over Innistrad|34b|U||Creature - Human Cleric|2|4|Whenever Wayward Disciple or another creature you control dies, target opponent loses 1 life and you gain 1 life.| Reaper of Flight Moonsilver|Shadows over Innistrad|36|U|{3}{W}{W}|Creature - Angel|3|3|Flying$Delirium — Sacrifice another creature: Reaper of Flight Moonsilver gets +2/+1 until end of turn. Activate this ability only if there are four or more card types among cards in your graveyard.| @@ -56965,8 +56966,9 @@ Nephalia Moondrakes|Shadows over Innistrad|75|R|{5}{U}{U}|Creature - Drake|5|5|F Niblis of Dusk|Shadows over Innistrad|76|C|{2}{U}|Creature - Spirit|2|1|Flying$Prowess| Pieces of the Puzzle|Shadows over Innistrad|78|C|{2}{U}|Sorcery|||Reveal the top five cards of your library. Put up to two instant and/or sorcery cards from among them into your hand and the rest into your graveyard.| Pore Over the Pages|Shadows over Innistrad|79|U|{3}{U}{U}|Sorcery|||Draw three cards, untap up to two lands, then discard a card.| -Startled Awake|Shadows over Innistrad|88a|{2}{U}{U}|Sorcery|||Target opponent puts the top thirteen cards of his or her library into his or her graveyard.${3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery.| -Persistent Nightmare|Shadows over Innistrad|88b||Creature - Nightmare|1|1|Skulk (This creature can't be blocked by creatures with greater power.)$When Persistent Nightmare deals combat damage to a player, return it to its owner's hand.| +Rattlechains|Shadows over Innistrad|81|R|{1}{U}|Creature - Spirit|2|1|Flash$Flying$When Rattlechains enters the battlefield, target spirit gains hexproof until end of turn.$You may cast spirit cards as though they had flash.| +Startled Awake|Shadows over Innistrad|88a|M|{2}{U}{U}|Sorcery|||Target opponent puts the top thirteen cards of his or her library into his or her graveyard.${3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery.| +Persistent Nightmare|Shadows over Innistrad|88b|M||Creature - Nightmare|1|1|Skulk (This creature can't be blocked by creatures with greater power.)$When Persistent Nightmare deals combat damage to a player, return it to its owner's hand.| Stitched Mangler|Shadows over Innistrad|89|C|{2}{U}|Creature - Zombie Horror|2|3|Stitched Mangler enters the battlefield tapped.$When Stitched Mangler enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.| Thing in the Ice|Shadows over Innistrad|92a|R|{1}{U}|Creature - Horror|0|4|Defender$Thing in the Ice enters the battlefield with four ice counters on it.$Whenever you cast an instant or sorcery spell, remove an ice counter from Thing in the Ice. Then if it has no ice counters on it, transform it.| Awoken Horror|Shadows over Innistrad|92b|R||Creature - Kraken Horror|7|8|When this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands.| @@ -56978,25 +56980,29 @@ Farbog Revenant|Shadows over Innistrad|110|C|{2}{B}|Creature - Spirit|1|3|Skulk Heir of Falkenrath|Shadows over Innistrad|116a|U|{1}{B}|Creature - Vampire|2|1|Discard a card: Transform Heir of Falkenrath. Activate this ability only once each turn.| Heir to the Night|Shadows over Innistrad|116b|U||Creature - Vampire Berserker|3|2|Flying| Hound of the Farbogs|Shadows over Innistrad|117|C|{4}{B}|Creature - Zombie Hound|5|3|Delirium — Hound of the Farborgs has menace as long as there are four or more card types among cards in your graveyard. (A creature with menace can't be blocked except by two or more creatures.)| -Markov Dreadknight|Shadows over Innistrad|998|R|{3}{B}{B}|Creature - Vampire Knight|3|3|Flying${2}{B}, Discard a card: Put two +1/+1 counters on Markhov Dreadknight.| +Macabre Waltz|Shadows over Innistrad|121|C|{1}{B}|Sorcery|||Return up to two target creature cards from your graveyard to your hand, then discard a card.| +Markov Dreadknight|Shadows over Innistrad|122|R|{3}{B}{B}|Creature - Vampire Knight|3|3|Flying${2}{B}, Discard a card: Put two +1/+1 counters on Markhov Dreadknight.| Mindwrack Demon|Shadows over Innistrad|124|M|{2}{B}{B}|Creature - Demon|4|5|Flying, trample$When Mindwrack Demon enters the battlefield, put the top four cards of your library into your graveyard.$Delirium — At the beginning of your upkeep, unless there are four or more card types among card in your graveyard, you lose 4 life.| Pick the Brain|Shadows over Innistrad|129|U|{2}{B}|Sorcery|||Target opponent reveals his or her hand. You choose a nonland card from it and exile that card.$Delirium — If there are four or more card types among cards in your graveyard, search that player's graveyard, hand, and library for any number of cards with the same name as the exiled card, exile those cards, then that player shuffles his or her library.| Relentless Dead|Shadows over Innistrad|131|M|{B}{B}|Creature - Zombie|2|2|Menace (This creature can't be blocked except by two or more creatures.)$When Relentless Dead dies, you may pay {B}. If you do, return it to its owner's hand.$When Relentless Dead dies, you may pay {X}. If you do, return another target Zombie creature card with converted mana cost X from your graveyard to the battlefield.| Shamble Back|Shadows over Innistrad|134|C|{B}|Sorcery|||Exile target creature card from a graveyard. Put a 2/2 black Zombie creature token onto the battlefield. You gain 2 life.| Sinister Concoction|Shadows over Innistrad|135|U|{B}|Enchantment|||{B}, Pay 1 life, Put the top card of your library into your graveyard, Discard a card, Sacrifice Sinister Concoction: Destroy target creature.| +To the Slaughter|Shadows over Innistrad|139|R|{2}{B}|Instant|||Target player sacrifices a creature or planeswalker.$Delirium — If there are four or more card types among cards in your graveyard, instead that player sacrifices a creature and a planeswalker.| Tooth Collector|Shadows over Innistrad|140|U|{2}{B}|Creature - Human Rogue|3|2|When Tooth Collector enters the battlefield, target creature an opponent controls gets -1/-1 until end of turn.${Delirium — At the beginning of each opponent's upkeep, if there are four or more card types among cards in your graveyard, target creature that player controls gets -1/-1 until end of turn.| Dance with Devils|Shadows over Innistrad|150|U|{3}{R}|Instant|||Put two 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player."| Devil's Playground|Shadows over Innistrad|151|R|{4}{R}{R}|Sorcery|||Put four 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player."| Ember-Eye Wolf|Shadows over Innistrad|154|C|{2}{R}|Creature - Wolf|1|2|Haste${1}{R}: Ember-Eye Wolf gets +2/+0 until end of turn.| +Falkenrath Gorger|Shadows over Innistrad|155|R|{R}|Creature - Vampire Warrior|2|1|Each Vampire creature card you own that isn't on the battlefield has madness. Its madness cost is equal to its mana cost. (If you discard a card with madness, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| Fiery Temper|Shadows over Innistrad|156|C|{1}{R}{R}|Instant|||Fiery Temper deals 3 damage to target creature or player.$Madness {R} (If you discard this card, you may play it for its madness cost instead of putting it into your graveyard.)| Incorrigible Youths|Shadows over Innistrad|166|U|{3}{R}{R}|Creature - Vampire|4|3|Haste$Madness {2}{R} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| -Flameblade Angel|Shadows over Innistrad|997|R|{4}{R}{R}|Creature - Angel|4|4|Flying$Whenever a source an opponent controls deals damage to you or a permanent you control, you may have Flameblade Angel deal 1 damage to that source's controller.| +Flameblade Angel|Shadows over Innistrad|157|R|{4}{R}{R}|Creature - Angel|4|4|Flying$Whenever a source an opponent controls deals damage to you or a permanent you control, you may have Flameblade Angel deal 1 damage to that source's controller.| Lightning Axe|Shadows over Innistrad|999|U|{R}|Instant|||As an additional cost to cast Lightning Axe, discard a card or pay {5}.$Lightning Axe deals 5 damage to target creature.| Magmatic Chasm|Shadows over Innistrad|172|C|{1}{R}|Sorcery|||Creatures without flying can't block this turn.| Ravenous Bloodseeker|Shadows over Innistrad|175|U|{1}{R}|Creature - Vampire Berserker|1|3|Discard a card: Ravenous Bloodseeker gets +2/-2 until end of turn.| Sanguinary Mage|Shadows over Innistrad|178|C|{1}{R}|Creature - Vampire Wizard|1|3|Prowess| Structural Distortion|Shadows over Innistrad|185|C|{3}{R}|Sorcery|||Exile target artifact or land. Structural Distortion deals 2 damage to that permanent's controller.| Voldaren Duelist|Shadows over Innistrad|191|C|{3}{R}|Creature - Vampire Warrior|3|2|Haste$When Voldaren Duelist enters the battlefield, target creature can't block this turn.| +Wolf of Devil's Breach|Shadows over Innistrad|192|M|{3}{R}{R}|Creature - Elemental Wolf|5|5|Whenever Wolf of Devil's Breach attacks, you may pay {1}{R} and discard a card. If you do, Wolf of Devil's Breach deals damage to target creature or planeswalker equal to the discarded card's converted mana cost.| Clip Wings|Shadows over Innistrad|197|C|{1}{G}|Instant|||Each opponent sacrifices a creature with flying.| Duskwatch Recruiter|Shadows over Innistrad|203a|U|{1}{G}|Creature - Human Warrior Werewolf|2|2|{2}{G}: Look at the top three cards of your library. You may reveal a creature card from among them and put it into your hand. Put the rest on the bottom of your library in any order.$At the beginning of each upkeep, if no spells were cast last turn, transform Duskwatch Recruiter.| Krallenhorde Howler|Shadows over Innistrad|203b|U||Creature - Werewolf|3|3|Creature spells you cast cost {1} less to cast.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Krallenhorde Howler.| @@ -57004,14 +57010,20 @@ Hinterland Logger|Shadows over Innistrad|210a|C|{1}{G}|Creature - Human Werewolf Timber Shredder|Shadows over Innistrad|210b|C||Creature - Werewolf|4|2|Trample$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Timber Shredder.| Pack Guardian|Shadows over Innistrad|221|U|{2}{G}{G}|Creature - Wolf Spirit|4|3|Flash$When Pack Guardian enters the battlefield, you may discard a land card. If you do, put a 2/2 green Wolf creature token onto the battlefield.| Quilled Wolf|Shadows over Innistrad|222|C|{1}{G}|Creature - Wolf|2|2|{5}{G}: Quilled Wolf gets +4/+4 until end of turn.| +Sage of Ancient Lore|Shadows over Innistrad|225a|R|{4}{G}|Creature - Human Shaman Werewolf|0|0|Sage of Ancient Lore's power and toughness are each equal to the number of cards in your hand.$When Sage of Ancient Lore enters the battlefield, draw a card.$At the beginning of each upkeep, if no spells were cast last turn, transform Sage of Ancient Lore.| +Werewolf of Ancient Hunger|Shadows over Innistrad|225b|R||Creature - Werewolf|0|0|Vigilance, trample$Werewolf of Ancient Hunger's power and toughness are each equal to the total number of cards in all players' hands.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Werewolf of Ancient Hunger.| Soul Swallower|Shadows over Innistrad|230|R|{2}{G}{G}|Creature - Wurm|3|3|Trample$Delirium — At the beginning of your upkeep, if there are four or more card types among cards in your graveyard, put three +1/+1 counters on Soul Swallower.| Stoic Builder|Shadows over Innistrad|231|C|{2}{G}|Creature - Human|2|3|When Stoic Builder enters the battlefield, you may return target land card from your graveyard to your hand.| Watcher in the Web|Shadows over Innistrad|239|C|{4}{G}|Creature - Spider|2|5|Reach (This creature can block creature with flying.)$Watcher in the Web can block an additional seven creatures each combat.| Anguished Unmaking|Shadows over Innistrad|242|R|{1}{W}{B}|Instant|||Exile target nonland permanent. You lose 3 life.| -Nahiri, the Harbinger|Shadows over Innistrad|247|M|{2}{R}{W}|Planeswalker - Nahiri|||+2: You may discard a card. If you do, draw a card.$-2: Exile target enchantment, tapped artifact, or tapped creature.$-8 Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. Return it to your hand at the beginning of the next end step.| +Arlinn Kord|Shadows over Innistrad|243a|M|{2}{R}{G}|Planeswalker - Arlinn|||+1: Until end of turn, up to one target creature gets +2/+2 and gains vigilance and haste.$0: Put a 2/2 green Wolf creature token onto the battlefield. Transform Arlinn Kord.| +Arlinn, Embraced by the Moon|Shadows over Innistrad|243b|M||Planeswalker - Arlinn|||+1: Creatures you control get +1/+1 and gain trample until end of turn.$-1: Arlinn, Embraced by the Moon deals 3 damage to target creature or player. Transform Arlinn, Embraced by the Moon.$-6: You get an emblem with "Creatures you control have haste and '{T}: This creature deals damage equal to its power to target creature or player.'"| +Nahiri, the Harbinger|Shadows over Innistrad|247|M|{2}{R}{W}|Planeswalker - Nahiri|||+2: You may discard a card. If you do, draw a card.$-2: Exile target enchantment, tapped artifact, or tapped creature.$-8: Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. Return it to your hand at the beginning of the next end step.| Brain in a Jar|Shadows over Innistrad|252|R|{2}|Artifact|||{1}, {T}: Put a charge counter on Brain in a Jar, then you may cast an instant or sorcery card with converted mana costs equal to the number of charge counters on Brain in a Jar from your hand without paying its mana cost.${3}< {T}, Remove X charge counters from Brain in a Jar: Scry X.| Explosive Apparatus|Shadows over Innistrad|255|C|{1}|Artifact|||{3}, {T}, Sacrifice Explosive Apparatus: Explosive Apparatus deals 2 damage to target creature or player.| Magnifying Glass|Shadows over Innistrad|258|U|{3}|Artifact|||{T}: Add {C} to your mana pool.${4}, {T}: Investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| +Neglected Heirloom|Shadows over Innistrad|260a|U|{1}|Artifact - Equipment|||Equipped creature gets +1/+1.$When equipped creature transforms, transform Neglected Heirloom.$Equip {1}| +Ashmouth Blade|Shadows over Innistrad|260b|U||Artifact - Equipment|||Equipped creature gets +3/+3.$Equip {3}| Shard of Broken Glass|Shadows over Innistrad|262|C|{1}|Artifact - Equipment|||Equipped creature gets +1/+0.$Whenever equipped creature attacks, you may put the top two cards of your library into your graveyard.$Equip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.)| Tamiyo's Journal|Shadows over Innistrad|265|R|{5}|Legendary Artifact|||At the beginning of your upkeep, investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")${T}, Sacrifice three Clues: Search your library for a card and put that card into your hand. Then shuffle your library.| Forsaken Sanctuary|Shadows over Innistrad|273|U||Land|||Forsaken Sanctuary enters the battlefield tapped.${T}: Add {W} or {B} to your mana pool.|