* Fixed hybrid mana symbol display for characteristic-based card rendering. Removed not used import statements.

This commit is contained in:
LevelX2 2016-09-03 10:52:14 +02:00
parent cb91c5b9aa
commit 720a4457fd
9 changed files with 1490 additions and 1523 deletions

View file

@ -2,7 +2,6 @@ package org.mage.card.arcane;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Point; import java.awt.Point;
@ -20,8 +19,6 @@ import java.awt.image.BufferedImage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JPanel; import javax.swing.JPanel;
@ -30,7 +27,6 @@ import mage.cards.MagePermanent;
import mage.cards.TextPopup; import mage.cards.TextPopup;
import mage.cards.action.ActionCallback; import mage.cards.action.ActionCallback;
import mage.cards.action.TransferData; import mage.cards.action.TransferData;
import mage.client.components.layout.RelativeLayout;
import mage.client.plugins.adapters.MageActionCallback; import mage.client.plugins.adapters.MageActionCallback;
import mage.client.plugins.impl.Plugins; import mage.client.plugins.impl.Plugins;
import mage.client.util.audio.AudioManager; import mage.client.util.audio.AudioManager;
@ -75,7 +71,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public double flippedAngle = 0; public double flippedAngle = 0;
private final List<MagePermanent> links = new ArrayList<>(); private final List<MagePermanent> links = new ArrayList<>();
public JPanel buttonPanel; public JPanel buttonPanel;
private JButton dayNightButton; private JButton dayNightButton;
private JButton showCopySourceButton; private JButton showCopySourceButton;
@ -118,7 +114,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
this.gameCard = newGameCard; this.gameCard = newGameCard;
this.callback = callback; this.callback = callback;
this.gameId = gameId; this.gameId = gameId;
// Gather info about the card // Gather info about the card
this.isPermanent = this.gameCard instanceof PermanentView; this.isPermanent = this.gameCard instanceof PermanentView;
if (isPermanent) { if (isPermanent) {
@ -127,14 +123,14 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
// Set to requested size // Set to requested size
this.setCardBounds(0, 0, dimension.width, dimension.height); this.setCardBounds(0, 0, dimension.width, dimension.height);
// Create button panel for Transform and Show Source (copied cards) // Create button panel for Transform and Show Source (copied cards)
buttonPanel = new JPanel(); buttonPanel = new JPanel();
buttonPanel.setLayout(null); buttonPanel.setLayout(null);
buttonPanel.setOpaque(false); buttonPanel.setOpaque(false);
buttonPanel.setVisible(true); buttonPanel.setVisible(true);
add(buttonPanel); add(buttonPanel);
// Both card rendering implementations have a transform button // Both card rendering implementations have a transform button
if (this.gameCard.canTransform()) { if (this.gameCard.canTransform()) {
// Create the day night button // Create the day night button
@ -155,7 +151,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
Animation.transformCard(CardPanel.this, CardPanel.this, true); Animation.transformCard(CardPanel.this, CardPanel.this, true);
} }
}); });
// Add it // Add it
buttonPanel.add(dayNightButton); buttonPanel.add(dayNightButton);
} }
@ -199,14 +195,14 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0;
flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0;
} }
@Override @Override
public void doLayout() { public void doLayout() {
// Position transform and show source buttons // Position transform and show source buttons
buttonPanel.setLocation(cardXOffset, cardYOffset); buttonPanel.setLocation(cardXOffset, cardYOffset);
buttonPanel.setSize(cardWidth, cardHeight); buttonPanel.setSize(cardWidth, cardHeight);
int x = cardWidth/20; int x = cardWidth / 20;
int y = cardHeight/10; int y = cardHeight / 10;
if (dayNightButton != null) { if (dayNightButton != null) {
dayNightButton.setLocation(x, y); dayNightButton.setLocation(x, y);
y += 25; y += 25;
@ -216,9 +212,9 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
showCopySourceButton.setLocation(x, y); showCopySourceButton.setLocation(x, y);
} }
} }
public final void initialDraw() { public final void initialDraw() {
// Kick off // Kick off
if (gameCard.isTransformed()) { if (gameCard.isTransformed()) {
// this calls updateImage // this calls updateImage
toggleTransformed(); toggleTransformed();
@ -246,8 +242,8 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
this.callback = null; this.callback = null;
this.data = null; this.data = null;
} }
// Copy the graphical resources of another CardPanel over to this one, // Copy the graphical resources of another CardPanel over to this one,
// if possible (may not be possible if they have different implementations) // if possible (may not be possible if they have different implementations)
// Used when cards are moving between zones // Used when cards are moving between zones
public abstract void transferResources(CardPanel panel); public abstract void transferResources(CardPanel panel);
@ -273,7 +269,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public void setAnimationPanel(boolean isAnimationPanel) { public void setAnimationPanel(boolean isAnimationPanel) {
this.isAnimationPanel = isAnimationPanel; this.isAnimationPanel = isAnimationPanel;
} }
public boolean isAnimationPanel() { public boolean isAnimationPanel() {
return this.isAnimationPanel; return this.isAnimationPanel;
} }
@ -282,7 +278,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public void setSelected(boolean isSelected) { public void setSelected(boolean isSelected) {
this.isSelected = isSelected; this.isSelected = isSelected;
} }
public boolean isSelected() { public boolean isSelected() {
return this.isSelected; return this.isSelected;
} }
@ -291,16 +287,16 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public List<MagePermanent> getLinks() { public List<MagePermanent> getLinks() {
return links; return links;
} }
@Override @Override
public void setChoosable(boolean isChoosable) { public void setChoosable(boolean isChoosable) {
this.isChoosable = isChoosable; this.isChoosable = isChoosable;
} }
public boolean isChoosable() { public boolean isChoosable() {
return this.isChoosable; return this.isChoosable;
} }
public boolean hasSickness() { public boolean hasSickness() {
return this.hasSickness; return this.hasSickness;
} }
@ -308,7 +304,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public boolean isPermanent() { public boolean isPermanent() {
return this.isPermanent; return this.isPermanent;
} }
@Override @Override
public void setCardAreaRef(JPanel cardArea) { public void setCardAreaRef(JPanel cardArea) {
this.cardArea = cardArea; this.cardArea = cardArea;
@ -317,13 +313,13 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public void setShowCastingCost(boolean showCastingCost) { public void setShowCastingCost(boolean showCastingCost) {
this.showCastingCost = showCastingCost; this.showCastingCost = showCastingCost;
} }
public boolean getShowCastingCost() { public boolean getShowCastingCost() {
return this.showCastingCost; return this.showCastingCost;
} }
/** /**
* Overridden by different card rendering styles * Overridden by different card rendering styles
*/ */
protected abstract void paintCard(Graphics2D g); protected abstract void paintCard(Graphics2D g);
@ -356,7 +352,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
// Deferr to subclasses // Deferr to subclasses
paintCard(g2d); paintCard(g2d);
// Done, dispose of the context // Done, dispose of the context
g2d.dispose(); g2d.dispose();
} }
@ -372,7 +368,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
setBounds(x - cardXOffset, y - cardYOffset, getWidth(), getHeight()); setBounds(x - cardXOffset, y - cardYOffset, getWidth(), getHeight());
return; return;
} }
this.cardWidth = cardWidth; this.cardWidth = cardWidth;
this.symbolWidth = cardWidth / 7; this.symbolWidth = cardWidth / 7;
this.cardHeight = cardHeight; this.cardHeight = cardHeight;
@ -436,7 +432,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public final int getCardHeight() { public final int getCardHeight() {
return cardHeight; return cardHeight;
} }
public final int getSymbolWidth() { public final int getSymbolWidth() {
return symbolWidth; return symbolWidth;
} }
@ -510,15 +506,15 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
} }
/** /**
* Inheriting classes should implement update(CardView card) by * Inheriting classes should implement update(CardView card) by using this.
* using this. However, they should ALSO call repaint() after the superclass * However, they should ALSO call repaint() after the superclass call to
* call to this function, that can't be done here as the overriders may need * this function, that can't be done here as the overriders may need to do
* to do things both before and after this call before repainting. * things both before and after this call before repainting.
*/ */
@Override @Override
public void update(CardView card) { public void update(CardView card) {
this.updateCard = card; this.updateCard = card;
// Animation update // Animation update
if (isPermanent && (card instanceof PermanentView)) { if (isPermanent && (card instanceof PermanentView)) {
boolean needsTapping = isTapped() != ((PermanentView) card).isTapped(); boolean needsTapping = isTapped() != ((PermanentView) card).isTapped();
@ -538,11 +534,11 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
// Update panel attributes // Update panel attributes
this.isChoosable = card.isChoosable(); this.isChoosable = card.isChoosable();
this.isSelected = card.isSelected(); this.isSelected = card.isSelected();
// Update art? // Update art?
boolean mustUpdateArt = boolean mustUpdateArt
(!gameCard.getName().equals(card.getName())) || = (!gameCard.getName().equals(card.getName()))
(gameCard.isFaceDown() != card.isFaceDown()); || (gameCard.isFaceDown() != card.isFaceDown());
// Set the new card // Set the new card
this.gameCard = card; this.gameCard = card;
@ -550,12 +546,12 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
// Update tooltip text // Update tooltip text
String cardType = getType(card); String cardType = getType(card);
tooltipText.setText(getText(cardType, card)); tooltipText.setText(getText(cardType, card));
// Update the image // Update the image
if (mustUpdateArt) { if (mustUpdateArt) {
updateArtImage(); updateArtImage();
} }
// Update transform circle // Update transform circle
if (card.canTransform()) { if (card.canTransform()) {
BufferedImage transformIcon; BufferedImage transformIcon;
@ -742,13 +738,13 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public void update(PermanentView card) { public void update(PermanentView card) {
this.hasSickness = card.hasSummoningSickness(); this.hasSickness = card.hasSummoningSickness();
this.showCopySourceButton.setVisible(card.isCopy()); this.showCopySourceButton.setVisible(card.isCopy());
update((CardView)card); update((CardView) card);
} }
@Override @Override
public PermanentView getOriginalPermanent() { public PermanentView getOriginalPermanent() {
if (isPermanent) { if (isPermanent) {
return (PermanentView)this.gameCard; return (PermanentView) this.gameCard;
} }
throw new IllegalStateException("Is not permanent."); throw new IllegalStateException("Is not permanent.");
} }
@ -835,7 +831,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
public void setTextOffset(int yOffset) { public void setTextOffset(int yOffset) {
yTextOffset = yOffset; yTextOffset = yOffset;
} }
public int getTextOffset() { public int getTextOffset() {
return yTextOffset; return yTextOffset;
} }

View file

@ -1,398 +1,397 @@
package org.mage.card.arcane; package org.mage.card.arcane;
import com.google.common.base.Function; import com.google.common.collect.MapMaker;
import com.google.common.collect.MapMaker; import java.awt.Dimension;
import java.awt.Color; import java.awt.Graphics2D;
import java.awt.Dimension; import java.awt.Image;
import java.awt.Graphics2D; import java.awt.RenderingHints;
import java.awt.Image; import java.awt.image.BufferedImage;
import java.awt.RenderingHints; import java.io.File;
import java.awt.image.BufferedImage; import java.util.Map;
import java.io.File; import java.util.UUID;
import java.util.Map; import mage.cards.action.ActionCallback;
import java.util.UUID; import mage.constants.CardType;
import mage.cards.action.ActionCallback; import mage.view.CardView;
import mage.client.util.ImageCaches; import mage.view.CounterView;
import mage.constants.CardType; import mage.view.PermanentView;
import mage.view.CardView; import mage.view.StackAbilityView;
import mage.view.CounterView; import net.java.truevfs.access.TFile;
import mage.view.PermanentView; import org.apache.log4j.Logger;
import mage.view.StackAbilityView; import org.jdesktop.swingx.graphics.GraphicsUtilities;
import net.java.truevfs.access.TFile; import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL;
import org.apache.log4j.Logger; import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
import org.jdesktop.swingx.graphics.GraphicsUtilities; import org.mage.plugins.card.images.ImageCache;
import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL;
import org.mage.plugins.card.dl.sources.DirectLinksForDownload; public class CardPanelRenderImpl extends CardPanel {
import org.mage.plugins.card.images.ImageCache;
private static final Logger LOGGER = Logger.getLogger(CardPanelRenderImpl.class);
public class CardPanelRenderImpl extends CardPanel {
private static boolean cardViewEquals(CardView a, CardView b) {
private static final Logger LOGGER = Logger.getLogger(CardPanelRenderImpl.class); if (a == b) {
return true;
private static boolean cardViewEquals(CardView a, CardView b) { }
if (a == b) { if (a.getClass() != b.getClass()) {
return true; return false;
} }
if (a.getClass() != b.getClass()) { if (!a.getName().equals(b.getName())) {
return false; return false;
} }
if (!a.getName().equals(b.getName())) { if (!a.getPower().equals(b.getPower())) {
return false; return false;
} }
if (!a.getPower().equals(b.getPower())) { if (!a.getToughness().equals(b.getToughness())) {
return false; return false;
} }
if (!a.getToughness().equals(b.getToughness())) { if (!a.getLoyalty().equals(b.getLoyalty())) {
return false; return false;
} }
if (!a.getLoyalty().equals(b.getLoyalty())) { if (0 != a.getColor().compareTo(b.getColor())) {
return false; return false;
} }
if (0 != a.getColor().compareTo(b.getColor())) { if (!a.getCardTypes().equals(b.getCardTypes())) {
return false; return false;
} }
if (!a.getCardTypes().equals(b.getCardTypes())) { if (!a.getSubTypes().equals(b.getSubTypes())) {
return false; return false;
} }
if (!a.getSubTypes().equals(b.getSubTypes())) { if (!a.getSuperTypes().equals(b.getSuperTypes())) {
return false; return false;
} }
if (!a.getSuperTypes().equals(b.getSuperTypes())) { if (!a.getManaCost().equals(b.getManaCost())) {
return false; return false;
} }
if (!a.getManaCost().equals(b.getManaCost())) { if (!a.getRules().equals(b.getRules())) {
return false; return false;
} }
if (!a.getRules().equals(b.getRules())) { if (!a.getExpansionSetCode().equals(b.getExpansionSetCode())) {
return false; return false;
} }
if (!a.getExpansionSetCode().equals(b.getExpansionSetCode())) { if (a.getCounters() == null) {
return false; if (b.getCounters() != null) {
} return false;
if (a.getCounters() == null) { }
if (b.getCounters() != null) { } else if (!a.getCounters().equals(b.getCounters())) {
return false; return false;
} }
} else if (!a.getCounters().equals(b.getCounters())) { if (a.isFaceDown() != b.isFaceDown()) {
return false; return false;
} }
if (a.isFaceDown() != b.isFaceDown()) { if ((a instanceof PermanentView)) {
return false; PermanentView aa = (PermanentView) a;
} PermanentView bb = (PermanentView) b;
if ((a instanceof PermanentView)) { if (aa.hasSummoningSickness() != bb.hasSummoningSickness()) {
PermanentView aa = (PermanentView)a; // Note: b must be a permanentview too as we aleady checked that classes
PermanentView bb = (PermanentView)b; // are the same for a and b
if (aa.hasSummoningSickness() != bb.hasSummoningSickness()) { return false;
// Note: b must be a permanentview too as we aleady checked that classes }
// are the same for a and b if (aa.getDamage() != bb.getDamage()) {
return false; return false;
} }
if (aa.getDamage() != bb.getDamage()) { }
return false; return true;
} }
}
return true; class ImageKey {
}
final BufferedImage artImage;
class ImageKey { final int width;
final BufferedImage artImage; final int height;
final int width; final boolean isChoosable;
final int height; final boolean isSelected;
final boolean isChoosable; final CardView view;
final boolean isSelected; final int hashCode;
final CardView view;
final int hashCode; public ImageKey(CardView view, BufferedImage artImage, int width, int height, boolean isChoosable, boolean isSelected) {
this.view = view;
public ImageKey(CardView view, BufferedImage artImage, int width, int height, boolean isChoosable, boolean isSelected) { this.artImage = artImage;
this.view = view; this.width = width;
this.artImage = artImage; this.height = height;
this.width = width; this.isChoosable = isChoosable;
this.height = height; this.isSelected = isSelected;
this.isChoosable = isChoosable; this.hashCode = hashCodeImpl();
this.isSelected = isSelected; }
this.hashCode = hashCodeImpl();
} private int hashCodeImpl() {
StringBuilder sb = new StringBuilder();
private int hashCodeImpl() { sb.append((char) (artImage != null ? 1 : 0));
StringBuilder sb = new StringBuilder(); sb.append((char) width);
sb.append((char)(artImage != null ? 1 : 0)); sb.append((char) height);
sb.append((char)width); sb.append((char) (isSelected ? 1 : 0));
sb.append((char)height); sb.append((char) (isChoosable ? 1 : 0));
sb.append((char)(isSelected ? 1 : 0)); sb.append((char) (this.view.isPlayable() ? 1 : 0));
sb.append((char)(isChoosable ? 1 : 0)); sb.append((char) (this.view.isCanAttack() ? 1 : 0));
sb.append((char)(this.view.isPlayable() ? 1 : 0)); sb.append((char) (this.view.isFaceDown() ? 1 : 0));
sb.append((char)(this.view.isCanAttack() ? 1 : 0)); if (this.view instanceof PermanentView) {
sb.append((char)(this.view.isFaceDown() ? 1 : 0)); sb.append((char) (((PermanentView) this.view).hasSummoningSickness() ? 1 : 0));
if (this.view instanceof PermanentView) { sb.append((char) (((PermanentView) this.view).getDamage()));
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.getName()); sb.append(this.view.getToughness());
sb.append(this.view.getPower()); sb.append(this.view.getLoyalty());
sb.append(this.view.getToughness()); sb.append(this.view.getColor().toString());
sb.append(this.view.getLoyalty()); sb.append(this.view.getExpansionSetCode());
sb.append(this.view.getColor().toString()); for (CardType type : this.view.getCardTypes()) {
sb.append(this.view.getExpansionSetCode()); sb.append((char) type.ordinal());
for (CardType type: this.view.getCardTypes()) { }
sb.append((char)type.ordinal()); for (String s : this.view.getSuperTypes()) {
} sb.append(s);
for (String s: this.view.getSuperTypes()) { }
sb.append(s); for (String s : this.view.getSubTypes()) {
} sb.append(s);
for (String s: this.view.getSubTypes()) { }
sb.append(s); for (String s : this.view.getManaCost()) {
} sb.append(s);
for (String s: this.view.getManaCost()) { }
sb.append(s); for (String s : this.view.getRules()) {
} sb.append(s);
for (String s: this.view.getRules()) { }
sb.append(s); if (this.view.getCounters() != null) {
} for (CounterView v : this.view.getCounters()) {
if (this.view.getCounters() != null) { sb.append(v.getName()).append(v.getCount());
for (CounterView v: this.view.getCounters()) { }
sb.append(v.getName()).append(v.getCount()); }
} return sb.toString().hashCode();
} }
return sb.toString().hashCode();
} @Override
public int hashCode() {
@Override return hashCode;
public int hashCode() { }
return hashCode;
} @Override
public boolean equals(Object object) {
@Override // Initial checks
public boolean equals(Object object) { if (this == object) {
// Initial checks return true;
if (this == object) { }
return true; if (object == null) {
} return false;
if (object == null) { }
return false; if (!(object instanceof ImageKey)) {
} return false;
if (!(object instanceof ImageKey)) { }
return false; final ImageKey other = (ImageKey) object;
}
final ImageKey other = (ImageKey)object; // Compare
if ((artImage != null) != (other.artImage != null)) {
// Compare return false;
if ((artImage != null) != (other.artImage != null)) { }
return false; if (width != other.width) {
} return false;
if (width != other.width) { }
return false; if (height != other.height) {
} return false;
if (height != other.height) { }
return false; if (isChoosable != other.isChoosable) {
} return false;
if (isChoosable != other.isChoosable) { }
return false; if (isSelected != other.isSelected) {
} return false;
if (isSelected != other.isSelected) { }
return false; return cardViewEquals(view, other.view);
} }
return cardViewEquals(view, other.view); }
}
} // Map of generated images
private final static Map<ImageKey, BufferedImage> IMAGE_CACHE = new MapMaker().softValues().makeMap();
// Map of generated images
private final static Map<ImageKey, BufferedImage> IMAGE_CACHE = new MapMaker().softValues().makeMap(); // The art image for the card, loaded in from the disk
private BufferedImage artImage;
// The art image for the card, loaded in from the disk
private BufferedImage artImage; // The rendered card image, with or without the art image loaded yet
// = null while invalid
// The rendered card image, with or without the art image loaded yet private BufferedImage cardImage;
// = null while invalid private CardRenderer cardRenderer;
private BufferedImage cardImage;
private CardRenderer cardRenderer; public CardPanelRenderImpl(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension) {
// Call to super
public CardPanelRenderImpl(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension) { super(newGameCard, gameId, loadImage, callback, foil, dimension);
// Call to super
super(newGameCard, gameId, loadImage, callback, foil, dimension); // Renderer
cardRenderer = new ModernCardRenderer(gameCard, isTransformed());
// Renderer
cardRenderer = new ModernCardRenderer(gameCard, isTransformed()); // Draw the parts
initialDraw();
// Draw the parts }
initialDraw();
} @Override
public void transferResources(CardPanel panel) {
@Override if (panel instanceof CardPanelRenderImpl) {
public void transferResources(CardPanel panel) { CardPanelRenderImpl impl = (CardPanelRenderImpl) panel;
if (panel instanceof CardPanelRenderImpl) {
CardPanelRenderImpl impl = (CardPanelRenderImpl)panel; // Use the art image and current rendered image from the card
artImage = impl.artImage;
// Use the art image and current rendered image from the card cardRenderer.setArtImage(artImage);
artImage = impl.artImage; cardImage = impl.cardImage;
cardRenderer.setArtImage(artImage); }
cardImage = impl.cardImage; }
}
} @Override
protected void paintCard(Graphics2D g) {
@Override // Render the card if we don't have an image ready to use
protected void paintCard(Graphics2D g) { if (cardImage == null) {
// Render the card if we don't have an image ready to use // Try to get card image from cache based on our card characteristics
if (cardImage == null) { ImageKey key
// Try to get card image from cache based on our card characteristics = new ImageKey(gameCard, artImage,
ImageKey key = getCardWidth(), getCardHeight(),
new ImageKey(gameCard, artImage, isChoosable(), isSelected());
getCardWidth(), getCardHeight(), cardImage = IMAGE_CACHE.get(key);
isChoosable(), isSelected());
cardImage = IMAGE_CACHE.get(key); // No cached copy exists? Render one and cache it
if (cardImage == null) {
// No cached copy exists? Render one and cache it cardImage = renderCard();
if (cardImage == null) { IMAGE_CACHE.put(key, cardImage);
cardImage = renderCard(); }
IMAGE_CACHE.put(key, cardImage); }
}
} // And draw the image we now have
g.drawImage(cardImage, getCardXOffset(), getCardYOffset(), null);
// And draw the image we now have }
g.drawImage(cardImage, getCardXOffset(), getCardYOffset(), null);
} /**
* Render the card to a new BufferedImage at it's current dimensions
/** *
* Render the card to a new BufferedImage at it's current dimensions * @return
* @return */
*/ private BufferedImage renderCard() {
private BufferedImage renderCard() { int cardWidth = getCardWidth();
int cardWidth = getCardWidth(); int cardHeight = getCardHeight();
int cardHeight = getCardHeight();
// Create image to render to
// Create image to render to BufferedImage image
BufferedImage image = = GraphicsUtilities.createCompatibleTranslucentImage(cardWidth, cardHeight);
GraphicsUtilities.createCompatibleTranslucentImage(cardWidth, cardHeight); Graphics2D g2d = image.createGraphics();
Graphics2D g2d = image.createGraphics();
// Render with Antialialsing
// Render with Antialialsing g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Attributes
// Attributes CardPanelAttributes attribs
CardPanelAttributes attribs = = new CardPanelAttributes(cardWidth, cardHeight, isChoosable(), isSelected());
new CardPanelAttributes(cardWidth, cardHeight, isChoosable(), isSelected());
// Draw card itself
// Draw card itself cardRenderer.draw(g2d, attribs);
cardRenderer.draw(g2d, attribs);
// Done
// Done g2d.dispose();
g2d.dispose(); return image;
return image; }
}
private int updateArtImageStamp;
private int updateArtImageStamp;
@Override @Override
public void updateArtImage() { public void updateArtImage() {
// Invalidate // Invalidate
artImage = null; artImage = null;
cardImage = null; cardImage = null;
cardRenderer.setArtImage(null); cardRenderer.setArtImage(null);
// Stop animation // Stop animation
tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0;
flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0;
// Schedule a repaint // Schedule a repaint
repaint(); repaint();
// See if the image is already loaded // See if the image is already loaded
//artImage = ImageCache.tryGetImage(gameCard, getCardWidth(), getCardHeight()); //artImage = ImageCache.tryGetImage(gameCard, getCardWidth(), getCardHeight());
//this.cardRenderer.setArtImage(artImage); //this.cardRenderer.setArtImage(artImage);
// Submit a task to draw with the card art when it arrives
// Submit a task to draw with the card art when it arrives if (artImage == null) {
if (artImage == null) { final int stamp = ++updateArtImageStamp;
final int stamp = ++updateArtImageStamp; Util.threadPool.submit(new Runnable() {
Util.threadPool.submit(new Runnable() { @Override
@Override public void run() {
public void run() { try {
try { final BufferedImage srcImage;
final BufferedImage srcImage; if (gameCard.isFaceDown()) {
if (gameCard.isFaceDown()) { // Nothing to do
// Nothing to do srcImage = null;
srcImage = null; } else if (getCardWidth() > THUMBNAIL_SIZE_FULL.width) {
} else if (getCardWidth() > THUMBNAIL_SIZE_FULL.width) { srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight());
srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); } else {
} else { srcImage = ImageCache.getThumbnail(gameCard);
srcImage = ImageCache.getThumbnail(gameCard); }
} UI.invokeLater(new Runnable() {
UI.invokeLater(new Runnable() { @Override
@Override public void run() {
public void run() { if (stamp == updateArtImageStamp) {
if (stamp == updateArtImageStamp) { artImage = srcImage;
artImage = srcImage; cardRenderer.setArtImage(srcImage);
cardRenderer.setArtImage(srcImage); if (srcImage != null) {
if (srcImage != null) { // Invalidate and repaint
// Invalidate and repaint cardImage = null;
cardImage = null; repaint();
repaint(); }
} }
} }
} });
}); } catch (Exception e) {
} catch (Exception e) { e.printStackTrace();
e.printStackTrace(); } catch (Error err) {
} catch (Error err) { err.printStackTrace();
err.printStackTrace(); }
} }
} });
}); }
} }
}
@Override
@Override public void update(CardView card) {
public void update(CardView card) { // Update super
// Update super super.update(card);
super.update(card);
// Update renderer
// Update renderer cardImage = null;
cardImage = null; cardRenderer = new ModernCardRenderer(gameCard, isTransformed());
cardRenderer = new ModernCardRenderer(gameCard, isTransformed()); cardRenderer.setArtImage(artImage);
cardRenderer.setArtImage(artImage);
// Repaint
// Repaint repaint();
repaint(); }
}
@Override
@Override public void setCardBounds(int x, int y, int cardWidth, int cardHeight) {
public void setCardBounds(int x, int y, int cardWidth, int cardHeight) { int oldCardWidth = getCardWidth();
int oldCardWidth = getCardWidth(); int oldCardHeight = getCardHeight();
int oldCardHeight = getCardHeight();
super.setCardBounds(x, y, cardWidth, cardHeight);
super.setCardBounds(x, y, cardWidth, cardHeight);
// Rerender if card size changed
// Rerender if card size changed if (getCardWidth() != oldCardWidth || getCardHeight() != oldCardHeight) {
if (getCardWidth() != oldCardWidth || getCardHeight() != oldCardHeight) { cardImage = null;
cardImage = null; }
} }
}
private BufferedImage getFaceDownImage() {
private BufferedImage getFaceDownImage() { if (isPermanent()) {
if (isPermanent()) { if (((PermanentView) gameCard).isMorphed()) {
if (((PermanentView) gameCard).isMorphed()) { return ImageCache.getMorphImage();
return ImageCache.getMorphImage(); } else {
} else { return ImageCache.getManifestImage();
return ImageCache.getManifestImage(); }
} } else if (this.gameCard instanceof StackAbilityView) {
} else if (this.gameCard instanceof StackAbilityView) { return ImageCache.getMorphImage();
return ImageCache.getMorphImage(); } else {
} else { return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename));
return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); }
} }
}
@Override
@Override public Image getImage() {
public Image getImage() { if (artImage != null) {
if (artImage != null) { if (gameCard.isFaceDown()) {
if (gameCard.isFaceDown()) { return getFaceDownImage();
return getFaceDownImage(); } else {
} else { return ImageCache.getImageOriginal(gameCard);
return ImageCache.getImageOriginal(gameCard); }
} }
} return null;
return null; }
}
@Override
@Override public void showCardTitle() {
public void showCardTitle() { // Nothing to do, rendered cards always have a title
// Nothing to do, rendered cards always have a title }
} }
}

View file

@ -1,382 +1,374 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package org.mage.card.arcane; package org.mage.card.arcane;
import java.awt.BasicStroke; import java.awt.BasicStroke;
import java.awt.Color; import java.awt.Color;
import java.awt.Font; import java.awt.Font;
import java.awt.FontMetrics; import java.awt.Graphics2D;
import java.awt.Graphics2D; import java.awt.Image;
import java.awt.Image; import java.awt.Paint;
import java.awt.Paint; import java.awt.Polygon;
import java.awt.Polygon; import java.awt.image.BufferedImage;
import java.awt.Rectangle; import java.util.ArrayList;
import java.awt.image.BufferedImage; import mage.client.dialog.PreferencesDialog;
import java.text.AttributedString; import mage.constants.AbilityType;
import java.util.ArrayList; import mage.constants.CardType;
import java.util.List; import mage.utils.CardUtil;
import mage.client.dialog.PreferencesDialog; import mage.view.CardView;
import mage.constants.AbilityType; import mage.view.CounterView;
import mage.constants.CardType; import mage.view.PermanentView;
import mage.constants.Rarity; import org.apache.log4j.Logger;
import mage.counters.Counter;
import mage.utils.CardUtil; /**
import mage.view.CardView; * @author stravant@gmail.com
import mage.view.CounterView; *
import mage.view.PermanentView; * Common base class for card renderers for each card frame / card type.
import org.apache.log4j.Logger; *
* Follows the template method pattern to implement a new renderer, implement
/** * the following methods (they are called in the following order):
* @author stravant@gmail.com *
* * * drawBorder() Draws the outermost border of the card, white border or black
* Common base class for card renderers for each card frame / card type. * border
* *
* Follows the template method pattern to implement a new renderer, implement * * drawBackground() Draws the background texture / color of the card
* the following methods (they are called in the following order): *
* * * drawArt() Draws the card's art
* * drawBorder() *
* Draws the outermost border of the card, white border or black border * * drawFrame() Draws the card frame (over the art and background)
* *
* * drawBackground() * * drawOverlays() Draws summoning sickness and possible other overlays
* Draws the background texture / color of the card *
* * * drawCounters() Draws counters on the card, such as +1/+1 and -1/-1
* * drawArt() * counters
* Draws the card's art *
* * Predefined methods that the implementations can use:
* * drawFrame() *
* Draws the card frame (over the art and background) * * drawRules(font, bounding box)
* *
* * drawOverlays() * * drawNameLine(font, bounding box)
* Draws summoning sickness and possible other overlays *
* * * drawTypeLine(font, bounding box)
* * drawCounters() *
* Draws counters on the card, such as +1/+1 and -1/-1 counters */
* public abstract class CardRenderer {
* Predefined methods that the implementations can use:
* private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
* * drawRules(font, bounding box)
* ///////////////////////////////////////////////////////////////////////////
* * drawNameLine(font, bounding box) // Common layout metrics between all cards
* // The card to be rendered
* * drawTypeLine(font, bounding box) protected final CardView cardView;
*
*/ // Is the card transformed?
public abstract class CardRenderer { protected final boolean isTransformed;
private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
// The card image
/////////////////////////////////////////////////////////////////////////// protected BufferedImage artImage;
// Common layout metrics between all cards
///////////////////////////////////////////////////////////////////////////
// The card to be rendered // Common layout metrics between all cards
protected final CardView cardView; // Polygons for counters
private static final Polygon PLUS_COUNTER_POLY = new Polygon(new int[]{
// Is the card transformed? 0, 5, 10, 10, 5, 0
protected final boolean isTransformed; }, new int[]{
3, 0, 3, 10, 9, 10
// The card image }, 6);
protected BufferedImage artImage; private static final Polygon MINUS_COUNTER_POLY = new Polygon(new int[]{
0, 5, 10, 10, 5, 0
/////////////////////////////////////////////////////////////////////////// }, new int[]{
// Common layout metrics between all cards 0, 1, 0, 7, 10, 7
}, 6);
// Polygons for counters private static final Polygon TIME_COUNTER_POLY = new Polygon(new int[]{
private static final Polygon PLUS_COUNTER_POLY = new Polygon(new int[]{ 0, 10, 8, 10, 0, 2
0, 5, 10, 10, 5, 0 }, new int[]{
}, new int[]{ 0, 0, 5, 10, 10, 5
3, 0, 3, 10, 9, 10 }, 6);
}, 6); private static final Polygon OTHER_COUNTER_POLY = new Polygon(new int[]{
private static final Polygon MINUS_COUNTER_POLY = new Polygon(new int[]{ 1, 9, 9, 1
0, 5, 10, 10, 5, 0 }, new int[]{
}, new int[]{ 1, 1, 9, 9
0, 1, 0, 7, 10, 7 }, 4);
}, 6);
private static final Polygon TIME_COUNTER_POLY = new Polygon(new int[]{ // Paint for a card back
0, 10, 8, 10, 0, 2 public static Paint BG_TEXTURE_CARDBACK = new Color(153, 102, 51);
}, new int[]{
0, 0, 5, 10, 10, 5 // The size of the card
}, 6); protected int cardWidth;
private static final Polygon OTHER_COUNTER_POLY = new Polygon(new int[]{ protected int cardHeight;
1, 9, 9, 1
}, new int[]{ // Is it selectable / selected
1, 1, 9, 9 protected boolean isChoosable;
}, 4); protected boolean isSelected;
// Paint for a card back // Radius of the corners of the cards
public static Paint BG_TEXTURE_CARDBACK = new Color(153, 102, 51); protected static float CORNER_RADIUS_FRAC = 0.1f; //x cardWidth
protected static int CORNER_RADIUS_MIN = 3;
// The size of the card protected int cornerRadius;
protected int cardWidth;
protected int cardHeight; // The inset of the actual card from the black / white border around it
protected static float BORDER_WIDTH_FRAC = 0.03f; //x cardWidth
// Is it selectable / selected protected static float BORDER_WIDTH_MIN = 2;
protected boolean isChoosable; protected int borderWidth;
protected boolean isSelected;
// The parsed text of the card
// Radius of the corners of the cards protected ArrayList<TextboxRule> textboxRules = new ArrayList<>();
protected static float CORNER_RADIUS_FRAC = 0.1f; //x cardWidth protected ArrayList<TextboxRule> textboxKeywords = new ArrayList<>();
protected static int CORNER_RADIUS_MIN = 3;
protected int cornerRadius; // The Construtor
// The constructor should prepare all of the things that it can
// The inset of the actual card from the black / white border around it // without knowing the dimensions that the card will be rendered at.
protected static float BORDER_WIDTH_FRAC = 0.03f; //x cardWidth // Then, the CardRenderer can be called on multiple times to render the
protected static float BORDER_WIDTH_MIN = 2; // card at various sizes (for instance, during animation)
protected int borderWidth; public CardRenderer(CardView card, boolean isTransformed) {
// Set base parameters
// The parsed text of the card this.cardView = card;
protected ArrayList<TextboxRule> textboxRules = new ArrayList<>(); this.isTransformed = isTransformed;
protected ArrayList<TextboxRule> textboxKeywords = new ArrayList<>();
// Translate the textbox text
// The Construtor for (String rule : card.getRules()) {
// The constructor should prepare all of the things that it can // Kill reminder text
// without knowing the dimensions that the card will be rendered at. if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_REMINDER_TEXT, "false").equals("false")) {
// Then, the CardRenderer can be called on multiple times to render the rule = CardRendererUtils.killReminderText(rule).trim();
// card at various sizes (for instance, during animation) }
public CardRenderer(CardView card, boolean isTransformed) { if (!rule.isEmpty()) {
// Set base parameters TextboxRule tbRule = TextboxRuleParser.parse(card, rule);
this.cardView = card; if (tbRule.type == TextboxRuleType.SIMPLE_KEYWORD) {
this.isTransformed = isTransformed; textboxKeywords.add(tbRule);
} else if (tbRule.text.isEmpty()) {
// Translate the textbox text // Nothing to do, rule is empty
for (String rule: card.getRules()) { } else {
// Kill reminder text textboxRules.add(tbRule);
if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_REMINDER_TEXT, "false").equals("false")) { }
rule = CardRendererUtils.killReminderText(rule).trim(); }
} }
if (!rule.isEmpty()) { }
TextboxRule tbRule = TextboxRuleParser.parse(card, rule);
if (tbRule.type == TextboxRuleType.SIMPLE_KEYWORD) { // Layout operation
textboxKeywords.add(tbRule); // Calculate common layout metrics that will be used by several
} else if (tbRule.text.isEmpty()) { // of the operations in the template method.
// Nothing to do, rule is empty protected void layout(int cardWidth, int cardHeight) {
} else { // Store the dimensions for the template methods to use
textboxRules.add(tbRule); this.cardWidth = cardWidth;
} this.cardHeight = cardHeight;
}
} // Corner radius and border width
} cornerRadius = (int) Math.max(
CORNER_RADIUS_MIN,
// Layout operation CORNER_RADIUS_FRAC * cardWidth);
// Calculate common layout metrics that will be used by several
// of the operations in the template method. borderWidth = (int) Math.max(
protected void layout(int cardWidth, int cardHeight) { BORDER_WIDTH_MIN,
// Store the dimensions for the template methods to use BORDER_WIDTH_FRAC * cardWidth);
this.cardWidth = cardWidth; }
this.cardHeight = cardHeight;
// The Draw Method
// Corner radius and border width // The draw method takes the information caculated by the constructor
cornerRadius = (int)Math.max( // and uses it to draw to a concrete size of card and graphics.
CORNER_RADIUS_MIN, public void draw(Graphics2D g, CardPanelAttributes attribs) {
CORNER_RADIUS_FRAC * cardWidth); // Pre template method layout, to calculate shared layout info
layout(attribs.cardWidth, attribs.cardHeight);
borderWidth = (int)Math.max( isSelected = attribs.isSelected;
BORDER_WIDTH_MIN, isChoosable = attribs.isChoosable;
BORDER_WIDTH_FRAC * cardWidth);
} // Call the template methods
drawBorder(g);
// The Draw Method drawBackground(g);
// The draw method takes the information caculated by the constructor drawArt(g);
// and uses it to draw to a concrete size of card and graphics. drawFrame(g);
public void draw(Graphics2D g, CardPanelAttributes attribs) { if (!cardView.isAbility()) {
// Pre template method layout, to calculate shared layout info drawOverlays(g);
layout(attribs.cardWidth, attribs.cardHeight); drawCounters(g);
isSelected = attribs.isSelected; }
isChoosable = attribs.isChoosable; }
// Call the template methods // Template methods to be implemented by sub classes
drawBorder(g); // For instance, for the Modern vs Old border card frames
drawBackground(g); protected abstract void drawBorder(Graphics2D g);
drawArt(g);
drawFrame(g); protected abstract void drawBackground(Graphics2D g);
if (!cardView.isAbility()) {
drawOverlays(g); protected abstract void drawArt(Graphics2D g);
drawCounters(g);
} protected abstract void drawFrame(Graphics2D g);
}
// Template methods that are possible to override, but unlikely to be
// Template methods to be implemented by sub classes // overridden.
// For instance, for the Modern vs Old border card frames // Draw the card back
protected abstract void drawBorder(Graphics2D g); protected void drawCardBack(Graphics2D g) {
protected abstract void drawBackground(Graphics2D g); g.setPaint(BG_TEXTURE_CARDBACK);
protected abstract void drawArt(Graphics2D g); g.fillRect(borderWidth, borderWidth,
protected abstract void drawFrame(Graphics2D g); cardWidth - 2 * borderWidth, cardHeight - 2 * borderWidth);
}
// Template methods that are possible to override, but unlikely to be
// overridden. // Draw summoning sickness overlay, and possibly other overlays
protected void drawOverlays(Graphics2D g) {
// Draw the card back if (CardUtil.isCreature(cardView) && cardView instanceof PermanentView) {
protected void drawCardBack(Graphics2D g) { if (((PermanentView) cardView).hasSummoningSickness()) {
g.setPaint(BG_TEXTURE_CARDBACK); int x1 = (int) (0.2 * cardWidth);
g.fillRect(borderWidth, borderWidth, int x2 = (int) (0.8 * cardWidth);
cardWidth - 2*borderWidth, cardHeight - 2*borderWidth); int y1 = (int) (0.2 * cardHeight);
} int y2 = (int) (0.8 * cardHeight);
int xPoints[] = {
// Draw summoning sickness overlay, and possibly other overlays x1, x2, x1, x2
protected void drawOverlays(Graphics2D g) { };
if (CardUtil.isCreature(cardView) && cardView instanceof PermanentView) { int yPoints[] = {
if (((PermanentView)cardView).hasSummoningSickness()) { y1, y1, y2, y2
int x1 = (int)(0.2*cardWidth); };
int x2 = (int)(0.8*cardWidth); g.setColor(new Color(255, 255, 255, 200));
int y1 = (int)(0.2*cardHeight); g.setStroke(new BasicStroke(7));
int y2 = (int)(0.8*cardHeight); g.drawPolygon(xPoints, yPoints, 4);
int xPoints[] = { g.setColor(new Color(0, 0, 0, 200));
x1, x2, x1, x2 g.setStroke(new BasicStroke(5));
}; g.drawPolygon(xPoints, yPoints, 4);
int yPoints[] = { g.setStroke(new BasicStroke(1));
y1, y1, y2, y2 int[] xPoints2 = {
}; x1, x2, cardWidth / 2
g.setColor(new Color(255, 255, 255, 200)); };
g.setStroke(new BasicStroke(7)); int[] yPoints2 = {
g.drawPolygon(xPoints, yPoints, 4); y1, y1, cardHeight / 2
g.setColor(new Color(0, 0, 0, 200)); };
g.setStroke(new BasicStroke(5)); g.setColor(new Color(0, 0, 0, 100));
g.drawPolygon(xPoints, yPoints, 4); g.fillPolygon(xPoints2, yPoints2, 3);
g.setStroke(new BasicStroke(1)); }
int[] xPoints2 = { }
x1, x2, cardWidth/2 }
};
int[] yPoints2 = { // Draw +1/+1 and other counters
y1, y1, cardHeight/2 protected void drawCounters(Graphics2D g) {
}; int xPos = (int) (0.65 * cardWidth);
g.setColor(new Color(0, 0, 0, 100)); int yPos = (int) (0.15 * cardHeight);
g.fillPolygon(xPoints2, yPoints2, 3); if (cardView.getCounters() != null) {
} for (CounterView v : cardView.getCounters()) {
} // Don't render loyalty, we do that in the bottom corner
} if (!v.getName().equals("loyalty")) {
Polygon p;
// Draw +1/+1 and other counters if (v.getName().equals("+1/+1")) {
protected void drawCounters(Graphics2D g) { p = PLUS_COUNTER_POLY;
int xPos = (int)(0.65*cardWidth); } else if (v.getName().equals("-1/-1")) {
int yPos = (int)(0.15*cardHeight); p = MINUS_COUNTER_POLY;
if (cardView.getCounters() != null) { } else if (v.getName().equals("time")) {
for (CounterView v: cardView.getCounters()) { p = TIME_COUNTER_POLY;
// Don't render loyalty, we do that in the bottom corner } else {
if (!v.getName().equals("loyalty")) { p = OTHER_COUNTER_POLY;
Polygon p; }
if (v.getName().equals("+1/+1")) { double scale = (0.1 * 0.25 * cardWidth);
p = PLUS_COUNTER_POLY; Graphics2D g2 = (Graphics2D) g.create();
} else if (v.getName().equals("-1/-1")) { g2.translate(xPos, yPos);
p = MINUS_COUNTER_POLY; g2.scale(scale, scale);
} else if (v.getName().equals("time")) { g2.setColor(Color.white);
p = TIME_COUNTER_POLY; g2.fillPolygon(p);
} else { g2.setColor(Color.black);
p = OTHER_COUNTER_POLY; g2.drawPolygon(p);
} g2.setFont(new Font("Arial", Font.BOLD, 7));
double scale = (0.1*0.25*cardWidth); String cstr = "" + v.getCount();
Graphics2D g2 = (Graphics2D)g.create(); int strW = g2.getFontMetrics().stringWidth(cstr);
g2.translate(xPos, yPos); g2.drawString(cstr, 5 - strW / 2, 8);
g2.scale(scale, scale); g2.dispose();
g2.setColor(Color.white); yPos += ((int) (0.30 * cardWidth));
g2.fillPolygon(p); }
g2.setColor(Color.black); }
g2.drawPolygon(p); }
g2.setFont(new Font("Arial", Font.BOLD, 7)); }
String cstr = "" + v.getCount();
int strW = g2.getFontMetrics().stringWidth(cstr); // Draw an expansion symbol, right justified, in a given region
g2.drawString(cstr, 5 - strW/2, 8); // Return the width of the drawn symbol
g2.dispose(); protected int drawExpansionSymbol(Graphics2D g, int x, int y, int w, int h) {
yPos += ((int)(0.30*cardWidth)); // Draw the expansion symbol
} Image setSymbol = ManaSymbols.getSetSymbolImage(cardView.getExpansionSetCode(), cardView.getRarity().getCode());
} int setSymbolWidth;
} if (setSymbol == null) {
} // Don't draw anything when we don't have a set symbol
return 0;
// Draw an expansion symbol, right justified, in a given region /*
// Return the width of the drawn symbol // Just draw the as a code
protected int drawExpansionSymbol(Graphics2D g, int x, int y, int w, int h) { String code = cardView.getExpansionSetCode();
// Draw the expansion symbol code = (code != null) ? code.toUpperCase() : "";
Image setSymbol = ManaSymbols.getSetSymbolImage(cardView.getExpansionSetCode(), cardView.getRarity().getCode()); FontMetrics metrics = g.getFontMetrics();
int setSymbolWidth; setSymbolWidth = metrics.stringWidth(code);
if (setSymbol == null) { if (cardView.getRarity() == Rarity.COMMON) {
// Don't draw anything when we don't have a set symbol g.setColor(Color.white);
return 0; } else {
/* g.setColor(Color.black);
// Just draw the as a code }
String code = cardView.getExpansionSetCode(); g.fillRoundRect(
code = (code != null) ? code.toUpperCase() : ""; x + w - setSymbolWidth - 1, y + 2,
FontMetrics metrics = g.getFontMetrics(); setSymbolWidth+2, h - 5,
setSymbolWidth = metrics.stringWidth(code); 5, 5);
if (cardView.getRarity() == Rarity.COMMON) { g.setColor(getRarityColor());
g.setColor(Color.white); g.drawString(code, x + w - setSymbolWidth, y + h - 3);
} else { */
g.setColor(Color.black); } else {
} // Draw the set symbol
g.fillRoundRect( int height = setSymbol.getHeight(null);
x + w - setSymbolWidth - 1, y + 2, int scale = 1;
setSymbolWidth+2, h - 5, if (height != -1) {
5, 5); while (height > h + 2) {
g.setColor(getRarityColor()); scale *= 2;
g.drawString(code, x + w - setSymbolWidth, y + h - 3); height /= 2;
*/ }
} else { }
// Draw the set symbol setSymbolWidth = setSymbol.getWidth(null) / scale;
int height = setSymbol.getHeight(null); g.drawImage(setSymbol,
int scale = 1; x + w - setSymbolWidth, y + (h - height) / 2,
if (height != -1) { setSymbolWidth, height,
while (height > h+2) { null);
scale *= 2; }
height /= 2; return setSymbolWidth;
} }
}
setSymbolWidth = setSymbol.getWidth(null) / scale; private Color getRarityColor() {
g.drawImage(setSymbol, switch (cardView.getRarity()) {
x + w - setSymbolWidth, y + (h - height)/2, case RARE:
setSymbolWidth, height, return new Color(255, 191, 0);
null); case UNCOMMON:
} return new Color(192, 192, 192);
return setSymbolWidth; case MYTHIC:
} return new Color(213, 51, 11);
private Color getRarityColor() { case SPECIAL:
switch (cardView.getRarity()) { return new Color(204, 0, 255);
case RARE: case BONUS:
return new Color(255, 191, 0); return new Color(129, 228, 228);
case UNCOMMON: case COMMON:
return new Color(192, 192, 192); default:
case MYTHIC: return Color.black;
return new Color(213, 51, 11); }
case SPECIAL: }
return new Color(204, 0, 255);
case BONUS: // Get a string representing the type line
return new Color(129, 228, 228); protected String getCardTypeLine() {
case COMMON: if (cardView.isAbility()) {
default: if (AbilityType.TRIGGERED.equals(cardView.getAbilityType())) {
return Color.black; return "Triggered Ability";
} } else if (AbilityType.ACTIVATED.equals(cardView.getAbilityType())) {
} return "Activated Ability";
} else {
// Get a string representing the type line return "??? Ability";
protected String getCardTypeLine() { }
if (cardView.isAbility()) { } else {
if (AbilityType.TRIGGERED.equals(cardView.getAbilityType())) { StringBuilder sbType = new StringBuilder();
return "Triggered Ability"; for (String superType : cardView.getSuperTypes()) {
} else if (AbilityType.ACTIVATED.equals(cardView.getAbilityType())) { sbType.append(superType).append(" ");
return "Activated Ability"; }
} else { for (CardType cardType : cardView.getCardTypes()) {
return "??? Ability"; sbType.append(cardType.toString()).append(" ");
} }
} else { if (cardView.getSubTypes().size() > 0) {
StringBuilder sbType = new StringBuilder(); sbType.append("- ");
for (String superType : cardView.getSuperTypes()) { for (String subType : cardView.getSubTypes()) {
sbType.append(superType).append(" "); sbType.append(subType).append(" ");
} }
for (CardType cardType : cardView.getCardTypes()) { }
sbType.append(cardType.toString()).append(" "); return sbType.toString();
} }
if (cardView.getSubTypes().size() > 0) { }
sbType.append("- ");
for (String subType : cardView.getSubTypes()) { // Set the card art image (CardPanel will give it to us when it
sbType.append(subType).append(" "); // is loaded and ready)
} public void setArtImage(Image image) {
} artImage = CardRendererUtils.toBufferedImage(image);
return sbType.toString(); }
} }
}
// Set the card art image (CardPanel will give it to us when it
// is loaded and ready)
public void setArtImage(Image image) {
artImage = CardRendererUtils.toBufferedImage(image);
}
}

View file

@ -2,20 +2,12 @@ package org.mage.card.arcane;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.MapMaker; import com.google.common.collect.MapMaker;
import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.font.FontRenderContext; import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer; import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute; import java.awt.font.TextAttribute;
import java.awt.font.TextLayout; import java.awt.font.TextLayout;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator;
import java.text.AttributedString; import java.text.AttributedString;
@ -23,10 +15,12 @@ import java.text.BreakIterator;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import javax.swing.*;
import mage.client.util.ImageCaches; import mage.client.util.ImageCaches;
import org.jdesktop.swingx.graphics.GraphicsUtilities; import org.jdesktop.swingx.graphics.GraphicsUtilities;
public class GlowText extends JLabel { public class GlowText extends JLabel {
private static final long serialVersionUID = 1827677946939348001L; private static final long serialVersionUID = 1827677946939348001L;
private int glowSize; private int glowSize;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -36,12 +30,12 @@ public class GlowText extends JLabel {
private int lineCount = 0; private int lineCount = 0;
private static Map<Key, BufferedImage> IMAGE_CACHE; private static Map<Key, BufferedImage> IMAGE_CACHE;
private final static class Key private final static class Key {
{
final int width; final int width;
final int height; final int height;
final String text; final String text;
final Map<TextAttribute,?> fontAttributes; final Map<TextAttribute, ?> fontAttributes;
final Color color; final Color color;
final int glowSize; final int glowSize;
final float glowIntensity; final float glowIntensity;
@ -53,8 +47,9 @@ public class GlowText extends JLabel {
Font getFont() { Font getFont() {
Font res = this.originalFont.get(); Font res = this.originalFont.get();
if(res == null) if (res == null) {
res = Font.getFont(this.fontAttributes); res = Font.getFont(this.fontAttributes);
}
return res; return res;
} }
@ -138,18 +133,18 @@ public class GlowText extends JLabel {
})); }));
} }
public void setGlow (Color glowColor, int size, float intensity) { public void setGlow(Color glowColor, int size, float intensity) {
this.glowColor = glowColor; this.glowColor = glowColor;
this.glowSize = size; this.glowSize = size;
this.glowIntensity = intensity; this.glowIntensity = intensity;
} }
public void setWrap (boolean wrap) { public void setWrap(boolean wrap) {
this.wrap = wrap; this.wrap = wrap;
} }
@Override @Override
public Dimension getPreferredSize () { public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize(); Dimension size = super.getPreferredSize();
size.width += glowSize; size.width += glowSize;
size.height += glowSize / 2; size.height += glowSize / 2;
@ -157,7 +152,7 @@ public class GlowText extends JLabel {
} }
@Override @Override
public void paint (Graphics g) { public void paint(Graphics g) {
if (getText().length() == 0) { if (getText().length() == 0) {
return; return;
} }
@ -165,7 +160,7 @@ public class GlowText extends JLabel {
g.drawImage(IMAGE_CACHE.get(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap)), 0, 0, null); g.drawImage(IMAGE_CACHE.get(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap)), 0, 0, null);
} }
private static BufferedImage createImage (Key key) { private static BufferedImage createImage(Key key) {
Dimension size = new Dimension(key.width, key.height); Dimension size = new Dimension(key.width, key.height);
BufferedImage image = GraphicsUtilities.createCompatibleTranslucentImage(size.width, size.height); BufferedImage image = GraphicsUtilities.createCompatibleTranslucentImage(size.width, size.height);
Graphics2D g2d = image.createGraphics(); Graphics2D g2d = image.createGraphics();

View file

@ -1,28 +1,28 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package org.mage.card.arcane; package org.mage.card.arcane;
import java.text.AttributedString; import java.util.List;
import java.util.List;
/**
/** * @author StravantUser
* @author StravantUser *
* * Level rule associated with leveler cards
* Level rule associated with leveler cards */
*/ public class TextboxLevelRule extends TextboxRule {
public class TextboxLevelRule extends TextboxRule {
// The levels that this rule applies to // The levels that this rule applies to
public int levelFrom; public int levelFrom;
public int levelTo; public int levelTo;
public static int AND_HIGHER = 100; public static int AND_HIGHER = 100;
public TextboxLevelRule(String text, List<AttributeRegion> regions, int levelFrom, int levelTo) { public TextboxLevelRule(String text, List<AttributeRegion> regions, int levelFrom, int levelTo) {
super(text, regions, TextboxRuleType.LEVEL); super(text, regions, TextboxRuleType.LEVEL);
this.levelFrom = levelFrom; this.levelFrom = levelFrom;
this.levelTo = levelTo; this.levelTo = levelTo;
} }
} }

View file

@ -1,33 +1,33 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package org.mage.card.arcane; package org.mage.card.arcane;
import java.text.AttributedString; import java.util.List;
import java.util.List;
/**
/** * @author StravantUser
* @author StravantUser */
*/ public class TextboxLoyaltyRule extends TextboxRule {
public class TextboxLoyaltyRule extends TextboxRule {
public int loyaltyChange; public int loyaltyChange;
public static int MINUS_X = 100; public static int MINUS_X = 100;
public String getChangeString() { public String getChangeString() {
if (loyaltyChange == MINUS_X) { if (loyaltyChange == MINUS_X) {
return "-X"; return "-X";
} else if (loyaltyChange > 0) { } else if (loyaltyChange > 0) {
return "+" + loyaltyChange; return "+" + loyaltyChange;
} else { } else {
return "" + loyaltyChange; return "" + loyaltyChange;
} }
} }
public TextboxLoyaltyRule(String text, List<AttributeRegion> regions, int loyaltyChange) { public TextboxLoyaltyRule(String text, List<AttributeRegion> regions, int loyaltyChange) {
super(text, regions, TextboxRuleType.LOYALTY); super(text, regions, TextboxRuleType.LOYALTY);
this.loyaltyChange = loyaltyChange; this.loyaltyChange = loyaltyChange;
} }
} }

View file

@ -1,94 +1,98 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package org.mage.card.arcane; package org.mage.card.arcane;
import java.awt.Font; import java.awt.Font;
import java.awt.Image; import java.awt.Image;
import java.awt.font.GraphicAttribute; import java.awt.font.GraphicAttribute;
import java.awt.font.ImageGraphicAttribute; import java.awt.font.ImageGraphicAttribute;
import java.awt.font.TextAttribute; import java.awt.font.TextAttribute;
import java.text.AttributedString; import java.text.AttributedString;
import java.util.List; import java.util.List;
/** /**
* @author stravant@gmail.com * @author stravant@gmail.com
* *
* Class describing parsed & translated rules in the text box of a card, * Class describing parsed & translated rules in the text box of a card, ready
* ready to be rendered. * to be rendered.
*/ */
public class TextboxRule { public class TextboxRule {
// An attributed region in the text, which can be applied to an
// attributed string. // An attributed region in the text, which can be applied to an
public interface AttributeRegion { // attributed string.
public void applyToAttributedString(AttributedString str, Font normal, Font italic); public interface AttributeRegion {
}
public void applyToAttributedString(AttributedString str, Font normal, Font italic);
// A region of italics, or bold text in a }
public static class ItalicRegion implements AttributeRegion {
ItalicRegion(int start, int end) { // A region of italics, or bold text in a
this.start = start; public static class ItalicRegion implements AttributeRegion {
this.end = end;
} ItalicRegion(int start, int end) {
private final int start; this.start = start;
private final int end; this.end = end;
}
@Override private final int start;
public void applyToAttributedString(AttributedString str, Font normal, Font italic) { private final int end;
if (end > start+1) {
str.addAttribute(TextAttribute.FONT, italic, start, end); @Override
} public void applyToAttributedString(AttributedString str, Font normal, Font italic) {
} if (end > start + 1) {
} str.addAttribute(TextAttribute.FONT, italic, start, end);
}
// A special symbol embedded at some point in a string }
public static class EmbeddedSymbol implements AttributeRegion { }
EmbeddedSymbol(String symbol, int location) {
this.symbol = symbol; // A special symbol embedded at some point in a string
this.location = location; public static class EmbeddedSymbol implements AttributeRegion {
}
private final String symbol; EmbeddedSymbol(String symbol, int location) {
private final int location; this.symbol = symbol;
this.location = location;
@Override }
public void applyToAttributedString(AttributedString str, Font normal, Font italic) { private final String symbol;
Image symbolImage = ManaSymbols.getSizedManaSymbol(symbol, normal.getSize()); private final int location;
if (symbolImage != null) {
ImageGraphicAttribute imgAttr = @Override
new ImageGraphicAttribute(symbolImage, GraphicAttribute.BOTTOM_ALIGNMENT); public void applyToAttributedString(AttributedString str, Font normal, Font italic) {
str.addAttribute(TextAttribute.CHAR_REPLACEMENT, imgAttr, location, location+1); Image symbolImage = ManaSymbols.getSizedManaSymbol(symbol.replace("/", ""), normal.getSize());
} if (symbolImage != null) {
} ImageGraphicAttribute imgAttr
} = new ImageGraphicAttribute(symbolImage, GraphicAttribute.BOTTOM_ALIGNMENT);
str.addAttribute(TextAttribute.CHAR_REPLACEMENT, imgAttr, location, location + 1);
public String text; }
public TextboxRuleType type; }
}
private List<AttributeRegion> regions;
public String text;
protected TextboxRule(String text, List<AttributeRegion> regions, TextboxRuleType type) { public TextboxRuleType type;
this.text = text;
this.type = type; private List<AttributeRegion> regions;
this.regions = regions;
} protected TextboxRule(String text, List<AttributeRegion> regions, TextboxRuleType type) {
this.text = text;
public TextboxRule(String text, List<AttributeRegion> regions) { this.type = type;
this(text, regions, TextboxRuleType.NORMAL); this.regions = regions;
} }
public AttributedString generateAttributedString(Font normal, Font italic) { public TextboxRule(String text, List<AttributeRegion> regions) {
// Build the final attributed text using the regions this(text, regions, TextboxRuleType.NORMAL);
// Do it in reverse order for proper handling of regions where }
// there are multiple attributes stacked (EG: bold + italic)
AttributedString attributedRule = new AttributedString(text); public AttributedString generateAttributedString(Font normal, Font italic) {
if (text.length() != 0) { // Build the final attributed text using the regions
attributedRule.addAttribute(TextAttribute.FONT, normal); // Do it in reverse order for proper handling of regions where
for (int i = regions.size()-1; i >= 0; --i) { // there are multiple attributes stacked (EG: bold + italic)
regions.get(i).applyToAttributedString(attributedRule, normal, italic); AttributedString attributedRule = new AttributedString(text);
} if (text.length() != 0) {
} attributedRule.addAttribute(TextAttribute.FONT, normal);
return attributedRule; for (int i = regions.size() - 1; i >= 0; --i) {
} regions.get(i).applyToAttributedString(attributedRule, normal, italic);
} }
}
return attributedRule;
}
}

View file

@ -1,251 +1,251 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package org.mage.card.arcane; package org.mage.card.arcane;
import java.awt.Font; import java.awt.Image;
import java.awt.Image; import java.util.ArrayDeque;
import java.awt.font.GraphicAttribute; import java.util.ArrayList;
import java.awt.font.ImageGraphicAttribute; import java.util.Deque;
import java.awt.font.TextAttribute; import java.util.regex.Matcher;
import java.text.AttributedString; import java.util.regex.Pattern;
import java.util.ArrayDeque; import mage.view.CardView;
import java.util.ArrayList; import org.apache.log4j.Logger;
import java.util.Deque;
import java.util.List; /**
import java.util.regex.Matcher; *
import java.util.regex.Pattern; * @author StravantUser
import mage.client.dialog.PreferencesDialog; */
import mage.view.CardView; public class TextboxRuleParser {
import org.apache.log4j.Logger;
private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
/**
* private static final Pattern LevelAbilityPattern = Pattern.compile("Level (\\d+)-?(\\d*)(\\+?)");
* @author StravantUser private static final Pattern LoyaltyAbilityPattern = Pattern.compile("^(\\+|\\-)(\\d+|X): ");
*/ private static final Pattern SimpleKeywordPattern = Pattern.compile("^(\\w+( \\w+)?)\\s*(\\([^\\)]*\\))?\\s*$");
public class TextboxRuleParser {
private static final Logger LOGGER = Logger.getLogger(CardPanel.class); // Parse a given rule (given as a string) into a TextboxRule, replacing
// symbol annotations, italics, etc, parsing out information such as
private static final Pattern LevelAbilityPattern = Pattern.compile("Level (\\d+)-?(\\d*)(\\+?)"); // if the ability is a loyalty ability, and returning an TextboxRule
private static final Pattern LoyaltyAbilityPattern = Pattern.compile("^(\\+|\\-)(\\d+|X): "); // representing that information, which can be used to render the rule in
private static final Pattern SimpleKeywordPattern = Pattern.compile("^(\\w+( \\w+)?)\\s*(\\([^\\)]*\\))?\\s*$"); // the textbox of a card.
public static TextboxRule parse(CardView source, String rule) {
// Parse a given rule (given as a string) into a TextboxRule, replacing // List of regions to apply
// symbol annotations, italics, etc, parsing out information such as ArrayList<TextboxRule.AttributeRegion> regions = new ArrayList<>();
// if the ability is a loyalty ability, and returning an TextboxRule
// representing that information, which can be used to render the rule in // Leveler / loyalty
// the textbox of a card. boolean isLeveler = false;
public static TextboxRule parse(CardView source, String rule) { int levelFrom = 0;
// List of regions to apply int levelTo = 0;
ArrayList<TextboxRule.AttributeRegion> regions = new ArrayList<>();
boolean isLoyalty = false;
// Leveler / loyalty int loyaltyChange = 0;
boolean isLeveler = false;
int levelFrom = 0; // Parse the attributedString contents
int levelTo = 0; int index = 0;
int outputIndex = 0;
boolean isLoyalty = false;
int loyaltyChange = 0; // Is it a simple keyword ability?
{
// Parse the attributedString contents Matcher simpleKeywordMatch = SimpleKeywordPattern.matcher(rule);
int index = 0; if (simpleKeywordMatch.find()) {
int outputIndex = 0; return new TextboxKeywordRule(simpleKeywordMatch.group(1), regions);
}
// Is it a simple keyword ability? }
{
Matcher simpleKeywordMatch = SimpleKeywordPattern.matcher(rule); // Check if it's a loyalty ability. Must be right at the start of the rule
if (simpleKeywordMatch.find()) { {
return new TextboxKeywordRule(simpleKeywordMatch.group(1), regions); Matcher loyaltyMatch = LoyaltyAbilityPattern.matcher(rule);
} if (loyaltyMatch.find()) {
} // Get the loyalty change
if (loyaltyMatch.group(2).equals("X")) {
// Check if it's a loyalty ability. Must be right at the start of the rule loyaltyChange = TextboxLoyaltyRule.MINUS_X;
{ } else {
Matcher loyaltyMatch = LoyaltyAbilityPattern.matcher(rule); loyaltyChange = Integer.parseInt(loyaltyMatch.group(2));
if (loyaltyMatch.find()) { if (loyaltyMatch.group(1).equals("-")) {
// Get the loyalty change loyaltyChange = -loyaltyChange;
if (loyaltyMatch.group(2).equals("X")) { }
loyaltyChange = TextboxLoyaltyRule.MINUS_X; }
} else { isLoyalty = true;
loyaltyChange = Integer.parseInt(loyaltyMatch.group(2));
if (loyaltyMatch.group(1).equals("-")) { // Go past the match
loyaltyChange = -loyaltyChange; index = loyaltyMatch.group().length();
} }
} }
isLoyalty = true;
Deque<Integer> openingStack = new ArrayDeque<>();
// Go past the match StringBuilder build = new StringBuilder();
index = loyaltyMatch.group().length(); while (index < rule.length()) {
} int initialIndex = index;
} char ch = rule.charAt(index);
switch (ch) {
Deque<Integer> openingStack = new ArrayDeque<>(); case '{': {
StringBuilder build = new StringBuilder(); // Handling for `{this}`
while (index < rule.length()) { int closeIndex = rule.indexOf('}', index);
int initialIndex = index; if (closeIndex == -1) {
char ch = rule.charAt(index); // Malformed input, nothing to do
if (ch == '{') { ++index;
// Handling for `{this}` ++outputIndex;
int closeIndex = rule.indexOf('}', index); build.append(ch);
if (closeIndex == -1) { } else {
// Malformed input, nothing to do String contents = rule.substring(index + 1, closeIndex);
++index; if (contents.equals("this") || contents.equals("source")) {
++outputIndex; // Replace {this} with the card's name
build.append(ch); String cardName = source.getName();
} else { build.append(cardName);
String contents = rule.substring(index+1, closeIndex); index += contents.length() + 2;
if (contents.equals("this") || contents.equals("source")) { outputIndex += cardName.length();
// Replace {this} with the card's name } else {
String cardName = source.getName(); Image symbol = ManaSymbols.getSizedManaSymbol(contents.replace("/", ""), 10);
build.append(cardName); if (symbol != null) {
index += contents.length() + 2; // Mana or other inline symbol
outputIndex += cardName.length(); build.append('#');
} else { regions.add(new TextboxRule.EmbeddedSymbol(contents, outputIndex));
Image symbol = ManaSymbols.getSizedManaSymbol(contents, 10); ++outputIndex;
if (symbol != null) { index = closeIndex + 1;
// Mana or other inline symbol } else {
build.append('#'); // Bad entry
regions.add(new TextboxRule.EmbeddedSymbol(contents, outputIndex)); build.append('{');
++outputIndex; build.append(contents);
index = closeIndex+1; build.append('}');
} else { index = closeIndex + 1;
// Bad entry outputIndex += (contents.length() + 2);
build.append('{'); }
build.append(contents); }
build.append('}'); }
index = closeIndex+1; break;
outputIndex += (contents.length() + 2); }
} case '&':
} // Handling for `&mdash;`
} if (rule.startsWith("&mdash;", index)) {
build.append('—');
} else if (ch == '&') { index += 7;
// Handling for `&mdash;` ++outputIndex;
if (rule.startsWith("&mdash;", index)) { } else if (rule.startsWith("&bull", index)) {
build.append(''); build.append('');
index += 7; index += 5;
++outputIndex; ++outputIndex;
} else if (rule.startsWith("&bull", index)) { } else {
build.append('•'); LOGGER.error("Bad &...; sequence `" + rule.substring(index + 1, index + 10) + "` in rule.");
index += 5; build.append('&');
++outputIndex; ++index;
} else { ++outputIndex;
LOGGER.error("Bad &...; sequence `" + rule.substring(index+1, index+10) + "` in rule."); }
build.append('&'); break;
++index; case '<': {
++outputIndex; // Handling for `<i>` and `<br/>`
} int closeIndex = rule.indexOf('>', index);
if (closeIndex != -1) {
// Is a tag
} else if (ch == '<') { String tag = rule.substring(index + 1, closeIndex);
// Handling for `<i>` and `<br/>` if (tag.charAt(tag.length() - 1) == '/') {
int closeIndex = rule.indexOf('>', index); // Pure closing tag (like <br/>)
if (closeIndex != -1) { if (tag.equals("br/")) {
// Is a tag build.append('\n');
String tag = rule.substring(index+1, closeIndex); ++outputIndex;
if (tag.charAt(tag.length()-1) == '/') { } else {
// Pure closing tag (like <br/>) // Unknown
if (tag.equals("br/")) { build.append('<').append(tag).append('>');
build.append('\n'); outputIndex += (tag.length() + 2);
++outputIndex; }
} else { } else if (tag.charAt(0) == '/') {
// Unknown // Opening index for the tag
build.append('<').append(tag).append('>'); int openingIndex;
outputIndex += (tag.length() + 2); if (openingStack.isEmpty()) {
} // Malformed input, just make an empty interval
} else if (tag.charAt(0) == '/') { openingIndex = outputIndex;
// Opening index for the tag } else {
int openingIndex; openingIndex = openingStack.pop();
if (openingStack.isEmpty()) { }
// Malformed input, just make an empty interval
openingIndex = outputIndex; // What tag is it?
} else { switch (tag) {
openingIndex = openingStack.pop(); case "/i":
} // Italics
regions.add(new TextboxRule.ItalicRegion(openingIndex, outputIndex));
// What tag is it? break;
if (tag.equals("/i")) { case "/b":
// Italics // Bold, see if it's a level ability
regions.add(new TextboxRule.ItalicRegion(openingIndex, outputIndex)); String content = build.substring(openingIndex);
} else if (tag.equals("/b")) { Matcher levelMatch = LevelAbilityPattern.matcher(content);
// Bold, see if it's a level ability if (levelMatch.find()) {
String content = build.substring(openingIndex); try {
levelFrom = Integer.parseInt(levelMatch.group(1));
Matcher levelMatch = LevelAbilityPattern.matcher(content); if (!levelMatch.group(2).equals("")) {
if (levelMatch.find()) { levelTo = Integer.parseInt(levelMatch.group(2));
try { }
levelFrom = Integer.parseInt(levelMatch.group(1)); if (!levelMatch.group(3).equals("")) {
if (!levelMatch.group(2).equals("")) { levelTo = TextboxLevelRule.AND_HIGHER;
levelTo = Integer.parseInt(levelMatch.group(2)); }
} isLeveler = true;
if (!levelMatch.group(3).equals("")) { } catch (Exception e) {
levelTo = TextboxLevelRule.AND_HIGHER; LOGGER.error("Bad leveler levels in rule `" + rule + "`.");
} }
isLeveler = true; }
} catch (Exception e) { break;
LOGGER.error("Bad leveler levels in rule `" + rule + "`."); default:
} // Unknown
} build.append('<').append(tag).append('>');
} else { outputIndex += (tag.length() + 2);
// Unknown break;
build.append('<').append(tag).append('>'); }
outputIndex += (tag.length() + 2); } else // Is it a <br> tag special case? [Why can't it have a closing `/`... =( ]
} {
} else { if (tag.equals("br")) {
// Is it a <br> tag special case? [Why can't it have a closing `/`... =( ] build.append('\n');
if (tag.equals("br")) { ++outputIndex;
build.append('\n'); } else {
++outputIndex; // Opening tag
} else { openingStack.push(outputIndex);
// Opening tag }
openingStack.push(outputIndex); }
} // Skip characters
} index = closeIndex + 1;
// Skip characters } else {
index = closeIndex+1; // Malformed tag
} else { build.append('<');
// Malformed tag ++outputIndex;
build.append('<'); ++index;
++outputIndex; }
++index; break;
} }
default:
} else { // Normal character
// Normal character ++index;
++index; ++outputIndex;
++outputIndex; build.append(ch);
build.append(ch); break;
} }
if (outputIndex != build.length()) { if (outputIndex != build.length()) {
// Somehow our parsing code output symbols but didn't update the output index correspondingly // Somehow our parsing code output symbols but didn't update the output index correspondingly
LOGGER.error("The human is dead; mismatch! Failed on rule: `" + rule + "` due to not updating outputIndex properly."); LOGGER.error("The human is dead; mismatch! Failed on rule: `" + rule + "` due to not updating outputIndex properly.");
// Bail out // Bail out
build = new StringBuilder(rule); build = new StringBuilder(rule);
regions.clear(); regions.clear();
break; break;
} }
if (index == initialIndex) { if (index == initialIndex) {
// Somehow our parsing failed to consume the // Somehow our parsing failed to consume the
LOGGER.error("Failed on rule `" + rule + "` due to not consuming a character."); LOGGER.error("Failed on rule `" + rule + "` due to not consuming a character.");
// Bail out // Bail out
build = new StringBuilder(rule); build = new StringBuilder(rule);
regions.clear(); regions.clear();
break; break;
} }
} }
// Build and return the rule // Build and return the rule
rule = build.toString(); rule = build.toString();
if (isLoyalty) { if (isLoyalty) {
return new TextboxLoyaltyRule(rule, regions, loyaltyChange); return new TextboxLoyaltyRule(rule, regions, loyaltyChange);
} else if (isLeveler) { } else if (isLeveler) {
return new TextboxLevelRule(rule, regions, levelFrom, levelTo); return new TextboxLevelRule(rule, regions, levelFrom, levelTo);
} else { } else {
return new TextboxRule(rule, regions); return new TextboxRule(rule, regions);
} }
} }
} }