mirror of
https://github.com/correl/mage.git
synced 2024-12-25 03:00:15 +00:00
* Fixed hybrid mana symbol display for characteristic-based card rendering. Removed not used import statements.
This commit is contained in:
parent
cb91c5b9aa
commit
720a4457fd
9 changed files with 1490 additions and 1523 deletions
|
@ -2,7 +2,6 @@ package org.mage.card.arcane;
|
|||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
|
@ -20,8 +19,6 @@ import java.awt.image.BufferedImage;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JPanel;
|
||||
|
@ -30,7 +27,6 @@ import mage.cards.MagePermanent;
|
|||
import mage.cards.TextPopup;
|
||||
import mage.cards.action.ActionCallback;
|
||||
import mage.cards.action.TransferData;
|
||||
import mage.client.components.layout.RelativeLayout;
|
||||
import mage.client.plugins.adapters.MageActionCallback;
|
||||
import mage.client.plugins.impl.Plugins;
|
||||
import mage.client.util.audio.AudioManager;
|
||||
|
@ -75,7 +71,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
public double flippedAngle = 0;
|
||||
|
||||
private final List<MagePermanent> links = new ArrayList<>();
|
||||
|
||||
|
||||
public JPanel buttonPanel;
|
||||
private JButton dayNightButton;
|
||||
private JButton showCopySourceButton;
|
||||
|
@ -118,7 +114,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
this.gameCard = newGameCard;
|
||||
this.callback = callback;
|
||||
this.gameId = gameId;
|
||||
|
||||
|
||||
// Gather info about the card
|
||||
this.isPermanent = this.gameCard instanceof PermanentView;
|
||||
if (isPermanent) {
|
||||
|
@ -127,14 +123,14 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
|
||||
// Set to requested size
|
||||
this.setCardBounds(0, 0, dimension.width, dimension.height);
|
||||
|
||||
|
||||
// Create button panel for Transform and Show Source (copied cards)
|
||||
buttonPanel = new JPanel();
|
||||
buttonPanel.setLayout(null);
|
||||
buttonPanel.setOpaque(false);
|
||||
buttonPanel.setVisible(true);
|
||||
add(buttonPanel);
|
||||
|
||||
|
||||
// Both card rendering implementations have a transform button
|
||||
if (this.gameCard.canTransform()) {
|
||||
// Create the day night button
|
||||
|
@ -155,7 +151,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
Animation.transformCard(CardPanel.this, CardPanel.this, true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Add it
|
||||
buttonPanel.add(dayNightButton);
|
||||
}
|
||||
|
@ -199,14 +195,14 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0;
|
||||
flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doLayout() {
|
||||
// Position transform and show source buttons
|
||||
buttonPanel.setLocation(cardXOffset, cardYOffset);
|
||||
buttonPanel.setSize(cardWidth, cardHeight);
|
||||
int x = cardWidth/20;
|
||||
int y = cardHeight/10;
|
||||
int x = cardWidth / 20;
|
||||
int y = cardHeight / 10;
|
||||
if (dayNightButton != null) {
|
||||
dayNightButton.setLocation(x, y);
|
||||
y += 25;
|
||||
|
@ -216,9 +212,9 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
showCopySourceButton.setLocation(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final void initialDraw() {
|
||||
// Kick off
|
||||
// Kick off
|
||||
if (gameCard.isTransformed()) {
|
||||
// this calls updateImage
|
||||
toggleTransformed();
|
||||
|
@ -246,8 +242,8 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
this.callback = 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)
|
||||
// Used when cards are moving between zones
|
||||
public abstract void transferResources(CardPanel panel);
|
||||
|
@ -273,7 +269,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
public void setAnimationPanel(boolean isAnimationPanel) {
|
||||
this.isAnimationPanel = isAnimationPanel;
|
||||
}
|
||||
|
||||
|
||||
public boolean isAnimationPanel() {
|
||||
return this.isAnimationPanel;
|
||||
}
|
||||
|
@ -282,7 +278,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
public void setSelected(boolean isSelected) {
|
||||
this.isSelected = isSelected;
|
||||
}
|
||||
|
||||
|
||||
public boolean isSelected() {
|
||||
return this.isSelected;
|
||||
}
|
||||
|
@ -291,16 +287,16 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
public List<MagePermanent> getLinks() {
|
||||
return links;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setChoosable(boolean isChoosable) {
|
||||
this.isChoosable = isChoosable;
|
||||
}
|
||||
|
||||
|
||||
public boolean isChoosable() {
|
||||
return this.isChoosable;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasSickness() {
|
||||
return this.hasSickness;
|
||||
}
|
||||
|
@ -308,7 +304,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
public boolean isPermanent() {
|
||||
return this.isPermanent;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setCardAreaRef(JPanel cardArea) {
|
||||
this.cardArea = cardArea;
|
||||
|
@ -317,13 +313,13 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
public void setShowCastingCost(boolean showCastingCost) {
|
||||
this.showCastingCost = showCastingCost;
|
||||
}
|
||||
|
||||
|
||||
public boolean getShowCastingCost() {
|
||||
return this.showCastingCost;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overridden by different card rendering styles
|
||||
* Overridden by different card rendering styles
|
||||
*/
|
||||
protected abstract void paintCard(Graphics2D g);
|
||||
|
||||
|
@ -356,7 +352,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
|
||||
// Deferr to subclasses
|
||||
paintCard(g2d);
|
||||
|
||||
|
||||
// Done, dispose of the context
|
||||
g2d.dispose();
|
||||
}
|
||||
|
@ -372,7 +368,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;
|
||||
|
@ -436,7 +432,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
public final int getCardHeight() {
|
||||
return cardHeight;
|
||||
}
|
||||
|
||||
|
||||
public final int getSymbolWidth() {
|
||||
return symbolWidth;
|
||||
}
|
||||
|
@ -510,15 +506,15 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
}
|
||||
|
||||
/**
|
||||
* Inheriting classes should implement update(CardView card) by
|
||||
* using this. However, they should ALSO call repaint() after the superclass
|
||||
* call to this function, that can't be done here as the overriders may need
|
||||
* to do things both before and after this call before repainting.
|
||||
* Inheriting classes should implement update(CardView card) by using this.
|
||||
* However, they should ALSO call repaint() after the superclass call to
|
||||
* this function, that can't be done here as the overriders may need to do
|
||||
* things both before and after this call before repainting.
|
||||
*/
|
||||
@Override
|
||||
public void update(CardView card) {
|
||||
this.updateCard = card;
|
||||
|
||||
|
||||
// Animation update
|
||||
if (isPermanent && (card instanceof PermanentView)) {
|
||||
boolean needsTapping = isTapped() != ((PermanentView) card).isTapped();
|
||||
|
@ -538,11 +534,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());
|
||||
boolean mustUpdateArt
|
||||
= (!gameCard.getName().equals(card.getName()))
|
||||
|| (gameCard.isFaceDown() != card.isFaceDown());
|
||||
|
||||
// Set the new card
|
||||
this.gameCard = card;
|
||||
|
@ -550,12 +546,12 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
// Update tooltip text
|
||||
String cardType = getType(card);
|
||||
tooltipText.setText(getText(cardType, card));
|
||||
|
||||
|
||||
// Update the image
|
||||
if (mustUpdateArt) {
|
||||
updateArtImage();
|
||||
}
|
||||
|
||||
|
||||
// Update transform circle
|
||||
if (card.canTransform()) {
|
||||
BufferedImage transformIcon;
|
||||
|
@ -742,13 +738,13 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
public void update(PermanentView card) {
|
||||
this.hasSickness = card.hasSummoningSickness();
|
||||
this.showCopySourceButton.setVisible(card.isCopy());
|
||||
update((CardView)card);
|
||||
update((CardView) card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermanentView getOriginalPermanent() {
|
||||
if (isPermanent) {
|
||||
return (PermanentView)this.gameCard;
|
||||
return (PermanentView) this.gameCard;
|
||||
}
|
||||
throw new IllegalStateException("Is not permanent.");
|
||||
}
|
||||
|
@ -835,7 +831,7 @@ public abstract class CardPanel extends MagePermanent implements MouseListener,
|
|||
public void setTextOffset(int yOffset) {
|
||||
yTextOffset = yOffset;
|
||||
}
|
||||
|
||||
|
||||
public int getTextOffset() {
|
||||
return yTextOffset;
|
||||
}
|
||||
|
|
|
@ -1,398 +1,397 @@
|
|||
package org.mage.card.arcane;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.MapMaker;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
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 mage.view.StackAbilityView;
|
||||
import net.java.truevfs.access.TFile;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.jdesktop.swingx.graphics.GraphicsUtilities;
|
||||
import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL;
|
||||
import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
|
||||
import org.mage.plugins.card.images.ImageCache;
|
||||
|
||||
public class CardPanelRenderImpl extends CardPanel {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CardPanelRenderImpl.class);
|
||||
|
||||
private static boolean cardViewEquals(CardView a, CardView b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
if (a.getClass() != b.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getName().equals(b.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getPower().equals(b.getPower())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getToughness().equals(b.getToughness())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getLoyalty().equals(b.getLoyalty())) {
|
||||
return false;
|
||||
}
|
||||
if (0 != a.getColor().compareTo(b.getColor())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getCardTypes().equals(b.getCardTypes())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getSubTypes().equals(b.getSubTypes())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getSuperTypes().equals(b.getSuperTypes())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getManaCost().equals(b.getManaCost())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getRules().equals(b.getRules())) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
class ImageKey {
|
||||
final BufferedImage artImage;
|
||||
final int width;
|
||||
final int height;
|
||||
final boolean isChoosable;
|
||||
final boolean isSelected;
|
||||
final CardView view;
|
||||
final int hashCode;
|
||||
|
||||
public ImageKey(CardView view, BufferedImage artImage, int width, int height, boolean isChoosable, boolean isSelected) {
|
||||
this.view = view;
|
||||
this.artImage = artImage;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.isChoosable = isChoosable;
|
||||
this.isSelected = isSelected;
|
||||
this.hashCode = hashCodeImpl();
|
||||
}
|
||||
|
||||
private int hashCodeImpl() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append((char)(artImage != null ? 1 : 0));
|
||||
sb.append((char)width);
|
||||
sb.append((char)height);
|
||||
sb.append((char)(isSelected ? 1 : 0));
|
||||
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());
|
||||
sb.append(this.view.getLoyalty());
|
||||
sb.append(this.view.getColor().toString());
|
||||
sb.append(this.view.getExpansionSetCode());
|
||||
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.getSubTypes()) {
|
||||
sb.append(s);
|
||||
}
|
||||
for (String s: this.view.getManaCost()) {
|
||||
sb.append(s);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
// Initial checks
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (object == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(object instanceof ImageKey)) {
|
||||
return false;
|
||||
}
|
||||
final ImageKey other = (ImageKey)object;
|
||||
|
||||
// Compare
|
||||
if ((artImage != null) != (other.artImage != null)) {
|
||||
return false;
|
||||
}
|
||||
if (width != other.width) {
|
||||
return false;
|
||||
}
|
||||
if (height != other.height) {
|
||||
return false;
|
||||
}
|
||||
if (isChoosable != other.isChoosable) {
|
||||
return false;
|
||||
}
|
||||
if (isSelected != other.isSelected) {
|
||||
return false;
|
||||
}
|
||||
return cardViewEquals(view, other.view);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 rendered card image, with or without the art image loaded yet
|
||||
// = null while invalid
|
||||
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
|
||||
super(newGameCard, gameId, loadImage, callback, foil, dimension);
|
||||
|
||||
// Renderer
|
||||
cardRenderer = new ModernCardRenderer(gameCard, isTransformed());
|
||||
|
||||
// Draw the parts
|
||||
initialDraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferResources(CardPanel panel) {
|
||||
if (panel instanceof CardPanelRenderImpl) {
|
||||
CardPanelRenderImpl impl = (CardPanelRenderImpl)panel;
|
||||
|
||||
// Use the art image and current rendered image from the card
|
||||
artImage = impl.artImage;
|
||||
cardRenderer.setArtImage(artImage);
|
||||
cardImage = impl.cardImage;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintCard(Graphics2D g) {
|
||||
// Render the card if we don't have an image ready to use
|
||||
if (cardImage == null) {
|
||||
// Try to get card image from cache based on our card characteristics
|
||||
ImageKey key =
|
||||
new ImageKey(gameCard, artImage,
|
||||
getCardWidth(), getCardHeight(),
|
||||
isChoosable(), isSelected());
|
||||
cardImage = IMAGE_CACHE.get(key);
|
||||
|
||||
// No cached copy exists? Render one and cache it
|
||||
if (cardImage == null) {
|
||||
cardImage = renderCard();
|
||||
IMAGE_CACHE.put(key, cardImage);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
* @return
|
||||
*/
|
||||
private BufferedImage renderCard() {
|
||||
int cardWidth = getCardWidth();
|
||||
int cardHeight = getCardHeight();
|
||||
|
||||
// Create image to render to
|
||||
BufferedImage image =
|
||||
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
|
||||
cardRenderer.draw(g2d, attribs);
|
||||
|
||||
// Done
|
||||
g2d.dispose();
|
||||
return image;
|
||||
}
|
||||
|
||||
private int updateArtImageStamp;
|
||||
@Override
|
||||
public void updateArtImage() {
|
||||
// Invalidate
|
||||
artImage = null;
|
||||
cardImage = null;
|
||||
cardRenderer.setArtImage(null);
|
||||
|
||||
// Stop animation
|
||||
tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0;
|
||||
flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0;
|
||||
|
||||
// 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
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(CardView card) {
|
||||
// Update super
|
||||
super.update(card);
|
||||
|
||||
// Update renderer
|
||||
cardImage = null;
|
||||
cardRenderer = new ModernCardRenderer(gameCard, isTransformed());
|
||||
cardRenderer.setArtImage(artImage);
|
||||
|
||||
// Repaint
|
||||
repaint();
|
||||
}
|
||||
|
||||
@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 if card size changed
|
||||
if (getCardWidth() != oldCardWidth || getCardHeight() != oldCardHeight) {
|
||||
cardImage = null;
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage getFaceDownImage() {
|
||||
if (isPermanent()) {
|
||||
if (((PermanentView) gameCard).isMorphed()) {
|
||||
return ImageCache.getMorphImage();
|
||||
} else {
|
||||
return ImageCache.getManifestImage();
|
||||
}
|
||||
} else if (this.gameCard instanceof StackAbilityView) {
|
||||
return ImageCache.getMorphImage();
|
||||
} else {
|
||||
return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getImage() {
|
||||
if (artImage != null) {
|
||||
if (gameCard.isFaceDown()) {
|
||||
return getFaceDownImage();
|
||||
} else {
|
||||
return ImageCache.getImageOriginal(gameCard);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showCardTitle() {
|
||||
// Nothing to do, rendered cards always have a title
|
||||
}
|
||||
}
|
||||
package org.mage.card.arcane;
|
||||
|
||||
import com.google.common.collect.MapMaker;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import mage.cards.action.ActionCallback;
|
||||
import mage.constants.CardType;
|
||||
import mage.view.CardView;
|
||||
import mage.view.CounterView;
|
||||
import mage.view.PermanentView;
|
||||
import mage.view.StackAbilityView;
|
||||
import net.java.truevfs.access.TFile;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.jdesktop.swingx.graphics.GraphicsUtilities;
|
||||
import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL;
|
||||
import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
|
||||
import org.mage.plugins.card.images.ImageCache;
|
||||
|
||||
public class CardPanelRenderImpl extends CardPanel {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CardPanelRenderImpl.class);
|
||||
|
||||
private static boolean cardViewEquals(CardView a, CardView b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
if (a.getClass() != b.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getName().equals(b.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getPower().equals(b.getPower())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getToughness().equals(b.getToughness())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getLoyalty().equals(b.getLoyalty())) {
|
||||
return false;
|
||||
}
|
||||
if (0 != a.getColor().compareTo(b.getColor())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getCardTypes().equals(b.getCardTypes())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getSubTypes().equals(b.getSubTypes())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getSuperTypes().equals(b.getSuperTypes())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getManaCost().equals(b.getManaCost())) {
|
||||
return false;
|
||||
}
|
||||
if (!a.getRules().equals(b.getRules())) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
class ImageKey {
|
||||
|
||||
final BufferedImage artImage;
|
||||
final int width;
|
||||
final int height;
|
||||
final boolean isChoosable;
|
||||
final boolean isSelected;
|
||||
final CardView view;
|
||||
final int hashCode;
|
||||
|
||||
public ImageKey(CardView view, BufferedImage artImage, int width, int height, boolean isChoosable, boolean isSelected) {
|
||||
this.view = view;
|
||||
this.artImage = artImage;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.isChoosable = isChoosable;
|
||||
this.isSelected = isSelected;
|
||||
this.hashCode = hashCodeImpl();
|
||||
}
|
||||
|
||||
private int hashCodeImpl() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append((char) (artImage != null ? 1 : 0));
|
||||
sb.append((char) width);
|
||||
sb.append((char) height);
|
||||
sb.append((char) (isSelected ? 1 : 0));
|
||||
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());
|
||||
sb.append(this.view.getLoyalty());
|
||||
sb.append(this.view.getColor().toString());
|
||||
sb.append(this.view.getExpansionSetCode());
|
||||
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.getSubTypes()) {
|
||||
sb.append(s);
|
||||
}
|
||||
for (String s : this.view.getManaCost()) {
|
||||
sb.append(s);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
// Initial checks
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (object == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(object instanceof ImageKey)) {
|
||||
return false;
|
||||
}
|
||||
final ImageKey other = (ImageKey) object;
|
||||
|
||||
// Compare
|
||||
if ((artImage != null) != (other.artImage != null)) {
|
||||
return false;
|
||||
}
|
||||
if (width != other.width) {
|
||||
return false;
|
||||
}
|
||||
if (height != other.height) {
|
||||
return false;
|
||||
}
|
||||
if (isChoosable != other.isChoosable) {
|
||||
return false;
|
||||
}
|
||||
if (isSelected != other.isSelected) {
|
||||
return false;
|
||||
}
|
||||
return cardViewEquals(view, other.view);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 rendered card image, with or without the art image loaded yet
|
||||
// = null while invalid
|
||||
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
|
||||
super(newGameCard, gameId, loadImage, callback, foil, dimension);
|
||||
|
||||
// Renderer
|
||||
cardRenderer = new ModernCardRenderer(gameCard, isTransformed());
|
||||
|
||||
// Draw the parts
|
||||
initialDraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferResources(CardPanel panel) {
|
||||
if (panel instanceof CardPanelRenderImpl) {
|
||||
CardPanelRenderImpl impl = (CardPanelRenderImpl) panel;
|
||||
|
||||
// Use the art image and current rendered image from the card
|
||||
artImage = impl.artImage;
|
||||
cardRenderer.setArtImage(artImage);
|
||||
cardImage = impl.cardImage;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintCard(Graphics2D g) {
|
||||
// Render the card if we don't have an image ready to use
|
||||
if (cardImage == null) {
|
||||
// Try to get card image from cache based on our card characteristics
|
||||
ImageKey key
|
||||
= new ImageKey(gameCard, artImage,
|
||||
getCardWidth(), getCardHeight(),
|
||||
isChoosable(), isSelected());
|
||||
cardImage = IMAGE_CACHE.get(key);
|
||||
|
||||
// No cached copy exists? Render one and cache it
|
||||
if (cardImage == null) {
|
||||
cardImage = renderCard();
|
||||
IMAGE_CACHE.put(key, cardImage);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private BufferedImage renderCard() {
|
||||
int cardWidth = getCardWidth();
|
||||
int cardHeight = getCardHeight();
|
||||
|
||||
// Create image to render to
|
||||
BufferedImage image
|
||||
= 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
|
||||
cardRenderer.draw(g2d, attribs);
|
||||
|
||||
// Done
|
||||
g2d.dispose();
|
||||
return image;
|
||||
}
|
||||
|
||||
private int updateArtImageStamp;
|
||||
|
||||
@Override
|
||||
public void updateArtImage() {
|
||||
// Invalidate
|
||||
artImage = null;
|
||||
cardImage = null;
|
||||
cardRenderer.setArtImage(null);
|
||||
|
||||
// Stop animation
|
||||
tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0;
|
||||
flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0;
|
||||
|
||||
// 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
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(CardView card) {
|
||||
// Update super
|
||||
super.update(card);
|
||||
|
||||
// Update renderer
|
||||
cardImage = null;
|
||||
cardRenderer = new ModernCardRenderer(gameCard, isTransformed());
|
||||
cardRenderer.setArtImage(artImage);
|
||||
|
||||
// Repaint
|
||||
repaint();
|
||||
}
|
||||
|
||||
@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 if card size changed
|
||||
if (getCardWidth() != oldCardWidth || getCardHeight() != oldCardHeight) {
|
||||
cardImage = null;
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage getFaceDownImage() {
|
||||
if (isPermanent()) {
|
||||
if (((PermanentView) gameCard).isMorphed()) {
|
||||
return ImageCache.getMorphImage();
|
||||
} else {
|
||||
return ImageCache.getManifestImage();
|
||||
}
|
||||
} else if (this.gameCard instanceof StackAbilityView) {
|
||||
return ImageCache.getMorphImage();
|
||||
} else {
|
||||
return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getImage() {
|
||||
if (artImage != null) {
|
||||
if (gameCard.isFaceDown()) {
|
||||
return getFaceDownImage();
|
||||
} else {
|
||||
return ImageCache.getImageOriginal(gameCard);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showCardTitle() {
|
||||
// Nothing to do, rendered cards always have a title
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,382 +1,374 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Paint;
|
||||
import java.awt.Polygon;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.text.AttributedString;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import mage.client.dialog.PreferencesDialog;
|
||||
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;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
* @author stravant@gmail.com
|
||||
*
|
||||
* Common base class for card renderers for each card frame / card type.
|
||||
*
|
||||
* Follows the template method pattern to implement a new renderer, implement
|
||||
* the following methods (they are called in the following order):
|
||||
*
|
||||
* * drawBorder()
|
||||
* Draws the outermost border of the card, white border or black border
|
||||
*
|
||||
* * drawBackground()
|
||||
* Draws the background texture / color of the card
|
||||
*
|
||||
* * drawArt()
|
||||
* Draws the card's art
|
||||
*
|
||||
* * drawFrame()
|
||||
* Draws the card frame (over the art and background)
|
||||
*
|
||||
* * drawOverlays()
|
||||
* Draws summoning sickness and possible other overlays
|
||||
*
|
||||
* * drawCounters()
|
||||
* Draws counters on the card, such as +1/+1 and -1/-1 counters
|
||||
*
|
||||
* Predefined methods that the implementations can use:
|
||||
*
|
||||
* * drawRules(font, bounding box)
|
||||
*
|
||||
* * drawNameLine(font, bounding box)
|
||||
*
|
||||
* * drawTypeLine(font, bounding box)
|
||||
*
|
||||
*/
|
||||
public abstract class CardRenderer {
|
||||
private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Common layout metrics between all cards
|
||||
|
||||
// The card to be rendered
|
||||
protected final CardView cardView;
|
||||
|
||||
// Is the card transformed?
|
||||
protected final boolean isTransformed;
|
||||
|
||||
// The card image
|
||||
protected BufferedImage artImage;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Common layout metrics between all cards
|
||||
|
||||
// Polygons for counters
|
||||
private static final Polygon PLUS_COUNTER_POLY = new Polygon(new int[]{
|
||||
0, 5, 10, 10, 5, 0
|
||||
}, new int[]{
|
||||
3, 0, 3, 10, 9, 10
|
||||
}, 6);
|
||||
private static final Polygon MINUS_COUNTER_POLY = new Polygon(new int[]{
|
||||
0, 5, 10, 10, 5, 0
|
||||
}, new int[]{
|
||||
0, 1, 0, 7, 10, 7
|
||||
}, 6);
|
||||
private static final Polygon TIME_COUNTER_POLY = new Polygon(new int[]{
|
||||
0, 10, 8, 10, 0, 2
|
||||
}, new int[]{
|
||||
0, 0, 5, 10, 10, 5
|
||||
}, 6);
|
||||
private static final Polygon OTHER_COUNTER_POLY = new Polygon(new int[]{
|
||||
1, 9, 9, 1
|
||||
}, new int[]{
|
||||
1, 1, 9, 9
|
||||
}, 4);
|
||||
|
||||
// Paint for a card back
|
||||
public static Paint BG_TEXTURE_CARDBACK = new Color(153, 102, 51);
|
||||
|
||||
// The size of the card
|
||||
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;
|
||||
protected int cornerRadius;
|
||||
|
||||
// The inset of the actual card from the black / white border around it
|
||||
protected static float BORDER_WIDTH_FRAC = 0.03f; //x cardWidth
|
||||
protected static float BORDER_WIDTH_MIN = 2;
|
||||
protected int borderWidth;
|
||||
|
||||
// The parsed text of the card
|
||||
protected ArrayList<TextboxRule> textboxRules = new ArrayList<>();
|
||||
protected ArrayList<TextboxRule> textboxKeywords = new ArrayList<>();
|
||||
|
||||
// The Construtor
|
||||
// The constructor should prepare all of the things that it can
|
||||
// without knowing the dimensions that the card will be rendered at.
|
||||
// Then, the CardRenderer can be called on multiple times to render the
|
||||
// card at various sizes (for instance, during animation)
|
||||
public CardRenderer(CardView card, boolean isTransformed) {
|
||||
// Set base parameters
|
||||
this.cardView = card;
|
||||
this.isTransformed = isTransformed;
|
||||
|
||||
// Translate the textbox text
|
||||
for (String rule: card.getRules()) {
|
||||
// Kill reminder text
|
||||
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) {
|
||||
textboxKeywords.add(tbRule);
|
||||
} else if (tbRule.text.isEmpty()) {
|
||||
// Nothing to do, rule is empty
|
||||
} else {
|
||||
textboxRules.add(tbRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layout operation
|
||||
// Calculate common layout metrics that will be used by several
|
||||
// of the operations in the template method.
|
||||
protected void layout(int cardWidth, int cardHeight) {
|
||||
// Store the dimensions for the template methods to use
|
||||
this.cardWidth = cardWidth;
|
||||
this.cardHeight = cardHeight;
|
||||
|
||||
// Corner radius and border width
|
||||
cornerRadius = (int)Math.max(
|
||||
CORNER_RADIUS_MIN,
|
||||
CORNER_RADIUS_FRAC * cardWidth);
|
||||
|
||||
borderWidth = (int)Math.max(
|
||||
BORDER_WIDTH_MIN,
|
||||
BORDER_WIDTH_FRAC * cardWidth);
|
||||
}
|
||||
|
||||
// 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, CardPanelAttributes attribs) {
|
||||
// Pre template method layout, to calculate shared layout info
|
||||
layout(attribs.cardWidth, attribs.cardHeight);
|
||||
isSelected = attribs.isSelected;
|
||||
isChoosable = attribs.isChoosable;
|
||||
|
||||
// Call the template methods
|
||||
drawBorder(g);
|
||||
drawBackground(g);
|
||||
drawArt(g);
|
||||
drawFrame(g);
|
||||
if (!cardView.isAbility()) {
|
||||
drawOverlays(g);
|
||||
drawCounters(g);
|
||||
}
|
||||
}
|
||||
|
||||
// Template methods to be implemented by sub classes
|
||||
// For instance, for the Modern vs Old border card frames
|
||||
protected abstract void drawBorder(Graphics2D g);
|
||||
protected abstract void drawBackground(Graphics2D g);
|
||||
protected abstract void drawArt(Graphics2D g);
|
||||
protected abstract void drawFrame(Graphics2D g);
|
||||
|
||||
// Template methods that are possible to override, but unlikely to be
|
||||
// overridden.
|
||||
|
||||
// Draw the card back
|
||||
protected void drawCardBack(Graphics2D g) {
|
||||
g.setPaint(BG_TEXTURE_CARDBACK);
|
||||
g.fillRect(borderWidth, borderWidth,
|
||||
cardWidth - 2*borderWidth, cardHeight - 2*borderWidth);
|
||||
}
|
||||
|
||||
// Draw summoning sickness overlay, and possibly other overlays
|
||||
protected void drawOverlays(Graphics2D g) {
|
||||
if (CardUtil.isCreature(cardView) && cardView instanceof PermanentView) {
|
||||
if (((PermanentView)cardView).hasSummoningSickness()) {
|
||||
int x1 = (int)(0.2*cardWidth);
|
||||
int x2 = (int)(0.8*cardWidth);
|
||||
int y1 = (int)(0.2*cardHeight);
|
||||
int y2 = (int)(0.8*cardHeight);
|
||||
int xPoints[] = {
|
||||
x1, x2, x1, x2
|
||||
};
|
||||
int yPoints[] = {
|
||||
y1, y1, y2, y2
|
||||
};
|
||||
g.setColor(new Color(255, 255, 255, 200));
|
||||
g.setStroke(new BasicStroke(7));
|
||||
g.drawPolygon(xPoints, yPoints, 4);
|
||||
g.setColor(new Color(0, 0, 0, 200));
|
||||
g.setStroke(new BasicStroke(5));
|
||||
g.drawPolygon(xPoints, yPoints, 4);
|
||||
g.setStroke(new BasicStroke(1));
|
||||
int[] xPoints2 = {
|
||||
x1, x2, cardWidth/2
|
||||
};
|
||||
int[] yPoints2 = {
|
||||
y1, y1, cardHeight/2
|
||||
};
|
||||
g.setColor(new Color(0, 0, 0, 100));
|
||||
g.fillPolygon(xPoints2, yPoints2, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw +1/+1 and other counters
|
||||
protected void drawCounters(Graphics2D g) {
|
||||
int xPos = (int)(0.65*cardWidth);
|
||||
int yPos = (int)(0.15*cardHeight);
|
||||
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;
|
||||
if (v.getName().equals("+1/+1")) {
|
||||
p = PLUS_COUNTER_POLY;
|
||||
} else if (v.getName().equals("-1/-1")) {
|
||||
p = MINUS_COUNTER_POLY;
|
||||
} else if (v.getName().equals("time")) {
|
||||
p = TIME_COUNTER_POLY;
|
||||
} else {
|
||||
p = OTHER_COUNTER_POLY;
|
||||
}
|
||||
double scale = (0.1*0.25*cardWidth);
|
||||
Graphics2D g2 = (Graphics2D)g.create();
|
||||
g2.translate(xPos, yPos);
|
||||
g2.scale(scale, scale);
|
||||
g2.setColor(Color.white);
|
||||
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);
|
||||
g2.drawString(cstr, 5 - strW/2, 8);
|
||||
g2.dispose();
|
||||
yPos += ((int)(0.30*cardWidth));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw an expansion symbol, right justified, in a given region
|
||||
// Return the width of the drawn symbol
|
||||
protected int drawExpansionSymbol(Graphics2D g, int x, int y, int w, int h) {
|
||||
// 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;
|
||||
/*
|
||||
// Just draw the as a code
|
||||
String code = cardView.getExpansionSetCode();
|
||||
code = (code != null) ? code.toUpperCase() : "";
|
||||
FontMetrics metrics = g.getFontMetrics();
|
||||
setSymbolWidth = metrics.stringWidth(code);
|
||||
if (cardView.getRarity() == Rarity.COMMON) {
|
||||
g.setColor(Color.white);
|
||||
} else {
|
||||
g.setColor(Color.black);
|
||||
}
|
||||
g.fillRoundRect(
|
||||
x + w - setSymbolWidth - 1, y + 2,
|
||||
setSymbolWidth+2, h - 5,
|
||||
5, 5);
|
||||
g.setColor(getRarityColor());
|
||||
g.drawString(code, x + w - setSymbolWidth, y + h - 3);
|
||||
*/
|
||||
} else {
|
||||
// Draw the set symbol
|
||||
int height = setSymbol.getHeight(null);
|
||||
int scale = 1;
|
||||
if (height != -1) {
|
||||
while (height > h+2) {
|
||||
scale *= 2;
|
||||
height /= 2;
|
||||
}
|
||||
}
|
||||
setSymbolWidth = setSymbol.getWidth(null) / scale;
|
||||
g.drawImage(setSymbol,
|
||||
x + w - setSymbolWidth, y + (h - height)/2,
|
||||
setSymbolWidth, height,
|
||||
null);
|
||||
}
|
||||
return setSymbolWidth;
|
||||
}
|
||||
private Color getRarityColor() {
|
||||
switch (cardView.getRarity()) {
|
||||
case RARE:
|
||||
return new Color(255, 191, 0);
|
||||
case UNCOMMON:
|
||||
return new Color(192, 192, 192);
|
||||
case MYTHIC:
|
||||
return new Color(213, 51, 11);
|
||||
case SPECIAL:
|
||||
return new Color(204, 0, 255);
|
||||
case BONUS:
|
||||
return new Color(129, 228, 228);
|
||||
case COMMON:
|
||||
default:
|
||||
return Color.black;
|
||||
}
|
||||
}
|
||||
|
||||
// Get a string representing the type line
|
||||
protected String getCardTypeLine() {
|
||||
if (cardView.isAbility()) {
|
||||
if (AbilityType.TRIGGERED.equals(cardView.getAbilityType())) {
|
||||
return "Triggered Ability";
|
||||
} else if (AbilityType.ACTIVATED.equals(cardView.getAbilityType())) {
|
||||
return "Activated Ability";
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Paint;
|
||||
import java.awt.Polygon;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import mage.client.dialog.PreferencesDialog;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.constants.CardType;
|
||||
import mage.utils.CardUtil;
|
||||
import mage.view.CardView;
|
||||
import mage.view.CounterView;
|
||||
import mage.view.PermanentView;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
* @author stravant@gmail.com
|
||||
*
|
||||
* Common base class for card renderers for each card frame / card type.
|
||||
*
|
||||
* Follows the template method pattern to implement a new renderer, implement
|
||||
* the following methods (they are called in the following order):
|
||||
*
|
||||
* * drawBorder() Draws the outermost border of the card, white border or black
|
||||
* border
|
||||
*
|
||||
* * drawBackground() Draws the background texture / color of the card
|
||||
*
|
||||
* * drawArt() Draws the card's art
|
||||
*
|
||||
* * drawFrame() Draws the card frame (over the art and background)
|
||||
*
|
||||
* * drawOverlays() Draws summoning sickness and possible other overlays
|
||||
*
|
||||
* * drawCounters() Draws counters on the card, such as +1/+1 and -1/-1
|
||||
* counters
|
||||
*
|
||||
* Predefined methods that the implementations can use:
|
||||
*
|
||||
* * drawRules(font, bounding box)
|
||||
*
|
||||
* * drawNameLine(font, bounding box)
|
||||
*
|
||||
* * drawTypeLine(font, bounding box)
|
||||
*
|
||||
*/
|
||||
public abstract class CardRenderer {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Common layout metrics between all cards
|
||||
// The card to be rendered
|
||||
protected final CardView cardView;
|
||||
|
||||
// Is the card transformed?
|
||||
protected final boolean isTransformed;
|
||||
|
||||
// The card image
|
||||
protected BufferedImage artImage;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Common layout metrics between all cards
|
||||
// Polygons for counters
|
||||
private static final Polygon PLUS_COUNTER_POLY = new Polygon(new int[]{
|
||||
0, 5, 10, 10, 5, 0
|
||||
}, new int[]{
|
||||
3, 0, 3, 10, 9, 10
|
||||
}, 6);
|
||||
private static final Polygon MINUS_COUNTER_POLY = new Polygon(new int[]{
|
||||
0, 5, 10, 10, 5, 0
|
||||
}, new int[]{
|
||||
0, 1, 0, 7, 10, 7
|
||||
}, 6);
|
||||
private static final Polygon TIME_COUNTER_POLY = new Polygon(new int[]{
|
||||
0, 10, 8, 10, 0, 2
|
||||
}, new int[]{
|
||||
0, 0, 5, 10, 10, 5
|
||||
}, 6);
|
||||
private static final Polygon OTHER_COUNTER_POLY = new Polygon(new int[]{
|
||||
1, 9, 9, 1
|
||||
}, new int[]{
|
||||
1, 1, 9, 9
|
||||
}, 4);
|
||||
|
||||
// Paint for a card back
|
||||
public static Paint BG_TEXTURE_CARDBACK = new Color(153, 102, 51);
|
||||
|
||||
// The size of the card
|
||||
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;
|
||||
protected int cornerRadius;
|
||||
|
||||
// The inset of the actual card from the black / white border around it
|
||||
protected static float BORDER_WIDTH_FRAC = 0.03f; //x cardWidth
|
||||
protected static float BORDER_WIDTH_MIN = 2;
|
||||
protected int borderWidth;
|
||||
|
||||
// The parsed text of the card
|
||||
protected ArrayList<TextboxRule> textboxRules = new ArrayList<>();
|
||||
protected ArrayList<TextboxRule> textboxKeywords = new ArrayList<>();
|
||||
|
||||
// The Construtor
|
||||
// The constructor should prepare all of the things that it can
|
||||
// without knowing the dimensions that the card will be rendered at.
|
||||
// Then, the CardRenderer can be called on multiple times to render the
|
||||
// card at various sizes (for instance, during animation)
|
||||
public CardRenderer(CardView card, boolean isTransformed) {
|
||||
// Set base parameters
|
||||
this.cardView = card;
|
||||
this.isTransformed = isTransformed;
|
||||
|
||||
// Translate the textbox text
|
||||
for (String rule : card.getRules()) {
|
||||
// Kill reminder text
|
||||
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) {
|
||||
textboxKeywords.add(tbRule);
|
||||
} else if (tbRule.text.isEmpty()) {
|
||||
// Nothing to do, rule is empty
|
||||
} else {
|
||||
textboxRules.add(tbRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layout operation
|
||||
// Calculate common layout metrics that will be used by several
|
||||
// of the operations in the template method.
|
||||
protected void layout(int cardWidth, int cardHeight) {
|
||||
// Store the dimensions for the template methods to use
|
||||
this.cardWidth = cardWidth;
|
||||
this.cardHeight = cardHeight;
|
||||
|
||||
// Corner radius and border width
|
||||
cornerRadius = (int) Math.max(
|
||||
CORNER_RADIUS_MIN,
|
||||
CORNER_RADIUS_FRAC * cardWidth);
|
||||
|
||||
borderWidth = (int) Math.max(
|
||||
BORDER_WIDTH_MIN,
|
||||
BORDER_WIDTH_FRAC * cardWidth);
|
||||
}
|
||||
|
||||
// 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, CardPanelAttributes attribs) {
|
||||
// Pre template method layout, to calculate shared layout info
|
||||
layout(attribs.cardWidth, attribs.cardHeight);
|
||||
isSelected = attribs.isSelected;
|
||||
isChoosable = attribs.isChoosable;
|
||||
|
||||
// Call the template methods
|
||||
drawBorder(g);
|
||||
drawBackground(g);
|
||||
drawArt(g);
|
||||
drawFrame(g);
|
||||
if (!cardView.isAbility()) {
|
||||
drawOverlays(g);
|
||||
drawCounters(g);
|
||||
}
|
||||
}
|
||||
|
||||
// Template methods to be implemented by sub classes
|
||||
// For instance, for the Modern vs Old border card frames
|
||||
protected abstract void drawBorder(Graphics2D g);
|
||||
|
||||
protected abstract void drawBackground(Graphics2D g);
|
||||
|
||||
protected abstract void drawArt(Graphics2D g);
|
||||
|
||||
protected abstract void drawFrame(Graphics2D g);
|
||||
|
||||
// Template methods that are possible to override, but unlikely to be
|
||||
// overridden.
|
||||
// Draw the card back
|
||||
protected void drawCardBack(Graphics2D g) {
|
||||
g.setPaint(BG_TEXTURE_CARDBACK);
|
||||
g.fillRect(borderWidth, borderWidth,
|
||||
cardWidth - 2 * borderWidth, cardHeight - 2 * borderWidth);
|
||||
}
|
||||
|
||||
// Draw summoning sickness overlay, and possibly other overlays
|
||||
protected void drawOverlays(Graphics2D g) {
|
||||
if (CardUtil.isCreature(cardView) && cardView instanceof PermanentView) {
|
||||
if (((PermanentView) cardView).hasSummoningSickness()) {
|
||||
int x1 = (int) (0.2 * cardWidth);
|
||||
int x2 = (int) (0.8 * cardWidth);
|
||||
int y1 = (int) (0.2 * cardHeight);
|
||||
int y2 = (int) (0.8 * cardHeight);
|
||||
int xPoints[] = {
|
||||
x1, x2, x1, x2
|
||||
};
|
||||
int yPoints[] = {
|
||||
y1, y1, y2, y2
|
||||
};
|
||||
g.setColor(new Color(255, 255, 255, 200));
|
||||
g.setStroke(new BasicStroke(7));
|
||||
g.drawPolygon(xPoints, yPoints, 4);
|
||||
g.setColor(new Color(0, 0, 0, 200));
|
||||
g.setStroke(new BasicStroke(5));
|
||||
g.drawPolygon(xPoints, yPoints, 4);
|
||||
g.setStroke(new BasicStroke(1));
|
||||
int[] xPoints2 = {
|
||||
x1, x2, cardWidth / 2
|
||||
};
|
||||
int[] yPoints2 = {
|
||||
y1, y1, cardHeight / 2
|
||||
};
|
||||
g.setColor(new Color(0, 0, 0, 100));
|
||||
g.fillPolygon(xPoints2, yPoints2, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw +1/+1 and other counters
|
||||
protected void drawCounters(Graphics2D g) {
|
||||
int xPos = (int) (0.65 * cardWidth);
|
||||
int yPos = (int) (0.15 * cardHeight);
|
||||
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;
|
||||
if (v.getName().equals("+1/+1")) {
|
||||
p = PLUS_COUNTER_POLY;
|
||||
} else if (v.getName().equals("-1/-1")) {
|
||||
p = MINUS_COUNTER_POLY;
|
||||
} else if (v.getName().equals("time")) {
|
||||
p = TIME_COUNTER_POLY;
|
||||
} else {
|
||||
p = OTHER_COUNTER_POLY;
|
||||
}
|
||||
double scale = (0.1 * 0.25 * cardWidth);
|
||||
Graphics2D g2 = (Graphics2D) g.create();
|
||||
g2.translate(xPos, yPos);
|
||||
g2.scale(scale, scale);
|
||||
g2.setColor(Color.white);
|
||||
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);
|
||||
g2.drawString(cstr, 5 - strW / 2, 8);
|
||||
g2.dispose();
|
||||
yPos += ((int) (0.30 * cardWidth));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw an expansion symbol, right justified, in a given region
|
||||
// Return the width of the drawn symbol
|
||||
protected int drawExpansionSymbol(Graphics2D g, int x, int y, int w, int h) {
|
||||
// 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;
|
||||
/*
|
||||
// Just draw the as a code
|
||||
String code = cardView.getExpansionSetCode();
|
||||
code = (code != null) ? code.toUpperCase() : "";
|
||||
FontMetrics metrics = g.getFontMetrics();
|
||||
setSymbolWidth = metrics.stringWidth(code);
|
||||
if (cardView.getRarity() == Rarity.COMMON) {
|
||||
g.setColor(Color.white);
|
||||
} else {
|
||||
g.setColor(Color.black);
|
||||
}
|
||||
g.fillRoundRect(
|
||||
x + w - setSymbolWidth - 1, y + 2,
|
||||
setSymbolWidth+2, h - 5,
|
||||
5, 5);
|
||||
g.setColor(getRarityColor());
|
||||
g.drawString(code, x + w - setSymbolWidth, y + h - 3);
|
||||
*/
|
||||
} else {
|
||||
// Draw the set symbol
|
||||
int height = setSymbol.getHeight(null);
|
||||
int scale = 1;
|
||||
if (height != -1) {
|
||||
while (height > h + 2) {
|
||||
scale *= 2;
|
||||
height /= 2;
|
||||
}
|
||||
}
|
||||
setSymbolWidth = setSymbol.getWidth(null) / scale;
|
||||
g.drawImage(setSymbol,
|
||||
x + w - setSymbolWidth, y + (h - height) / 2,
|
||||
setSymbolWidth, height,
|
||||
null);
|
||||
}
|
||||
return setSymbolWidth;
|
||||
}
|
||||
|
||||
private Color getRarityColor() {
|
||||
switch (cardView.getRarity()) {
|
||||
case RARE:
|
||||
return new Color(255, 191, 0);
|
||||
case UNCOMMON:
|
||||
return new Color(192, 192, 192);
|
||||
case MYTHIC:
|
||||
return new Color(213, 51, 11);
|
||||
case SPECIAL:
|
||||
return new Color(204, 0, 255);
|
||||
case BONUS:
|
||||
return new Color(129, 228, 228);
|
||||
case COMMON:
|
||||
default:
|
||||
return Color.black;
|
||||
}
|
||||
}
|
||||
|
||||
// Get a string representing the type line
|
||||
protected String getCardTypeLine() {
|
||||
if (cardView.isAbility()) {
|
||||
if (AbilityType.TRIGGERED.equals(cardView.getAbilityType())) {
|
||||
return "Triggered Ability";
|
||||
} else if (AbilityType.ACTIVATED.equals(cardView.getAbilityType())) {
|
||||
return "Activated Ability";
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,20 +2,12 @@ package org.mage.card.arcane;
|
|||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.MapMaker;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.LineBreakMeasurer;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextLayout;
|
||||
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.text.AttributedCharacterIterator;
|
||||
import java.text.AttributedString;
|
||||
|
@ -23,10 +15,12 @@ import java.text.BreakIterator;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import javax.swing.*;
|
||||
import mage.client.util.ImageCaches;
|
||||
import org.jdesktop.swingx.graphics.GraphicsUtilities;
|
||||
|
||||
public class GlowText extends JLabel {
|
||||
|
||||
private static final long serialVersionUID = 1827677946939348001L;
|
||||
private int glowSize;
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -36,12 +30,12 @@ public class GlowText extends JLabel {
|
|||
private int lineCount = 0;
|
||||
private static Map<Key, BufferedImage> IMAGE_CACHE;
|
||||
|
||||
private final static class Key
|
||||
{
|
||||
private final static class Key {
|
||||
|
||||
final int width;
|
||||
final int height;
|
||||
final String text;
|
||||
final Map<TextAttribute,?> fontAttributes;
|
||||
final Map<TextAttribute, ?> fontAttributes;
|
||||
final Color color;
|
||||
final int glowSize;
|
||||
final float glowIntensity;
|
||||
|
@ -53,8 +47,9 @@ public class GlowText extends JLabel {
|
|||
|
||||
Font getFont() {
|
||||
Font res = this.originalFont.get();
|
||||
if(res == null)
|
||||
if (res == null) {
|
||||
res = Font.getFont(this.fontAttributes);
|
||||
}
|
||||
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.glowSize = size;
|
||||
this.glowIntensity = intensity;
|
||||
}
|
||||
|
||||
public void setWrap (boolean wrap) {
|
||||
public void setWrap(boolean wrap) {
|
||||
this.wrap = wrap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize () {
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension size = super.getPreferredSize();
|
||||
size.width += glowSize;
|
||||
size.height += glowSize / 2;
|
||||
|
@ -157,7 +152,7 @@ public class GlowText extends JLabel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void paint (Graphics g) {
|
||||
public void paint(Graphics g) {
|
||||
if (getText().length() == 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
private static BufferedImage createImage (Key key) {
|
||||
private static BufferedImage createImage(Key key) {
|
||||
Dimension size = new Dimension(key.width, key.height);
|
||||
BufferedImage image = GraphicsUtilities.createCompatibleTranslucentImage(size.width, size.height);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,28 +1,28 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.text.AttributedString;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author StravantUser
|
||||
*
|
||||
* Level rule associated with leveler cards
|
||||
*/
|
||||
public class TextboxLevelRule extends TextboxRule {
|
||||
// The levels that this rule applies to
|
||||
public int levelFrom;
|
||||
public int levelTo;
|
||||
|
||||
public static int AND_HIGHER = 100;
|
||||
|
||||
public TextboxLevelRule(String text, List<AttributeRegion> regions, int levelFrom, int levelTo) {
|
||||
super(text, regions, TextboxRuleType.LEVEL);
|
||||
this.levelFrom = levelFrom;
|
||||
this.levelTo = levelTo;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author StravantUser
|
||||
*
|
||||
* Level rule associated with leveler cards
|
||||
*/
|
||||
public class TextboxLevelRule extends TextboxRule {
|
||||
|
||||
// The levels that this rule applies to
|
||||
public int levelFrom;
|
||||
public int levelTo;
|
||||
|
||||
public static int AND_HIGHER = 100;
|
||||
|
||||
public TextboxLevelRule(String text, List<AttributeRegion> regions, int levelFrom, int levelTo) {
|
||||
super(text, regions, TextboxRuleType.LEVEL);
|
||||
this.levelFrom = levelFrom;
|
||||
this.levelTo = levelTo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.text.AttributedString;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author StravantUser
|
||||
*/
|
||||
public class TextboxLoyaltyRule extends TextboxRule {
|
||||
public int loyaltyChange;
|
||||
|
||||
public static int MINUS_X = 100;
|
||||
|
||||
public String getChangeString() {
|
||||
if (loyaltyChange == MINUS_X) {
|
||||
return "-X";
|
||||
} else if (loyaltyChange > 0) {
|
||||
return "+" + loyaltyChange;
|
||||
} else {
|
||||
return "" + loyaltyChange;
|
||||
}
|
||||
}
|
||||
|
||||
public TextboxLoyaltyRule(String text, List<AttributeRegion> regions, int loyaltyChange) {
|
||||
super(text, regions, TextboxRuleType.LOYALTY);
|
||||
this.loyaltyChange = loyaltyChange;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author StravantUser
|
||||
*/
|
||||
public class TextboxLoyaltyRule extends TextboxRule {
|
||||
|
||||
public int loyaltyChange;
|
||||
|
||||
public static int MINUS_X = 100;
|
||||
|
||||
public String getChangeString() {
|
||||
if (loyaltyChange == MINUS_X) {
|
||||
return "-X";
|
||||
} else if (loyaltyChange > 0) {
|
||||
return "+" + loyaltyChange;
|
||||
} else {
|
||||
return "" + loyaltyChange;
|
||||
}
|
||||
}
|
||||
|
||||
public TextboxLoyaltyRule(String text, List<AttributeRegion> regions, int loyaltyChange) {
|
||||
super(text, regions, TextboxRuleType.LOYALTY);
|
||||
this.loyaltyChange = loyaltyChange;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,94 +1,98 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.font.GraphicAttribute;
|
||||
import java.awt.font.ImageGraphicAttribute;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.text.AttributedString;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author stravant@gmail.com
|
||||
*
|
||||
* Class describing parsed & translated rules in the text box of a card,
|
||||
* ready to be rendered.
|
||||
*/
|
||||
public class TextboxRule {
|
||||
// An attributed region in the text, which can be applied to an
|
||||
// attributed string.
|
||||
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) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
private final int start;
|
||||
private final int 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;
|
||||
this.location = location;
|
||||
}
|
||||
private final String symbol;
|
||||
private final int location;
|
||||
|
||||
@Override
|
||||
public void applyToAttributedString(AttributedString str, Font normal, Font italic) {
|
||||
Image symbolImage = ManaSymbols.getSizedManaSymbol(symbol, 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;
|
||||
|
||||
protected TextboxRule(String text, List<AttributeRegion> regions, TextboxRuleType type) {
|
||||
this.text = text;
|
||||
this.type = type;
|
||||
this.regions = regions;
|
||||
}
|
||||
|
||||
public TextboxRule(String text, List<AttributeRegion> regions) {
|
||||
this(text, regions, TextboxRuleType.NORMAL);
|
||||
}
|
||||
|
||||
public AttributedString generateAttributedString(Font normal, Font italic) {
|
||||
// Build the final attributed text using the regions
|
||||
// Do it in reverse order for proper handling of regions where
|
||||
// there are multiple attributes stacked (EG: bold + italic)
|
||||
AttributedString attributedRule = new AttributedString(text);
|
||||
if (text.length() != 0) {
|
||||
attributedRule.addAttribute(TextAttribute.FONT, normal);
|
||||
for (int i = regions.size()-1; i >= 0; --i) {
|
||||
regions.get(i).applyToAttributedString(attributedRule, normal, italic);
|
||||
}
|
||||
}
|
||||
return attributedRule;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.font.GraphicAttribute;
|
||||
import java.awt.font.ImageGraphicAttribute;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.text.AttributedString;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author stravant@gmail.com
|
||||
*
|
||||
* Class describing parsed & translated rules in the text box of a card, ready
|
||||
* to be rendered.
|
||||
*/
|
||||
public class TextboxRule {
|
||||
|
||||
// An attributed region in the text, which can be applied to an
|
||||
// attributed string.
|
||||
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) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
private final int start;
|
||||
private final int 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;
|
||||
this.location = location;
|
||||
}
|
||||
private final String symbol;
|
||||
private final int location;
|
||||
|
||||
@Override
|
||||
public void applyToAttributedString(AttributedString str, Font normal, Font italic) {
|
||||
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;
|
||||
|
||||
protected TextboxRule(String text, List<AttributeRegion> regions, TextboxRuleType type) {
|
||||
this.text = text;
|
||||
this.type = type;
|
||||
this.regions = regions;
|
||||
}
|
||||
|
||||
public TextboxRule(String text, List<AttributeRegion> regions) {
|
||||
this(text, regions, TextboxRuleType.NORMAL);
|
||||
}
|
||||
|
||||
public AttributedString generateAttributedString(Font normal, Font italic) {
|
||||
// Build the final attributed text using the regions
|
||||
// Do it in reverse order for proper handling of regions where
|
||||
// there are multiple attributes stacked (EG: bold + italic)
|
||||
AttributedString attributedRule = new AttributedString(text);
|
||||
if (text.length() != 0) {
|
||||
attributedRule.addAttribute(TextAttribute.FONT, normal);
|
||||
for (int i = regions.size() - 1; i >= 0; --i) {
|
||||
regions.get(i).applyToAttributedString(attributedRule, normal, italic);
|
||||
}
|
||||
}
|
||||
return attributedRule;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,251 +1,251 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.font.GraphicAttribute;
|
||||
import java.awt.font.ImageGraphicAttribute;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.text.AttributedString;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author StravantUser
|
||||
*/
|
||||
public class TextboxRuleParser {
|
||||
private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
|
||||
|
||||
private static final Pattern LevelAbilityPattern = Pattern.compile("Level (\\d+)-?(\\d*)(\\+?)");
|
||||
private static final Pattern LoyaltyAbilityPattern = Pattern.compile("^(\\+|\\-)(\\d+|X): ");
|
||||
private static final Pattern SimpleKeywordPattern = Pattern.compile("^(\\w+( \\w+)?)\\s*(\\([^\\)]*\\))?\\s*$");
|
||||
|
||||
// Parse a given rule (given as a string) into a TextboxRule, replacing
|
||||
// symbol annotations, italics, etc, parsing out information such as
|
||||
// if the ability is a loyalty ability, and returning an TextboxRule
|
||||
// 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) {
|
||||
// List of regions to apply
|
||||
ArrayList<TextboxRule.AttributeRegion> regions = new ArrayList<>();
|
||||
|
||||
// Leveler / loyalty
|
||||
boolean isLeveler = false;
|
||||
int levelFrom = 0;
|
||||
int levelTo = 0;
|
||||
|
||||
boolean isLoyalty = false;
|
||||
int loyaltyChange = 0;
|
||||
|
||||
// Parse the attributedString contents
|
||||
int index = 0;
|
||||
int outputIndex = 0;
|
||||
|
||||
// Is it a simple keyword ability?
|
||||
{
|
||||
Matcher simpleKeywordMatch = SimpleKeywordPattern.matcher(rule);
|
||||
if (simpleKeywordMatch.find()) {
|
||||
return new TextboxKeywordRule(simpleKeywordMatch.group(1), regions);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a loyalty ability. Must be right at the start of the rule
|
||||
{
|
||||
Matcher loyaltyMatch = LoyaltyAbilityPattern.matcher(rule);
|
||||
if (loyaltyMatch.find()) {
|
||||
// Get the loyalty change
|
||||
if (loyaltyMatch.group(2).equals("X")) {
|
||||
loyaltyChange = TextboxLoyaltyRule.MINUS_X;
|
||||
} else {
|
||||
loyaltyChange = Integer.parseInt(loyaltyMatch.group(2));
|
||||
if (loyaltyMatch.group(1).equals("-")) {
|
||||
loyaltyChange = -loyaltyChange;
|
||||
}
|
||||
}
|
||||
isLoyalty = true;
|
||||
|
||||
// Go past the match
|
||||
index = loyaltyMatch.group().length();
|
||||
}
|
||||
}
|
||||
|
||||
Deque<Integer> openingStack = new ArrayDeque<>();
|
||||
StringBuilder build = new StringBuilder();
|
||||
while (index < rule.length()) {
|
||||
int initialIndex = index;
|
||||
char ch = rule.charAt(index);
|
||||
if (ch == '{') {
|
||||
// Handling for `{this}`
|
||||
int closeIndex = rule.indexOf('}', index);
|
||||
if (closeIndex == -1) {
|
||||
// Malformed input, nothing to do
|
||||
++index;
|
||||
++outputIndex;
|
||||
build.append(ch);
|
||||
} else {
|
||||
String contents = rule.substring(index+1, closeIndex);
|
||||
if (contents.equals("this") || contents.equals("source")) {
|
||||
// Replace {this} with the card's name
|
||||
String cardName = source.getName();
|
||||
build.append(cardName);
|
||||
index += contents.length() + 2;
|
||||
outputIndex += cardName.length();
|
||||
} else {
|
||||
Image symbol = ManaSymbols.getSizedManaSymbol(contents, 10);
|
||||
if (symbol != null) {
|
||||
// Mana or other inline symbol
|
||||
build.append('#');
|
||||
regions.add(new TextboxRule.EmbeddedSymbol(contents, outputIndex));
|
||||
++outputIndex;
|
||||
index = closeIndex+1;
|
||||
} else {
|
||||
// Bad entry
|
||||
build.append('{');
|
||||
build.append(contents);
|
||||
build.append('}');
|
||||
index = closeIndex+1;
|
||||
outputIndex += (contents.length() + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (ch == '&') {
|
||||
// Handling for `—`
|
||||
if (rule.startsWith("—", index)) {
|
||||
build.append('—');
|
||||
index += 7;
|
||||
++outputIndex;
|
||||
} else if (rule.startsWith("&bull", index)) {
|
||||
build.append('•');
|
||||
index += 5;
|
||||
++outputIndex;
|
||||
} else {
|
||||
LOGGER.error("Bad &...; sequence `" + rule.substring(index+1, index+10) + "` in rule.");
|
||||
build.append('&');
|
||||
++index;
|
||||
++outputIndex;
|
||||
}
|
||||
|
||||
|
||||
} else if (ch == '<') {
|
||||
// Handling for `<i>` and `<br/>`
|
||||
int closeIndex = rule.indexOf('>', index);
|
||||
if (closeIndex != -1) {
|
||||
// Is a tag
|
||||
String tag = rule.substring(index+1, closeIndex);
|
||||
if (tag.charAt(tag.length()-1) == '/') {
|
||||
// Pure closing tag (like <br/>)
|
||||
if (tag.equals("br/")) {
|
||||
build.append('\n');
|
||||
++outputIndex;
|
||||
} else {
|
||||
// Unknown
|
||||
build.append('<').append(tag).append('>');
|
||||
outputIndex += (tag.length() + 2);
|
||||
}
|
||||
} else if (tag.charAt(0) == '/') {
|
||||
// Opening index for the tag
|
||||
int openingIndex;
|
||||
if (openingStack.isEmpty()) {
|
||||
// Malformed input, just make an empty interval
|
||||
openingIndex = outputIndex;
|
||||
} else {
|
||||
openingIndex = openingStack.pop();
|
||||
}
|
||||
|
||||
// What tag is it?
|
||||
if (tag.equals("/i")) {
|
||||
// Italics
|
||||
regions.add(new TextboxRule.ItalicRegion(openingIndex, outputIndex));
|
||||
} else if (tag.equals("/b")) {
|
||||
// Bold, see if it's a level ability
|
||||
String content = build.substring(openingIndex);
|
||||
|
||||
Matcher levelMatch = LevelAbilityPattern.matcher(content);
|
||||
if (levelMatch.find()) {
|
||||
try {
|
||||
levelFrom = Integer.parseInt(levelMatch.group(1));
|
||||
if (!levelMatch.group(2).equals("")) {
|
||||
levelTo = Integer.parseInt(levelMatch.group(2));
|
||||
}
|
||||
if (!levelMatch.group(3).equals("")) {
|
||||
levelTo = TextboxLevelRule.AND_HIGHER;
|
||||
}
|
||||
isLeveler = true;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Bad leveler levels in rule `" + rule + "`.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Unknown
|
||||
build.append('<').append(tag).append('>');
|
||||
outputIndex += (tag.length() + 2);
|
||||
}
|
||||
} else {
|
||||
// Is it a <br> tag special case? [Why can't it have a closing `/`... =( ]
|
||||
if (tag.equals("br")) {
|
||||
build.append('\n');
|
||||
++outputIndex;
|
||||
} else {
|
||||
// Opening tag
|
||||
openingStack.push(outputIndex);
|
||||
}
|
||||
}
|
||||
// Skip characters
|
||||
index = closeIndex+1;
|
||||
} else {
|
||||
// Malformed tag
|
||||
build.append('<');
|
||||
++outputIndex;
|
||||
++index;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Normal character
|
||||
++index;
|
||||
++outputIndex;
|
||||
build.append(ch);
|
||||
}
|
||||
if (outputIndex != build.length()) {
|
||||
// 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.");
|
||||
|
||||
// Bail out
|
||||
build = new StringBuilder(rule);
|
||||
regions.clear();
|
||||
break;
|
||||
}
|
||||
if (index == initialIndex) {
|
||||
// Somehow our parsing failed to consume the
|
||||
LOGGER.error("Failed on rule `" + rule + "` due to not consuming a character.");
|
||||
|
||||
// Bail out
|
||||
build = new StringBuilder(rule);
|
||||
regions.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build and return the rule
|
||||
rule = build.toString();
|
||||
if (isLoyalty) {
|
||||
return new TextboxLoyaltyRule(rule, regions, loyaltyChange);
|
||||
} else if (isLeveler) {
|
||||
return new TextboxLevelRule(rule, regions, levelFrom, levelTo);
|
||||
} else {
|
||||
return new TextboxRule(rule, regions);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import mage.view.CardView;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author StravantUser
|
||||
*/
|
||||
public class TextboxRuleParser {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
|
||||
|
||||
private static final Pattern LevelAbilityPattern = Pattern.compile("Level (\\d+)-?(\\d*)(\\+?)");
|
||||
private static final Pattern LoyaltyAbilityPattern = Pattern.compile("^(\\+|\\-)(\\d+|X): ");
|
||||
private static final Pattern SimpleKeywordPattern = Pattern.compile("^(\\w+( \\w+)?)\\s*(\\([^\\)]*\\))?\\s*$");
|
||||
|
||||
// Parse a given rule (given as a string) into a TextboxRule, replacing
|
||||
// symbol annotations, italics, etc, parsing out information such as
|
||||
// if the ability is a loyalty ability, and returning an TextboxRule
|
||||
// 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) {
|
||||
// List of regions to apply
|
||||
ArrayList<TextboxRule.AttributeRegion> regions = new ArrayList<>();
|
||||
|
||||
// Leveler / loyalty
|
||||
boolean isLeveler = false;
|
||||
int levelFrom = 0;
|
||||
int levelTo = 0;
|
||||
|
||||
boolean isLoyalty = false;
|
||||
int loyaltyChange = 0;
|
||||
|
||||
// Parse the attributedString contents
|
||||
int index = 0;
|
||||
int outputIndex = 0;
|
||||
|
||||
// Is it a simple keyword ability?
|
||||
{
|
||||
Matcher simpleKeywordMatch = SimpleKeywordPattern.matcher(rule);
|
||||
if (simpleKeywordMatch.find()) {
|
||||
return new TextboxKeywordRule(simpleKeywordMatch.group(1), regions);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a loyalty ability. Must be right at the start of the rule
|
||||
{
|
||||
Matcher loyaltyMatch = LoyaltyAbilityPattern.matcher(rule);
|
||||
if (loyaltyMatch.find()) {
|
||||
// Get the loyalty change
|
||||
if (loyaltyMatch.group(2).equals("X")) {
|
||||
loyaltyChange = TextboxLoyaltyRule.MINUS_X;
|
||||
} else {
|
||||
loyaltyChange = Integer.parseInt(loyaltyMatch.group(2));
|
||||
if (loyaltyMatch.group(1).equals("-")) {
|
||||
loyaltyChange = -loyaltyChange;
|
||||
}
|
||||
}
|
||||
isLoyalty = true;
|
||||
|
||||
// Go past the match
|
||||
index = loyaltyMatch.group().length();
|
||||
}
|
||||
}
|
||||
|
||||
Deque<Integer> openingStack = new ArrayDeque<>();
|
||||
StringBuilder build = new StringBuilder();
|
||||
while (index < rule.length()) {
|
||||
int initialIndex = index;
|
||||
char ch = rule.charAt(index);
|
||||
switch (ch) {
|
||||
case '{': {
|
||||
// Handling for `{this}`
|
||||
int closeIndex = rule.indexOf('}', index);
|
||||
if (closeIndex == -1) {
|
||||
// Malformed input, nothing to do
|
||||
++index;
|
||||
++outputIndex;
|
||||
build.append(ch);
|
||||
} else {
|
||||
String contents = rule.substring(index + 1, closeIndex);
|
||||
if (contents.equals("this") || contents.equals("source")) {
|
||||
// Replace {this} with the card's name
|
||||
String cardName = source.getName();
|
||||
build.append(cardName);
|
||||
index += contents.length() + 2;
|
||||
outputIndex += cardName.length();
|
||||
} else {
|
||||
Image symbol = ManaSymbols.getSizedManaSymbol(contents.replace("/", ""), 10);
|
||||
if (symbol != null) {
|
||||
// Mana or other inline symbol
|
||||
build.append('#');
|
||||
regions.add(new TextboxRule.EmbeddedSymbol(contents, outputIndex));
|
||||
++outputIndex;
|
||||
index = closeIndex + 1;
|
||||
} else {
|
||||
// Bad entry
|
||||
build.append('{');
|
||||
build.append(contents);
|
||||
build.append('}');
|
||||
index = closeIndex + 1;
|
||||
outputIndex += (contents.length() + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '&':
|
||||
// Handling for `—`
|
||||
if (rule.startsWith("—", index)) {
|
||||
build.append('—');
|
||||
index += 7;
|
||||
++outputIndex;
|
||||
} else if (rule.startsWith("&bull", index)) {
|
||||
build.append('•');
|
||||
index += 5;
|
||||
++outputIndex;
|
||||
} else {
|
||||
LOGGER.error("Bad &...; sequence `" + rule.substring(index + 1, index + 10) + "` in rule.");
|
||||
build.append('&');
|
||||
++index;
|
||||
++outputIndex;
|
||||
}
|
||||
break;
|
||||
case '<': {
|
||||
// Handling for `<i>` and `<br/>`
|
||||
int closeIndex = rule.indexOf('>', index);
|
||||
if (closeIndex != -1) {
|
||||
// Is a tag
|
||||
String tag = rule.substring(index + 1, closeIndex);
|
||||
if (tag.charAt(tag.length() - 1) == '/') {
|
||||
// Pure closing tag (like <br/>)
|
||||
if (tag.equals("br/")) {
|
||||
build.append('\n');
|
||||
++outputIndex;
|
||||
} else {
|
||||
// Unknown
|
||||
build.append('<').append(tag).append('>');
|
||||
outputIndex += (tag.length() + 2);
|
||||
}
|
||||
} else if (tag.charAt(0) == '/') {
|
||||
// Opening index for the tag
|
||||
int openingIndex;
|
||||
if (openingStack.isEmpty()) {
|
||||
// Malformed input, just make an empty interval
|
||||
openingIndex = outputIndex;
|
||||
} else {
|
||||
openingIndex = openingStack.pop();
|
||||
}
|
||||
|
||||
// What tag is it?
|
||||
switch (tag) {
|
||||
case "/i":
|
||||
// Italics
|
||||
regions.add(new TextboxRule.ItalicRegion(openingIndex, outputIndex));
|
||||
break;
|
||||
case "/b":
|
||||
// Bold, see if it's a level ability
|
||||
String content = build.substring(openingIndex);
|
||||
Matcher levelMatch = LevelAbilityPattern.matcher(content);
|
||||
if (levelMatch.find()) {
|
||||
try {
|
||||
levelFrom = Integer.parseInt(levelMatch.group(1));
|
||||
if (!levelMatch.group(2).equals("")) {
|
||||
levelTo = Integer.parseInt(levelMatch.group(2));
|
||||
}
|
||||
if (!levelMatch.group(3).equals("")) {
|
||||
levelTo = TextboxLevelRule.AND_HIGHER;
|
||||
}
|
||||
isLeveler = true;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Bad leveler levels in rule `" + rule + "`.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Unknown
|
||||
build.append('<').append(tag).append('>');
|
||||
outputIndex += (tag.length() + 2);
|
||||
break;
|
||||
}
|
||||
} else // Is it a <br> tag special case? [Why can't it have a closing `/`... =( ]
|
||||
{
|
||||
if (tag.equals("br")) {
|
||||
build.append('\n');
|
||||
++outputIndex;
|
||||
} else {
|
||||
// Opening tag
|
||||
openingStack.push(outputIndex);
|
||||
}
|
||||
}
|
||||
// Skip characters
|
||||
index = closeIndex + 1;
|
||||
} else {
|
||||
// Malformed tag
|
||||
build.append('<');
|
||||
++outputIndex;
|
||||
++index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Normal character
|
||||
++index;
|
||||
++outputIndex;
|
||||
build.append(ch);
|
||||
break;
|
||||
}
|
||||
if (outputIndex != build.length()) {
|
||||
// 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.");
|
||||
|
||||
// Bail out
|
||||
build = new StringBuilder(rule);
|
||||
regions.clear();
|
||||
break;
|
||||
}
|
||||
if (index == initialIndex) {
|
||||
// Somehow our parsing failed to consume the
|
||||
LOGGER.error("Failed on rule `" + rule + "` due to not consuming a character.");
|
||||
|
||||
// Bail out
|
||||
build = new StringBuilder(rule);
|
||||
regions.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build and return the rule
|
||||
rule = build.toString();
|
||||
if (isLoyalty) {
|
||||
return new TextboxLoyaltyRule(rule, regions, loyaltyChange);
|
||||
} else if (isLeveler) {
|
||||
return new TextboxLevelRule(rule, regions, levelFrom, levelTo);
|
||||
} else {
|
||||
return new TextboxRule(rule, regions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue