GUI: improved rendering of Adventure cards (#11764)

* GUI: Improved rendering of Adventure cards

---------

Co-authored-by: Matthew Wilson <matthew_w@vaadin.com>
This commit is contained in:
Matthew Wilson 2024-02-09 09:55:34 +02:00 committed by GitHub
parent 0f9e36ace0
commit 506e94d519
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 156 additions and 37 deletions

View file

@ -408,9 +408,26 @@ public class TestCardRenderDialog extends MageDialog {
/* test split, transform and mdf in hands
cardViews.add(createHandCard(game, playerYou.getId(), "SOI", "97")); // Accursed Witch
cardViews.add(createHandCard(game, playerYou.getId(), "UMA", "225")); // Fire // Ice
cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "14")); // Giant Killer
cardViews.add(createHandCard(game, playerYou.getId(), "ZNR", "134")); // Akoum Warrior
cardViews.add(createHandCard(game, playerYou.getId(), "UMA", "225")); // Fire // Ice
cardViews.add(createHandCard(game, playerYou.getId(), "DGM", "123")); // Beck // Call
cardViews.add(createHandCard(game, playerYou.getId(), "AKH", "210")); // Dusk // Dawn
//*/
//* test adventure cards in hands
cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "14")); // Giant Killer
cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "222")); // Cruel Somnophage
cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "227")); // Gingerbread Hunter
cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "221")); // Callous Sell-Sword
cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "149")); // Beanstalk Giant
cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "220")); // Beluna Grandsquall
//*/
/* test saga and case cards in hands
cardViews.add(createHandCard(game, playerYou.getId(), "DOM", "90")); // The Eldest Reborn
cardViews.add(createHandCard(game, playerYou.getId(), "MH2", "259")); // Urza's Saga
cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "113")); // Case of the Burning Masks
cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "155")); // Case of the Locked Hothouse
//*/
/* test meld cards in hands and battlefield
@ -441,7 +458,7 @@ public class TestCardRenderDialog extends MageDialog {
//cardViews.add(createPermanentCard(game, playerYou.getId(), "KHM", "50", 1, 1, 0, true, false, additionalIcons)); // Cosima, God of the Voyage
//*/
//* test tokens
/* test tokens
// normal
cardViews.add(createToken(game, playerYou.getId(), new ZombieToken(), "10E", 0, false, false));
cardViews.add(createToken(game, playerYou.getId(), new ZombieToken(), "XXX", 1, false, false));

View file

@ -106,8 +106,8 @@ public abstract class CardRenderer {
protected int borderWidth;
// The parsed text of the card
protected final ArrayList<TextboxRule> textboxRules = new ArrayList<>();
protected final ArrayList<TextboxRule> textboxKeywords = new ArrayList<>();
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

View file

@ -1,6 +1,5 @@
package org.mage.card.arcane;
import mage.cards.ArtRect;
import mage.view.CardView;
/**
@ -12,9 +11,7 @@ public class CardRendererFactory {
}
public CardRenderer create(CardView card) {
if (card.isSplitCard() && card.getArtRect() != ArtRect.SPLIT_FUSED) {
// Split fused cards still render with the normal frame, showing all abilities
// from both halves in one frame.
if (card.isSplitCard()) {
return new ModernSplitCardRenderer(card);
} else {
return new ModernCardRenderer(card);

View file

@ -15,14 +15,10 @@ import org.apache.log4j.Logger;
import static org.mage.card.arcane.ManaSymbols.getSizedManaSymbol;
import static org.mage.card.arcane.ModernCardResourceLoader.*;
import javax.swing.*;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
@ -147,6 +143,8 @@ public class ModernCardRenderer extends CardRenderer {
public static final Color ERROR_COLOR = new Color(255, 0, 255);
static String SUB_TYPE_ADVENTURE = "Adventure";
///////////////////////////////////////////////////////////////////////////
// Layout metrics for modern border cards
// How far the main box, art, and name / type line are inset from the
@ -189,7 +187,10 @@ public class ModernCardRenderer extends CardRenderer {
protected Font ptTextFont;
// Processed mana cost string
protected final String manaCostString;
protected String manaCostString;
// Is an adventure
protected boolean isAdventure = false;
public ModernCardRenderer(CardView card) {
// Pass off to parent
@ -197,6 +198,14 @@ public class ModernCardRenderer extends CardRenderer {
// Mana cost string
manaCostString = ManaSymbols.getClearManaCost(cardView.getManaCostStr());
if (cardView.isSplitCard()) {
isAdventure = cardView.getRightSplitTypeLine().contains(SUB_TYPE_ADVENTURE);
}
}
protected boolean isAdventure() {
return isAdventure;
}
@Override
@ -406,19 +415,7 @@ public class ModernCardRenderer extends CardRenderer {
if (cardView.getMageObjectType() == MageObjectType.SPELL) {
useFaceArt = false;
ArtRect rect = cardView.getArtRect();
if (rect == ArtRect.SPLIT_FUSED) {
// Special handling for fused, draw the art from both halves stacked on top of one and other
// each filling half of the art rect
drawArtIntoRect(g,
totalContentInset + 1, totalContentInset + boxHeight,
contentWidth - 2, (typeLineY - totalContentInset - boxHeight) / 2,
ArtRect.SPLIT_LEFT.rect, useInventionFrame());
drawArtIntoRect(g,
totalContentInset + 1, totalContentInset + boxHeight + (typeLineY - totalContentInset - boxHeight) / 2,
contentWidth - 2, (typeLineY - totalContentInset - boxHeight) / 2,
ArtRect.SPLIT_RIGHT.rect, useInventionFrame());
return;
} else if (rect != ArtRect.NORMAL) {
if (rect != ArtRect.NORMAL) {
sourceRect = rect.rect;
shouldPreserveAspect = false;
}
@ -701,6 +698,10 @@ public class ModernCardRenderer extends CardRenderer {
drawRulesText(g, textboxKeywords, textboxRules,
contentWidth / 2 + totalContentInset + 4, totalContentInset + boxHeight + 2,
contentWidth / 2 - 8, typeLineY - totalContentInset - boxHeight - 6, false);
} else if (isAdventure) {
drawRulesText(g, textboxKeywords, textboxRules,
contentWidth / 2 + totalContentInset + 4, typeLineY + boxHeight + 2,
contentWidth / 2 - 8, cardHeight - typeLineY - boxHeight - 4 - borderWidth * 3, false);
} else if (!isZenUst) {
drawRulesText(g, textboxKeywords, textboxRules,
totalContentInset + 2, typeLineY + boxHeight + 2,

View file

@ -18,6 +18,14 @@ import java.util.List;
*/
public class ModernSplitCardRenderer extends ModernCardRenderer {
public static final Color ADVENTURE_BOX_WHITE = new Color(135, 122, 103);
public static final Color ADVENTURE_BOX_BLUE = new Color(2, 96, 131);
public static final Color ADVENTURE_BOX_BLACK = new Color(52, 44, 46);
public static final Color ADVENTURE_BOX_RED = new Color(126, 61, 42);
public static final Color ADVENTURE_BOX_GREEN = new Color(9, 51, 30);
public static final Color ADVENTURE_BOX_GOLD = new Color(118, 92, 42);
public static final Color ADVENTURE_BOX_COLORLESS = new Color(131, 133, 135);
static String RULES_MARK_FUSE = "Fuse";
static String RULES_MARK_AFTERMATH = "Aftermath";
@ -29,8 +37,8 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
String typeLineString;
String manaCostString;
ObjectColor color;
List<TextboxRule> rules = new ArrayList<>();
List<TextboxRule> keywords = new ArrayList<>();
ArrayList<TextboxRule> rules = new ArrayList<>();
ArrayList<TextboxRule> keywords = new ArrayList<>();
}
private static final List<CardType> ONLY_LAND_TYPE = Arrays.asList(CardType.LAND);
@ -47,6 +55,13 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
private boolean isFuse = false;
private boolean isAftermath = false;
private static String trimAdventure(String rule) {
if (rule.startsWith("Adventure")) {
return rule.substring(rule.lastIndexOf("&mdash;") + 8);
}
return rule;
}
public ModernSplitCardRenderer(CardView view) {
super(view);
@ -56,7 +71,15 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
rightHalf.color = new ObjectColor(cardView.getRightSplitCostsStr());
leftHalf.color = new ObjectColor(cardView.getLeftSplitCostsStr());
parseRules(view.getRightSplitRules(), rightHalf.keywords, rightHalf.rules);
if (isAdventure()) {
List<String> trimmedRules = new ArrayList<>();
for (String rule : view.getRightSplitRules()) {
trimmedRules.add(trimAdventure(rule));
}
parseRules(trimmedRules, rightHalf.keywords, rightHalf.rules);
} else {
parseRules(view.getRightSplitRules(), rightHalf.keywords, rightHalf.rules);
}
parseRules(view.getLeftSplitRules(), leftHalf.keywords, leftHalf.rules);
rightHalf.typeLineString = cardView.getRightSplitTypeLine();
@ -71,7 +94,12 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
// It's easier for rendering to swap the card halves here because for aftermath cards
// they "rotate" in opposite directions making consquence and normal split cards
// have the "right" vs "left" as the top half.
if (!isAftermath()) {
// Adventures are treated differently and not rotated at all.
if (isAdventure()) {
manaCostString = leftHalf.manaCostString;
textboxKeywords = leftHalf.keywords;
textboxRules = leftHalf.rules;
} else if (!isAftermath()) {
HalfCardProps tmp = leftHalf;
leftHalf = rightHalf;
rightHalf = tmp;
@ -131,6 +159,8 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
protected void drawBackground(Graphics2D g) {
if (cardView.isFaceDown()) {
drawCardBack(g);
} if (isAdventure()) {
super.drawBackground(g);
} else {
{ // Left half background (top of the card)
// Set texture to paint the left with
@ -174,7 +204,9 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
@Override
protected void drawArt(Graphics2D g) {
if (artImage != null && !cardView.isFaceDown()) {
if (isAdventure) {
super.drawArt(g);
} else if (artImage != null && !cardView.isFaceDown()) {
if (isAftermath()) {
Rectangle2D topRect = ArtRect.AFTERMATH_TOP.rect;
int topLineY = (int) (leftHalf.ch * TYPE_LINE_Y_FRAC);
@ -286,7 +318,43 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
@Override
protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) {
if (isAftermath()) {
if (isAdventure()) {
super.drawFrame(g, attribs, image, lessOpaqueRulesTextBox);
CardPanelAttributes adventureAttribs = new CardPanelAttributes(
attribs.cardWidth, attribs.cardHeight, attribs.isChoosable,
attribs.isSelected, true);
// Draw the adventure name line box
g.setPaint(getBoxColor(rightHalf.color, cardView.getCardTypes(), true));
g.fillRect(totalContentInset, typeLineY + boxHeight + 1,
contentWidth / 2 - 1, boxHeight - 2);
// Draw the adventure type line box
g.setPaint(getAdventureBoxColor(rightHalf.color));
g.fillRect(totalContentInset , typeLineY + boxHeight * 2 - 1,
contentWidth / 2 - 1, boxHeight - 2);
// Draw the adventure text box
g.setPaint(getTextboxPaint(rightHalf.color, cardView.getCardTypes(), cardWidth, lessOpaqueRulesTextBox));
g.fillRect(totalContentInset, typeLineY + boxHeight * 3 - 3,
contentWidth / 2 - 1, cardHeight - borderWidth * 3 - typeLineY - boxHeight * 3 + 2);
// Draw the adventure name line
drawNameLine(g, adventureAttribs, rightHalf.name, rightHalf.manaCostString,
totalContentInset + 2, typeLineY + boxHeight,
contentWidth / 2 - 8, boxHeight - 2);
// Draw the adventure type line
drawTypeLine(g, adventureAttribs, rightHalf.typeLineString,
totalContentInset + 2, typeLineY + boxHeight * 2 - 2,
contentWidth / 2 - 8, boxHeight - 2, true);
// Draw the adventure textbox rules
drawRulesText(g, rightHalf.keywords, rightHalf.rules,
totalContentInset + 3, typeLineY + boxHeight * 3 - 1,
contentWidth / 2 - 8, cardHeight - borderWidth * 3 - typeLineY - boxHeight * 3 + 2, false);
} else if (isAftermath()) {
drawSplitHalfFrame(getUnmodifiedHalfContext(g), attribs, leftHalf, (int) (leftHalf.ch * TYPE_LINE_Y_FRAC));
drawSplitHalfFrame(getAftermathHalfContext(g), attribs, rightHalf, (rightHalf.ch - boxHeight) / 2);
} else {
@ -308,4 +376,24 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
}
}
}
protected Color getAdventureBoxColor(ObjectColor colors) {
if (colors.isMulticolored()) {
return ADVENTURE_BOX_GOLD;
} else if (colors.isColorless()) {
return ADVENTURE_BOX_COLORLESS;
} else if (colors.isWhite()) {
return ADVENTURE_BOX_WHITE;
} else if (colors.isBlue()) {
return ADVENTURE_BOX_BLUE;
} else if (colors.isBlack()) {
return ADVENTURE_BOX_BLACK;
} else if (colors.isRed()) {
return ADVENTURE_BOX_RED;
} else if (colors.isGreen()) {
return ADVENTURE_BOX_GREEN;
} else {
return ERROR_COLOR;
}
}
}

View file

@ -398,11 +398,20 @@ public class CardView extends SimpleCardView {
this.manaCostLeftStr = mainCard.getLeftHalfCard().getManaCostSymbols();
this.manaCostRightStr = mainCard.getRightHalfCard().getManaCostSymbols();
} else if (card instanceof AdventureCard) {
this.isSplitCard = true;
AdventureCard adventureCard = ((AdventureCard) card);
leftSplitName = adventureCard.getName();
leftSplitCostsStr = String.join("", adventureCard.getManaCostSymbols());
leftSplitRules = adventureCard.getSharedRules(game);
leftSplitTypeLine = getCardTypeLine(game, adventureCard);
AdventureCardSpell adventureCardSpell = adventureCard.getSpellCard();
rightSplitName = adventureCardSpell.getName();
rightSplitCostsStr = String.join("", adventureCardSpell.getManaCostSymbols());
rightSplitRules = adventureCardSpell.getRules(game);
rightSplitTypeLine = getCardTypeLine(game, adventureCardSpell);
fullCardName = adventureCard.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + adventureCardSpell.getName();
this.manaCostLeftStr = adventureCardSpell.getManaCostSymbols();
this.manaCostRightStr = adventureCard.getManaCostSymbols();
this.manaCostLeftStr = adventureCard.getManaCostSymbols();
this.manaCostRightStr = adventureCardSpell.getManaCostSymbols();
} else if (card instanceof MockCard) {
// deck editor cards
fullCardName = ((MockCard) card).getFullName(true);

View file

@ -9,6 +9,7 @@ import mage.constants.SpellAbilityType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
import mage.util.CardUtil;
import java.util.List;
import java.util.UUID;
@ -129,6 +130,12 @@ public abstract class AdventureCard extends CardImpl {
return super.getAbilities(game);
}
public List<String> getSharedRules(Game game) {
// rules without spellcard
Abilities<Ability> sourceAbilities = this.getSharedAbilities(game);
return CardUtil.getCardRulesWithAdditionalInfo(game, this.getId(), this.getName(), sourceAbilities, sourceAbilities);
}
@Override
public void setOwnerId(UUID ownerId) {
super.setOwnerId(ownerId);