diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java index eed6403067..7b42ea47ab 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardRenderer.java @@ -18,41 +18,40 @@ import mage.view.PermanentView; import java.awt.*; import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; import java.awt.image.RasterFormatException; import java.util.ArrayList; import java.util.List; -import java.awt.image.BufferedImage; /** * @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 { @@ -74,24 +73,24 @@ public abstract class CardRenderer { // 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 + 0, 5, 10, 10, 5, 0 }, new int[]{ - 3, 0, 3, 10, 9, 10 + 3, 0, 3, 10, 9, 10 }, 6); private static final Polygon MINUS_COUNTER_POLY = new Polygon(new int[]{ - 0, 5, 10, 10, 5, 0 + 0, 5, 10, 10, 5, 0 }, new int[]{ - 0, 1, 0, 7, 10, 7 + 0, 1, 0, 7, 10, 7 }, 6); private static final Polygon TIME_COUNTER_POLY = new Polygon(new int[]{ - 0, 10, 8, 10, 0, 2 + 0, 10, 8, 10, 0, 2 }, new int[]{ - 0, 0, 5, 10, 10, 5 + 0, 0, 5, 10, 10, 5 }, 6); private static final Polygon OTHER_COUNTER_POLY = new Polygon(new int[]{ - 1, 9, 9, 1 + 1, 9, 9, 1 }, new int[]{ - 1, 1, 9, 9 + 1, 1, 9, 9 }, 4); // Paint for a card back @@ -253,11 +252,11 @@ public abstract class CardRenderer { 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[] xPoints = { + x1, x2, x1, x2 }; - int yPoints[] = { - y1, y1, y2, y2 + int[] yPoints = { + y1, y1, y2, y2 }; g.setColor(new Color(255, 255, 255, 200)); g.setStroke(new BasicStroke(7)); @@ -267,10 +266,10 @@ public abstract class CardRenderer { g.drawPolygon(xPoints, yPoints, 4); g.setStroke(new BasicStroke(1)); int[] xPoints2 = { - x1, x2, cardWidth / 2 + x1, x2, cardWidth / 2 }; int[] yPoints2 = { - y1, y1, cardHeight / 2 + y1, y1, cardHeight / 2 }; g.setColor(new Color(0, 0, 0, 100)); g.fillPolygon(xPoints2, yPoints2, 3); @@ -300,8 +299,8 @@ public abstract class CardRenderer { try { BufferedImage subImg = artImage.getSubimage( - (int) (artRect.getX() * fullCardImgWidth), (int) (artRect.getY() * fullCardImgHeight), - (int) artWidth, (int) artHeight); + (int) (artRect.getX() * fullCardImgWidth), (int) (artRect.getY() * fullCardImgHeight), + (int) artWidth, (int) artHeight); g.drawImage(subImg, x, y, (int) targetWidth, (int) targetHeight, @@ -438,20 +437,25 @@ public abstract class CardRenderer { } 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; + if (cardView.getRarity() != null) { + 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; + } + } else { + // tokens + return Color.black; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java index 821d98c311..5f544c9abd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java @@ -70,4 +70,40 @@ public class EnterLeaveBattlefieldExileTargetTest extends CardTestPlayerBase { assertHandCount(playerB, "Pillarfield Ox", 1); assertExileCount(playerB, 0); } + + @Test + public void testAngelOfSerenityTargets() { + // test NPE error while AI targeting battlefield with tokens + + // Flying + // When Angel of Serenity enters the battlefield, you may exile up to three other target creatures from the battlefield and/or creature cards from graveyards. + addCard(Zone.HAND, playerA, "Angel of Serenity"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 7); + // + // Create two 2/2 white Knight Ally creature tokens. + addCard(Zone.HAND, playerA, "Allied Reinforcements", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + // + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 2); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 2); + addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 2); + addCard(Zone.GRAVEYARD, playerB, "Balduvian Bears", 2); + + // create tokens + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Allied Reinforcements"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // angel + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Angel of Serenity"); + setChoice(playerA, "Yes"); + //addTarget(playerA, "Silvercoat Lion^Balduvian Bears"); // AI must target + + //setStrictChooseMode(true); // AI must target + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Knight Ally", 2); + assertPermanentCount(playerA, "Angel of Serenity", 1); + } } diff --git a/Mage/src/main/java/mage/cards/Card.java b/Mage/src/main/java/mage/cards/Card.java index a4ea5154a5..7b049229c2 100644 --- a/Mage/src/main/java/mage/cards/Card.java +++ b/Mage/src/main/java/mage/cards/Card.java @@ -1,8 +1,5 @@ - package mage.cards; -import java.util.List; -import java.util.UUID; import mage.MageObject; import mage.Mana; import mage.abilities.Abilities; @@ -17,13 +14,16 @@ import mage.game.Game; import mage.game.GameState; import mage.game.permanent.Permanent; +import java.util.List; +import java.util.UUID; + public interface Card extends MageObject { UUID getOwnerId(); String getCardNumber(); - Rarity getRarity(); + Rarity getRarity(); // null for tokens void setOwnerId(UUID ownerId); @@ -77,15 +77,15 @@ public interface Card extends MageObject { * @param zone * @param sourceId * @param game - * @param flag If zone - *

+ * @param flag If zone + * * @return true if card was moved to zone */ boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag); @@ -95,8 +95,8 @@ public interface Card extends MageObject { /** * Moves the card to an exile zone * - * @param exileId set to null for generic exile zone - * @param name used for exile zone with the specified exileId + * @param exileId set to null for generic exile zone + * @param name used for exile zone with the specified exileId * @param sourceId * @param game * @return true if card was moved to zone @@ -122,7 +122,6 @@ public interface Card extends MageObject { List getMana(); /** - * * @return true if there exists various art images for this card */ boolean getUsesVariousArt(); @@ -149,7 +148,6 @@ public interface Card extends MageObject { Card copy(); /** - * * @return The main card of a split half card or adventure spell card, otherwise the card itself is * returned */ @@ -169,7 +167,7 @@ public interface Card extends MageObject { boolean removeAttachment(UUID permanentId, Game game); - default boolean isOwnedBy(UUID controllerId){ + default boolean isOwnedBy(UUID controllerId) { return getOwnerId().equals(controllerId); } } diff --git a/Mage/src/main/java/mage/game/draft/RateCard.java b/Mage/src/main/java/mage/game/draft/RateCard.java index c8ac75c97c..fa64ec8778 100644 --- a/Mage/src/main/java/mage/game/draft/RateCard.java +++ b/Mage/src/main/java/mage/game/draft/RateCard.java @@ -186,22 +186,27 @@ public final class RateCard { // ratings from card rarity // some cards can have different rarity -- it's will be used from first set int newRating; - switch (card.getRarity()) { - case COMMON: - newRating = DEFAULT_NOT_RATED_CARD_RATING; - break; - case UNCOMMON: - newRating = DEFAULT_NOT_RATED_UNCOMMON_RATING; - break; - case RARE: - newRating = DEFAULT_NOT_RATED_RARE_RATING; - break; - case MYTHIC: - newRating = DEFAULT_NOT_RATED_MYTHIC_RATING; - break; - default: - newRating = DEFAULT_NOT_RATED_CARD_RATING; - break; + if (card.getRarity() != null) { + switch (card.getRarity()) { + case COMMON: + newRating = DEFAULT_NOT_RATED_CARD_RATING; + break; + case UNCOMMON: + newRating = DEFAULT_NOT_RATED_UNCOMMON_RATING; + break; + case RARE: + newRating = DEFAULT_NOT_RATED_RARE_RATING; + break; + case MYTHIC: + newRating = DEFAULT_NOT_RATED_MYTHIC_RATING; + break; + default: + newRating = DEFAULT_NOT_RATED_CARD_RATING; + break; + } + } else { + // tokens + newRating = DEFAULT_NOT_RATED_CARD_RATING; } int oldRating = baseRatings.getOrDefault(card.getName(), 0);