From d33f8a636e86258e1acd178ba6d7085c356ba0f7 Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Wed, 31 Aug 2016 23:37:31 -0600 Subject: [PATCH] Most obvious bugs ironed out. Ready for PR. --- .../src/main/java/mage/client/cards/Card.java | 2 +- .../main/java/mage/client/cards/Cards.java | 2 +- .../java/org/mage/card/arcane/CardPanel.java | 23 ++- .../mage/card/arcane/CardPanelAttributes.java | 25 ++++ .../card/arcane/CardPanelComponentImpl.java | 10 +- .../mage/card/arcane/CardPanelRenderImpl.java | 137 ++++++++++++------ .../org/mage/card/arcane/CardRenderer.java | 61 +++++--- .../mage/card/arcane/ModernCardRenderer.java | 132 ++++++++++++++--- .../mage/card/arcane/TextboxRuleParser.java | 6 + .../mage/plugins/card/images/ImageCache.java | 44 ++++++ .../resources/cardrender/beleren-bold.ttf | Bin 0 -> 92612 bytes Mage.Common/src/mage/cards/MageCard.java | 2 +- Mage.Common/src/mage/view/CounterView.java | 17 +++ Mage/src/main/java/mage/MageObjectImpl.java | 44 +++++- .../main/java/mage/cards/mock/MockCard.java | 2 +- .../java/mage/cards/repository/CardInfo.java | 3 +- .../mage/cards/repository/CardRepository.java | 4 +- .../mage/game/permanent/PermanentCard.java | 1 + .../mage/game/permanent/PermanentToken.java | 1 + 19 files changed, 415 insertions(+), 101 deletions(-) create mode 100644 Mage.Client/src/main/java/org/mage/card/arcane/CardPanelAttributes.java create mode 100644 Mage.Client/src/main/resources/cardrender/beleren-bold.ttf diff --git a/Mage.Client/src/main/java/mage/client/cards/Card.java b/Mage.Client/src/main/java/mage/client/cards/Card.java index 05edb0f7d4b..ddfbc789e95 100644 --- a/Mage.Client/src/main/java/mage/client/cards/Card.java +++ b/Mage.Client/src/main/java/mage/client/cards/Card.java @@ -222,7 +222,7 @@ public class Card extends MagePermanent implements MouseMotionListener, MouseLis } @Override - public void updateImage() { + public void updateArtImage() { } diff --git a/Mage.Client/src/main/java/mage/client/cards/Cards.java b/Mage.Client/src/main/java/mage/client/cards/Cards.java index 67287d54d8d..d4b91472b65 100644 --- a/Mage.Client/src/main/java/mage/client/cards/Cards.java +++ b/Mage.Client/src/main/java/mage/client/cards/Cards.java @@ -114,7 +114,7 @@ public class Cards extends javax.swing.JPanel { setGUISize(); for (MageCard mageCard : cards.values()) { mageCard.setCardBounds(0, 0, getCardDimension().width, getCardDimension().height); - mageCard.updateImage(); + mageCard.updateArtImage(); mageCard.doLayout(); } layoutCards(); 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 33657fba3e5..72f4afc1cdf 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 @@ -223,7 +223,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, // this calls updateImage toggleTransformed(); } else { - updateImage(); + updateArtImage(); } } @@ -372,7 +372,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, setBounds(x - cardXOffset, y - cardYOffset, getWidth(), getHeight()); return; } - + this.cardWidth = cardWidth; this.symbolWidth = cardWidth / 7; this.cardHeight = cardHeight; @@ -389,8 +389,8 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, int height = -yOffset + rotCenterY + rotCenterToBottomCorner; setBounds(x + xOffset, y + yOffset, width, height); } else { - cardXOffset = 5; - cardYOffset = 5; + cardXOffset = 0; + cardYOffset = 0; int width = cardXOffset * 2 + cardWidth; int height = cardYOffset * 2 + cardHeight; setBounds(x - cardXOffset, y - cardYOffset, width, height); @@ -538,6 +538,11 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, // Update panel attributes this.isChoosable = card.isChoosable(); this.isSelected = card.isSelected(); + + // Update art? + boolean mustUpdateArt = + (!gameCard.getName().equals(card.getName())) || + (gameCard.isFaceDown() != card.isFaceDown()); // Set the new card this.gameCard = card; @@ -546,8 +551,12 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, String cardType = getType(card); tooltipText.setText(getText(cardType, card)); - // Update the image and transform circle if needed - updateImage(); + // Update the image + if (mustUpdateArt) { + updateArtImage(); + } + + // Update transform circle if (card.canTransform()) { BufferedImage transformIcon; if (isTransformed() || card.isTransformed()) { @@ -783,7 +792,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener, String temp = this.gameCard.getAlternateName(); this.gameCard.setAlternateName(this.gameCard.getOriginalName()); this.gameCard.setOriginalName(temp); - updateImage(); + updateArtImage(); } @Override diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelAttributes.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelAttributes.java new file mode 100644 index 00000000000..9382cb9a526 --- /dev/null +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelAttributes.java @@ -0,0 +1,25 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.card.arcane; + +/** + * @author stravant@gmail.com + * Attributes of a card panel outside of the CardView itself that the renderer + * needs to know in order to render a card. + */ +public class CardPanelAttributes { + public final int cardWidth; + public final int cardHeight; + public final boolean isSelected; + public final boolean isChoosable; + + public CardPanelAttributes(int cardWidth, int cardHeight, boolean isChoosable, boolean isSelected) { + this.cardWidth = cardWidth; + this.cardHeight = cardHeight; + this.isChoosable = isChoosable; + this.isSelected = isSelected; + } +} diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java index d7cbe76f5dd..549242710ca 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelComponentImpl.java @@ -503,7 +503,7 @@ public class CardPanelComponentImpl extends CardPanel { // Update image if (imagePanel != null && imagePanel.getSrcImage() != null) { - updateImage(); + updateArtImage(); } } @@ -523,15 +523,15 @@ public class CardPanelComponentImpl extends CardPanel { /////////////////////////////////////////////////////////// // Image updating code - private int updateImageStamp; + private int updateArtImageStamp; @Override - public void updateImage() { + public void updateArtImage() { tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; //final CardView gameCard = this.gameCard; - final int stamp = ++updateImageStamp; + final int stamp = ++updateArtImageStamp; Util.threadPool.submit(new Runnable() { @Override @@ -548,7 +548,7 @@ public class CardPanelComponentImpl extends CardPanel { UI.invokeLater(new Runnable() { @Override public void run() { - if (stamp == updateImageStamp) { + if (stamp == updateArtImageStamp) { hasImage = srcImage != null; setText(gameCard); setImage(srcImage); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java index ed9a061bb60..28ad02fc1c8 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanelRenderImpl.java @@ -14,6 +14,8 @@ import mage.cards.action.ActionCallback; import mage.client.util.ImageCaches; import mage.constants.CardType; import mage.view.CardView; +import mage.view.CounterView; +import mage.view.PermanentView; import org.apache.log4j.Logger; import org.jdesktop.swingx.graphics.GraphicsUtilities; import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL; @@ -27,6 +29,9 @@ public class CardPanelRenderImpl extends CardPanel { if (a == b) { return true; } + if (a.getClass() != b.getClass()) { + return false; + } if (!a.getName().equals(b.getName())) { return false; } @@ -60,6 +65,28 @@ public class CardPanelRenderImpl extends CardPanel { if (!a.getExpansionSetCode().equals(b.getExpansionSetCode())) { return false; } + if (a.getCounters() == null) { + if (b.getCounters() != null) { + return false; + } + } else if (!a.getCounters().equals(b.getCounters())) { + return false; + } + if (a.isFaceDown() != b.isFaceDown()) { + return false; + } + if ((a instanceof PermanentView)) { + PermanentView aa = (PermanentView)a; + PermanentView bb = (PermanentView)b; + if (aa.hasSummoningSickness() != bb.hasSummoningSickness()) { + // Note: b must be a permanentview too as we aleady checked that classes + // are the same for a and b + return false; + } + if (aa.getDamage() != bb.getDamage()) { + return false; + } + } return true; } @@ -91,6 +118,11 @@ public class CardPanelRenderImpl extends CardPanel { sb.append((char)(isChoosable ? 1 : 0)); sb.append((char)(this.view.isPlayable() ? 1 : 0)); sb.append((char)(this.view.isCanAttack() ? 1 : 0)); + sb.append((char)(this.view.isFaceDown() ? 1 : 0)); + if (this.view instanceof PermanentView) { + sb.append((char)(((PermanentView)this.view).hasSummoningSickness() ? 1 : 0)); + sb.append((char)(((PermanentView)this.view).getDamage())); + } sb.append(this.view.getName()); sb.append(this.view.getPower()); sb.append(this.view.getToughness()); @@ -112,6 +144,11 @@ public class CardPanelRenderImpl extends CardPanel { for (String s: this.view.getRules()) { sb.append(s); } + if (this.view.getCounters() != null) { + for (CounterView v: this.view.getCounters()) { + sb.append(v.getName()).append(v.getCount()); + } + } return sb.toString().hashCode(); } @@ -207,7 +244,7 @@ public class CardPanelRenderImpl extends CardPanel { } // And draw the image we now have - g.drawImage(cardImage, 0, 0, null); + g.drawImage(cardImage, getCardXOffset(), getCardYOffset(), null); } /** @@ -217,33 +254,34 @@ public class CardPanelRenderImpl extends CardPanel { private BufferedImage renderCard() { int cardWidth = getCardWidth(); int cardHeight = getCardHeight(); - int cardXOffset = getCardXOffset(); - int cardYOffset = getCardYOffset(); // Create image to render to BufferedImage image = - GraphicsUtilities.createCompatibleTranslucentImage(getWidth(), getHeight()); + GraphicsUtilities.createCompatibleTranslucentImage(cardWidth, cardHeight); Graphics2D g2d = image.createGraphics(); // Render with Antialialsing g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // Attributes + CardPanelAttributes attribs = + new CardPanelAttributes(cardWidth, cardHeight, isChoosable(), isSelected()); + // Draw card itself - g2d.translate(cardXOffset, cardYOffset); - cardRenderer.draw(g2d, cardWidth - cardXOffset, cardHeight - cardYOffset); - g2d.translate(-cardXOffset, -cardYOffset); + cardRenderer.draw(g2d, attribs); // Done g2d.dispose(); return image; } - private int updateImageStamp; + private int updateArtImageStamp; @Override - public void updateImage() { + public void updateArtImage() { // Invalidate artImage = null; cardImage = null; + cardRenderer.setArtImage(null); // Stop animation tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; @@ -251,43 +289,49 @@ public class CardPanelRenderImpl extends CardPanel { // Schedule a repaint repaint(); + + // See if the image is already loaded + //artImage = ImageCache.tryGetImage(gameCard, getCardWidth(), getCardHeight()); + //this.cardRenderer.setArtImage(artImage); // Submit a task to draw with the card art when it arrives - final int stamp = ++updateImageStamp; - Util.threadPool.submit(new Runnable() { - @Override - public void run() { - try { - final BufferedImage srcImage; - if (gameCard.isFaceDown()) { - // Nothing to do - srcImage = null; - } else if (getCardWidth() > THUMBNAIL_SIZE_FULL.width) { - srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); - } else { - srcImage = ImageCache.getThumbnail(gameCard); - } - UI.invokeLater(new Runnable() { - @Override - public void run() { - if (stamp == updateImageStamp) { - CardPanelRenderImpl.this.artImage = srcImage; - CardPanelRenderImpl.this.cardRenderer.setArtImage(srcImage); - if (srcImage != null) { - // Invalidate and repaint - CardPanelRenderImpl.this.cardImage = null; - repaint(); + if (artImage == null) { + final int stamp = ++updateArtImageStamp; + Util.threadPool.submit(new Runnable() { + @Override + public void run() { + try { + final BufferedImage srcImage; + if (gameCard.isFaceDown()) { + // Nothing to do + srcImage = null; + } else if (getCardWidth() > THUMBNAIL_SIZE_FULL.width) { + srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); + } else { + srcImage = ImageCache.getThumbnail(gameCard); + } + UI.invokeLater(new Runnable() { + @Override + public void run() { + if (stamp == updateArtImageStamp) { + artImage = srcImage; + cardRenderer.setArtImage(srcImage); + if (srcImage != null) { + // Invalidate and repaint + cardImage = null; + repaint(); + } } } - } - }); - } catch (Exception e) { - e.printStackTrace(); - } catch (Error err) { - err.printStackTrace(); + }); + } catch (Exception e) { + e.printStackTrace(); + } catch (Error err) { + err.printStackTrace(); + } } - } - }); + }); + } } @Override @@ -296,7 +340,9 @@ public class CardPanelRenderImpl extends CardPanel { super.update(card); // Update renderer + cardImage = null; cardRenderer = new ModernCardRenderer(gameCard, isTransformed()); + cardRenderer.setArtImage(artImage); // Repaint repaint(); @@ -304,10 +350,15 @@ public class CardPanelRenderImpl extends CardPanel { @Override public void setCardBounds(int x, int y, int cardWidth, int cardHeight) { + int oldCardWidth = getCardWidth(); + int oldCardHeight = getCardHeight(); + super.setCardBounds(x, y, cardWidth, cardHeight); - // Rerender - cardImage = null; + // Rerender if card size changed + if (getCardWidth() != oldCardWidth || getCardHeight() != oldCardHeight) { + cardImage = null; + } } @Override diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java index 8edfa98993b..0c3a06e97c9 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java @@ -18,9 +18,11 @@ import java.awt.image.BufferedImage; import java.text.AttributedString; import java.util.ArrayList; import java.util.List; +import mage.constants.AbilityType; import mage.constants.CardType; import mage.constants.Rarity; import mage.counters.Counter; +import mage.utils.CardUtil; import mage.view.CardView; import mage.view.CounterView; import mage.view.PermanentView; @@ -108,6 +110,10 @@ public abstract class CardRenderer { protected int cardWidth; protected int cardHeight; + // Is it selectable / selected + protected boolean isChoosable; + protected boolean isSelected; + // Radius of the corners of the cards protected static float CORNER_RADIUS_FRAC = 0.1f; //x cardWidth protected static int CORNER_RADIUS_MIN = 3; @@ -164,17 +170,21 @@ public abstract class CardRenderer { // The Draw Method // The draw method takes the information caculated by the constructor // and uses it to draw to a concrete size of card and graphics. - public void draw(Graphics2D g, int cardWidth, int cardHeight) { + public void draw(Graphics2D g, CardPanelAttributes attribs) { // Pre template method layout, to calculate shared layout info - layout(cardWidth, cardHeight); + layout(attribs.cardWidth, attribs.cardHeight); + isSelected = attribs.isSelected; + isChoosable = attribs.isChoosable; // Call the template methods drawBorder(g); drawBackground(g); drawArt(g); drawFrame(g); - drawOverlays(g); - drawCounters(g); + if (!cardView.isAbility()) { + drawOverlays(g); + drawCounters(g); + } } // Template methods to be implemented by sub classes @@ -196,7 +206,7 @@ public abstract class CardRenderer { // Draw summoning sickness overlay, and possibly other overlays protected void drawOverlays(Graphics2D g) { - if (cardView instanceof PermanentView) { + if (CardUtil.isCreature(cardView) && cardView instanceof PermanentView) { if (((PermanentView)cardView).hasSummoningSickness()) { int x1 = (int)(0.2*cardWidth); int x2 = (int)(0.8*cardWidth); @@ -229,7 +239,7 @@ public abstract class CardRenderer { // Draw +1/+1 and other counters protected void drawCounters(Graphics2D g) { - int xPos = (int)(0.7*cardWidth); + int xPos = (int)(0.65*cardWidth); int yPos = (int)(0.15*cardHeight); if (cardView.getCounters() != null) { for (CounterView v: cardView.getCounters()) { @@ -245,7 +255,7 @@ public abstract class CardRenderer { } else { p = OTHER_COUNTER_POLY; } - double scale = (0.1*0.18*cardWidth); + double scale = (0.1*0.25*cardWidth); Graphics2D g2 = (Graphics2D)g.create(); g2.translate(xPos, yPos); g2.scale(scale, scale); @@ -258,7 +268,7 @@ public abstract class CardRenderer { int strW = g2.getFontMetrics().stringWidth(cstr); g2.drawString(cstr, 5 - strW/2, 8); g2.dispose(); - yPos += ((int)(0.22*cardWidth)); + yPos += ((int)(0.30*cardWidth)); } } } @@ -324,20 +334,31 @@ public abstract class CardRenderer { // Get a string representing the type line protected String getCardTypeLine() { - StringBuilder sbType = new StringBuilder(); - for (String superType : cardView.getSuperTypes()) { - sbType.append(superType).append(" "); - } - for (CardType cardType : cardView.getCardTypes()) { - sbType.append(cardType.toString()).append(" "); - } - if (cardView.getSubTypes().size() > 0) { - sbType.append("- "); - for (String subType : cardView.getSubTypes()) { - sbType.append(subType).append(" "); + if (cardView.isAbility()) { + switch (cardView.getAbilityType()) { + case TRIGGERED: + return "Triggered Ability"; + case ACTIVATED: + return "Activated Ability"; + default: + return "??? Ability"; } + } else { + StringBuilder sbType = new StringBuilder(); + for (String superType : cardView.getSuperTypes()) { + sbType.append(superType).append(" "); + } + for (CardType cardType : cardView.getCardTypes()) { + sbType.append(cardType.toString()).append(" "); + } + if (cardView.getSubTypes().size() > 0) { + sbType.append("- "); + for (String subType : cardView.getSubTypes()) { + sbType.append(subType).append(" "); + } + } + return sbType.toString(); } - return sbType.toString(); } // Set the card art image (CardPanel will give it to us when it diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java index 52644b6c093..4cc50fefa59 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java @@ -8,6 +8,7 @@ package org.mage.card.arcane; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; +import java.awt.FontFormatException; import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.Image; @@ -22,7 +23,10 @@ import java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.font.TextMeasurer; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; +import java.io.IOException; import java.net.URL; import java.text.AttributedCharacterIterator; import java.text.AttributedString; @@ -35,6 +39,7 @@ import mage.ObjectColor; import mage.constants.CardType; import mage.view.CardView; import mage.view.PermanentView; +import net.java.balloontip.styles.RoundedBalloonStyle; import org.apache.log4j.Logger; import org.mage.card.arcane.CardRenderer; import org.mage.card.arcane.CardRendererUtils; @@ -82,6 +87,20 @@ public class ModernCardRenderer extends CardRenderer { BufferedImage img = CardRendererUtils.toBufferedImage(icon.getImage()); return new TexturePaint(img, new Rectangle(0, 0, img.getWidth(), img.getHeight())); } + private static Font loadFont(String name) { + try { + return Font.createFont( + Font.TRUETYPE_FONT, + ModernCardRenderer.class.getResourceAsStream("/cardrender/" + name + ".ttf")); + } catch (IOException e) { + LOGGER.info("Failed to load font `" + name + "`, couldn't find resource."); + } catch (FontFormatException e) { + LOGGER.info("Failed to load font `" + name + "`, bad format."); + } + return new Font("Arial", Font.PLAIN, 1); + } + public static Font BASE_BELEREN_FONT = loadFont("beleren-bold"); + public static Paint BG_TEXTURE_WHITE = loadBackgroundTexture("white"); public static Paint BG_TEXTURE_BLUE = loadBackgroundTexture("blue"); public static Paint BG_TEXTURE_BLACK = loadBackgroundTexture("black"); @@ -152,6 +171,7 @@ public class ModernCardRenderer extends CardRenderer { // How far down the card is the type line placed? protected static float TYPE_LINE_Y_FRAC = 0.57f; // x cardHeight + protected static float TYPE_LINE_Y_FRAC_TOKEN = 0.70f; protected int typeLineY; // How large is the box text, and how far is it down the boxes @@ -195,17 +215,21 @@ public class ModernCardRenderer extends CardRenderer { BOX_HEIGHT_FRAC * cardHeight); // Type line at - typeLineY = (int)(TYPE_LINE_Y_FRAC * cardHeight); + if (cardView.isToken()) { + typeLineY = (int)(TYPE_LINE_Y_FRAC_TOKEN * cardHeight); + } else { + typeLineY = (int)(TYPE_LINE_Y_FRAC * cardHeight); + } // Box text height boxTextHeight = getTextHeightForBoxHeight(boxHeight); boxTextOffset = (boxHeight - boxTextHeight)/2; - boxTextFont = new Font("Beleren", Font.PLAIN, boxTextHeight); + boxTextFont = BASE_BELEREN_FONT.deriveFont(Font.PLAIN, boxTextHeight); // Box text height ptTextHeight = getPTTextHeightForLineHeight(boxHeight); ptTextOffset = (boxHeight - ptTextHeight)/2; - ptTextFont = new Font("Beleren", Font.PLAIN, ptTextHeight); + ptTextFont = BASE_BELEREN_FONT.deriveFont(Font.PLAIN, ptTextHeight); } @Override @@ -213,6 +237,33 @@ public class ModernCardRenderer extends CardRenderer { // Draw border as one rounded rectangle g.setColor(Color.black); g.fillRoundRect(0, 0, cardWidth, cardHeight, cornerRadius, cornerRadius); + + // Selection Borders + Color borderColor; + if (isSelected) { + borderColor = Color.green; + } else if (isChoosable) { + borderColor = new Color(250, 250, 0, 230); + } else if (cardView.isPlayable()) { + borderColor = new Color(153, 102, 204, 200); + } else if (cardView instanceof PermanentView && ((PermanentView)cardView).isCanAttack()) { + borderColor = new Color(0, 0, 255, 230); + } else { + borderColor = null; + } + if (borderColor != null) { + float hwidth = borderWidth / 2.0f; + Graphics2D g2 = (Graphics2D)g.create(); + g2.setColor(borderColor); + g2.setStroke(new BasicStroke(borderWidth)); + RoundRectangle2D.Float rect + = new RoundRectangle2D.Float( + hwidth, hwidth, + cardWidth - borderWidth, cardHeight - borderWidth, + cornerRadius, cornerRadius); + g2.draw(rect); + g2.dispose(); + } } @Override @@ -247,7 +298,7 @@ public class ModernCardRenderer extends CardRenderer { @Override protected void drawArt(Graphics2D g) { - if (artImage != null) { + if (artImage != null && !cardView.isFaceDown()) { int imgWidth = artImage.getWidth(); int imgHeight = artImage.getHeight(); BufferedImage subImg = @@ -354,14 +405,29 @@ public class ModernCardRenderer extends CardRenderer { // Draw the name line protected void drawNameLine(Graphics2D g, int x, int y, int w, int h) { // Width of the mana symbols - int manaCostWidth = CardRendererUtils.getManaCostWidth(manaCostString, boxTextHeight); - + int manaCostWidth; + if (cardView.isAbility()) { + manaCostWidth = 0; + } else { + manaCostWidth = CardRendererUtils.getManaCostWidth(manaCostString, boxTextHeight); + } + // Available width for name. Add a little bit of slop so that one character // can partially go underneath the mana cost int availableWidth = w - manaCostWidth + 2; // Draw the name - AttributedString str = new AttributedString(cardView.getName()); + String nameStr; + if (cardView.isFaceDown()) { + if (cardView instanceof PermanentView && ((PermanentView)cardView).isManifested()) { + nameStr = "Manifest: " + cardView.getName(); + } else { + nameStr = "Morph: " + cardView.getName(); + } + } else { + nameStr = cardView.getName(); + } + AttributedString str = new AttributedString(nameStr); str.addAttribute(TextAttribute.FONT, boxTextFont); TextMeasurer measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext()); TextLayout layout = measure.getLayout(0, measure.getLineBreakIndex(0, availableWidth)); @@ -369,13 +435,20 @@ public class ModernCardRenderer extends CardRenderer { layout.draw(g, x, y + boxTextOffset + boxTextHeight - 1); // Draw the mana symbols - ManaSymbols.draw(g, manaCostString, x + w - manaCostWidth, y + boxTextOffset, boxTextHeight); + if (!cardView.isAbility() && !cardView.isFaceDown()) { + ManaSymbols.draw(g, manaCostString, x + w - manaCostWidth, y + boxTextOffset, boxTextHeight); + } } // Draw the type line (color indicator, types, and expansion symbol) protected void drawTypeLine(Graphics2D g, int x, int y, int w, int h) { // Draw expansion symbol - int expansionSymbolWidth = drawExpansionSymbol(g, x, y, w, h); + int expansionSymbolWidth; + if (cardView.isAbility()) { + expansionSymbolWidth = 0; + } else { + expansionSymbolWidth = drawExpansionSymbol(g, x, y, w, h); + } // Draw type line text int availableWidth = w - expansionSymbolWidth + 1; @@ -387,16 +460,23 @@ public class ModernCardRenderer extends CardRenderer { types = types.replace("Legendary", "L."); } - AttributedString str = new AttributedString(types); - str.addAttribute(TextAttribute.FONT, boxTextFont); - TextMeasurer measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext()); - TextLayout layout = measure.getLayout(0, measure.getLineBreakIndex(0, availableWidth)); - g.setColor(getBoxTextColor()); - layout.draw(g, x, y + boxTextOffset + boxTextHeight - 1); + if (!types.isEmpty()) { + AttributedString str = new AttributedString(types); + str.addAttribute(TextAttribute.FONT, boxTextFont); + TextMeasurer measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext()); + TextLayout layout = measure.getLayout(0, measure.getLineBreakIndex(0, availableWidth)); + g.setColor(getBoxTextColor()); + layout.draw(g, x, y + boxTextOffset + boxTextHeight - 1); + } } // Draw the P/T and/or Loyalty boxes protected void drawBottomRight(Graphics2D g, Paint borderPaint, Color fill) { + // No bottom right for abilities + if (cardView.isAbility()) { + return; + } + // Where to start drawing the things int curY = cardHeight - (int)(0.03f*cardHeight); @@ -493,6 +573,20 @@ public class ModernCardRenderer extends CardRenderer { // Advance curY -= (int)(1.2*y); } + + // does it have damage on it? + if ((cardView instanceof PermanentView) && ((PermanentView)cardView).getDamage() > 0) { + int x = cardWidth - partWidth - borderWidth; + int y = curY - boxHeight; + String damage = "" + ((PermanentView)cardView).getDamage(); + g.setFont(ptTextFont); + int txWidth = g.getFontMetrics().stringWidth(damage); + g.setColor(Color.red); + g.fillRect(x, y, partWidth, boxHeight); + g.setColor(Color.white); + g.drawRect(x, y, partWidth, boxHeight); + g.drawString(damage, x + (partWidth - txWidth)/2, curY - 1); + } } // Draw the card's textbox in a given rect @@ -763,6 +857,8 @@ public class ModernCardRenderer extends CardRenderer { protected Color getBoxTextColor() { if (isNightCard()) { return Color.white; + } else if (cardView.isAbility()) { + return Color.white; } else { return Color.black; } @@ -800,8 +896,10 @@ public class ModernCardRenderer extends CardRenderer { } // Get the box color for the given colors - protected static Color getBoxColor(ObjectColor colors, Collection types, boolean isNightCard) { - if (colors.getColorCount() == 2 && types.contains(CardType.LAND)) { + protected Color getBoxColor(ObjectColor colors, Collection types, boolean isNightCard) { + if (cardView.isAbility()) { + return Color.BLACK; + } else if (colors.getColorCount() == 2 && types.contains(CardType.LAND)) { // Special case for two color lands. Boxes should be normal land colored // rather than multicolor. Three or greater color lands use a multi-color // box as normal. diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRuleParser.java b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRuleParser.java index de492bf327b..5f755b1c3e1 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRuleParser.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/TextboxRuleParser.java @@ -17,6 +17,7 @@ import java.util.Deque; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import mage.client.dialog.PreferencesDialog; import mage.view.CardView; import org.apache.log4j.Logger; @@ -37,6 +38,11 @@ public class TextboxRuleParser { // representing that information, which can be used to render the rule in // the textbox of a card. public static TextboxRule parse(CardView source, String rule) { + // Kill reminder text + if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_REMINDER_TEXT, "false").equals("false")) { + rule = CardRendererUtils.killReminderText(rule); + } + // List of regions to apply ArrayList regions = new ArrayList<>(); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java index db0d9958ba8..6b52d0db472 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java @@ -200,6 +200,10 @@ public class ImageCache { public static BufferedImage getThumbnail(CardView card) { return getImage(getKey(card, card.getName(), "#thumb")); } + + public static BufferedImage tryGetThumbnail(CardView card) { + return tryGetImage(getKey(card, card.getName(), "#thumb")); + } public static BufferedImage getImageOriginal(CardView card) { return getImage(getKey(card, card.getName(), "")); @@ -230,6 +234,18 @@ public class ImageCache { return null; } } + + /** + * Returns the Image corresponding to the key only if it already exists + * in the cache. + */ + private static BufferedImage tryGetImage(String key) { + if (IMAGE_CACHE.containsKey(key)) { + return IMAGE_CACHE.get(key); + } else { + return null; + } + } /** * Returns the map key for a card, without any suffixes for the image size. @@ -343,6 +359,34 @@ public class ImageCache { return TransformedImageCache.getResizedImage(original, (int) (original.getWidth() * scale), (int) (original.getHeight() * scale)); } + + /** + * Returns the image appropriate to display for a card in a picture panel, but + * only it was ALREADY LOADED. That is, the call is immediate and will not block + * on file IO. + * @param card + * @param width + * @param height + * @return + */ + public static BufferedImage tryGetImage(CardView card, int width, int height) { + if (Constants.THUMBNAIL_SIZE_FULL.width + 10 > width) { + return tryGetThumbnail(card); + } + String key = getKey(card, card.getName(), Integer.toString(width)); + BufferedImage original = tryGetImage(key); + if (original == null) { + LOGGER.debug(key + " not found"); + return null; + } + + double scale = Math.min((double) width / original.getWidth(), (double) height / original.getHeight()); + if (scale >= 1) { + return original; + } + + return TransformedImageCache.getResizedImage(original, (int) (original.getWidth() * scale), (int) (original.getHeight() * scale)); + } public static TFile getTFile(String path) { try { diff --git a/Mage.Client/src/main/resources/cardrender/beleren-bold.ttf b/Mage.Client/src/main/resources/cardrender/beleren-bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7dd1bff6a756375020ea4e080ac02b6e04877a1b GIT binary patch literal 92612 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 manaAdded = mana.getNetMana(game); + for (Mana m: manaAdded) { + if (m.getAny() > 0) { + return new ObjectColor("WUBRG"); + } + if (m.getWhite() > 0) { + cl.setWhite(true); + } + if (m.getBlue() > 0) { + cl.setBlue(true); + } + if (m.getBlack() > 0) { + cl.setBlack(true); + } + if (m.getRed() > 0) { + cl.setRed(true); + } + if (m.getGreen() > 0) { + cl.setGreen(true); + } + } + } catch (NullPointerException e) { + // Ability depends on game + // but no game passed + // All such abilities are 5-color ones + return new ObjectColor("WUBRG"); + } + } + } + return cl; + } else { + // For everything else, just return the frame colors + return frameColor; + } } @Override diff --git a/Mage/src/main/java/mage/cards/mock/MockCard.java b/Mage/src/main/java/mage/cards/mock/MockCard.java index e7a755f8896..7a0d375d972 100644 --- a/Mage/src/main/java/mage/cards/mock/MockCard.java +++ b/Mage/src/main/java/mage/cards/mock/MockCard.java @@ -50,7 +50,7 @@ public class MockCard extends CardImpl { if (this.cardType.contains(CardType.PLANESWALKER)) { String startingLoyaltyString = card.getStartingLoyalty(); if (startingLoyaltyString.isEmpty()) { - Logger.getLogger(MockCard.class).warn("Planeswalker `" + this.name + "` has empty starting loyalty."); + //Logger.getLogger(MockCard.class).warn("Planeswalker `" + this.name + "` has empty starting loyalty."); } else { try { this.startingLoyalty = Integer.parseInt(startingLoyaltyString); diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index 446f344a967..9e7f6f86070 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -42,7 +42,6 @@ import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.common.PlanswalkerEntersWithLoyalityCountersAbility; -import mage.abilities.effects.PlaneswalkerRedirectionEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.mock.MockCard; @@ -190,7 +189,7 @@ public class CardInfo { } } if (this.startingLoyalty == null) { - Logger.getLogger(CardInfo.class).warn("Planeswalker `" + card.getName() + "` missing starting loyalty"); + //Logger.getLogger(CardInfo.class).warn("Planeswalker `" + card.getName() + "` missing starting loyalty"); this.startingLoyalty = ""; } } else { diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 6f0b2e78854..448771998b8 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -61,9 +61,9 @@ public enum CardRepository { private static final String JDBC_URL = "jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE"; private static final String VERSION_ENTITY_NAME = "card"; // raise this if db structure was changed - private static final long CARD_DB_VERSION = 44; + private static final long CARD_DB_VERSION = 46; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 55; + private static final long CARD_CONTENT_VERSION = 57; private final Random random = new Random(); private Dao cardDao; diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index 6fd8f345743..62c93d409e8 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -118,6 +118,7 @@ public class PermanentCard extends PermanentImpl { this.cardType.clear(); this.cardType.addAll(card.getCardType()); this.color = card.getColor(null).copy(); + this.frameColor = card.getFrameColor(null).copy(); this.manaCost = card.getManaCost().copy(); if (card instanceof PermanentCard) { this.maxLevelCounters = ((PermanentCard) card).maxLevelCounters; diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index 2c649bf5f8b..1026c0b0911 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -82,6 +82,7 @@ public class PermanentToken extends PermanentImpl { } this.cardType = token.getCardType(); this.color = token.getColor(game).copy(); + this.frameColor = token.getFrameColor(game); this.power.modifyBaseValue(token.getPower().getBaseValueModified()); this.toughness.modifyBaseValue(token.getToughness().getBaseValueModified()); this.supertype = token.getSupertype();