Fixes and additions to card rendering

* Vehicles are rendered with a vehicle background and changed P/T box color
* Kaladesh Inventions are rendered with the invention frame
* Kaladesh Inventions use the full-frame art if they detect that you have that full art downloaded instead of normal card images.
* Changed land text-box colors to be a bit more saturated to make it clearer what colors they produce
* Added mana symbol in the textbox of basic lands instead of rules text as long as they have no additional abilities.
* Fixed problem with triggered abilities on the stack showing up with a type of "??? Ability". Not sure what the real cause is but those abilities have abilityType = null, just worked around it.
This commit is contained in:
Mark Langen 2016-09-14 19:32:53 -06:00
parent 0c8ff64027
commit 577bdaea84
36 changed files with 297 additions and 47 deletions

View file

@ -14,6 +14,8 @@ import java.awt.Paint;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import mage.abilities.Ability;
import mage.client.dialog.PreferencesDialog;
import mage.constants.AbilityType;
import mage.constants.CardType;
@ -345,6 +347,9 @@ public abstract class CardRenderer {
return "Triggered Ability";
} else if (AbilityType.ACTIVATED.equals(cardView.getAbilityType())) {
return "Activated Ability";
} else if (cardView.getAbilityType() == null) {
// TODO: Triggered abilities waiting to be put onto the stack have abilityType = null. Figure out why
return "Triggered Ability";
} else {
return "??? Ability";
}

View file

@ -5,16 +5,7 @@
*/
package org.mage.card.arcane;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.TexturePaint;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
@ -32,7 +23,9 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.ImageIcon;
import mage.ObjectColor;
import mage.cards.FrameStyle;
import mage.client.dialog.PreferencesDialog;
import mage.constants.CardType;
import mage.view.CardView;
@ -77,6 +70,12 @@ public class ModernCardRenderer extends CardRenderer {
return new TexturePaint(img, new Rectangle(0, 0, img.getWidth(), img.getHeight()));
}
private static BufferedImage loadFramePart(String name) {
URL url = ModernCardRenderer.class.getResource("/cardrender/" + name + ".png");
ImageIcon icon = new ImageIcon(url);
return CardRendererUtils.toBufferedImage(icon.getImage());
}
private static Font loadFont(String name) {
try {
return Font.createFont(
@ -99,6 +98,9 @@ public class ModernCardRenderer extends CardRenderer {
public static Paint BG_TEXTURE_GOLD = loadBackgroundTexture("gold");
public static Paint BG_TEXTURE_ARTIFACT = loadBackgroundTexture("artifact");
public static Paint BG_TEXTURE_LAND = loadBackgroundTexture("land");
public static Paint BG_TEXTURE_VEHICLE = loadBackgroundTexture("vehicle");
public static BufferedImage FRAME_INVENTION = loadFramePart("invention_frame");
public static Color BORDER_WHITE = new Color(216, 203, 188);
public static Color BORDER_BLUE = new Color(20, 121, 175);
@ -117,6 +119,8 @@ public class ModernCardRenderer extends CardRenderer {
public static Color BOX_GOLD = new Color(223, 195, 136);
public static Color BOX_COLORLESS = new Color(220, 228, 232);
public static Color BOX_LAND = new Color(220, 215, 213);
public static Color BOX_INVENTION = new Color(209, 97, 33);
public static Color BOX_VEHICLE = new Color(155, 105, 60);
public static Color BOX_WHITE_NIGHT = new Color(169, 160, 145);
public static Color BOX_BLUE_NIGHT = new Color(46, 133, 176);
@ -126,6 +130,13 @@ public class ModernCardRenderer extends CardRenderer {
public static Color BOX_GOLD_NIGHT = new Color(171, 134, 70);
public static Color BOX_COLORLESS_NIGHT = new Color(118, 147, 158);
public static Color LAND_TEXTBOX_WHITE = new Color(248, 232, 188, 244);
public static Color LAND_TEXTBOX_BLUE = new Color(189, 212, 236, 244);
public static Color LAND_TEXTBOX_BLACK = new Color(174, 164, 162, 244);
public static Color LAND_TEXTBOX_RED = new Color(242, 168, 133, 244);
public static Color LAND_TEXTBOX_GREEN = new Color(198, 220, 198, 244);
public static Color LAND_TEXTBOX_GOLD = new Color(236, 229, 207, 244);
public static Color TEXTBOX_WHITE = new Color(252, 249, 244, 244);
public static Color TEXTBOX_BLUE = new Color(229, 238, 247, 244);
public static Color TEXTBOX_BLACK = new Color(241, 241, 240, 244);
@ -272,7 +283,7 @@ public class ModernCardRenderer extends CardRenderer {
drawCardBack(g);
} else {
// Set texture to paint with
g.setPaint(getBackgroundPaint(cardView.getColor(), cardView.getCardTypes()));
g.setPaint(getBackgroundPaint(cardView.getColor(), cardView.getCardTypes(), cardView.getSubTypes()));
// Draw main part (most of card)
g.fillRoundRect(
@ -299,7 +310,9 @@ public class ModernCardRenderer extends CardRenderer {
*/
private Rectangle2D getArtRect() {
Rectangle2D rect;
if (cardView.getFrameStyle().isFullArt() || cardView.isToken()) {
if (useInventionFrame()) {
rect = new Rectangle2D.Float(0, 0, 1, 1);
} else if (cardView.getFrameStyle().isFullArt() || cardView.isToken()) {
rect = new Rectangle2D.Float(.079f, .11f, .84f, .63f);
} else {
rect = new Rectangle2D.Float(.079f, .11f, .84f, .42f);
@ -317,6 +330,23 @@ public class ModernCardRenderer extends CardRenderer {
}
}
protected boolean isSourceArtFullArt() {
int color = artImage.getRGB(0, artImage.getHeight() / 2);
return (((color & 0x00FF0000) > 0x00200000) ||
((color & 0x0000FF00) > 0x00002000) ||
((color & 0x000000FF) > 0x00000020));
}
private boolean useInventionFrame() {
if (cardView.getFrameStyle() != FrameStyle.KLD_INVENTION) {
return false;
} else if (artImage == null) {
return true;
} else {
return isSourceArtFullArt();
}
}
@Override
protected void drawArt(Graphics2D g) {
if (artImage != null && !cardView.isFaceDown()) {
@ -331,22 +361,33 @@ public class ModernCardRenderer extends CardRenderer {
double targetWidth = contentWidth - 2;
double targetHeight = typeLineY - totalContentInset - boxHeight;
double targetAspect = targetWidth / targetHeight;
if (targetAspect * artHeight < artWidth) {
// Trim off some width
artWidth = targetAspect * artHeight;
if (useInventionFrame()) {
// No adjustment to art
} else {
// Trim off some height
artHeight = artWidth / targetAspect;
if (targetAspect * artHeight < artWidth) {
// Trim off some width
artWidth = targetAspect * artHeight;
} else {
// Trim off some height
artHeight = artWidth / targetAspect;
}
}
try {
BufferedImage subImg
= artImage.getSubimage(
(int) (artRect.getX() * fullCardImgWidth), (int) (artRect.getY() * fullCardImgHeight),
(int) artWidth, (int) artHeight);
g.drawImage(subImg,
totalContentInset + 1, totalContentInset + boxHeight,
(int) targetWidth, (int) targetHeight,
null);
if (useInventionFrame()) {
g.drawImage(subImg,
borderWidth, borderWidth,
cardWidth - 2*borderWidth, cardHeight - 2*borderWidth,
null);
} else {
g.drawImage(subImg,
totalContentInset + 1, totalContentInset + boxHeight,
(int) targetWidth, (int) targetHeight,
null);
}
} catch (RasterFormatException e) {
// At very small card sizes we may encounter a problem with rounding error making the rect not fit
}
@ -363,14 +404,30 @@ public class ModernCardRenderer extends CardRenderer {
Paint textboxPaint = getTextboxPaint(frameColors, cardView.getCardTypes(), cardWidth);
Paint borderPaint = getBorderPaint(frameColors, cardView.getCardTypes(), cardWidth);
// Special colors
if (cardView.getFrameStyle() == FrameStyle.KLD_INVENTION) {
boxColor = BOX_INVENTION;
}
// Draw the main card content border
g.setPaint(borderPaint);
g.drawRect(
totalContentInset, totalContentInset,
contentWidth - 1, cardHeight - borderWidth * 3 - totalContentInset - 1);
if (cardView.getFrameStyle() == FrameStyle.KLD_INVENTION) {
g.drawImage(FRAME_INVENTION, 0, 0, cardWidth, cardHeight, null);
g.drawRect(
totalContentInset, typeLineY,
contentWidth - 1, cardHeight - borderWidth * 3 - typeLineY - 1);
} else {
g.drawRect(
totalContentInset, totalContentInset,
contentWidth - 1, cardHeight - borderWidth * 3 - totalContentInset - 1);
}
// Draw the textbox fill
g.setPaint(textboxPaint);
if (useInventionFrame()) {
g.setPaint(new Color(255, 255, 255, 150));
} else {
g.setPaint(textboxPaint);
}
g.fillRect(
totalContentInset + 1, typeLineY,
contentWidth - 2, cardHeight - borderWidth * 3 - typeLineY - 1);
@ -383,16 +440,18 @@ public class ModernCardRenderer extends CardRenderer {
cardWidth / 16, cardHeight - typeLineY - boxHeight - borderWidth * 3);
}
// Draw a shadow highlight at the right edge of the content frame
g.setColor(new Color(0, 0, 0, 100));
g.fillRect(
totalContentInset - 1, totalContentInset,
1, cardHeight - borderWidth * 3 - totalContentInset - 1);
if (cardView.getFrameStyle() != FrameStyle.KLD_INVENTION) {
// Draw a shadow highlight at the right edge of the content frame
g.setColor(new Color(0, 0, 0, 100));
g.fillRect(
totalContentInset - 1, totalContentInset,
1, cardHeight - borderWidth * 3 - totalContentInset - 1);
// Draw a shadow highlight separating the card art and rest of frame
g.drawRect(
totalContentInset + 1, totalContentInset + boxHeight,
contentWidth - 3, typeLineY - totalContentInset - boxHeight - 1);
// Draw a shadow highlight separating the card art and rest of frame
g.drawRect(
totalContentInset + 1, totalContentInset + boxHeight,
contentWidth - 3, typeLineY - totalContentInset - boxHeight - 1);
}
// Draw the name line box
CardRendererUtils.drawRoundedBox(g,
@ -549,7 +608,8 @@ public class ModernCardRenderer extends CardRenderer {
int partWidth = (int) Math.max(30, 0.20f * cardWidth);
// Is it a creature?
if (cardView.getCardTypes().contains(CardType.CREATURE) || cardView.getSubTypes().contains("Vehicle")) {
boolean isVehicle = cardView.getSubTypes().contains("Vehicle");
if (cardView.getCardTypes().contains(CardType.CREATURE) || isVehicle) {
int x = cardWidth - borderWidth - partWidth;
// Draw PT box
@ -558,7 +618,7 @@ public class ModernCardRenderer extends CardRenderer {
partWidth, boxHeight,
contentInset,
borderPaint,
fill);
isVehicle ? BOX_VEHICLE : fill);
// Draw shadow line top
g.setColor(new Color(0, 0, 0, 150));
@ -567,7 +627,7 @@ public class ModernCardRenderer extends CardRenderer {
partWidth - 2 * contentInset, 1);
// Draw text
g.setColor(getBoxTextColor());
g.setColor(isVehicle ? Color.white : getBoxTextColor());
g.setFont(ptTextFont);
String ptText = cardView.getPower() + "/" + cardView.getToughness();
int ptTextWidth = g.getFontMetrics().stringWidth(ptText);
@ -703,6 +763,12 @@ public class ModernCardRenderer extends CardRenderer {
allRules.add(keywordsRule);
}
// Basic mana draw mana symbol in textbox (for basic lands)
if (allRules.size() == 1 && allRules.get(0) instanceof TextboxBasicManaRule) {
drawBasicManaTextbox(g, x, y, w, h, ((TextboxBasicManaRule)allRules.get(0)).getBasicManaSymbol());
return;
}
// Go through possible font sizes in descending order to find the best fit
RuleLayout bestLayout = null;
for (int fontSize: RULES_TEXT_FONT_SIZES) {
@ -745,6 +811,14 @@ public class ModernCardRenderer extends CardRenderer {
}
}
// Draw a basic mana symbol
private void drawBasicManaTextbox(Graphics2D g, int x, int y, int w, int h, String symbol) {
String symbs = symbol;
int symbHeight = (int)(0.8*h);
int manaCostWidth = CardRendererUtils.getManaCostWidth(symbs, symbHeight);
ManaSymbols.draw(g, symbs, x + (w - manaCostWidth)/2, y + (h - symbHeight)/2, symbHeight);
}
// Get the first line of the textbox, the keyword string
private String getKeywordRulesString() {
StringBuilder builder = new StringBuilder();
@ -944,8 +1018,10 @@ public class ModernCardRenderer extends CardRenderer {
// Determine which background paint to use from a set of colors
// and the current card.
protected static Paint getBackgroundPaint(ObjectColor colors, Collection<CardType> types) {
if (types.contains(CardType.LAND)) {
protected static Paint getBackgroundPaint(ObjectColor colors, Collection<CardType> types, Collection<String> subTypes) {
if (subTypes.contains("Vehicle")) {
return BG_TEXTURE_VEHICLE;
} else if (types.contains(CardType.LAND)) {
return BG_TEXTURE_LAND;
} else if (types.contains(CardType.ARTIFACT)) {
return BG_TEXTURE_ARTIFACT;
@ -1074,22 +1150,53 @@ public class ModernCardRenderer extends CardRenderer {
}
}
// Determine the land textbox color for a single color. Uses the same colors as the
// type / name line.
protected static Color getLandTextboxColor(ObjectColor color) {
if (color.isWhite()) {
return LAND_TEXTBOX_WHITE;
} else if (color.isBlue()) {
return LAND_TEXTBOX_BLUE;
} else if (color.isBlack()) {
return LAND_TEXTBOX_BLACK;
} else if (color.isRed()) {
return LAND_TEXTBOX_RED;
} else if (color.isGreen()) {
return LAND_TEXTBOX_GREEN;
} else {
return ERROR_COLOR;
}
}
// Determine the border paint to use, based on an ObjectColors
protected static Paint getTextboxPaint(ObjectColor colors, Collection<CardType> types, int width) {
if (colors.isMulticolored()) {
if (colors.getColorCount() == 2) {
List<ObjectColor> twoColors = colors.getColors();
Color[] translatedColors;
if (types.contains(CardType.LAND)) {
translatedColors = new Color[]{
getLandTextboxColor(twoColors.get(0)),
getLandTextboxColor(twoColors.get(1))
};
} else {
translatedColors = new Color[]{
getTextboxColor(twoColors.get(0)),
getTextboxColor(twoColors.get(1))
};
}
// Special case for two colors, gradient paint
return new LinearGradientPaint(
0, 0, width, 0,
new float[]{0.4f, 0.6f},
new Color[]{
getTextboxColor(twoColors.get(0)),
getTextboxColor(twoColors.get(1))
});
translatedColors);
} else {
return TEXTBOX_GOLD;
if (types.contains(CardType.LAND)) {
return LAND_TEXTBOX_GOLD;
} else {
return TEXTBOX_GOLD;
}
}
} else if (colors.isColorless()) {
if (types.contains(CardType.LAND)) {
@ -1098,7 +1205,11 @@ public class ModernCardRenderer extends CardRenderer {
return TEXTBOX_COLORLESS;
}
} else {
return getTextboxColor(colors);
if (types.contains(CardType.LAND)) {
return getLandTextboxColor(colors);
} else {
return getTextboxColor(colors);
}
}
}
}

View file

@ -0,0 +1,20 @@
package org.mage.card.arcane;
import java.util.ArrayList;
/**
* Created by stravant@gmail.com on 2016-09-14.
*/
public class TextboxBasicManaRule extends TextboxRule {
private String basicManaSymbol;
public TextboxBasicManaRule(String rule, ArrayList<AttributeRegion> regions, String basicManaSymbol) {
super(rule, regions, TextboxRuleType.BASIC_MANA);
this.basicManaSymbol = basicManaSymbol;
}
public String getBasicManaSymbol() {
return basicManaSymbol;
}
}

View file

@ -22,6 +22,7 @@ public class TextboxRuleParser {
private static final Logger LOGGER = Logger.getLogger(CardPanel.class);
private static final Pattern BasicManaAbility = Pattern.compile("\\{T\\}: Add \\{(\\w)\\} to your mana pool\\.");
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*$");
@ -35,7 +36,7 @@ public class TextboxRuleParser {
// List of regions to apply
ArrayList<TextboxRule.AttributeRegion> regions = new ArrayList<>();
// Leveler / loyalty
// Leveler / loyalty / basic
boolean isLeveler = false;
int levelFrom = 0;
int levelTo = 0;
@ -43,6 +44,9 @@ public class TextboxRuleParser {
boolean isLoyalty = false;
int loyaltyChange = 0;
boolean isBasicMana = false;
String basicManaSymbol = "";
// Parse the attributedString contents
int index = 0;
int outputIndex = 0;
@ -55,6 +59,15 @@ public class TextboxRuleParser {
}
}
// Is it a basic mana ability?
{
Matcher basicManaMatcher = BasicManaAbility.matcher(rule);
if (basicManaMatcher.find()) {
isBasicMana = true;
basicManaSymbol = basicManaMatcher.group(1);
}
}
// Check if it's a loyalty ability. Must be right at the start of the rule
{
Matcher loyaltyMatch = LoyaltyAbilityPattern.matcher(rule);
@ -244,6 +257,8 @@ public class TextboxRuleParser {
return new TextboxLoyaltyRule(rule, regions, loyaltyChange);
} else if (isLeveler) {
return new TextboxLevelRule(rule, regions, levelFrom, levelTo);
} else if (isBasicMana) {
return new TextboxBasicManaRule(rule, regions, basicManaSymbol);
} else {
return new TextboxRule(rule, regions);
}

View file

@ -19,6 +19,9 @@ public enum TextboxRuleType {
/* Loyalty abilities on planeswalkers */
LOYALTY,
/* Basic mana ability */
BASIC_MANA,
/* Levelup creature - static ability at a given level */
LEVEL