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 /* test split, transform and mdf in hands
cardViews.add(createHandCard(game, playerYou.getId(), "SOI", "97")); // Accursed Witch 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(), "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 /* 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 //cardViews.add(createPermanentCard(game, playerYou.getId(), "KHM", "50", 1, 1, 0, true, false, additionalIcons)); // Cosima, God of the Voyage
//*/ //*/
//* test tokens /* test tokens
// normal // normal
cardViews.add(createToken(game, playerYou.getId(), new ZombieToken(), "10E", 0, false, false)); cardViews.add(createToken(game, playerYou.getId(), new ZombieToken(), "10E", 0, false, false));
cardViews.add(createToken(game, playerYou.getId(), new ZombieToken(), "XXX", 1, false, false)); cardViews.add(createToken(game, playerYou.getId(), new ZombieToken(), "XXX", 1, false, false));
@ -826,7 +843,7 @@ public class TestCardRenderDialog extends MageDialog {
}//GEN-LAST:event_comboRenderModeItemStateChanged }//GEN-LAST:event_comboRenderModeItemStateChanged
private void sliderSizeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sliderSizeStateChanged private void sliderSizeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sliderSizeStateChanged
// from DragCardGrid // from DragCardGrid
// Fraction in [-1, 1] // Fraction in [-1, 1]
float sliderFrac = ((float) (sliderSize.getValue() - 50)) / 50; float sliderFrac = ((float) (sliderSize.getValue() - 50)) / 50;
// Convert to frac in [0.5, 2.0] exponentially // Convert to frac in [0.5, 2.0] exponentially

View file

@ -106,8 +106,8 @@ public abstract class CardRenderer {
protected int borderWidth; protected int borderWidth;
// The parsed text of the card // The parsed text of the card
protected final ArrayList<TextboxRule> textboxRules = new ArrayList<>(); protected ArrayList<TextboxRule> textboxRules = new ArrayList<>();
protected final ArrayList<TextboxRule> textboxKeywords = new ArrayList<>(); protected ArrayList<TextboxRule> textboxKeywords = new ArrayList<>();
// The Construtor // The Construtor
// The constructor should prepare all of the things that it can // The constructor should prepare all of the things that it can

View file

@ -1,6 +1,5 @@
package org.mage.card.arcane; package org.mage.card.arcane;
import mage.cards.ArtRect;
import mage.view.CardView; import mage.view.CardView;
/** /**
@ -12,9 +11,7 @@ public class CardRendererFactory {
} }
public CardRenderer create(CardView card) { public CardRenderer create(CardView card) {
if (card.isSplitCard() && card.getArtRect() != ArtRect.SPLIT_FUSED) { if (card.isSplitCard()) {
// Split fused cards still render with the normal frame, showing all abilities
// from both halves in one frame.
return new ModernSplitCardRenderer(card); return new ModernSplitCardRenderer(card);
} else { } else {
return new ModernCardRenderer(card); 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.ManaSymbols.getSizedManaSymbol;
import static org.mage.card.arcane.ModernCardResourceLoader.*; import static org.mage.card.arcane.ModernCardResourceLoader.*;
import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.font.*; import java.awt.font.*;
import java.awt.geom.*; import java.awt.geom.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator;
import java.text.AttributedString; import java.text.AttributedString;
import java.text.CharacterIterator; import java.text.CharacterIterator;
@ -147,6 +143,8 @@ public class ModernCardRenderer extends CardRenderer {
public static final Color ERROR_COLOR = new Color(255, 0, 255); public static final Color ERROR_COLOR = new Color(255, 0, 255);
static String SUB_TYPE_ADVENTURE = "Adventure";
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Layout metrics for modern border cards // Layout metrics for modern border cards
// How far the main box, art, and name / type line are inset from the // 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; protected Font ptTextFont;
// Processed mana cost string // Processed mana cost string
protected final String manaCostString; protected String manaCostString;
// Is an adventure
protected boolean isAdventure = false;
public ModernCardRenderer(CardView card) { public ModernCardRenderer(CardView card) {
// Pass off to parent // Pass off to parent
@ -197,6 +198,14 @@ public class ModernCardRenderer extends CardRenderer {
// Mana cost string // Mana cost string
manaCostString = ManaSymbols.getClearManaCost(cardView.getManaCostStr()); manaCostString = ManaSymbols.getClearManaCost(cardView.getManaCostStr());
if (cardView.isSplitCard()) {
isAdventure = cardView.getRightSplitTypeLine().contains(SUB_TYPE_ADVENTURE);
}
}
protected boolean isAdventure() {
return isAdventure;
} }
@Override @Override
@ -406,19 +415,7 @@ public class ModernCardRenderer extends CardRenderer {
if (cardView.getMageObjectType() == MageObjectType.SPELL) { if (cardView.getMageObjectType() == MageObjectType.SPELL) {
useFaceArt = false; useFaceArt = false;
ArtRect rect = cardView.getArtRect(); ArtRect rect = cardView.getArtRect();
if (rect == ArtRect.SPLIT_FUSED) { if (rect != ArtRect.NORMAL) {
// 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) {
sourceRect = rect.rect; sourceRect = rect.rect;
shouldPreserveAspect = false; shouldPreserveAspect = false;
} }
@ -701,6 +698,10 @@ public class ModernCardRenderer extends CardRenderer {
drawRulesText(g, textboxKeywords, textboxRules, drawRulesText(g, textboxKeywords, textboxRules,
contentWidth / 2 + totalContentInset + 4, totalContentInset + boxHeight + 2, contentWidth / 2 + totalContentInset + 4, totalContentInset + boxHeight + 2,
contentWidth / 2 - 8, typeLineY - totalContentInset - boxHeight - 6, false); 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) { } else if (!isZenUst) {
drawRulesText(g, textboxKeywords, textboxRules, drawRulesText(g, textboxKeywords, textboxRules,
totalContentInset + 2, typeLineY + boxHeight + 2, totalContentInset + 2, typeLineY + boxHeight + 2,

View file

@ -18,6 +18,14 @@ import java.util.List;
*/ */
public class ModernSplitCardRenderer extends ModernCardRenderer { 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_FUSE = "Fuse";
static String RULES_MARK_AFTERMATH = "Aftermath"; static String RULES_MARK_AFTERMATH = "Aftermath";
@ -29,8 +37,8 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
String typeLineString; String typeLineString;
String manaCostString; String manaCostString;
ObjectColor color; ObjectColor color;
List<TextboxRule> rules = new ArrayList<>(); ArrayList<TextboxRule> rules = new ArrayList<>();
List<TextboxRule> keywords = new ArrayList<>(); ArrayList<TextboxRule> keywords = new ArrayList<>();
} }
private static final List<CardType> ONLY_LAND_TYPE = Arrays.asList(CardType.LAND); 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 isFuse = false;
private boolean isAftermath = 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) { public ModernSplitCardRenderer(CardView view) {
super(view); super(view);
@ -56,7 +71,15 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
rightHalf.color = new ObjectColor(cardView.getRightSplitCostsStr()); rightHalf.color = new ObjectColor(cardView.getRightSplitCostsStr());
leftHalf.color = new ObjectColor(cardView.getLeftSplitCostsStr()); 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); parseRules(view.getLeftSplitRules(), leftHalf.keywords, leftHalf.rules);
rightHalf.typeLineString = cardView.getRightSplitTypeLine(); 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 // 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 // they "rotate" in opposite directions making consquence and normal split cards
// have the "right" vs "left" as the top half. // 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; HalfCardProps tmp = leftHalf;
leftHalf = rightHalf; leftHalf = rightHalf;
rightHalf = tmp; rightHalf = tmp;
@ -131,6 +159,8 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
protected void drawBackground(Graphics2D g) { protected void drawBackground(Graphics2D g) {
if (cardView.isFaceDown()) { if (cardView.isFaceDown()) {
drawCardBack(g); drawCardBack(g);
} if (isAdventure()) {
super.drawBackground(g);
} else { } else {
{ // Left half background (top of the card) { // Left half background (top of the card)
// Set texture to paint the left with // Set texture to paint the left with
@ -174,7 +204,9 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
@Override @Override
protected void drawArt(Graphics2D g) { protected void drawArt(Graphics2D g) {
if (artImage != null && !cardView.isFaceDown()) { if (isAdventure) {
super.drawArt(g);
} else if (artImage != null && !cardView.isFaceDown()) {
if (isAftermath()) { if (isAftermath()) {
Rectangle2D topRect = ArtRect.AFTERMATH_TOP.rect; Rectangle2D topRect = ArtRect.AFTERMATH_TOP.rect;
int topLineY = (int) (leftHalf.ch * TYPE_LINE_Y_FRAC); int topLineY = (int) (leftHalf.ch * TYPE_LINE_Y_FRAC);
@ -286,7 +318,43 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
@Override @Override
protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) { 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(getUnmodifiedHalfContext(g), attribs, leftHalf, (int) (leftHalf.ch * TYPE_LINE_Y_FRAC));
drawSplitHalfFrame(getAftermathHalfContext(g), attribs, rightHalf, (rightHalf.ch - boxHeight) / 2); drawSplitHalfFrame(getAftermathHalfContext(g), attribs, rightHalf, (rightHalf.ch - boxHeight) / 2);
} else { } 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.manaCostLeftStr = mainCard.getLeftHalfCard().getManaCostSymbols();
this.manaCostRightStr = mainCard.getRightHalfCard().getManaCostSymbols(); this.manaCostRightStr = mainCard.getRightHalfCard().getManaCostSymbols();
} else if (card instanceof AdventureCard) { } else if (card instanceof AdventureCard) {
this.isSplitCard = true;
AdventureCard adventureCard = ((AdventureCard) card); AdventureCard adventureCard = ((AdventureCard) card);
leftSplitName = adventureCard.getName();
leftSplitCostsStr = String.join("", adventureCard.getManaCostSymbols());
leftSplitRules = adventureCard.getSharedRules(game);
leftSplitTypeLine = getCardTypeLine(game, adventureCard);
AdventureCardSpell adventureCardSpell = adventureCard.getSpellCard(); 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(); fullCardName = adventureCard.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + adventureCardSpell.getName();
this.manaCostLeftStr = adventureCardSpell.getManaCostSymbols(); this.manaCostLeftStr = adventureCard.getManaCostSymbols();
this.manaCostRightStr = adventureCard.getManaCostSymbols(); this.manaCostRightStr = adventureCardSpell.getManaCostSymbols();
} else if (card instanceof MockCard) { } else if (card instanceof MockCard) {
// deck editor cards // deck editor cards
fullCardName = ((MockCard) card).getFullName(true); fullCardName = ((MockCard) card).getFullName(true);

View file

@ -9,6 +9,7 @@ import mage.constants.SpellAbilityType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.ZoneChangeEvent; import mage.game.events.ZoneChangeEvent;
import mage.util.CardUtil;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -129,6 +130,12 @@ public abstract class AdventureCard extends CardImpl {
return super.getAbilities(game); 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 @Override
public void setOwnerId(UUID ownerId) { public void setOwnerId(UUID ownerId) {
super.setOwnerId(ownerId); super.setOwnerId(ownerId);

View file

@ -181,4 +181,4 @@ class AdventureCardSpellAbility extends SpellAbility {
public AdventureCardSpellAbility copy() { public AdventureCardSpellAbility copy() {
return new AdventureCardSpellAbility(this); return new AdventureCardSpellAbility(this);
} }
} }