mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 10:40:06 -08:00
Face down images and cards rework (#11873)
Face down changes: * GUI: added visible face down type and real card name for controller/owner (opponent can see it after game ends); * GUI: added day/night button to view real card for controller/owner (opponent can see it after game ends); * game: fixed that faced-down card can render symbols, abilities and other hidden data from a real card; * images: added image support for normal faced-down cards; * images: added image support for morph and megamorph faced-down cards; * images: added image support for foretell faced-down cards; Other changes: * images: fixed missing tokens from DDD set; * images: no more client restart to apply newly downloaded images or render settings; * images: improved backface image quality (use main menu -> symbols to download it);
This commit is contained in:
parent
4901de12c1
commit
e38a79f231
104 changed files with 2178 additions and 1495 deletions
|
|
@ -1517,7 +1517,11 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
||||||
|
|
||||||
public void setConnectButtonText(String status) {
|
public void setConnectButtonText(String status) {
|
||||||
this.btnConnect.setText(status);
|
this.btnConnect.setText(status);
|
||||||
changeGUISize(); // Needed to layout the tooltbar after text length change
|
|
||||||
|
// Needed to layout the tooltbar after text length change
|
||||||
|
// TODO: need research, is it actual?
|
||||||
|
GUISizeHelper.refreshGUIAndCards();
|
||||||
|
|
||||||
this.btnConnect.repaint();
|
this.btnConnect.repaint();
|
||||||
this.btnConnect.revalidate();
|
this.btnConnect.revalidate();
|
||||||
}
|
}
|
||||||
|
|
@ -1741,8 +1745,13 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeGUISize() {
|
/**
|
||||||
ImageCaches.flush();
|
* Refresh whole GUI including cards and card images.
|
||||||
|
* Use it after new images downloaded, new fonts or theme settings selected.
|
||||||
|
*/
|
||||||
|
public void refreshGUIAndCards() {
|
||||||
|
ImageCaches.clearAll();
|
||||||
|
|
||||||
setGUISize();
|
setGUISize();
|
||||||
|
|
||||||
setGUISizeTooltipContainer();
|
setGUISizeTooltipContainer();
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,12 @@ public class MageRoundPane extends JPanel {
|
||||||
private int Y_OFFSET = 30;
|
private int Y_OFFSET = 30;
|
||||||
private final Color defaultBackgroundColor = new Color(141, 130, 112, 200); // color of the frame of the popup window
|
private final Color defaultBackgroundColor = new Color(141, 130, 112, 200); // color of the frame of the popup window
|
||||||
private Color backgroundColor = defaultBackgroundColor;
|
private Color backgroundColor = defaultBackgroundColor;
|
||||||
private static final SoftValuesLoadingCache<ShadowKey, BufferedImage> SHADOW_IMAGE_CACHE;
|
private static final SoftValuesLoadingCache<ShadowKey, BufferedImage> ROUND_PANEL_SHADOW_IMAGES_CACHE;
|
||||||
private static final SoftValuesLoadingCache<Key, BufferedImage> IMAGE_CACHE;
|
private static final SoftValuesLoadingCache<Key, BufferedImage> ROUND_PANEL_IMAGES_CACHE;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
SHADOW_IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createShadowImage));
|
ROUND_PANEL_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createImage));
|
||||||
IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createImage));
|
ROUND_PANEL_SHADOW_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(MageRoundPane::createShadowImage));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ShadowKey {
|
private static final class ShadowKey {
|
||||||
|
|
@ -132,7 +132,7 @@ public class MageRoundPane extends JPanel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
g.drawImage(IMAGE_CACHE.getOrThrow(new Key(getWidth(), getHeight(), X_OFFSET, Y_OFFSET, backgroundColor)), 0, 0, null);
|
g.drawImage(ROUND_PANEL_IMAGES_CACHE.getOrThrow(new Key(getWidth(), getHeight(), X_OFFSET, Y_OFFSET, backgroundColor)), 0, 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BufferedImage createImage(Key key) {
|
private static BufferedImage createImage(Key key) {
|
||||||
|
|
@ -146,7 +146,7 @@ public class MageRoundPane extends JPanel {
|
||||||
Graphics2D g2 = image.createGraphics();
|
Graphics2D g2 = image.createGraphics();
|
||||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
BufferedImage shadow = SHADOW_IMAGE_CACHE.getOrThrow(new ShadowKey(w, h));
|
BufferedImage shadow = ROUND_PANEL_SHADOW_IMAGES_CACHE.getOrThrow(new ShadowKey(w, h));
|
||||||
|
|
||||||
{
|
{
|
||||||
int xOffset = (shadow.getWidth() - w) / 2;
|
int xOffset = (shadow.getWidth() - w) / 2;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import mage.client.util.audio.AudioManager;
|
||||||
import mage.client.util.sets.ConstructedFormats;
|
import mage.client.util.sets.ConstructedFormats;
|
||||||
import mage.components.ImagePanel;
|
import mage.components.ImagePanel;
|
||||||
import mage.components.ImagePanelStyle;
|
import mage.components.ImagePanelStyle;
|
||||||
|
import mage.constants.MageObjectType;
|
||||||
import mage.game.command.Dungeon;
|
import mage.game.command.Dungeon;
|
||||||
import mage.game.command.Emblem;
|
import mage.game.command.Emblem;
|
||||||
import mage.game.command.Plane;
|
import mage.game.command.Plane;
|
||||||
|
|
@ -396,7 +397,7 @@ public class MageBook extends JComponent {
|
||||||
draftRating.setBounds(rectangle.x, rectangle.y + cardImg.getCardLocation().getCardHeight() + dy, cardDimensions.getFrameWidth(), 20);
|
draftRating.setBounds(rectangle.x, rectangle.y + cardImg.getCardLocation().getCardHeight() + dy, cardDimensions.getFrameWidth(), 20);
|
||||||
draftRating.setHorizontalAlignment(SwingConstants.CENTER);
|
draftRating.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
draftRating.setFont(jLayeredPane.getFont().deriveFont(jLayeredPane.getFont().getStyle() | Font.BOLD));
|
draftRating.setFont(jLayeredPane.getFont().deriveFont(jLayeredPane.getFont().getStyle() | Font.BOLD));
|
||||||
if (card.isOriginalACard()) {
|
if (card.getMageObjectType().equals(MageObjectType.CARD)) {
|
||||||
// card
|
// card
|
||||||
draftRating.setText("draft rating: " + RateCard.rateCard(card, null));
|
draftRating.setText("draft rating: " + RateCard.rateCard(card, null));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,8 @@ public class MageCardComparator implements CardViewComparator {
|
||||||
bCom = RateCard.rateCard(b, null);
|
bCom = RateCard.rateCard(b, null);
|
||||||
break;
|
break;
|
||||||
case 10:
|
case 10:
|
||||||
aCom = a.getColorIdentityStr();
|
aCom = a.getOriginalColorIdentity();
|
||||||
bCom = b.getColorIdentityStr();
|
bCom = b.getOriginalColorIdentity();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ public class TableModel extends AbstractTableModel implements ICardGrid {
|
||||||
case 9:
|
case 9:
|
||||||
return RateCard.rateCard(c, null);
|
return RateCard.rateCard(c, null);
|
||||||
case 10:
|
case 10:
|
||||||
return ManaSymbols.getClearManaCost(c.getColorIdentityStr());
|
return ManaSymbols.getClearManaCost(c.getOriginalColorIdentity());
|
||||||
default:
|
default:
|
||||||
return "error";
|
return "error";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2928,8 +2928,8 @@ public class PreferencesDialog extends javax.swing.JDialog {
|
||||||
save(prefs, dialog.sliderCardSizeMinBattlefield, KEY_GUI_CARD_BATTLEFIELD_MIN_SIZE, "true", "false", UPDATE_CACHE_POLICY);
|
save(prefs, dialog.sliderCardSizeMinBattlefield, KEY_GUI_CARD_BATTLEFIELD_MIN_SIZE, "true", "false", UPDATE_CACHE_POLICY);
|
||||||
save(prefs, dialog.sliderCardSizeMaxBattlefield, KEY_GUI_CARD_BATTLEFIELD_MAX_SIZE, "true", "false", UPDATE_CACHE_POLICY);
|
save(prefs, dialog.sliderCardSizeMaxBattlefield, KEY_GUI_CARD_BATTLEFIELD_MAX_SIZE, "true", "false", UPDATE_CACHE_POLICY);
|
||||||
|
|
||||||
// do as worker job
|
// refresh full GUI with new settings
|
||||||
GUISizeHelper.changeGUISize();
|
GUISizeHelper.refreshGUIAndCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exitButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitButtonActionPerformed
|
private void exitButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitButtonActionPerformed
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package mage.client.dialog;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
||||||
import mage.abilities.icon.*;
|
import mage.abilities.icon.*;
|
||||||
import mage.abilities.keyword.TransformAbility;
|
import mage.abilities.keyword.TransformAbility;
|
||||||
import mage.cards.*;
|
import mage.cards.*;
|
||||||
|
|
@ -184,6 +185,18 @@ public class TestCardRenderDialog extends MageDialog {
|
||||||
if (perm.isTransformable()) {
|
if (perm.isTransformable()) {
|
||||||
perm.setTransformed(true);
|
perm.setTransformed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// workaround to apply face down image and other settings
|
||||||
|
if (perm.isFaceDown(game)) {
|
||||||
|
BecomesFaceDownCreatureEffect.makeFaceDownObject(
|
||||||
|
game,
|
||||||
|
null,
|
||||||
|
perm,
|
||||||
|
BecomesFaceDownCreatureEffect.findFaceDownType(game, perm),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
PermanentView cardView = new PermanentView(perm, permCard, controllerId, game);
|
PermanentView cardView = new PermanentView(perm, permCard, controllerId, game);
|
||||||
cardView.setInViewerOnly(false); // must false for face down
|
cardView.setInViewerOnly(false); // must false for face down
|
||||||
return cardView;
|
return cardView;
|
||||||
|
|
@ -386,9 +399,9 @@ public class TestCardRenderDialog extends MageDialog {
|
||||||
|
|
||||||
List<CardView> cardViews = new ArrayList<>();
|
List<CardView> cardViews = new ArrayList<>();
|
||||||
|
|
||||||
/* test morphed
|
//* test face down
|
||||||
cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "263", 0, 0, 0, false, null)); // mountain
|
cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "263", 0, 0, 0, false, false, null)); // mountain
|
||||||
cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "185", 0, 0, 0, true, null)); // Judith, the Scourge Diva
|
cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "185", 0, 0, 0, true, false, null)); // Judith, the Scourge Diva
|
||||||
cardViews.add(createHandCard(game, playerYou.getId(), "DIS", "153")); // Odds // Ends (split card)
|
cardViews.add(createHandCard(game, playerYou.getId(), "DIS", "153")); // Odds // Ends (split card)
|
||||||
cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "38")); // Animating Faerie (adventure card)
|
cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "38")); // Animating Faerie (adventure card)
|
||||||
cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, false, false)); // face down
|
cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, false, false)); // face down
|
||||||
|
|
@ -414,7 +427,7 @@ public class TestCardRenderDialog extends MageDialog {
|
||||||
cardViews.add(createHandCard(game, playerYou.getId(), "AKH", "210")); // Dusk // Dawn
|
cardViews.add(createHandCard(game, playerYou.getId(), "AKH", "210")); // Dusk // Dawn
|
||||||
//*/
|
//*/
|
||||||
|
|
||||||
//* test adventure cards in hands
|
/* test adventure cards in hands
|
||||||
cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "14")); // Giant Killer
|
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", "222")); // Cruel Somnophage
|
||||||
cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "227")); // Gingerbread Hunter
|
cardViews.add(createHandCard(game, playerYou.getId(), "WOE", "227")); // Gingerbread Hunter
|
||||||
|
|
@ -430,7 +443,7 @@ public class TestCardRenderDialog extends MageDialog {
|
||||||
cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "155")); // Case of the Locked Hothouse
|
cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "155")); // Case of the Locked Hothouse
|
||||||
//*/
|
//*/
|
||||||
|
|
||||||
//* test case, class and saga cards in hands
|
/* test case, class and saga cards in hands
|
||||||
cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "113")); // Case of the Burning Masks
|
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
|
cardViews.add(createHandCard(game, playerYou.getId(), "MKM", "155")); // Case of the Locked Hothouse
|
||||||
cardViews.add(createHandCard(game, playerYou.getId(), "AFR", "6")); // Cleric Class
|
cardViews.add(createHandCard(game, playerYou.getId(), "AFR", "6")); // Cleric Class
|
||||||
|
|
@ -504,7 +517,7 @@ public class TestCardRenderDialog extends MageDialog {
|
||||||
PermanentView oldPermanent = (PermanentView) main.getGameCard();
|
PermanentView oldPermanent = (PermanentView) main.getGameCard();
|
||||||
PermanentView newPermament = new PermanentView(
|
PermanentView newPermament = new PermanentView(
|
||||||
oldPermanent,
|
oldPermanent,
|
||||||
game.getCard(oldPermanent.getOriginalId()),
|
game.getCard(oldPermanent.getId()),
|
||||||
UUID.randomUUID(),
|
UUID.randomUUID(),
|
||||||
game
|
game
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ public class MageActionCallback implements ActionCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e, TransferData data, boolean doubleClick) {
|
public void mouseClicked(MouseEvent e, TransferData data, boolean doubleClick) {
|
||||||
// send mouse clicked event to the card's area and other cards list components for processing
|
// send mouse clicked event to the card's area and other cards list components for processing
|
||||||
if (e.isConsumed()) {
|
if (e.isConsumed()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -720,7 +720,7 @@ public class MageActionCallback implements ActionCallback {
|
||||||
switch (enlargeMode) {
|
switch (enlargeMode) {
|
||||||
case COPY:
|
case COPY:
|
||||||
if (cardView instanceof PermanentView) {
|
if (cardView instanceof PermanentView) {
|
||||||
image = ImageCache.getImageOriginal(((PermanentView) cardView).getOriginal()).getImage();
|
image = ImageCache.getCardImageOriginal(((PermanentView) cardView).getOriginal()).getImage();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ALTERNATE:
|
case ALTERNATE:
|
||||||
|
|
@ -729,10 +729,14 @@ public class MageActionCallback implements ActionCallback {
|
||||||
&& !cardView.isFlipCard()
|
&& !cardView.isFlipCard()
|
||||||
&& !cardView.canTransform()
|
&& !cardView.canTransform()
|
||||||
&& ((PermanentView) cardView).isCopy()) {
|
&& ((PermanentView) cardView).isCopy()) {
|
||||||
image = ImageCache.getImageOriginal(((PermanentView) cardView).getOriginal()).getImage();
|
image = ImageCache.getCardImageOriginal(((PermanentView) cardView).getOriginal()).getImage();
|
||||||
} else {
|
} else {
|
||||||
image = ImageCache.getImageOriginalAlternateName(cardView).getImage();
|
image = ImageCache.getCardImageAlternate(cardView).getImage();
|
||||||
displayCard = displayCard.getSecondCardFace();
|
displayCard = displayCard.getSecondCardFace();
|
||||||
|
if (displayCard == null) {
|
||||||
|
// opponent's face down cards are hidden, so no alternative
|
||||||
|
displayCard = cardPanel.getOriginal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -745,7 +749,6 @@ public class MageActionCallback implements ActionCallback {
|
||||||
} else {
|
} else {
|
||||||
logger.warn("No Card preview Pane in Mage Frame defined. Card: " + cardView.getName());
|
logger.warn("No Card preview Pane in Mage Frame defined. Card: " + cardView.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("Problem dring display of enlarged card", e);
|
logger.warn("Problem dring display of enlarged card", e);
|
||||||
}
|
}
|
||||||
|
|
@ -786,6 +789,7 @@ public class MageActionCallback implements ActionCallback {
|
||||||
|
|
||||||
private void displayCardInfo(CardView card, Image image, BigCard bigCard) {
|
private void displayCardInfo(CardView card, Image image, BigCard bigCard) {
|
||||||
if (image instanceof BufferedImage) {
|
if (image instanceof BufferedImage) {
|
||||||
|
// IMAGE MODE
|
||||||
// XXX: scaled to fit width
|
// XXX: scaled to fit width
|
||||||
bigCard.setCard(card.getId(), enlargeMode, image, card.getRules(), card.isToRotate());
|
bigCard.setCard(card.getId(), enlargeMode, image, card.getRules(), card.isToRotate());
|
||||||
// if it's an ability, show only the ability text as overlay
|
// if it's an ability, show only the ability text as overlay
|
||||||
|
|
@ -795,6 +799,7 @@ public class MageActionCallback implements ActionCallback {
|
||||||
bigCard.hideTextComponent();
|
bigCard.hideTextComponent();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// TEXT MODE
|
||||||
JXPanel panel = GuiDisplayUtil.getDescription(card, bigCard.getWidth(), bigCard.getHeight());
|
JXPanel panel = GuiDisplayUtil.getDescription(card, bigCard.getWidth(), bigCard.getHeight());
|
||||||
panel.setVisible(true);
|
panel.setVisible(true);
|
||||||
bigCard.hideTextComponent();
|
bigCard.hideTextComponent();
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ package mage.client.themes;
|
||||||
|
|
||||||
import mage.abilities.hint.HintUtils;
|
import mage.abilities.hint.HintUtils;
|
||||||
import mage.abilities.icon.CardIconColor;
|
import mage.abilities.icon.CardIconColor;
|
||||||
|
import mage.client.util.GUISizeHelper;
|
||||||
|
import mage.client.util.ImageCaches;
|
||||||
import org.mage.card.arcane.SvgUtils;
|
import org.mage.card.arcane.SvgUtils;
|
||||||
import org.mage.plugins.card.images.ImageCache;
|
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
|
|
@ -350,6 +351,6 @@ public enum ThemeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload card icons and other rendering things from cache - it can depend on current theme
|
// reload card icons and other rendering things from cache - it can depend on current theme
|
||||||
ImageCache.clearCache();
|
GUISizeHelper.refreshGUIAndCards();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
public final class CardsViewUtil {
|
public final class CardsViewUtil {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,11 @@ public final class GUISizeHelper {
|
||||||
return new Font("Arial", Font.PLAIN, 14);
|
return new Font("Arial", Font.PLAIN, 14);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void changeGUISize() {
|
public static void refreshGUIAndCards() {
|
||||||
calculateGUISizes();
|
calculateGUISizes();
|
||||||
MageFrame.getInstance().changeGUISize();
|
if (MageFrame.getInstance() != null) {
|
||||||
|
MageFrame.getInstance().refreshGUIAndCards();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void calculateGUISizes() {
|
public static void calculateGUISizes() {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
|
|
||||||
package mage.client.util;
|
package mage.client.util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* GUI: collect info about all used image caches, so it can be cleared from a single place
|
||||||
*
|
*
|
||||||
* @author draxdyn
|
* @author draxdyn
|
||||||
*/
|
*/
|
||||||
|
|
@ -22,7 +22,11 @@ public final class ImageCaches {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void flush() {
|
/**
|
||||||
|
* Global method to clear all images cache.
|
||||||
|
* Warning, GUI must be refreshed too for card updates, so use GUISizeHelper.refreshGUIAndCards instead
|
||||||
|
*/
|
||||||
|
public static void clearAll() {
|
||||||
for (Cache<?, ?> map : IMAGE_CACHES) {
|
for (Cache<?, ?> map : IMAGE_CACHES) {
|
||||||
map.invalidateAll();
|
map.invalidateAll();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,10 @@ public final class TransformedImageCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final SoftValuesLoadingCache<Key, SoftValuesLoadingCache<BufferedImage, BufferedImage>> IMAGE_CACHE;
|
private static final SoftValuesLoadingCache<Key, SoftValuesLoadingCache<BufferedImage, BufferedImage>> TRANSFORMED_IMAGES_CACHE;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// TODO: can we use a single map?
|
TRANSFORMED_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(TransformedImageCache::createTransformedImageCache));
|
||||||
IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(TransformedImageCache::createTransformedImageCache));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SoftValuesLoadingCache<BufferedImage, BufferedImage> createTransformedImageCache(Key key) {
|
private static SoftValuesLoadingCache<BufferedImage, BufferedImage> createTransformedImageCache(Key key) {
|
||||||
|
|
@ -139,6 +138,6 @@ public final class TransformedImageCache {
|
||||||
if (resHeight < 3) {
|
if (resHeight < 3) {
|
||||||
resHeight = 3;
|
resHeight = 3;
|
||||||
}
|
}
|
||||||
return IMAGE_CACHE.getOrThrow(new Key(resWidth, resHeight, angle)).getOrThrow(image);
|
return TRANSFORMED_IMAGES_CACHE.getOrThrow(new Key(resWidth, resHeight, angle)).getOrThrow(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -377,7 +377,7 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
Graphics2D g2d = (Graphics2D) (g.create());
|
Graphics2D g2d = (Graphics2D) (g.create());
|
||||||
|
|
||||||
// Deferr to subclasses
|
// Defer to subclasses
|
||||||
paintCard(g2d);
|
paintCard(g2d);
|
||||||
|
|
||||||
// Done, dispose of the context
|
// Done, dispose of the context
|
||||||
|
|
@ -854,7 +854,7 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen
|
||||||
// VIEW mode (user can change card side at any time by n/d button)
|
// VIEW mode (user can change card side at any time by n/d button)
|
||||||
this.guiTransformed = !this.guiTransformed;
|
this.guiTransformed = !this.guiTransformed;
|
||||||
|
|
||||||
if (dayNightButton != null) { // if transformbable card is copied, button can be null
|
if (dayNightButton != null) { // if transformable card is copied, button can be null
|
||||||
BufferedImage image = this.isTransformed() ? ImageManagerImpl.instance.getNightImage() : ImageManagerImpl.instance.getDayImage();
|
BufferedImage image = this.isTransformed() ? ImageManagerImpl.instance.getNightImage() : ImageManagerImpl.instance.getDayImage();
|
||||||
dayNightButton.setIcon(new ImageIcon(image));
|
dayNightButton.setIcon(new ImageIcon(image));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ import mage.constants.SubType;
|
||||||
import mage.util.DebugUtil;
|
import mage.util.DebugUtil;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import mage.view.CounterView;
|
import mage.view.CounterView;
|
||||||
import mage.view.PermanentView;
|
|
||||||
import mage.view.StackAbilityView;
|
|
||||||
import org.jdesktop.swingx.graphics.GraphicsUtilities;
|
import org.jdesktop.swingx.graphics.GraphicsUtilities;
|
||||||
import org.mage.plugins.card.images.ImageCache;
|
import org.mage.plugins.card.images.ImageCache;
|
||||||
import org.mage.plugins.card.images.ImageCacheData;
|
import org.mage.plugins.card.images.ImageCacheData;
|
||||||
|
|
@ -37,7 +35,7 @@ public class CardPanelRenderModeImage extends CardPanel {
|
||||||
|
|
||||||
private static final long serialVersionUID = -3272134219262184411L;
|
private static final long serialVersionUID = -3272134219262184411L;
|
||||||
|
|
||||||
private static final SoftValuesLoadingCache<Key, BufferedImage> IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelRenderModeImage::createImage));
|
private static final SoftValuesLoadingCache<Key, BufferedImage> IMAGE_MODE_RENDERED_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelRenderModeImage::createImage));
|
||||||
|
|
||||||
private static final int WIDTH_LIMIT = 90; // card width limit to create smaller counter
|
private static final int WIDTH_LIMIT = 90; // card width limit to create smaller counter
|
||||||
|
|
||||||
|
|
@ -472,11 +470,7 @@ public class CardPanelRenderModeImage extends CardPanel {
|
||||||
@Override
|
@Override
|
||||||
public Image getImage() {
|
public Image getImage() {
|
||||||
if (this.hasImage) {
|
if (this.hasImage) {
|
||||||
if (getGameCard().isFaceDown()) {
|
return ImageCache.getCardImageOriginal(getGameCard()).getImage();
|
||||||
return getFaceDownImage().getImage();
|
|
||||||
} else {
|
|
||||||
return ImageCache.getImageOriginal(getGameCard()).getImage();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -492,7 +486,7 @@ public class CardPanelRenderModeImage extends CardPanel {
|
||||||
// draw background (selected/chooseable/playable)
|
// draw background (selected/chooseable/playable)
|
||||||
MageCardLocation cardLocation = getCardLocation();
|
MageCardLocation cardLocation = getCardLocation();
|
||||||
g2d.drawImage(
|
g2d.drawImage(
|
||||||
IMAGE_CACHE.getOrThrow(
|
IMAGE_MODE_RENDERED_CACHE.getOrThrow(
|
||||||
new Key(getInsets(),
|
new Key(getInsets(),
|
||||||
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
|
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
|
||||||
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
|
cardLocation.getCardWidth(), cardLocation.getCardHeight(),
|
||||||
|
|
@ -640,14 +634,9 @@ public class CardPanelRenderModeImage extends CardPanel {
|
||||||
|
|
||||||
Util.threadPool.submit(() -> {
|
Util.threadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
final ImageCacheData data;
|
ImageCacheData data = ImageCache.getCardImage(getGameCard(), getCardWidth(), getCardHeight());
|
||||||
if (getGameCard().isFaceDown()) {
|
|
||||||
data = getFaceDownImage();
|
|
||||||
} else {
|
|
||||||
data = ImageCache.getImage(getGameCard(), getCardWidth(), getCardHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
// show path on miss image
|
// save missing image
|
||||||
if (data.getImage() == null) {
|
if (data.getImage() == null) {
|
||||||
setFullPath(data.getPath());
|
setFullPath(data.getPath());
|
||||||
}
|
}
|
||||||
|
|
@ -665,21 +654,6 @@ public class CardPanelRenderModeImage extends CardPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageCacheData getFaceDownImage() {
|
|
||||||
// TODO: add download default images
|
|
||||||
if (isPermanent() && getGameCard() instanceof PermanentView) {
|
|
||||||
if (((PermanentView) getGameCard()).isMorphed()) {
|
|
||||||
return ImageCache.getMorphImage();
|
|
||||||
} else {
|
|
||||||
return ImageCache.getManifestImage();
|
|
||||||
}
|
|
||||||
} else if (this.getGameCard() instanceof StackAbilityView) {
|
|
||||||
return ImageCache.getMorphImage();
|
|
||||||
} else {
|
|
||||||
return ImageCache.getCardbackImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getManaWidth(String manaCost, int symbolMarginX) {
|
private int getManaWidth(String manaCost, int symbolMarginX) {
|
||||||
int width = 0;
|
int width = 0;
|
||||||
manaCost = manaCost.replace("\\", "");
|
manaCost = manaCost.replace("\\", "");
|
||||||
|
|
|
||||||
|
|
@ -3,46 +3,40 @@ package org.mage.card.arcane;
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import mage.cards.action.ActionCallback;
|
import mage.cards.action.ActionCallback;
|
||||||
|
import mage.client.util.ImageCaches;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
import mage.constants.SuperType;
|
import mage.constants.SuperType;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import mage.view.CounterView;
|
import mage.view.CounterView;
|
||||||
import mage.view.PermanentView;
|
import mage.view.PermanentView;
|
||||||
import mage.view.StackAbilityView;
|
|
||||||
import org.jdesktop.swingx.graphics.GraphicsUtilities;
|
import org.jdesktop.swingx.graphics.GraphicsUtilities;
|
||||||
import org.mage.plugins.card.images.ImageCache;
|
import org.mage.plugins.card.images.ImageCache;
|
||||||
import org.mage.plugins.card.images.ImageCacheData;
|
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render mode: MTGO
|
* Render mode: MTGO
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class CardPanelRenderModeMTGO extends CardPanel {
|
public class CardPanelRenderModeMTGO extends CardPanel {
|
||||||
|
|
||||||
//
|
|
||||||
// https://www.mtg.onl/evolution-of-magic-token-card-frame-design/
|
// https://www.mtg.onl/evolution-of-magic-token-card-frame-design/
|
||||||
|
|
||||||
// Map of generated images
|
private static final Cache<ImageKey, BufferedImage> MTGO_MODE_RENDERED_CACHE = ImageCaches.register(
|
||||||
private static final Cache<ImageKey, BufferedImage> IMAGE_CACHE = CacheBuilder
|
CacheBuilder
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.maximumSize(3000)
|
.maximumSize(3000)
|
||||||
.expireAfterAccess(60, TimeUnit.MINUTES)
|
.expireAfterAccess(60, TimeUnit.MINUTES)
|
||||||
.softValues()
|
.softValues()
|
||||||
.build();
|
.build()
|
||||||
|
);
|
||||||
|
|
||||||
// The art image for the card, loaded in from the disk
|
// The art image for the card, loaded in from the disk
|
||||||
private BufferedImage artImage;
|
private BufferedImage artImage;
|
||||||
|
|
||||||
// The faceart image for the card, loaded in from the disk (based on artid from mtgo)
|
|
||||||
private BufferedImage faceArtImage;
|
|
||||||
|
|
||||||
// Factory to generate card appropriate views
|
// Factory to generate card appropriate views
|
||||||
private final CardRendererFactory cardRendererFactory = new CardRendererFactory();
|
private final CardRendererFactory cardRendererFactory = new CardRendererFactory();
|
||||||
|
|
||||||
|
|
@ -161,11 +155,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
|
||||||
if (artImage == null) {
|
if (artImage == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (getGameCard().isFaceDown()) {
|
return ImageCache.getCardImageOriginal(getGameCard()).getImage();
|
||||||
return getFaceDownImage().getImage();
|
|
||||||
} else {
|
|
||||||
return ImageCache.getImageOriginal(getGameCard()).getImage();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -173,16 +163,21 @@ public class CardPanelRenderModeMTGO extends CardPanel {
|
||||||
// Render the card if we don't have an image ready to use
|
// Render the card if we don't have an image ready to use
|
||||||
if (cardImage == null) {
|
if (cardImage == null) {
|
||||||
// Try to get card image from cache based on our card characteristics
|
// Try to get card image from cache based on our card characteristics
|
||||||
ImageKey key = new ImageKey(getGameCard(), artImage,
|
ImageKey key = new ImageKey(
|
||||||
getCardWidth(), getCardHeight(),
|
getGameCard(),
|
||||||
isChoosable(), isSelected(), isTransformed());
|
artImage,
|
||||||
|
getCardWidth(),
|
||||||
|
getCardHeight(),
|
||||||
|
isChoosable(),
|
||||||
|
isSelected(),
|
||||||
|
isTransformed()
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
cardImage = IMAGE_CACHE.get(key, this::renderCard);
|
cardImage = MTGO_MODE_RENDERED_CACHE.get(key, this::renderCard);
|
||||||
} catch (ExecutionException e) {
|
} catch (Exception e) {
|
||||||
|
// TODO: research and replace with logs, message and backface image
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No cached copy exists? Render one and cache it
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// And draw the image we now have
|
// And draw the image we now have
|
||||||
|
|
@ -237,8 +232,6 @@ public class CardPanelRenderModeMTGO extends CardPanel {
|
||||||
// Use the art image and current rendered image from the card
|
// Use the art image and current rendered image from the card
|
||||||
artImage = impl.artImage;
|
artImage = impl.artImage;
|
||||||
cardRenderer.setArtImage(artImage);
|
cardRenderer.setArtImage(artImage);
|
||||||
faceArtImage = impl.faceArtImage;
|
|
||||||
cardRenderer.setFaceArtImage(faceArtImage);
|
|
||||||
cardImage = impl.cardImage;
|
cardImage = impl.cardImage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +245,6 @@ public class CardPanelRenderModeMTGO extends CardPanel {
|
||||||
cardImage = null;
|
cardImage = null;
|
||||||
cardRenderer = cardRendererFactory.create(getGameCard());
|
cardRenderer = cardRendererFactory.create(getGameCard());
|
||||||
cardRenderer.setArtImage(artImage);
|
cardRenderer.setArtImage(artImage);
|
||||||
cardRenderer.setFaceArtImage(faceArtImage);
|
|
||||||
|
|
||||||
// Repaint
|
// Repaint
|
||||||
repaint();
|
repaint();
|
||||||
|
|
@ -264,7 +256,6 @@ public class CardPanelRenderModeMTGO extends CardPanel {
|
||||||
artImage = null;
|
artImage = null;
|
||||||
cardImage = null;
|
cardImage = null;
|
||||||
cardRenderer.setArtImage(null);
|
cardRenderer.setArtImage(null);
|
||||||
cardRenderer.setFaceArtImage(null);
|
|
||||||
|
|
||||||
// Stop animation
|
// Stop animation
|
||||||
setTappedAngle(isTapped() ? CardPanel.TAPPED_ANGLE : 0);
|
setTappedAngle(isTapped() ? CardPanel.TAPPED_ANGLE : 0);
|
||||||
|
|
@ -276,29 +267,18 @@ public class CardPanelRenderModeMTGO extends CardPanel {
|
||||||
// See if the image is already loaded
|
// See if the image is already loaded
|
||||||
//artImage = ImageCache.tryGetImage(gameCard, getCardWidth(), getCardHeight());
|
//artImage = ImageCache.tryGetImage(gameCard, getCardWidth(), getCardHeight());
|
||||||
//this.cardRenderer.setArtImage(artImage);
|
//this.cardRenderer.setArtImage(artImage);
|
||||||
|
|
||||||
// Submit a task to draw with the card art when it arrives
|
// Submit a task to draw with the card art when it arrives
|
||||||
if (artImage == null) {
|
if (artImage == null) {
|
||||||
final int stamp = ++updateArtImageStamp;
|
final int stamp = ++updateArtImageStamp;
|
||||||
Util.threadPool.submit(() -> {
|
Util.threadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
final BufferedImage srcImage;
|
final BufferedImage srcImage;
|
||||||
final BufferedImage faceArtSrcImage;
|
srcImage = ImageCache.getCardImage(getGameCard(), getCardWidth(), getCardHeight()).getImage();
|
||||||
if (getGameCard().isFaceDown()) {
|
|
||||||
// Nothing to do
|
|
||||||
srcImage = null;
|
|
||||||
faceArtSrcImage = null;
|
|
||||||
} else {
|
|
||||||
srcImage = ImageCache.getImage(getGameCard(), getCardWidth(), getCardHeight()).getImage();
|
|
||||||
faceArtSrcImage = ImageCache.getFaceImage(getGameCard(), getCardWidth(), getCardHeight()).getImage();
|
|
||||||
}
|
|
||||||
|
|
||||||
UI.invokeLater(() -> {
|
UI.invokeLater(() -> {
|
||||||
if (stamp == updateArtImageStamp) {
|
if (stamp == updateArtImageStamp) {
|
||||||
artImage = srcImage;
|
artImage = srcImage;
|
||||||
cardRenderer.setArtImage(srcImage);
|
cardRenderer.setArtImage(srcImage);
|
||||||
faceArtImage = faceArtSrcImage;
|
|
||||||
cardRenderer.setFaceArtImage(faceArtSrcImage);
|
|
||||||
|
|
||||||
if (srcImage != null) {
|
if (srcImage != null) {
|
||||||
// Invalidate and repaint
|
// Invalidate and repaint
|
||||||
cardImage = null;
|
cardImage = null;
|
||||||
|
|
@ -317,21 +297,6 @@ public class CardPanelRenderModeMTGO extends CardPanel {
|
||||||
return new CardPanelAttributes(getCardWidth(), getCardHeight(), isChoosable(), isSelected(), isTransformed());
|
return new CardPanelAttributes(getCardWidth(), getCardHeight(), isChoosable(), isSelected(), isTransformed());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageCacheData getFaceDownImage() {
|
|
||||||
// TODO: add download default images
|
|
||||||
if (isPermanent() && getGameCard() instanceof PermanentView) {
|
|
||||||
if (((PermanentView) getGameCard()).isMorphed()) {
|
|
||||||
return ImageCache.getMorphImage();
|
|
||||||
} else {
|
|
||||||
return ImageCache.getManifestImage();
|
|
||||||
}
|
|
||||||
} else if (this.getGameCard() instanceof StackAbilityView) {
|
|
||||||
return ImageCache.getMorphImage();
|
|
||||||
} else {
|
|
||||||
return ImageCache.getCardbackImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the card to a new BufferedImage at it's current dimensions
|
* Render the card to a new BufferedImage at it's current dimensions
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,7 @@ public abstract class CardRenderer {
|
||||||
protected final CardView cardView;
|
protected final CardView cardView;
|
||||||
|
|
||||||
// The card image
|
// The card image
|
||||||
protected BufferedImage artImage;
|
protected BufferedImage artImage; // TODO: make sure it changed/reset on face down/up change
|
||||||
|
|
||||||
// The face card image
|
|
||||||
protected BufferedImage faceArtImage;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Common layout metrics between all cards
|
// Common layout metrics between all cards
|
||||||
|
|
@ -206,7 +203,7 @@ public abstract class CardRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Draw Method
|
// The Draw Method
|
||||||
// The draw method takes the information caculated by the constructor
|
// The draw method takes the information calculated by the constructor
|
||||||
// and uses it to draw to a concrete size of card and graphics.
|
// and uses it to draw to a concrete size of card and graphics.
|
||||||
public void draw(Graphics2D g, CardPanelAttributes attribs, BufferedImage image) {
|
public void draw(Graphics2D g, CardPanelAttributes attribs, BufferedImage image) {
|
||||||
|
|
||||||
|
|
@ -313,51 +310,6 @@ public abstract class CardRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean lessOpaqueRulesTextBox = false;
|
private boolean lessOpaqueRulesTextBox = false;
|
||||||
protected void drawFaceArtIntoRect(Graphics2D g, int x, int y, int w, int h, int alternate_h, Rectangle2D artRect, boolean shouldPreserveAspect) {
|
|
||||||
// Perform a process to make sure that the art is scaled uniformly to fill the frame, cutting
|
|
||||||
// off the minimum amount necessary to make it completely fill the frame without "squashing" it.
|
|
||||||
double fullCardImgWidth = faceArtImage.getWidth();
|
|
||||||
double fullCardImgHeight = faceArtImage.getHeight();
|
|
||||||
double artWidth = fullCardImgWidth;
|
|
||||||
double artHeight = fullCardImgHeight;
|
|
||||||
double targetWidth = w;
|
|
||||||
double targetHeight = h;
|
|
||||||
double targetAspect = targetWidth / targetHeight;
|
|
||||||
if (!shouldPreserveAspect) {
|
|
||||||
// No adjustment to art
|
|
||||||
} else if (targetAspect * artHeight < artWidth) {
|
|
||||||
// Trim off some width
|
|
||||||
artWidth = targetAspect * artHeight;
|
|
||||||
} else {
|
|
||||||
// Trim off some height
|
|
||||||
artHeight = artWidth / targetAspect;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/*BufferedImage subImg
|
|
||||||
= faceArtImage.getSubimage(
|
|
||||||
(int) (artRect.getX() * fullCardImgWidth), (int) (artRect.getY() * fullCardImgHeight),
|
|
||||||
(int) artWidth, (int) artHeight);*/
|
|
||||||
RenderingHints rh = new RenderingHints(
|
|
||||||
RenderingHints.KEY_INTERPOLATION,
|
|
||||||
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
|
||||||
g.setRenderingHints(rh);
|
|
||||||
if (fullCardImgWidth > fullCardImgHeight) {
|
|
||||||
g.drawImage(faceArtImage,
|
|
||||||
x, y,
|
|
||||||
(int) targetWidth, (int) targetHeight,
|
|
||||||
null);
|
|
||||||
} else {
|
|
||||||
g.drawImage(faceArtImage,
|
|
||||||
x, y,
|
|
||||||
(int) targetWidth, alternate_h, // alernate_h is roughly (targetWidth / 0.74)
|
|
||||||
null);
|
|
||||||
lessOpaqueRulesTextBox = true;
|
|
||||||
}
|
|
||||||
} catch (RasterFormatException e) {
|
|
||||||
// At very small card sizes we may encounter a problem with rounding error making the rect not fit
|
|
||||||
System.out.println(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw +1/+1 and other counters
|
// Draw +1/+1 and other counters
|
||||||
protected void drawCounters(Graphics2D g) {
|
protected void drawCounters(Graphics2D g) {
|
||||||
|
|
@ -532,10 +484,4 @@ public abstract class CardRenderer {
|
||||||
public void setArtImage(Image image) {
|
public void setArtImage(Image image) {
|
||||||
artImage = CardRendererUtils.toBufferedImage(image);
|
artImage = CardRendererUtils.toBufferedImage(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the card art image (CardPanel will give it to us when it
|
|
||||||
// is loaded and ready)
|
|
||||||
public void setFaceArtImage(Image image) {
|
|
||||||
faceArtImage = CardRendererUtils.toBufferedImage(image);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ public class GlowText extends JLabel {
|
||||||
private Color glowColor;
|
private Color glowColor;
|
||||||
private boolean wrap;
|
private boolean wrap;
|
||||||
private int lineCount = 0;
|
private int lineCount = 0;
|
||||||
private static final SoftValuesLoadingCache<Key, BufferedImage> IMAGE_CACHE;
|
private static final SoftValuesLoadingCache<Key, BufferedImage> GLOW_TEXT_IMAGES_CACHE;
|
||||||
|
|
||||||
private static final class Key {
|
private static final class Key {
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ public class GlowText extends JLabel {
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(GlowText::createGlowImage));
|
GLOW_TEXT_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(GlowText::createGlowImage));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGlow(Color glowColor, int size, float intensity) {
|
public void setGlow(Color glowColor, int size, float intensity) {
|
||||||
|
|
@ -153,7 +153,7 @@ public class GlowText extends JLabel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getGlowImage() {
|
public BufferedImage getGlowImage() {
|
||||||
return IMAGE_CACHE.getOrThrow(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap));
|
return GLOW_TEXT_IMAGES_CACHE.getOrThrow(new Key(getWidth(), getHeight(), getText(), getFont(), getForeground(), glowSize, glowIntensity, glowColor, wrap));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BufferedImage createGlowImage(Key key) {
|
private static BufferedImage createGlowImage(Key key) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import mage.client.dialog.PreferencesDialog;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.MageObjectType;
|
import mage.constants.MageObjectType;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import mage.util.SubTypes;
|
import mage.util.SubTypes;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import mage.view.PermanentView;
|
import mage.view.PermanentView;
|
||||||
|
|
@ -26,27 +27,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
private void cardRendererBasedRender(Graphics2D g) {
|
|
||||||
// Prepare for draw
|
|
||||||
g.translate(cardXOffset, cardYOffset);
|
|
||||||
int cardWidth = this.cardWidth - cardXOffset;
|
|
||||||
int cardHeight = this.cardHeight - cardYOffset;
|
|
||||||
|
|
||||||
// AA on
|
|
||||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
||||||
|
|
||||||
// Renderer
|
|
||||||
CardRenderer render = new ModernCardRenderer(gameCard, transformed);
|
|
||||||
Image img = imagePanel.getSrcImage();
|
|
||||||
if (img != null) {
|
|
||||||
render.setArtImage(img);
|
|
||||||
}
|
|
||||||
render.draw(g, cardWidth, cardHeight);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author stravant@gmail.com, JayDi85
|
* @author stravant@gmail.com, JayDi85
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -285,7 +265,8 @@ public class ModernCardRenderer extends CardRenderer {
|
||||||
protected void drawBackground(Graphics2D g) {
|
protected void drawBackground(Graphics2D g) {
|
||||||
// Draw background, in 3 parts
|
// Draw background, in 3 parts
|
||||||
|
|
||||||
if (cardView.isFaceDown()) {
|
if (false && cardView.isFaceDown()) {
|
||||||
|
// TODO: delete un-used code?!
|
||||||
// Just draw a brown rectangle
|
// Just draw a brown rectangle
|
||||||
drawCardBack(g);
|
drawCardBack(g);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -392,16 +373,9 @@ public class ModernCardRenderer extends CardRenderer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void drawArt(Graphics2D g) {
|
protected void drawArt(Graphics2D g) {
|
||||||
if ((artImage != null || faceArtImage != null) && !cardView.isFaceDown()) {
|
if (artImage != null) {
|
||||||
|
|
||||||
boolean useFaceArt = false;
|
|
||||||
if (faceArtImage != null && !isZendikarFullArtLand()) {
|
|
||||||
useFaceArt = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invention rendering, art fills the entire frame
|
// Invention rendering, art fills the entire frame
|
||||||
if (useInventionFrame()) {
|
if (useInventionFrame()) {
|
||||||
useFaceArt = false;
|
|
||||||
drawArtIntoRect(g,
|
drawArtIntoRect(g,
|
||||||
borderWidth, borderWidth,
|
borderWidth, borderWidth,
|
||||||
cardWidth - 2 * borderWidth, cardHeight - 2 * borderWidth,
|
cardWidth - 2 * borderWidth, cardHeight - 2 * borderWidth,
|
||||||
|
|
@ -412,7 +386,6 @@ public class ModernCardRenderer extends CardRenderer {
|
||||||
Rectangle2D sourceRect = getArtRect();
|
Rectangle2D sourceRect = getArtRect();
|
||||||
|
|
||||||
if (cardView.getMageObjectType() == MageObjectType.SPELL) {
|
if (cardView.getMageObjectType() == MageObjectType.SPELL) {
|
||||||
useFaceArt = false;
|
|
||||||
ArtRect rect = cardView.getArtRect();
|
ArtRect rect = cardView.getArtRect();
|
||||||
if (rect != ArtRect.NORMAL) {
|
if (rect != ArtRect.NORMAL) {
|
||||||
sourceRect = rect.rect;
|
sourceRect = rect.rect;
|
||||||
|
|
@ -421,14 +394,7 @@ public class ModernCardRenderer extends CardRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal drawing of art from a source part of the card frame into the rect
|
// Normal drawing of art from a source part of the card frame into the rect
|
||||||
if (useFaceArt) {
|
if (cardView.getArtRect() == ArtRect.FULL_LENGTH_RIGHT) {
|
||||||
int alternate_height = cardHeight - boxHeight * 2 - totalContentInset;
|
|
||||||
drawFaceArtIntoRect(g,
|
|
||||||
totalContentInset + 1, totalContentInset + boxHeight,
|
|
||||||
contentWidth - 2, typeLineY - totalContentInset - boxHeight,
|
|
||||||
alternate_height,
|
|
||||||
sourceRect, shouldPreserveAspect);
|
|
||||||
} else if (cardView.getArtRect() == ArtRect.FULL_LENGTH_RIGHT) {
|
|
||||||
drawArtIntoRect(g,
|
drawArtIntoRect(g,
|
||||||
contentWidth / 2 + totalContentInset + 1, totalContentInset + boxHeight,
|
contentWidth / 2 + totalContentInset + 1, totalContentInset + boxHeight,
|
||||||
contentWidth / 2 - 1, typeLineY - totalContentInset - boxHeight,
|
contentWidth / 2 - 1, typeLineY - totalContentInset - boxHeight,
|
||||||
|
|
@ -713,27 +679,20 @@ public class ModernCardRenderer extends CardRenderer {
|
||||||
|
|
||||||
public void drawZendikarCurvedFace(Graphics2D g2, BufferedImage image, int x, int y, int x2, int y2,
|
public void drawZendikarCurvedFace(Graphics2D g2, BufferedImage image, int x, int y, int x2, int y2,
|
||||||
Color boxColor, Paint paint) {
|
Color boxColor, Paint paint) {
|
||||||
|
if (artImage == null) {
|
||||||
BufferedImage artToUse = faceArtImage;
|
return;
|
||||||
boolean hadToUseFullArt = false;
|
|
||||||
if (faceArtImage == null) {
|
|
||||||
if (artImage == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hadToUseFullArt = true;
|
|
||||||
artToUse = artImage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BufferedImage artToUse = artImage;
|
||||||
int srcW = artToUse.getWidth();
|
int srcW = artToUse.getWidth();
|
||||||
int srcH = artToUse.getHeight();
|
int srcH = artToUse.getHeight();
|
||||||
|
|
||||||
if (hadToUseFullArt) {
|
// Get a box based on the standard scan from gatherer.
|
||||||
// Get a box based on the standard scan from gatherer.
|
// Width = 185/223 pixels (centered)
|
||||||
// Width = 185/223 pixels (centered)
|
// Height = 220/310, 38 pixels from top
|
||||||
// Height = 220/310, 38 pixels from top
|
int subx = 19 * srcW / 223;
|
||||||
int subx = 19 * srcW / 223;
|
int suby = 38 * srcH / 310;
|
||||||
int suby = 38 * srcH / 310;
|
artToUse = artImage.getSubimage(subx, suby, 185 * srcW / 223, 220 * srcH / 310);
|
||||||
artToUse = artImage.getSubimage(subx, suby, 185 * srcW / 223, 220 * srcH / 310);
|
|
||||||
}
|
|
||||||
|
|
||||||
Path2D.Double curve = new Path2D.Double();
|
Path2D.Double curve = new Path2D.Double();
|
||||||
|
|
||||||
|
|
@ -762,26 +721,19 @@ public class ModernCardRenderer extends CardRenderer {
|
||||||
public void drawBFZCurvedFace(Graphics2D g2, BufferedImage image, int x, int y, int x2, int y2,
|
public void drawBFZCurvedFace(Graphics2D g2, BufferedImage image, int x, int y, int x2, int y2,
|
||||||
int topxdelta, int endydelta,
|
int topxdelta, int endydelta,
|
||||||
Color boxColor, Paint paint) {
|
Color boxColor, Paint paint) {
|
||||||
BufferedImage artToUse = faceArtImage;
|
if (artImage == null) {
|
||||||
boolean hadToUseFullArt = false;
|
return;
|
||||||
if (faceArtImage == null) {
|
|
||||||
if (artImage == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hadToUseFullArt = true;
|
|
||||||
artToUse = artImage;
|
|
||||||
}
|
}
|
||||||
|
BufferedImage artToUse = artImage;
|
||||||
int srcW = artToUse.getWidth();
|
int srcW = artToUse.getWidth();
|
||||||
int srcH = artToUse.getHeight();
|
int srcH = artToUse.getHeight();
|
||||||
|
|
||||||
if (hadToUseFullArt) {
|
// Get a box based on the standard scan from gatherer.
|
||||||
// Get a box based on the standard scan from gatherer.
|
// Width = 185/223 pixels (centered)
|
||||||
// Width = 185/223 pixels (centered)
|
// Height = 220/310, 38 pixels from top
|
||||||
// Height = 220/310, 38 pixels from top
|
int subx = 19 * srcW / 223;
|
||||||
int subx = 19 * srcW / 223;
|
int suby = 38 * srcH / 310;
|
||||||
int suby = 38 * srcH / 310;
|
artToUse = artImage.getSubimage(subx, suby, 185 * srcW / 223, 220 * srcH / 310);
|
||||||
artToUse = artImage.getSubimage(subx, suby, 185 * srcW / 223, 220 * srcH / 310);
|
|
||||||
}
|
|
||||||
|
|
||||||
Path2D.Double curve = new Path2D.Double();
|
Path2D.Double curve = new Path2D.Double();
|
||||||
curve.moveTo(x + topxdelta, y);
|
curve.moveTo(x + topxdelta, y);
|
||||||
|
|
@ -907,23 +859,13 @@ public class ModernCardRenderer extends CardRenderer {
|
||||||
int availableWidth = w - manaCostWidth + 2;
|
int availableWidth = w - manaCostWidth + 2;
|
||||||
|
|
||||||
// Draw the name
|
// Draw the name
|
||||||
String nameStr;
|
if (!baseName.isEmpty()) {
|
||||||
if (cardView.isFaceDown()) {
|
AttributedString str = new AttributedString(baseName);
|
||||||
if (cardView instanceof PermanentView && ((PermanentView) cardView).isManifested()) {
|
|
||||||
nameStr = "Manifest: " + cardView.getName();
|
|
||||||
} else {
|
|
||||||
nameStr = "Morph: " + cardView.getName();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nameStr = baseName;
|
|
||||||
}
|
|
||||||
if (!nameStr.isEmpty()) {
|
|
||||||
AttributedString str = new AttributedString(nameStr);
|
|
||||||
str.addAttribute(TextAttribute.FONT, boxTextFont);
|
str.addAttribute(TextAttribute.FONT, boxTextFont);
|
||||||
TextMeasurer measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext());
|
TextMeasurer measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext());
|
||||||
int breakIndex = measure.getLineBreakIndex(0, availableWidth);
|
int breakIndex = measure.getLineBreakIndex(0, availableWidth);
|
||||||
if (breakIndex < nameStr.length()) {
|
if (breakIndex < baseName.length()) {
|
||||||
str = new AttributedString(nameStr);
|
str = new AttributedString(baseName);
|
||||||
str.addAttribute(TextAttribute.FONT, boxTextFontNarrow);
|
str.addAttribute(TextAttribute.FONT, boxTextFontNarrow);
|
||||||
measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext());
|
measure = new TextMeasurer(str.getIterator(), g.getFontRenderContext());
|
||||||
breakIndex = measure.getLineBreakIndex(0, availableWidth);
|
breakIndex = measure.getLineBreakIndex(0, availableWidth);
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,8 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void drawBackground(Graphics2D g) {
|
protected void drawBackground(Graphics2D g) {
|
||||||
if (cardView.isFaceDown()) {
|
if (false && cardView.isFaceDown()) {
|
||||||
|
// TODO: delete un-used code?!
|
||||||
drawCardBack(g);
|
drawCardBack(g);
|
||||||
} if (isAdventure()) {
|
} if (isAdventure()) {
|
||||||
super.drawBackground(g);
|
super.drawBackground(g);
|
||||||
|
|
@ -206,7 +207,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
|
||||||
protected void drawArt(Graphics2D g) {
|
protected void drawArt(Graphics2D g) {
|
||||||
if (isAdventure) {
|
if (isAdventure) {
|
||||||
super.drawArt(g);
|
super.drawArt(g);
|
||||||
} else if (artImage != null && !cardView.isFaceDown()) {
|
} else if (artImage != null) {
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import mage.cards.MageCard;
|
||||||
import mage.cards.MagePermanent;
|
import mage.cards.MagePermanent;
|
||||||
import mage.cards.action.ActionCallback;
|
import mage.cards.action.ActionCallback;
|
||||||
import mage.client.util.GUISizeHelper;
|
import mage.client.util.GUISizeHelper;
|
||||||
|
import mage.client.util.ImageCaches;
|
||||||
import mage.interfaces.plugin.CardPlugin;
|
import mage.interfaces.plugin.CardPlugin;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import mage.view.CounterView;
|
import mage.view.CounterView;
|
||||||
|
|
@ -667,7 +668,7 @@ public class CardPluginImpl implements CardPlugin {
|
||||||
LOGGER.info("Symbols download finished");
|
LOGGER.info("Symbols download finished");
|
||||||
dialog.dispose();
|
dialog.dispose();
|
||||||
ManaSymbols.loadImages();
|
ManaSymbols.loadImages();
|
||||||
ImageCache.clearCache();
|
GUISizeHelper.refreshGUIAndCards();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -710,6 +711,6 @@ public class CardPluginImpl implements CardPlugin {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferedImage getOriginalImage(CardView card) {
|
public BufferedImage getOriginalImage(CardView card) {
|
||||||
return ImageCache.getImageOriginal(card).getImage();
|
return ImageCache.getCardImageOriginal(card).getImage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,17 @@ public class DownloadJob extends AbstractLaternaBean {
|
||||||
private final String name;
|
private final String name;
|
||||||
private Source source;
|
private Source source;
|
||||||
private final Destination destination;
|
private final Destination destination;
|
||||||
|
private final boolean forceToDownload; // download image everytime, do not keep old image
|
||||||
private final Property<State> state = properties.property("state", State.NEW);
|
private final Property<State> state = properties.property("state", State.NEW);
|
||||||
private final Property<String> message = properties.property("message");
|
private final Property<String> message = properties.property("message");
|
||||||
private final Property<Exception> error = properties.property("error");
|
private final Property<Exception> error = properties.property("error");
|
||||||
private final BoundedRangeModel progress = new DefaultBoundedRangeModel();
|
private final BoundedRangeModel progress = new DefaultBoundedRangeModel();
|
||||||
|
|
||||||
public DownloadJob(String name, Source source, Destination destination) {
|
public DownloadJob(String name, Source source, Destination destination, boolean forceToDownload) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
|
this.forceToDownload = forceToDownload;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -155,6 +157,10 @@ public class DownloadJob extends AbstractLaternaBean {
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isForceToDownload() {
|
||||||
|
return forceToDownload;
|
||||||
|
}
|
||||||
|
|
||||||
public static Source fromURL(final String url) {
|
public static Source fromURL(final String url) {
|
||||||
return fromURL(CardImageUtils.getProxyFromPreferences(), url);
|
return fromURL(CardImageUtils.getProxyFromPreferences(), url);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ public class Downloader extends AbstractLaternaBean {
|
||||||
Destination dst = job.getDestination();
|
Destination dst = job.getDestination();
|
||||||
BoundedRangeModel progress = job.getProgress();
|
BoundedRangeModel progress = job.getProgress();
|
||||||
|
|
||||||
if (dst.isValid()) {
|
if (dst.isValid() && !job.isForceToDownload()) {
|
||||||
// already done
|
// already done
|
||||||
progress.setMaximum(1);
|
progress.setMaximum(1);
|
||||||
progress.setValue(1);
|
progress.setValue(1);
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
|
|
||||||
package org.mage.plugins.card.dl.sources;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.mage.plugins.card.dl.DownloadJob;
|
|
||||||
import static org.mage.plugins.card.dl.DownloadJob.fromURL;
|
|
||||||
import static org.mage.plugins.card.dl.DownloadJob.toFile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author LevelX2
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class CardFrames implements Iterable<DownloadJob> {
|
|
||||||
|
|
||||||
private static final String FRAMES_PATH = File.separator + "frames";
|
|
||||||
private static final File DEFAULT_OUT_DIR = new File("plugins" + File.separator + "images" + FRAMES_PATH);
|
|
||||||
private static File outDir = DEFAULT_OUT_DIR;
|
|
||||||
|
|
||||||
static final String BASE_DOWNLOAD_URL = "http://ct-magefree.rhcloud.com/resources/img/";
|
|
||||||
static final String TEXTURES_FOLDER = "textures";
|
|
||||||
static final String PT_BOXES_FOLDER = "pt";
|
|
||||||
|
|
||||||
private static final String[] TEXTURES = {"U", "R", "G", "B", "W", "A",
|
|
||||||
"BG_LAND", "BR_LAND", "WU_LAND", "WB_LAND", "UB_LAND", "GW_LAND", "RW_LAND",
|
|
||||||
"RG_LAND", "GU_LAND", "UR_LAND"
|
|
||||||
// NOT => "BW_LAND","BU_LAND","WG_LAND","WR_LAND",
|
|
||||||
};
|
|
||||||
private static final String[] PT_BOXES = {"U", "R", "G", "B", "W", "A"};
|
|
||||||
|
|
||||||
public CardFrames(String path) {
|
|
||||||
if (path == null) {
|
|
||||||
useDefaultDir();
|
|
||||||
} else {
|
|
||||||
changeOutDir(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<DownloadJob> iterator() {
|
|
||||||
List<DownloadJob> jobs = new ArrayList<>();
|
|
||||||
for (String texture : TEXTURES) {
|
|
||||||
jobs.add(generateDownloadJob(TEXTURES_FOLDER, texture));
|
|
||||||
}
|
|
||||||
for (String pt_box : PT_BOXES) {
|
|
||||||
jobs.add(generateDownloadJob(PT_BOXES_FOLDER, pt_box));
|
|
||||||
}
|
|
||||||
return jobs.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
private DownloadJob generateDownloadJob(String dirName, String name) {
|
|
||||||
File dst = new File(outDir, name + ".png");
|
|
||||||
String url = BASE_DOWNLOAD_URL + dirName + '/' + name + ".png";
|
|
||||||
return new DownloadJob("frames-" + dirName + '-' + name, fromURL(url), toFile(dst));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void useDefaultDir() {
|
|
||||||
outDir = DEFAULT_OUT_DIR;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void changeOutDir(String path) {
|
|
||||||
File file = new File(path + FRAMES_PATH);
|
|
||||||
if (file.exists()) {
|
|
||||||
outDir = file;
|
|
||||||
} else {
|
|
||||||
file.mkdirs();
|
|
||||||
if (file.exists()) {
|
|
||||||
outDir = file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,6 +11,7 @@ import static org.mage.plugins.card.dl.DownloadJob.toFile;
|
||||||
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
|
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO: outdated, delete and use xmage tokens instead ?!
|
||||||
* Used when we need to point to direct links to download resources from.
|
* Used when we need to point to direct links to download resources from.
|
||||||
*
|
*
|
||||||
* @author noxx
|
* @author noxx
|
||||||
|
|
@ -20,11 +21,9 @@ public class DirectLinksForDownload implements Iterable<DownloadJob> {
|
||||||
private static final Map<String, String> directLinks = new LinkedHashMap<>();
|
private static final Map<String, String> directLinks = new LinkedHashMap<>();
|
||||||
|
|
||||||
public static final String cardbackFilename = "cardback.jpg";
|
public static final String cardbackFilename = "cardback.jpg";
|
||||||
public static final String foretellFilename = "foretell.jpg";
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
directLinks.put(cardbackFilename, "https://upload.wikimedia.org/wikipedia/en/a/aa/Magic_the_gathering-card_back.jpg");
|
directLinks.put(cardbackFilename, "https://upload.wikimedia.org/wikipedia/en/a/aa/Magic_the_gathering-card_back.jpg");
|
||||||
directLinks.put(foretellFilename, "https://api.scryfall.com/cards/tkhm/23/en?format=image");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final File outDir;
|
private final File outDir;
|
||||||
|
|
@ -42,7 +41,8 @@ public class DirectLinksForDownload implements Iterable<DownloadJob> {
|
||||||
|
|
||||||
for (Map.Entry<String, String> url : directLinks.entrySet()) {
|
for (Map.Entry<String, String> url : directLinks.entrySet()) {
|
||||||
File dst = new File(outDir, url.getKey());
|
File dst = new File(outDir, url.getKey());
|
||||||
jobs.add(new DownloadJob(url.getKey(), fromURL(url.getValue()), toFile(dst)));
|
// download images every time (need to update low quality image)
|
||||||
|
jobs.add(new DownloadJob(url.getKey(), fromURL(url.getValue()), toFile(dst), true));
|
||||||
}
|
}
|
||||||
return jobs.iterator();
|
return jobs.iterator();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -337,6 +337,6 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
set = codeReplacements.get(set);
|
set = codeReplacements.get(set);
|
||||||
}
|
}
|
||||||
String url = "https://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + urlRarity;
|
String url = "https://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + urlRarity;
|
||||||
return new DownloadJob(set + '-' + rarity, fromURL(url), toFile(dst));
|
return new DownloadJob(set + '-' + rarity, fromURL(url), toFile(dst), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ public class GathererSymbols implements Iterable<DownloadJob> {
|
||||||
|
|
||||||
String url = format(urlFmt, sizes[modSizeIndex], symbol);
|
String url = format(urlFmt, sizes[modSizeIndex], symbol);
|
||||||
|
|
||||||
return new DownloadJob(sym, fromURL(url), toFile(dst));
|
return new DownloadJob(sym, fromURL(url), toFile(dst), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -96,15 +96,13 @@ public enum ScryfallImageSource implements CardImageSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
// double faced cards (modal double faces cards too)
|
// double faced cards (modal double faces cards too)
|
||||||
if (card.isTwoFacedCard()) {
|
if (card.isSecondSide()) {
|
||||||
if (card.isSecondSide()) {
|
// back face - must be prepared before
|
||||||
// back face - must be prepared before
|
logger.warn("Can't find back face info in prepared list "
|
||||||
logger.warn("Can't find back face info in prepared list "
|
+ card.getName() + " (" + card.getSet() + ") #" + card.getCollectorId());
|
||||||
+ card.getName() + " (" + card.getSet() + ") #" + card.getCollectorId());
|
return new CardImageUrls(null, null);
|
||||||
return new CardImageUrls(null, null);
|
} else {
|
||||||
} else {
|
// front face - can be downloaded normally as basic card
|
||||||
// front face - can be downloaded normally as basic card
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// basic cards by api call (redirect to img link)
|
// basic cards by api call (redirect to img link)
|
||||||
|
|
@ -219,7 +217,7 @@ public enum ScryfallImageSource implements CardImageSource {
|
||||||
int needPrepareCount = 0;
|
int needPrepareCount = 0;
|
||||||
int currentPrepareCount = 0;
|
int currentPrepareCount = 0;
|
||||||
for (CardDownloadData card : downloadList) {
|
for (CardDownloadData card : downloadList) {
|
||||||
if (card.isTwoFacedCard() && card.isSecondSide()) {
|
if (card.isSecondSide()) {
|
||||||
needPrepareCount++;
|
needPrepareCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +230,7 @@ public enum ScryfallImageSource implements CardImageSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare the back face URL
|
// prepare the back face URL
|
||||||
if (card.isTwoFacedCard() && card.isSecondSide()) {
|
if (card.isSecondSide()) {
|
||||||
currentPrepareCount++;
|
currentPrepareCount++;
|
||||||
try {
|
try {
|
||||||
String url = getFaceImageUrl(proxy, card, card.isToken());
|
String url = getFaceImageUrl(proxy, card, card.isToken());
|
||||||
|
|
|
||||||
|
|
@ -1275,9 +1275,9 @@ public class ScryfallImageSupportTokens {
|
||||||
put("DDE/Saproling", "https://api.scryfall.com/cards/tdde/3/en?format=image");
|
put("DDE/Saproling", "https://api.scryfall.com/cards/tdde/3/en?format=image");
|
||||||
|
|
||||||
// DDD
|
// DDD
|
||||||
put("DDD/Beast/1", "https://api.scryfall.com/cards/tddd/1/en?format=image");
|
put("DDD/Beast/1", "https://api.scryfall.com/cards/tddd/T1/en?format=image");
|
||||||
put("DDD/Beast/2", "https://api.scryfall.com/cards/tddd/2/en?format=image");
|
put("DDD/Beast/2", "https://api.scryfall.com/cards/tddd/T2/en?format=image");
|
||||||
put("DDD/Elephant", "https://api.scryfall.com/cards/tddd/3/en?format=image");
|
put("DDD/Elephant", "https://api.scryfall.com/cards/tddd/T3/en?format=image");
|
||||||
|
|
||||||
// SOM
|
// SOM
|
||||||
put("SOM/Cat", "https://api.scryfall.com/cards/tsom/1/en?format=image");
|
put("SOM/Cat", "https://api.scryfall.com/cards/tsom/1/en?format=image");
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
package org.mage.plugins.card.dl.sources;
|
package org.mage.plugins.card.dl.sources;
|
||||||
|
|
||||||
|
import org.mage.plugins.card.dl.DownloadJob;
|
||||||
|
import org.mage.plugins.card.utils.CardImageUtils;
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.io.*;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.mage.plugins.card.dl.DownloadJob;
|
|
||||||
import org.mage.plugins.card.utils.CardImageUtils;
|
|
||||||
|
|
||||||
|
|
||||||
import static org.mage.card.arcane.ManaSymbols.getSymbolFileNameAsSVG;
|
import static org.mage.card.arcane.ManaSymbols.getSymbolFileNameAsSVG;
|
||||||
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
|
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
|
||||||
|
|
||||||
|
|
@ -105,7 +106,7 @@ public class ScryfallSymbolsSource implements Iterable<DownloadJob> {
|
||||||
if (destFile.exists() && (destFile.length() > 0)) {
|
if (destFile.exists() && (destFile.length() > 0)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try(FileOutputStream stream = new FileOutputStream(destFile)) {
|
try (FileOutputStream stream = new FileOutputStream(destFile)) {
|
||||||
// base64 transform
|
// base64 transform
|
||||||
String data64 = foundedData.get(searchCode);
|
String data64 = foundedData.get(searchCode);
|
||||||
Base64.Decoder dec = Base64.getDecoder();
|
Base64.Decoder dec = Base64.getDecoder();
|
||||||
|
|
@ -166,16 +167,14 @@ public class ScryfallSymbolsSource implements Iterable<DownloadJob> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String destFile = "";
|
|
||||||
|
|
||||||
public ScryfallSymbolsDownloadJob() {
|
public ScryfallSymbolsDownloadJob() {
|
||||||
// download init
|
// download init
|
||||||
super("Scryfall symbols source", fromURL(""), toFile(DOWNLOAD_TEMP_FILE)); // url setup on preparing stage
|
super("Scryfall symbols source", fromURL(""), toFile(DOWNLOAD_TEMP_FILE), true); // url setup on preparing stage
|
||||||
this.destFile = DOWNLOAD_TEMP_FILE;
|
String destFile = DOWNLOAD_TEMP_FILE;
|
||||||
this.addPropertyChangeListener(STATE_PROP_NAME, new ScryfallDownloadOnFinishedListener(this.destFile));
|
this.addPropertyChangeListener(STATE_PROP_NAME, new ScryfallDownloadOnFinishedListener(destFile));
|
||||||
|
|
||||||
// clear dest file (always download new data)
|
// duplicate a forceToDownload param above, but it's ok to clear temp file anyway
|
||||||
File file = new File(this.destFile);
|
File file = new File(destFile);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
file.delete();
|
file.delete();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,37 +14,18 @@ public class CardDownloadData {
|
||||||
private String set;
|
private String set;
|
||||||
private final String collectorId;
|
private final String collectorId;
|
||||||
private final Integer imageNumber;
|
private final Integer imageNumber;
|
||||||
private boolean token;
|
private boolean isToken;
|
||||||
private final boolean twoFacedCard;
|
private boolean isSecondSide;
|
||||||
private final boolean secondSide;
|
private boolean isFlippedSide;
|
||||||
private boolean flipCard;
|
private boolean isSplitCard;
|
||||||
private boolean flippedSide;
|
private final boolean isUsesVariousArt;
|
||||||
private boolean splitCard;
|
|
||||||
private final boolean usesVariousArt;
|
|
||||||
private String tokenClassName;
|
|
||||||
|
|
||||||
public CardDownloadData(String name, String setCode, String collectorId, boolean usesVariousArt, Integer imageNumber) {
|
public CardDownloadData(String name, String setCode, String collectorId, boolean isUsesVariousArt, Integer imageNumber) {
|
||||||
this(name, setCode, collectorId, usesVariousArt, imageNumber, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CardDownloadData(String name, String setCode, String collectorId, boolean usesVariousArt, Integer imageNumber, boolean token) {
|
|
||||||
this(name, setCode, collectorId, usesVariousArt, imageNumber, token, false, false, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public CardDownloadData(String name, String setCode, String collectorId, boolean usesVariousArt, Integer imageNumber, boolean token, boolean twoFacedCard, boolean secondSide) {
|
|
||||||
this(name, setCode, collectorId, usesVariousArt, imageNumber, token, twoFacedCard, secondSide, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public CardDownloadData(String name, String setCode, String collectorId, boolean usesVariousArt, Integer imageNumber, boolean token, boolean twoFacedCard, boolean secondSide, String tokenClassName) {
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.set = setCode;
|
this.set = setCode;
|
||||||
this.collectorId = collectorId;
|
this.collectorId = collectorId;
|
||||||
this.usesVariousArt = usesVariousArt;
|
this.isUsesVariousArt = isUsesVariousArt;
|
||||||
this.imageNumber = imageNumber;
|
this.imageNumber = imageNumber;
|
||||||
this.token = token;
|
|
||||||
this.twoFacedCard = twoFacedCard;
|
|
||||||
this.secondSide = secondSide;
|
|
||||||
this.tokenClassName = tokenClassName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardDownloadData(final CardDownloadData card) {
|
public CardDownloadData(final CardDownloadData card) {
|
||||||
|
|
@ -53,14 +34,11 @@ public class CardDownloadData {
|
||||||
this.set = card.set;
|
this.set = card.set;
|
||||||
this.collectorId = card.collectorId;
|
this.collectorId = card.collectorId;
|
||||||
this.imageNumber = card.imageNumber;
|
this.imageNumber = card.imageNumber;
|
||||||
this.token = card.token;
|
this.isToken = card.isToken;
|
||||||
this.twoFacedCard = card.twoFacedCard;
|
this.isSecondSide = card.isSecondSide;
|
||||||
this.secondSide = card.secondSide;
|
this.isFlippedSide = card.isFlippedSide;
|
||||||
this.flipCard = card.flipCard;
|
this.isSplitCard = card.isSplitCard;
|
||||||
this.flippedSide = card.flippedSide;
|
this.isUsesVariousArt = card.isUsesVariousArt;
|
||||||
this.splitCard = card.splitCard;
|
|
||||||
this.usesVariousArt = card.usesVariousArt;
|
|
||||||
this.tokenClassName = card.tokenClassName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -81,14 +59,11 @@ public class CardDownloadData {
|
||||||
if (!Objects.equals(this.collectorId, other.collectorId)) {
|
if (!Objects.equals(this.collectorId, other.collectorId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.token != other.token) {
|
if (this.isToken != other.isToken) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.twoFacedCard != other.twoFacedCard) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.secondSide == other.secondSide;
|
return this.isSecondSide == other.isSecondSide;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -98,9 +73,8 @@ public class CardDownloadData {
|
||||||
hash = 47 * hash + (this.set != null ? this.set.hashCode() : 0);
|
hash = 47 * hash + (this.set != null ? this.set.hashCode() : 0);
|
||||||
hash = 47 * hash + (this.collectorId != null ? this.collectorId.hashCode() : 0);
|
hash = 47 * hash + (this.collectorId != null ? this.collectorId.hashCode() : 0);
|
||||||
hash = 47 * hash + (this.imageNumber != null ? this.imageNumber.hashCode() : 0);
|
hash = 47 * hash + (this.imageNumber != null ? this.imageNumber.hashCode() : 0);
|
||||||
hash = 47 * hash + (this.token ? 1 : 0);
|
hash = 47 * hash + (this.isToken ? 1 : 0);
|
||||||
hash = 47 * hash + (this.twoFacedCard ? 1 : 0);
|
hash = 47 * hash + (this.isSecondSide ? 1 : 0);
|
||||||
hash = 47 * hash + (this.secondSide ? 1 : 0);
|
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,28 +123,20 @@ public class CardDownloadData {
|
||||||
this.set = set;
|
this.set = set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTokenClassName(String tokenClassName) {
|
|
||||||
this.tokenClassName = tokenClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAffectedClassName() {
|
|
||||||
return tokenClassName.isEmpty() ? name.replaceAll("[^a-zA-Z0-9]", "") : tokenClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isToken() {
|
public boolean isToken() {
|
||||||
return token;
|
return isToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setToken(boolean token) {
|
public void setToken(boolean token) {
|
||||||
this.token = token;
|
this.isToken = token;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTwoFacedCard() {
|
|
||||||
return twoFacedCard;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecondSide() {
|
public boolean isSecondSide() {
|
||||||
return secondSide;
|
return isSecondSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecondSide(boolean isSecondSide) {
|
||||||
|
this.isSecondSide = isSecondSide;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDownloadName() {
|
public String getDownloadName() {
|
||||||
|
|
@ -181,20 +147,12 @@ public class CardDownloadData {
|
||||||
this.downloadName = downloadName;
|
this.downloadName = downloadName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFlipCard() {
|
|
||||||
return flipCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFlipCard(boolean flipCard) {
|
|
||||||
this.flipCard = flipCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSplitCard() {
|
public boolean isSplitCard() {
|
||||||
return splitCard;
|
return isSplitCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSplitCard(boolean splitCard) {
|
public void setSplitCard(boolean splitCard) {
|
||||||
this.splitCard = splitCard;
|
this.isSplitCard = splitCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getImageNumber() {
|
public Integer getImageNumber() {
|
||||||
|
|
@ -202,14 +160,14 @@ public class CardDownloadData {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getUsesVariousArt() {
|
public boolean getUsesVariousArt() {
|
||||||
return usesVariousArt;
|
return isUsesVariousArt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFlippedSide() {
|
public boolean isFlippedSide() {
|
||||||
return flippedSide;
|
return isFlippedSide;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlippedSide(boolean flippedSide) {
|
public void setFlippedSide(boolean flippedSide) {
|
||||||
this.flippedSide = flippedSide;
|
this.isFlippedSide = flippedSide;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import mage.client.MageFrame;
|
||||||
import mage.client.dialog.DownloadImagesDialog;
|
import mage.client.dialog.DownloadImagesDialog;
|
||||||
import mage.client.dialog.PreferencesDialog;
|
import mage.client.dialog.PreferencesDialog;
|
||||||
import mage.client.util.CardLanguage;
|
import mage.client.util.CardLanguage;
|
||||||
|
import mage.client.util.GUISizeHelper;
|
||||||
|
import mage.client.util.ImageCaches;
|
||||||
import mage.client.util.sets.ConstructedFormats;
|
import mage.client.util.sets.ConstructedFormats;
|
||||||
import mage.remote.Connection;
|
import mage.remote.Connection;
|
||||||
import net.java.truevfs.access.TFile;
|
import net.java.truevfs.access.TFile;
|
||||||
|
|
@ -437,14 +439,19 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
||||||
&& !"0".equals(card.getCardNumber())
|
&& !"0".equals(card.getCardNumber())
|
||||||
&& !card.getSetCode().isEmpty()) {
|
&& !card.getSetCode().isEmpty()) {
|
||||||
String cardName = card.getName();
|
String cardName = card.getName();
|
||||||
CardDownloadData url = new CardDownloadData(cardName, card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), 0, false, card.isDoubleFaced(), card.isNightCard());
|
CardDownloadData url = new CardDownloadData(
|
||||||
|
cardName,
|
||||||
|
card.getSetCode(),
|
||||||
|
card.getCardNumber(),
|
||||||
|
card.usesVariousArt(),
|
||||||
|
0);
|
||||||
|
url.setSecondSide(card.isNightCard());
|
||||||
|
|
||||||
// variations must have diff file names with additional postfix
|
// variations must have diff file names with additional postfix
|
||||||
if (url.getUsesVariousArt()) {
|
if (url.getUsesVariousArt()) {
|
||||||
url.setDownloadName(createDownloadName(card));
|
url.setDownloadName(createDownloadName(card));
|
||||||
}
|
}
|
||||||
|
|
||||||
url.setFlipCard(card.isFlipCard());
|
|
||||||
url.setSplitCard(card.isSplitCard());
|
url.setSplitCard(card.isSplitCard());
|
||||||
|
|
||||||
// main side
|
// main side
|
||||||
|
|
@ -467,7 +474,9 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
||||||
card.getSetCode(),
|
card.getSetCode(),
|
||||||
secondSideCard.getCardNumber(),
|
secondSideCard.getCardNumber(),
|
||||||
card.usesVariousArt(),
|
card.usesVariousArt(),
|
||||||
0, false, card.isDoubleFaced(), true);
|
0
|
||||||
|
);
|
||||||
|
url.setSecondSide(true);
|
||||||
allCardsUrls.add(url);
|
allCardsUrls.add(url);
|
||||||
}
|
}
|
||||||
if (card.isFlipCard()) {
|
if (card.isFlipCard()) {
|
||||||
|
|
@ -479,9 +488,10 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
||||||
card.getSetCode(),
|
card.getSetCode(),
|
||||||
card.getCardNumber(),
|
card.getCardNumber(),
|
||||||
card.usesVariousArt(),
|
card.usesVariousArt(),
|
||||||
0, false, card.isDoubleFaced(), card.isNightCard());
|
0
|
||||||
cardDownloadData.setFlipCard(true);
|
);
|
||||||
cardDownloadData.setFlippedSide(true);
|
cardDownloadData.setFlippedSide(true);
|
||||||
|
cardDownloadData.setSecondSide(card.isNightCard());
|
||||||
allCardsUrls.add(cardDownloadData);
|
allCardsUrls.add(cardDownloadData);
|
||||||
}
|
}
|
||||||
if (card.getMeldsToCardName() != null) {
|
if (card.getMeldsToCardName() != null) {
|
||||||
|
|
@ -500,7 +510,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
||||||
card.getSetCode(),
|
card.getSetCode(),
|
||||||
meldsToCard.getCardNumber(),
|
meldsToCard.getCardNumber(),
|
||||||
card.usesVariousArt(),
|
card.usesVariousArt(),
|
||||||
0, false, false, false);
|
0
|
||||||
|
);
|
||||||
allCardsUrls.add(url);
|
allCardsUrls.add(url);
|
||||||
}
|
}
|
||||||
if (card.isModalDoubleFacedCard()) {
|
if (card.isModalDoubleFacedCard()) {
|
||||||
|
|
@ -512,7 +523,9 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
||||||
card.getSetCode(),
|
card.getSetCode(),
|
||||||
card.getCardNumber(),
|
card.getCardNumber(),
|
||||||
card.usesVariousArt(),
|
card.usesVariousArt(),
|
||||||
0, false, true, true);
|
0
|
||||||
|
);
|
||||||
|
cardDownloadData.setSecondSide(true);
|
||||||
allCardsUrls.add(cardDownloadData);
|
allCardsUrls.add(cardDownloadData);
|
||||||
}
|
}
|
||||||
} else if (card.getCardNumber().isEmpty() || "0".equals(card.getCardNumber())) {
|
} else if (card.getCardNumber().isEmpty() || "0".equals(card.getCardNumber())) {
|
||||||
|
|
@ -531,9 +544,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
||||||
token.getSetCode(),
|
token.getSetCode(),
|
||||||
"0",
|
"0",
|
||||||
false,
|
false,
|
||||||
token.getImageNumber(),
|
token.getImageNumber());
|
||||||
true
|
card.setToken(true);
|
||||||
);
|
|
||||||
allCardsUrls.add(card);
|
allCardsUrls.add(card);
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -674,8 +686,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
||||||
reloadCardsToDownload(uiDialog.getSetsCombo().getSelectedItem().toString());
|
reloadCardsToDownload(uiDialog.getSetsCombo().getSelectedItem().toString());
|
||||||
enableDialogButtons();
|
enableDialogButtons();
|
||||||
|
|
||||||
// reset images cache
|
// reset GUI and cards to use new images
|
||||||
ImageCache.clearCache();
|
GUISizeHelper.refreshGUIAndCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
static String convertStreamToString(InputStream is) {
|
static String convertStreamToString(InputStream is) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
package org.mage.plugins.card.images;
|
package org.mage.plugins.card.images;
|
||||||
|
|
||||||
import com.google.common.collect.ComputationException;
|
|
||||||
import mage.abilities.icon.CardIconColor;
|
import mage.abilities.icon.CardIconColor;
|
||||||
import mage.client.constants.Constants;
|
import mage.client.constants.Constants;
|
||||||
|
import mage.client.util.ImageCaches;
|
||||||
import mage.client.util.SoftValuesLoadingCache;
|
import mage.client.util.SoftValuesLoadingCache;
|
||||||
import mage.client.util.TransformedImageCache;
|
import mage.client.util.TransformedImageCache;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import net.java.truevfs.access.TFile;
|
import net.java.truevfs.access.TFile;
|
||||||
import net.java.truevfs.access.TFileInputStream;
|
import net.java.truevfs.access.TFileInputStream;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
import org.mage.card.arcane.CardPanelRenderModeImage;
|
||||||
import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
|
import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
|
||||||
import org.mage.plugins.card.utils.CardImageUtils;
|
import org.mage.plugins.card.utils.CardImageUtils;
|
||||||
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
|
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
|
||||||
|
|
@ -21,17 +22,9 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class stores ALL card images in a cache with soft values. this means
|
* This class stores ALL card images in a cache with soft values. This means
|
||||||
* that the images may be garbage collected when they are not needed any more,
|
* that the images may be garbage collected when they are not needed any more,
|
||||||
* but will be kept as long as possible.
|
* but will be kept as long as possible.
|
||||||
* <p>
|
|
||||||
* Key format: "[cardname]#[setname]#[type]#[collectorID]#[image size]#[additional data]"
|
|
||||||
*
|
|
||||||
* <li>#Normal: request for unrotated image</li>
|
|
||||||
* <li>#Tapped: request for rotated image</li>
|
|
||||||
* <li>#Cropped: request for cropped image that is used for Shandalar like card
|
|
||||||
* look</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
*
|
||||||
* @author JayDi85
|
* @author JayDi85
|
||||||
*/
|
*/
|
||||||
|
|
@ -39,195 +32,105 @@ public final class ImageCache {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ImageCache.class);
|
private static final Logger LOGGER = Logger.getLogger(ImageCache.class);
|
||||||
|
|
||||||
private static final SoftValuesLoadingCache<String, ImageCacheData> IMAGE_CACHE; // cards and tokens
|
// global cache for both mtgo and image render modes
|
||||||
private static final SoftValuesLoadingCache<String, ImageCacheData> FACE_IMAGE_CACHE;
|
private static final SoftValuesLoadingCache<String, ImageCacheData> SHARED_CARD_IMAGES_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(ImageCache::createCardOrTokenImage));
|
||||||
private static final SoftValuesLoadingCache<String, ImageCacheData> CARD_ICONS_CACHE;
|
private static final SoftValuesLoadingCache<String, ImageCacheData> SHARED_CARD_ICONS_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(ImageCache::createIcon));
|
||||||
|
|
||||||
/**
|
// format: name #setcode #imagenumber #cardnumber #size #usesVariousArt
|
||||||
* Common pattern for keys. See ImageCache.getKey for structure info
|
private static final Pattern CARD_IMAGE_KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)#(.*)#(.*)");
|
||||||
*/
|
|
||||||
private static final Pattern KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)#(.*)#(.*)");
|
// format: size #icon #color
|
||||||
private static final Pattern CARD_ICON_KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)");
|
private static final Pattern CARD_ICON_KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)");
|
||||||
|
|
||||||
static {
|
|
||||||
// softValues() = Specifies that each value (not key) stored in the map should be wrapped in a SoftReference
|
|
||||||
// (by default, strong references are used). Softly-referenced objects will be garbage-collected in a
|
|
||||||
// globally least-recently-used manner, in response to memory demand.
|
|
||||||
IMAGE_CACHE = SoftValuesLoadingCache.from(key -> {
|
|
||||||
try {
|
|
||||||
boolean usesVariousArt = false;
|
|
||||||
if (key.matches(".*#usesVariousArt.*")) {
|
|
||||||
usesVariousArt = true;
|
|
||||||
key = key.replace("#usesVariousArt", "");
|
|
||||||
}
|
|
||||||
Matcher m = KEY_PATTERN.matcher(key);
|
|
||||||
|
|
||||||
if (m.matches()) {
|
|
||||||
String name = m.group(1);
|
|
||||||
String setCode = m.group(2);
|
|
||||||
Integer type = Integer.parseInt(m.group(3));
|
|
||||||
String collectorId = m.group(4);
|
|
||||||
if (collectorId.equals("null")) {
|
|
||||||
collectorId = "0";
|
|
||||||
}
|
|
||||||
|
|
||||||
CardDownloadData info = new CardDownloadData(name, setCode, collectorId, usesVariousArt, type);
|
|
||||||
|
|
||||||
boolean cardback = false;
|
|
||||||
String path;
|
|
||||||
if (collectorId.isEmpty() || "0".equals(collectorId)) {
|
|
||||||
// TOKEN
|
|
||||||
// can be a normal token or a token from card (see embalm ability)
|
|
||||||
info.setToken(true);
|
|
||||||
TFile tokenFile;
|
|
||||||
|
|
||||||
// try normal token
|
|
||||||
path = CardImageUtils.buildImagePathToCardOrToken(info);
|
|
||||||
tokenFile = getTFile(path);
|
|
||||||
|
|
||||||
// try token from card
|
|
||||||
// TODO: return image from another set on empty image?
|
|
||||||
if (tokenFile == null || !tokenFile.exists()) {
|
|
||||||
CardDownloadData tempInfo = new CardDownloadData(info);
|
|
||||||
tempInfo.setToken(false);
|
|
||||||
path = CardImageUtils.buildImagePathToCardOrToken(info);
|
|
||||||
tokenFile = getTFile(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// try unknown token image
|
|
||||||
if (tokenFile == null || !tokenFile.exists()) {
|
|
||||||
// TODO: replace empty token by other default card, not cardback
|
|
||||||
cardback = true;
|
|
||||||
path = CardImageUtils.buildImagePathToDefault(DirectLinksForDownload.cardbackFilename);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// CARD
|
|
||||||
path = CardImageUtils.buildImagePathToCardOrToken(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
TFile file = getTFile(path);
|
|
||||||
if (file == null) {
|
|
||||||
return new ImageCacheData(path, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cardback) {
|
|
||||||
// TODO: is there any different in images styles? Cardback must be from scryfall, not wizards
|
|
||||||
// need cardback image
|
|
||||||
BufferedImage image = loadImage(file);
|
|
||||||
image = getRoundCorner(image);
|
|
||||||
return new ImageCacheData(path, image);
|
|
||||||
} else {
|
|
||||||
// need normal card image
|
|
||||||
BufferedImage image = loadImage(file);
|
|
||||||
image = getWizardsCard(image);
|
|
||||||
image = getRoundCorner(image);
|
|
||||||
return new ImageCacheData(path, image);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Requested image doesn't fit the requirement for key (<cardname>#<setname>#<collectorID>): " + key);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
if (ex instanceof ComputationException) {
|
|
||||||
throw (ComputationException) ex;
|
|
||||||
} else {
|
|
||||||
throw new ComputationException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
FACE_IMAGE_CACHE = SoftValuesLoadingCache.from(key -> {
|
|
||||||
try {
|
|
||||||
Matcher m = KEY_PATTERN.matcher(key);
|
|
||||||
|
|
||||||
if (m.matches()) {
|
|
||||||
String name = m.group(1);
|
|
||||||
String setCode = m.group(2);
|
|
||||||
// skip type
|
|
||||||
// skip collectorId
|
|
||||||
|
|
||||||
String path = CardImageUtils.generateFaceImagePath(name, setCode);
|
|
||||||
TFile file = getTFile(path);
|
|
||||||
if (file == null) {
|
|
||||||
return new ImageCacheData(path, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedImage image = loadImage(file);
|
|
||||||
return new ImageCacheData(path, image);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Requested face image doesn't fit the requirement for key (<cardname>#<artid>#: " + key);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
if (ex instanceof ComputationException) {
|
|
||||||
throw (ComputationException) ex;
|
|
||||||
} else {
|
|
||||||
throw new ComputationException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
CARD_ICONS_CACHE = SoftValuesLoadingCache.from(key -> {
|
|
||||||
try {
|
|
||||||
Matcher m = CARD_ICON_KEY_PATTERN.matcher(key);
|
|
||||||
|
|
||||||
if (m.matches()) {
|
|
||||||
int cardSize = Integer.parseInt(m.group(1));
|
|
||||||
String resourceName = m.group(2);
|
|
||||||
CardIconColor cardIconColor = CardIconColor.valueOf(m.group(3));
|
|
||||||
BufferedImage image = ImageManagerImpl.instance.getCardIcon(resourceName, cardSize, cardIconColor);
|
|
||||||
return new ImageCacheData(resourceName, image);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Wrong card icons image key format: " + key);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
if (ex instanceof ComputationException) {
|
|
||||||
throw (ComputationException) ex;
|
|
||||||
} else {
|
|
||||||
throw new ComputationException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearCache() {
|
|
||||||
IMAGE_CACHE.invalidateAll();
|
|
||||||
FACE_IMAGE_CACHE.invalidateAll();
|
|
||||||
CARD_ICONS_CACHE.invalidateAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImageCache() {
|
private ImageCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageCacheData getCardbackImage() {
|
private static ImageCacheData createCardOrTokenImage(String key) {
|
||||||
String path = CardImageUtils.buildImagePathToDefault(DirectLinksForDownload.cardbackFilename);
|
boolean usesVariousArt = false;
|
||||||
BufferedImage image = ImageCache.loadImage(getTFile(path));
|
if (key.matches(".*#usesVariousArt.*")) {
|
||||||
image = getRoundCorner(image);
|
usesVariousArt = true;
|
||||||
return new ImageCacheData(path, image);
|
key = key.replace("#usesVariousArt", "");
|
||||||
|
}
|
||||||
|
Matcher m = CARD_IMAGE_KEY_PATTERN.matcher(key);
|
||||||
|
|
||||||
|
if (m.matches()) {
|
||||||
|
String name = m.group(1);
|
||||||
|
String setCode = m.group(2);
|
||||||
|
Integer imageNumber = Integer.parseInt(m.group(3));
|
||||||
|
String collectorId = m.group(4);
|
||||||
|
if (collectorId.equals("null")) {
|
||||||
|
collectorId = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
CardDownloadData info = new CardDownloadData(name, setCode, collectorId, usesVariousArt, imageNumber);
|
||||||
|
|
||||||
|
boolean cardback = false;
|
||||||
|
String path;
|
||||||
|
if (collectorId.isEmpty() || "0".equals(collectorId)) {
|
||||||
|
// TOKEN
|
||||||
|
// can be a normal token or a token from card (see embalm ability)
|
||||||
|
info.setToken(true);
|
||||||
|
TFile tokenFile;
|
||||||
|
|
||||||
|
// try normal token
|
||||||
|
path = CardImageUtils.buildImagePathToCardOrToken(info);
|
||||||
|
tokenFile = getTFile(path);
|
||||||
|
|
||||||
|
// try token from card
|
||||||
|
// TODO: unused code?
|
||||||
|
// TODO: return image from another set on empty image?
|
||||||
|
if (tokenFile == null || !tokenFile.exists()) {
|
||||||
|
CardDownloadData tempInfo = new CardDownloadData(info);
|
||||||
|
tempInfo.setToken(false);
|
||||||
|
path = CardImageUtils.buildImagePathToCardOrToken(info);
|
||||||
|
tokenFile = getTFile(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// try unknown token image
|
||||||
|
if (tokenFile == null || !tokenFile.exists()) {
|
||||||
|
// TODO: replace empty token by other default card, not cardback
|
||||||
|
cardback = true;
|
||||||
|
path = CardImageUtils.buildImagePathToDefault(DirectLinksForDownload.cardbackFilename);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// CARD
|
||||||
|
path = CardImageUtils.buildImagePathToCardOrToken(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
TFile file = getTFile(path);
|
||||||
|
if (file == null) {
|
||||||
|
return new ImageCacheData(path, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardback) {
|
||||||
|
// TODO: is there any different in images styles? Cardback must be from scryfall, not wizards
|
||||||
|
// need cardback image
|
||||||
|
BufferedImage image = loadImage(file);
|
||||||
|
image = getRoundCorner(image);
|
||||||
|
return new ImageCacheData(path, image);
|
||||||
|
} else {
|
||||||
|
// need normal card image
|
||||||
|
BufferedImage image = loadImage(file);
|
||||||
|
image = getWizardsCard(image);
|
||||||
|
image = getRoundCorner(image);
|
||||||
|
return new ImageCacheData(path, image);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown card image's key format: " + key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageCacheData getMorphImage() {
|
private static ImageCacheData createIcon(String key) {
|
||||||
// TODO: replace by downloadable morth image
|
Matcher m = CARD_ICON_KEY_PATTERN.matcher(key);
|
||||||
CardDownloadData info = new CardDownloadData("Morph", "KTK", "0", false, 0);
|
if (m.matches()) {
|
||||||
info.setToken(true);
|
int cardSize = Integer.parseInt(m.group(1));
|
||||||
String path = CardImageUtils.buildImagePathToCardOrToken(info);
|
String resourceName = m.group(2);
|
||||||
|
CardIconColor cardIconColor = CardIconColor.valueOf(m.group(3));
|
||||||
TFile file = getTFile(path);
|
BufferedImage image = ImageManagerImpl.instance.getCardIcon(resourceName, cardSize, cardIconColor);
|
||||||
BufferedImage image = loadImage(file);
|
return new ImageCacheData(resourceName, image);
|
||||||
image = getRoundCorner(image);
|
} else {
|
||||||
return new ImageCacheData(path, image);
|
throw new IllegalArgumentException("Unknown card icon's key format: " + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageCacheData getManifestImage() {
|
|
||||||
// TODO: replace by downloadable manifestest image
|
|
||||||
CardDownloadData info = new CardDownloadData("Manifest", "FRF", "0", false, 0);
|
|
||||||
info.setToken(true);
|
|
||||||
String path = CardImageUtils.buildImagePathToCardOrToken(info);
|
|
||||||
|
|
||||||
TFile file = getTFile(path);
|
|
||||||
BufferedImage image = loadImage(file);
|
|
||||||
image = getRoundCorner(image);
|
|
||||||
return new ImageCacheData(path, image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BufferedImage getRoundCorner(BufferedImage image) {
|
public static BufferedImage getRoundCorner(BufferedImage image) {
|
||||||
|
|
@ -269,61 +172,52 @@ public final class ImageCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageCacheData getImageOriginal(CardView card) {
|
/** Find image for current side
|
||||||
return getImage(getKey(card, card.getName(), 0));
|
*/
|
||||||
|
public static ImageCacheData getCardImageOriginal(CardView card) {
|
||||||
|
return getCardImage(getKey(card, card.getName(), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageCacheData getImageOriginalAlternateName(CardView card) {
|
/**
|
||||||
return getImage(getKey(card, card.getAlternateName(), 0));
|
* Find image for other side
|
||||||
|
*/
|
||||||
|
public static ImageCacheData getCardImageAlternate(CardView card) {
|
||||||
|
return getCardImage(getKey(card, card.getAlternateName(), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageCacheData getCardIconImage(String resourceName, int iconSize, String cardColorName) {
|
public static ImageCacheData getCardIconImage(String resourceName, int iconSize, String cardColorName) {
|
||||||
return getCardIconImage(getCardIconKey(resourceName, iconSize, cardColorName));
|
return getCardIconImage(getCardIconKey(resourceName, iconSize, cardColorName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static ImageCacheData getCardImage(String key) {
|
||||||
* Returns the Image corresponding to the key
|
|
||||||
*/
|
|
||||||
private static ImageCacheData getImage(String key) {
|
|
||||||
try {
|
try {
|
||||||
ImageCacheData data = IMAGE_CACHE.getOrNull(key);
|
ImageCacheData data = SHARED_CARD_IMAGES_CACHE.getOrNull(key);
|
||||||
return data != null ? data : new ImageCacheData("ERROR: key - " + key, null);
|
return data != null ? data : new ImageCacheData("ERROR: key - " + key, null);
|
||||||
} catch (ComputationException ex) {
|
} catch (Exception e) {
|
||||||
// too low memory
|
if (e.getCause() instanceof NullPointerException) {
|
||||||
if (ex.getCause() instanceof NullPointerException) {
|
// low memory error???
|
||||||
return new ImageCacheData("ERROR: low memory?", null);
|
return new ImageCacheData("ERROR: possible low memory", null);
|
||||||
|
} else {
|
||||||
|
// other error
|
||||||
|
LOGGER.error("Error while loading card image: " + e, e);
|
||||||
|
return new ImageCacheData("ERROR: see client logs for details", null);
|
||||||
}
|
}
|
||||||
LOGGER.error(ex, ex);
|
|
||||||
return new ImageCacheData("ERROR: see logs", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Image corresponding to the key
|
|
||||||
*/
|
|
||||||
private static ImageCacheData getFaceImage(String key) {
|
|
||||||
try {
|
|
||||||
ImageCacheData data = FACE_IMAGE_CACHE.getOrNull(key);
|
|
||||||
return data != null ? data : new ImageCacheData("ERROR: key " + key, null);
|
|
||||||
} catch (ComputationException ex) {
|
|
||||||
if (ex.getCause() instanceof NullPointerException) {
|
|
||||||
return new ImageCacheData("ERROR: low memory?", null);
|
|
||||||
}
|
|
||||||
LOGGER.error(ex, ex);
|
|
||||||
return new ImageCacheData("ERROR: see logs", null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImageCacheData getCardIconImage(String key) {
|
private static ImageCacheData getCardIconImage(String key) {
|
||||||
try {
|
try {
|
||||||
ImageCacheData data = CARD_ICONS_CACHE.getOrNull(key);
|
ImageCacheData data = SHARED_CARD_ICONS_CACHE.getOrNull(key);
|
||||||
return data != null ? data : new ImageCacheData("ERROR: key - " + key, null);
|
return data != null ? data : new ImageCacheData("ERROR: key - " + key, null);
|
||||||
} catch (ComputationException ex) {
|
} catch (Exception e) {
|
||||||
if (ex.getCause() instanceof NullPointerException) {
|
if (e.getCause() instanceof NullPointerException) {
|
||||||
return new ImageCacheData("ERROR: low memory?", null);
|
// low memory error???
|
||||||
|
return new ImageCacheData("ERROR: possible low memory", null);
|
||||||
|
} else {
|
||||||
|
// other error
|
||||||
|
LOGGER.error("Error while loading card icon: " + e, e);
|
||||||
|
return new ImageCacheData("ERROR: see client logs for details", null);
|
||||||
}
|
}
|
||||||
LOGGER.error(ex, ex);
|
|
||||||
return new ImageCacheData("ERROR: see logs", null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,7 +226,7 @@ public final class ImageCache {
|
||||||
* the cache.
|
* the cache.
|
||||||
*/
|
*/
|
||||||
private static ImageCacheData tryGetImage(String key) {
|
private static ImageCacheData tryGetImage(String key) {
|
||||||
return IMAGE_CACHE.peekIfPresent(key);
|
return SHARED_CARD_IMAGES_CACHE.peekIfPresent(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -343,7 +237,11 @@ public final class ImageCache {
|
||||||
* @param imageSize - size info, 0 to use original image (with max size)
|
* @param imageSize - size info, 0 to use original image (with max size)
|
||||||
*/
|
*/
|
||||||
private static String getKey(CardView card, String cardName, int imageSize) {
|
private static String getKey(CardView card, String cardName, int imageSize) {
|
||||||
return (card.isToken() ? cardName.replace(" Token", "") : cardName)
|
String imageFileName = card.getImageFileName();
|
||||||
|
if (imageFileName.isEmpty()) {
|
||||||
|
imageFileName = cardName;
|
||||||
|
}
|
||||||
|
return imageFileName.replace(" Token", "")
|
||||||
+ '#' + card.getExpansionSetCode()
|
+ '#' + card.getExpansionSetCode()
|
||||||
+ '#' + card.getImageNumber()
|
+ '#' + card.getImageNumber()
|
||||||
+ '#' + card.getCardNumber()
|
+ '#' + card.getCardNumber()
|
||||||
|
|
@ -420,9 +318,9 @@ public final class ImageCache {
|
||||||
* @param height
|
* @param height
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static ImageCacheData getImage(CardView card, int width, int height) {
|
public static ImageCacheData getCardImage(CardView card, int width, int height) {
|
||||||
String key = getKey(card, card.getName(), width);
|
String key = getKey(card, card.getName(), width);
|
||||||
ImageCacheData data = getImage(key);
|
ImageCacheData data = getCardImage(key);
|
||||||
if (data.getImage() == null) {
|
if (data.getImage() == null) {
|
||||||
LOGGER.debug("Image doesn't exists in the cache: " + key);
|
LOGGER.debug("Image doesn't exists in the cache: " + key);
|
||||||
return data;
|
return data;
|
||||||
|
|
@ -438,23 +336,6 @@ public final class ImageCache {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the image appropriate to display the card in the picture panel
|
|
||||||
*
|
|
||||||
* @param card
|
|
||||||
* @param width
|
|
||||||
* @param height
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static ImageCacheData getFaceImage(CardView card, int width, int height) {
|
|
||||||
String key = getFaceKey(card, card.getName(), card.getExpansionSetCode());
|
|
||||||
ImageCacheData data = getFaceImage(key);
|
|
||||||
if (data.getImage() == null) {
|
|
||||||
LOGGER.debug(key + " (faceimage) not found");
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the image appropriate to display for a card in a picture panel,
|
* Returns the image appropriate to display for a card in a picture panel,
|
||||||
* but only it was ALREADY LOADED. That is, the call is immediate and will
|
* but only it was ALREADY LOADED. That is, the call is immediate and will
|
||||||
|
|
|
||||||
|
|
@ -146,37 +146,47 @@ public final class CardImageUtils {
|
||||||
*/
|
*/
|
||||||
public static String buildImagePathToCardView(CardView card) {
|
public static String buildImagePathToCardView(CardView card) {
|
||||||
String imageFile;
|
String imageFile;
|
||||||
if (card.getMageObjectType().isUseTokensRepository()) {
|
String imageFileName = card.getImageFileName();
|
||||||
// token images
|
if (imageFileName.isEmpty()) {
|
||||||
|
imageFileName = card.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageFileName.isEmpty()) {
|
||||||
|
return "ERROR: empty image file name, object type - " + card.getMageObjectType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card.getMageObjectType().isUseTokensRepository()
|
||||||
|
|| card.getExpansionSetCode().equals(TokenRepository.XMAGE_TOKENS_SET_CODE)) {
|
||||||
|
// token images or inner cards like face down
|
||||||
CardDownloadData cardData = new CardDownloadData(
|
CardDownloadData cardData = new CardDownloadData(
|
||||||
card.getName().replace(" Token", ""),
|
imageFileName.replace(" Token", ""),
|
||||||
card.getExpansionSetCode(),
|
card.getExpansionSetCode(),
|
||||||
"0",
|
card.getCardNumber(),
|
||||||
false,
|
card.getUsesVariousArt(),
|
||||||
card.getImageNumber(),
|
card.getImageNumber());
|
||||||
true);
|
cardData.setToken(true);
|
||||||
imageFile = CardImageUtils.buildImagePathToCardOrToken(cardData);
|
imageFile = CardImageUtils.buildImagePathToCardOrToken(cardData);
|
||||||
} else {
|
} else {
|
||||||
TokenRepository.instance.getAll();
|
|
||||||
// card images
|
// card images
|
||||||
// workaround to find various art settings first
|
// workaround to find various art settings first
|
||||||
|
// TODO: no needs in workaround?! ?!
|
||||||
|
boolean usesVariousArt = false;
|
||||||
CardInfo cardInfo = CardRepository.instance.findCardWithPreferredSetAndNumber(
|
CardInfo cardInfo = CardRepository.instance.findCardWithPreferredSetAndNumber(
|
||||||
card.getName(),
|
card.getName(),
|
||||||
card.getExpansionSetCode(),
|
card.getExpansionSetCode(),
|
||||||
card.getCardNumber()
|
card.getCardNumber()
|
||||||
);
|
);
|
||||||
if (cardInfo != null) {
|
if (cardInfo != null) {
|
||||||
CardDownloadData cardData = new CardDownloadData(
|
usesVariousArt = cardInfo.usesVariousArt();
|
||||||
cardInfo.getName(),
|
|
||||||
cardInfo.getSetCode(),
|
|
||||||
cardInfo.getCardNumber(),
|
|
||||||
cardInfo.usesVariousArt(),
|
|
||||||
card.getImageNumber()
|
|
||||||
);
|
|
||||||
imageFile = CardImageUtils.buildImagePathToCardOrToken(cardData);
|
|
||||||
} else {
|
|
||||||
imageFile = "ERROR: can't find card info in repository - " + card.getName();
|
|
||||||
}
|
}
|
||||||
|
CardDownloadData cardData = new CardDownloadData(
|
||||||
|
imageFileName,
|
||||||
|
card.getExpansionSetCode(),
|
||||||
|
card.getCardNumber(),
|
||||||
|
card.getUsesVariousArt(), // TODO: need to use usesVariousArt instead card?
|
||||||
|
card.getImageNumber()
|
||||||
|
);
|
||||||
|
imageFile = CardImageUtils.buildImagePathToCardOrToken(cardData);
|
||||||
}
|
}
|
||||||
return imageFile;
|
return imageFile;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
97
Mage.Common/src/main/java/mage/view/CardImageView.java
Normal file
97
Mage.Common/src/main/java/mage/view/CardImageView.java
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package mage.view;
|
||||||
|
|
||||||
|
import mage.cards.Card;
|
||||||
|
import mage.game.command.CommandObject;
|
||||||
|
import mage.game.permanent.token.Token;
|
||||||
|
import mage.util.Copyable;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: delete, no needs?!
|
||||||
|
*
|
||||||
|
* GUI: card drawing info
|
||||||
|
* Can be different from real card name, set code, etc - see morph, copy, etc)
|
||||||
|
*
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public class CardImageView implements Serializable, Copyable<CardImageView> {
|
||||||
|
|
||||||
|
private boolean isTokenRepository; // card or token database
|
||||||
|
private String cardName; // card or token
|
||||||
|
private String setCode; // card or token
|
||||||
|
private String cardNumber; // card only, token has "0"
|
||||||
|
private Integer imageNumber; // token only
|
||||||
|
private boolean isUseVariousArt; // card only
|
||||||
|
|
||||||
|
public CardImageView() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CardImageView(final CardImageView cardImageView) {
|
||||||
|
this.isTokenRepository = cardImageView.isTokenRepository;
|
||||||
|
this.cardName = cardImageView.cardName;
|
||||||
|
this.setCode = cardImageView.setCode;
|
||||||
|
this.cardNumber = cardImageView.cardNumber;
|
||||||
|
this.imageNumber = cardImageView.imageNumber;
|
||||||
|
this.isUseVariousArt = cardImageView.isUseVariousArt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CardImageView fromCard(Card card) {
|
||||||
|
this.isTokenRepository = false;
|
||||||
|
this.cardName = card.getName();
|
||||||
|
this.setCode = card.getExpansionSetCode();
|
||||||
|
this.cardNumber = card.getCardNumber();
|
||||||
|
this.imageNumber = card.getImageNumber();
|
||||||
|
this.isUseVariousArt = card.getUsesVariousArt();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CardImageView fromToken(Token token) {
|
||||||
|
this.isTokenRepository = true;
|
||||||
|
this.cardName = token.getName();
|
||||||
|
this.setCode = token.getExpansionSetCode();
|
||||||
|
this.cardNumber = token.getCardNumber();
|
||||||
|
this.imageNumber = token.getImageNumber();
|
||||||
|
this.isUseVariousArt = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CardImageView fromCommandObject(CommandObject commandObject) {
|
||||||
|
this.isTokenRepository = true;
|
||||||
|
this.cardName = commandObject.getName();
|
||||||
|
this.setCode = commandObject.getExpansionSetCode();
|
||||||
|
this.cardNumber = commandObject.getCardNumber();
|
||||||
|
this.imageNumber = commandObject.getImageNumber();
|
||||||
|
this.isUseVariousArt = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CardImageView copy() {
|
||||||
|
return new CardImageView(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTokenRepository() {
|
||||||
|
return isTokenRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCardName() {
|
||||||
|
return cardName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSetCode() {
|
||||||
|
return setCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCardNumber() {
|
||||||
|
return cardNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getImageNumber() {
|
||||||
|
return imageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUseVariousArt() {
|
||||||
|
return isUseVariousArt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,9 +16,12 @@ import mage.abilities.icon.CardIcon;
|
||||||
import mage.abilities.icon.CardIconImpl;
|
import mage.abilities.icon.CardIconImpl;
|
||||||
import mage.abilities.icon.CardIconType;
|
import mage.abilities.icon.CardIconType;
|
||||||
import mage.abilities.keyword.AftermathAbility;
|
import mage.abilities.keyword.AftermathAbility;
|
||||||
|
import mage.abilities.keyword.ForetellAbility;
|
||||||
import mage.cards.*;
|
import mage.cards.*;
|
||||||
import mage.cards.mock.MockCard;
|
import mage.cards.mock.MockCard;
|
||||||
import mage.cards.repository.CardInfo;
|
import mage.cards.repository.CardInfo;
|
||||||
|
import mage.cards.repository.TokenInfo;
|
||||||
|
import mage.cards.repository.TokenRepository;
|
||||||
import mage.constants.*;
|
import mage.constants.*;
|
||||||
import mage.counters.Counter;
|
import mage.counters.Counter;
|
||||||
import mage.counters.CounterType;
|
import mage.counters.CounterType;
|
||||||
|
|
@ -89,7 +92,8 @@ public class CardView extends SimpleCardView {
|
||||||
protected boolean isToken;
|
protected boolean isToken;
|
||||||
|
|
||||||
protected CardView ability;
|
protected CardView ability;
|
||||||
protected int imageNumber;
|
protected String imageFileName = "";
|
||||||
|
protected int imageNumber = 0;
|
||||||
|
|
||||||
protected boolean extraDeckCard;
|
protected boolean extraDeckCard;
|
||||||
protected boolean transformable; // can toggle one card side to another (transformable cards, modal double faces)
|
protected boolean transformable; // can toggle one card side to another (transformable cards, modal double faces)
|
||||||
|
|
@ -134,12 +138,12 @@ public class CardView extends SimpleCardView {
|
||||||
protected List<CardIcon> cardIcons = new ArrayList<>(); // additional icons to render
|
protected List<CardIcon> cardIcons = new ArrayList<>(); // additional icons to render
|
||||||
|
|
||||||
// GUI related: additional info about current object (example: real PT)
|
// GUI related: additional info about current object (example: real PT)
|
||||||
|
// warning, do not send full object, use some fields only (client must not get any server side data)
|
||||||
|
// warning, don't forget to hide it in face down cards (null)
|
||||||
protected MageInt originalPower = null;
|
protected MageInt originalPower = null;
|
||||||
protected MageInt originalToughness = null;
|
protected MageInt originalToughness = null;
|
||||||
protected FilterMana originalColorIdentity = null;
|
protected String originalColorIdentity = null; // GUI related info for sorting, searching, etc
|
||||||
protected UUID originalId = null;
|
|
||||||
protected boolean originalIsCopy = false;
|
protected boolean originalIsCopy = false;
|
||||||
protected boolean originalIsCard = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non game usage like deck editor
|
* Non game usage like deck editor
|
||||||
|
|
@ -154,6 +158,17 @@ public class CardView extends SimpleCardView {
|
||||||
this(card, game, false);
|
this(card, game, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param card
|
||||||
|
* @param game
|
||||||
|
* @param showAsControlled is the card view created for the card controller - used
|
||||||
|
* for morph / face down cards to know which player may see information for
|
||||||
|
* the card TODO: turn controller can be here too?
|
||||||
|
*/
|
||||||
|
public CardView(Card card, Game game, boolean showAsControlled) {
|
||||||
|
this(card, game, showAsControlled, false);
|
||||||
|
}
|
||||||
|
|
||||||
public CardView(Card card, SimpleCardView simpleCardView) {
|
public CardView(Card card, SimpleCardView simpleCardView) {
|
||||||
this(card, null, false);
|
this(card, null, false);
|
||||||
this.id = simpleCardView.getId();
|
this.id = simpleCardView.getId();
|
||||||
|
|
@ -163,11 +178,6 @@ public class CardView extends SimpleCardView {
|
||||||
this.isSelected = simpleCardView.isSelected;
|
this.isSelected = simpleCardView.isSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardView(Card card, Game game, UUID cardId) {
|
|
||||||
this(card, game, false);
|
|
||||||
this.id = cardId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CardView(final CardView cardView) {
|
public CardView(final CardView cardView) {
|
||||||
super(cardView);
|
super(cardView);
|
||||||
|
|
||||||
|
|
@ -192,6 +202,7 @@ public class CardView extends SimpleCardView {
|
||||||
|
|
||||||
this.expansionSetCode = cardView.expansionSetCode;
|
this.expansionSetCode = cardView.expansionSetCode;
|
||||||
this.cardNumber = cardView.cardNumber;
|
this.cardNumber = cardView.cardNumber;
|
||||||
|
this.imageFileName = cardView.imageFileName;
|
||||||
this.imageNumber = cardView.imageNumber;
|
this.imageNumber = cardView.imageNumber;
|
||||||
|
|
||||||
this.color = cardView.color.copy();
|
this.color = cardView.color.copy();
|
||||||
|
|
@ -246,32 +257,21 @@ public class CardView extends SimpleCardView {
|
||||||
this.canAttack = cardView.canAttack;
|
this.canAttack = cardView.canAttack;
|
||||||
this.canBlock = cardView.canBlock;
|
this.canBlock = cardView.canBlock;
|
||||||
this.inViewerOnly = cardView.inViewerOnly;
|
this.inViewerOnly = cardView.inViewerOnly;
|
||||||
this.originalPower = cardView.originalPower;
|
|
||||||
this.originalToughness = cardView.originalToughness;
|
|
||||||
this.originalColorIdentity = cardView.originalColorIdentity;
|
|
||||||
this.originalId = cardView.originalId;
|
|
||||||
this.originalIsCard = cardView.originalIsCard;
|
|
||||||
this.originalIsCopy = cardView.originalIsCopy;
|
|
||||||
if (cardView.cardIcons != null) {
|
if (cardView.cardIcons != null) {
|
||||||
cardView.cardIcons.forEach(icon -> this.cardIcons.add(icon.copy()));
|
cardView.cardIcons.forEach(icon -> this.cardIcons.add(icon.copy()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.originalPower = cardView.originalPower;
|
||||||
|
this.originalToughness = cardView.originalToughness;
|
||||||
|
this.originalColorIdentity = cardView.originalColorIdentity;
|
||||||
|
this.originalIsCopy = cardView.originalIsCopy;
|
||||||
|
|
||||||
this.playableStats = cardView.playableStats.copy();
|
this.playableStats = cardView.playableStats.copy();
|
||||||
this.isChoosable = cardView.isChoosable;
|
this.isChoosable = cardView.isChoosable;
|
||||||
this.isSelected = cardView.isSelected;
|
this.isSelected = cardView.isSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param card
|
|
||||||
* @param game
|
|
||||||
* @param controlled is the card view created for the card controller - used
|
|
||||||
* for morph / face down cards to know which player may see information for
|
|
||||||
* the card
|
|
||||||
*/
|
|
||||||
public CardView(Card card, Game game, boolean controlled) {
|
|
||||||
this(card, game, controlled, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getCardTypeLine(Game game, Card card) {
|
private static String getCardTypeLine(Game game, Card card) {
|
||||||
StringBuilder sbType = new StringBuilder();
|
StringBuilder sbType = new StringBuilder();
|
||||||
for (SuperType superType : card.getSuperType(game)) {
|
for (SuperType superType : card.getSuperType(game)) {
|
||||||
|
|
@ -290,145 +290,184 @@ public class CardView extends SimpleCardView {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param card
|
* @param sourceCard
|
||||||
* @param game
|
* @param game
|
||||||
* @param controlled is the card view created for the card controller - used
|
* @param showAsControlled is the card view created for the card controller/owner - used
|
||||||
* for morph / face down cards to know which player may see information for
|
* for morph / face down cards to know which player may see information for
|
||||||
* the card
|
* the card
|
||||||
* @param showFaceDownCard if true and the card is not on the battlefield,
|
|
||||||
* also a face down card is shown in the view, face down cards will be shown
|
|
||||||
* @param storeZone if true the card zone will be set in the zone attribute.
|
* @param storeZone if true the card zone will be set in the zone attribute.
|
||||||
*/
|
*/
|
||||||
public CardView(Card card, Game game, boolean controlled, boolean showFaceDownCard, boolean storeZone) {
|
public CardView(Card sourceCard, Game game, boolean showAsControlled, boolean storeZone) {
|
||||||
super(card.getId(), card.getExpansionSetCode(), card.getCardNumber(), card.getUsesVariousArt(), game != null);
|
super(sourceCard.getId(), sourceCard.getExpansionSetCode(), sourceCard.getCardNumber(), sourceCard.getUsesVariousArt(), game != null);
|
||||||
this.setOriginalValues(card);
|
|
||||||
this.imageNumber = card.getImageNumber();
|
// TODO: it's too big and can be buggy (something miss?) - must check and refactor: setup face down/up params, setup shared data like counters and targets
|
||||||
|
|
||||||
|
|
||||||
|
// Visible logic:
|
||||||
|
// * Normal card:
|
||||||
|
// - original name, original image
|
||||||
|
// * Face down card:
|
||||||
|
// * my cards or game end:
|
||||||
|
// - face down status + original name, face down image, day/night button
|
||||||
|
// * opponent cards:
|
||||||
|
// - face down status, face down image
|
||||||
|
|
||||||
|
// find real name from original card, cause face down status can be applied to card/spell
|
||||||
|
String sourceName = sourceCard.getMainCard().getName();
|
||||||
|
|
||||||
|
// find real spell characteristics before resolve
|
||||||
|
Card card = sourceCard.copy();
|
||||||
|
if (game != null && card instanceof Spell) {
|
||||||
|
card = ((Spell) card).getSpellAbility().getCharacteristics(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use isFaceDown(game) only here to find real status, all other code must use this.faceDown
|
||||||
|
this.faceDown = game != null && sourceCard.isFaceDown(game);
|
||||||
|
boolean showFaceUp = !this.faceDown;
|
||||||
|
|
||||||
|
// show real name and day/night button for controller or any player at the game's end
|
||||||
|
boolean showHiddenFaceDownData = showAsControlled || (game != null && game.hasEnded());
|
||||||
|
|
||||||
|
// default image info
|
||||||
|
this.expansionSetCode = card.getExpansionSetCode();
|
||||||
|
this.cardNumber = card.getCardNumber();
|
||||||
|
this.imageFileName = card.getImageFileName();
|
||||||
|
this.imageNumber = card.getImageNumber();
|
||||||
|
this.usesVariousArt = card.getUsesVariousArt();
|
||||||
|
|
||||||
|
// permanent data
|
||||||
|
if (showFaceUp) {
|
||||||
|
this.setOriginalValues(card);
|
||||||
|
}
|
||||||
|
|
||||||
// no information available for face down cards as long it's not a controlled face down morph card
|
|
||||||
// TODO: Better handle this in Framework (but currently I'm not sure how to do it there) LevelX2
|
|
||||||
boolean showFaceUp = true;
|
|
||||||
if (game != null) {
|
if (game != null) {
|
||||||
Zone cardZone = game.getState().getZone(card.getId());
|
Zone cardZone = game.getState().getZone(card.getId());
|
||||||
if (card.isFaceDown(game)) {
|
if (storeZone) {
|
||||||
showFaceUp = false;
|
// TODO: research, why it used here?
|
||||||
if (Zone.BATTLEFIELD != cardZone) {
|
this.zone = cardZone;
|
||||||
if (showFaceDownCard) {
|
}
|
||||||
showFaceUp = true;
|
}
|
||||||
|
|
||||||
|
// FACE DOWN
|
||||||
|
if (!showFaceUp) {
|
||||||
|
this.fillEmptyWithImageInfo(game, card, true);
|
||||||
|
|
||||||
|
// can show face up card name for controller or game end
|
||||||
|
String visibleName = CardUtil.getCardNameForGUI(showHiddenFaceDownData ? sourceName : "", this.imageFileName);
|
||||||
|
this.name = visibleName;
|
||||||
|
this.displayName = visibleName;
|
||||||
|
this.displayFullName = visibleName;
|
||||||
|
this.alternateName = visibleName;
|
||||||
|
|
||||||
|
// workaround to add PT, creature type and face up ability text (for stack and battlefield zones only)
|
||||||
|
// in other zones it has only face down status/name
|
||||||
|
if (sourceCard instanceof Spell
|
||||||
|
|| card instanceof Permanent) {
|
||||||
|
this.power = Integer.toString(card.getPower().getValue());
|
||||||
|
this.toughness = Integer.toString(card.getToughness().getValue());
|
||||||
|
this.cardTypes = new ArrayList<>(card.getCardType());
|
||||||
|
this.rules = new ArrayList<>(card.getRules());
|
||||||
|
|
||||||
|
// additional rules for stack (example: morph ability text)
|
||||||
|
if (sourceCard instanceof Spell) {
|
||||||
|
List<String> extraRules = sourceCard.getSpellAbility().getSpellAbilityCastMode().getAdditionalRulesOnStack();
|
||||||
|
if (extraRules != null) {
|
||||||
|
this.rules.addAll(extraRules);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storeZone) {
|
// GUI: enable day/night button to view original face up card
|
||||||
this.zone = cardZone;
|
if (showHiddenFaceDownData) {
|
||||||
|
this.transformable = true;
|
||||||
|
this.secondCardFace = new CardView(sourceCard.getMainCard()); // do not use game param, so it will take default card
|
||||||
|
this.alternateName = sourceCard.getMainCard().getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// boolean showFaceUp = game == null || !card.isFaceDown(game) || (!game.getState().getZone(card.getId()).equals(Zone.BATTLEFIELD) && showFaceDownCard);
|
|
||||||
|
|
||||||
if (!showFaceUp) {
|
// FACE UP and shared data like counters
|
||||||
this.fillEmpty(card, controlled);
|
|
||||||
if (card instanceof Spell) {
|
if (showFaceUp) {
|
||||||
// TODO: add face down image here???
|
SplitCard splitCard = null;
|
||||||
// special handling for casting of Morph cards
|
if (card instanceof SplitCard) {
|
||||||
if (controlled) {
|
splitCard = (SplitCard) card;
|
||||||
this.name = card.getName();
|
rotate = (card.getSpellAbility().getSpellAbilityType()) != SpellAbilityType.SPLIT_AFTERMATH;
|
||||||
this.displayName = card.getName();
|
} else if (card instanceof Spell) {
|
||||||
this.displayFullName = card.getName();
|
switch (card.getSpellAbility().getSpellAbilityType()) {
|
||||||
this.alternateName = card.getName();
|
case SPLIT_FUSED:
|
||||||
|
splitCard = (SplitCard) ((Spell) card).getCard();
|
||||||
|
rotate = true;
|
||||||
|
break;
|
||||||
|
case SPLIT_AFTERMATH:
|
||||||
|
splitCard = (SplitCard) ((Spell) card).getCard();
|
||||||
|
rotate = false;
|
||||||
|
break;
|
||||||
|
case SPLIT_LEFT:
|
||||||
|
case SPLIT_RIGHT:
|
||||||
|
rotate = true;
|
||||||
|
break;
|
||||||
|
case MODAL_LEFT:
|
||||||
|
case MODAL_RIGHT:
|
||||||
|
rotate = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
this.power = "2";
|
}
|
||||||
this.toughness = "2";
|
|
||||||
this.rules.add("You may cast this card as a 2/2 face-down creature, with no text,"
|
String fullCardName;
|
||||||
+ " no name, no subtypes, and no mana cost by paying {3} rather than paying its mana cost.");
|
if (splitCard != null) {
|
||||||
return;
|
this.isSplitCard = true;
|
||||||
} else if (card instanceof Permanent) {
|
leftSplitName = splitCard.getLeftHalfCard().getName();
|
||||||
this.power = Integer.toString(card.getPower().getValue());
|
leftSplitCostsStr = String.join("", splitCard.getLeftHalfCard().getManaCostSymbols());
|
||||||
this.toughness = Integer.toString(card.getToughness().getValue());
|
leftSplitRules = splitCard.getLeftHalfCard().getRules(game);
|
||||||
this.cardTypes = new ArrayList<>(card.getCardType(game));
|
leftSplitTypeLine = getCardTypeLine(game, splitCard.getLeftHalfCard());
|
||||||
this.faceDown = card.isFaceDown(game);
|
rightSplitName = splitCard.getRightHalfCard().getName();
|
||||||
|
rightSplitCostsStr = String.join("", splitCard.getRightHalfCard().getManaCostSymbols());
|
||||||
|
rightSplitRules = splitCard.getRightHalfCard().getRules(game);
|
||||||
|
rightSplitTypeLine = getCardTypeLine(game, splitCard.getRightHalfCard());
|
||||||
|
|
||||||
|
fullCardName = card.getName(); // split card contains full name as normal
|
||||||
|
this.manaCostLeftStr = splitCard.getLeftHalfCard().getManaCostSymbols();
|
||||||
|
this.manaCostRightStr = splitCard.getRightHalfCard().getManaCostSymbols();
|
||||||
|
} else if (card instanceof ModalDoubleFacedCard) {
|
||||||
|
this.isModalDoubleFacedCard = true;
|
||||||
|
ModalDoubleFacedCard mainCard = ((ModalDoubleFacedCard) card);
|
||||||
|
fullCardName = mainCard.getLeftHalfCard().getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + mainCard.getRightHalfCard().getName();
|
||||||
|
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 = adventureCard.getManaCostSymbols();
|
||||||
|
this.manaCostRightStr = adventureCardSpell.getManaCostSymbols();
|
||||||
|
} else if (card instanceof MockCard) {
|
||||||
|
// deck editor cards
|
||||||
|
fullCardName = ((MockCard) card).getFullName(true);
|
||||||
|
this.manaCostLeftStr = ((MockCard) card).getManaCostStr(CardInfo.ManaCostSide.LEFT);
|
||||||
|
this.manaCostRightStr = ((MockCard) card).getManaCostStr(CardInfo.ManaCostSide.RIGHT);
|
||||||
} else {
|
} else {
|
||||||
// this.hideInfo = true;
|
fullCardName = card.getName();
|
||||||
return;
|
this.manaCostLeftStr = card.getManaCostSymbols();
|
||||||
|
this.manaCostRightStr = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.name = card.getName();
|
||||||
|
this.displayName = card.getName();
|
||||||
|
this.displayFullName = fullCardName;
|
||||||
|
this.rules = new ArrayList<>(card.getRules(game));
|
||||||
|
this.manaValue = card.getManaValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
SplitCard splitCard = null;
|
// shared info - counters and other
|
||||||
if (card instanceof SplitCard) {
|
|
||||||
splitCard = (SplitCard) card;
|
|
||||||
rotate = (card.getSpellAbility().getSpellAbilityType()) != SpellAbilityType.SPLIT_AFTERMATH;
|
|
||||||
} else if (card instanceof Spell) {
|
|
||||||
switch (card.getSpellAbility().getSpellAbilityType()) {
|
|
||||||
case SPLIT_FUSED:
|
|
||||||
splitCard = (SplitCard) ((Spell) card).getCard();
|
|
||||||
rotate = true;
|
|
||||||
break;
|
|
||||||
case SPLIT_AFTERMATH:
|
|
||||||
splitCard = (SplitCard) ((Spell) card).getCard();
|
|
||||||
rotate = false;
|
|
||||||
break;
|
|
||||||
case SPLIT_LEFT:
|
|
||||||
case SPLIT_RIGHT:
|
|
||||||
rotate = true;
|
|
||||||
break;
|
|
||||||
case MODAL_LEFT:
|
|
||||||
case MODAL_RIGHT:
|
|
||||||
rotate = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String fullCardName;
|
|
||||||
if (splitCard != null) {
|
|
||||||
this.isSplitCard = true;
|
|
||||||
leftSplitName = splitCard.getLeftHalfCard().getName();
|
|
||||||
leftSplitCostsStr = String.join("", splitCard.getLeftHalfCard().getManaCostSymbols());
|
|
||||||
leftSplitRules = splitCard.getLeftHalfCard().getRules(game);
|
|
||||||
leftSplitTypeLine = getCardTypeLine(game, splitCard.getLeftHalfCard());
|
|
||||||
rightSplitName = splitCard.getRightHalfCard().getName();
|
|
||||||
rightSplitCostsStr = String.join("", splitCard.getRightHalfCard().getManaCostSymbols());
|
|
||||||
rightSplitRules = splitCard.getRightHalfCard().getRules(game);
|
|
||||||
rightSplitTypeLine = getCardTypeLine(game, splitCard.getRightHalfCard());
|
|
||||||
|
|
||||||
fullCardName = card.getName(); // split card contains full name as normal
|
|
||||||
this.manaCostLeftStr = splitCard.getLeftHalfCard().getManaCostSymbols();
|
|
||||||
this.manaCostRightStr = splitCard.getRightHalfCard().getManaCostSymbols();
|
|
||||||
} else if (card instanceof ModalDoubleFacedCard) {
|
|
||||||
this.isModalDoubleFacedCard = true;
|
|
||||||
ModalDoubleFacedCard mainCard = ((ModalDoubleFacedCard) card);
|
|
||||||
fullCardName = mainCard.getLeftHalfCard().getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + mainCard.getRightHalfCard().getName();
|
|
||||||
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 = adventureCard.getManaCostSymbols();
|
|
||||||
this.manaCostRightStr = adventureCardSpell.getManaCostSymbols();
|
|
||||||
} else if (card instanceof MockCard) {
|
|
||||||
// deck editor cards
|
|
||||||
fullCardName = ((MockCard) card).getFullName(true);
|
|
||||||
this.manaCostLeftStr = ((MockCard) card).getManaCostStr(CardInfo.ManaCostSide.LEFT);
|
|
||||||
this.manaCostRightStr = ((MockCard) card).getManaCostStr(CardInfo.ManaCostSide.RIGHT);
|
|
||||||
} else {
|
|
||||||
fullCardName = card.getName();
|
|
||||||
this.manaCostLeftStr = card.getManaCostSymbols();
|
|
||||||
this.manaCostRightStr = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.name = card.getName();
|
|
||||||
this.displayName = card.getName();
|
|
||||||
this.displayFullName = fullCardName;
|
|
||||||
this.rules = new ArrayList<>(card.getRules(game));
|
|
||||||
this.manaValue = card.getManaValue();
|
|
||||||
|
|
||||||
if (card instanceof Permanent) {
|
if (card instanceof Permanent) {
|
||||||
this.mageObjectType = MageObjectType.PERMANENT;
|
this.mageObjectType = MageObjectType.PERMANENT;
|
||||||
Permanent permanent = (Permanent) card;
|
Permanent permanent = (Permanent) card;
|
||||||
|
|
@ -466,61 +505,64 @@ public class CardView extends SimpleCardView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.power = Integer.toString(card.getPower().getValue());
|
// FACE UP INFO
|
||||||
this.toughness = Integer.toString(card.getToughness().getValue());
|
if (showFaceUp) {
|
||||||
this.cardTypes = new ArrayList<>(card.getCardType(game));
|
this.power = Integer.toString(card.getPower().getValue());
|
||||||
this.subTypes = card.getSubtype(game).copy();
|
this.toughness = Integer.toString(card.getToughness().getValue());
|
||||||
this.superTypes = card.getSuperType(game);
|
this.cardTypes = new ArrayList<>(card.getCardType(game));
|
||||||
this.color = card.getColor(game).copy();
|
this.subTypes = card.getSubtype(game).copy();
|
||||||
this.flipCard = card.isFlipCard();
|
this.superTypes = card.getSuperType(game);
|
||||||
this.faceDown = !showFaceUp;
|
this.color = card.getColor(game).copy();
|
||||||
|
this.flipCard = card.isFlipCard();
|
||||||
|
|
||||||
if (card instanceof PermanentToken) {
|
if (card instanceof PermanentToken) {
|
||||||
this.isToken = true;
|
this.isToken = true;
|
||||||
this.mageObjectType = MageObjectType.TOKEN;
|
this.mageObjectType = MageObjectType.TOKEN;
|
||||||
this.rarity = Rarity.COMMON;
|
this.rarity = Rarity.SPECIAL;
|
||||||
this.rules = new ArrayList<>(card.getRules(game));
|
this.rules = new ArrayList<>(card.getRules(game));
|
||||||
} else {
|
} else {
|
||||||
this.rarity = card.getRarity();
|
this.rarity = card.getRarity();
|
||||||
this.isToken = false;
|
this.isToken = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.extraDeckCard = card.isExtraDeckCard();
|
this.extraDeckCard = card.isExtraDeckCard();
|
||||||
|
|
||||||
// transformable, double faces cards
|
// transformable, double faces cards
|
||||||
this.transformable = card.isTransformable();
|
this.transformable = card.isTransformable();
|
||||||
|
|
||||||
Card secondSideCard = card.getSecondCardFace();
|
Card secondSideCard = card.getSecondCardFace();
|
||||||
if (secondSideCard != null) {
|
if (secondSideCard != null) {
|
||||||
this.secondCardFace = new CardView(secondSideCard, game);
|
this.secondCardFace = new CardView(secondSideCard, game);
|
||||||
this.alternateName = secondCardFace.getName();
|
this.alternateName = secondCardFace.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.flipCard = card.isFlipCard();
|
this.flipCard = card.isFlipCard();
|
||||||
if (card.isFlipCard() && card.getFlipCardName() != null) {
|
if (card.isFlipCard() && card.getFlipCardName() != null) {
|
||||||
this.alternateName = card.getFlipCardName();
|
this.alternateName = card.getFlipCardName();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (card instanceof ModalDoubleFacedCard) {
|
if (card instanceof ModalDoubleFacedCard) {
|
||||||
this.transformable = true; // enable GUI day/night button
|
this.transformable = true; // enable GUI day/night button
|
||||||
ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card;
|
ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card;
|
||||||
this.secondCardFace = new CardView(mdfCard.getRightHalfCard(), game);
|
this.secondCardFace = new CardView(mdfCard.getRightHalfCard(), game);
|
||||||
this.alternateName = mdfCard.getRightHalfCard().getName();
|
this.alternateName = mdfCard.getRightHalfCard().getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
Card meldsToCard = card.getMeldsToCard();
|
Card meldsToCard = card.getMeldsToCard();
|
||||||
if (meldsToCard != null) {
|
if (meldsToCard != null) {
|
||||||
this.transformable = true; // enable GUI day/night button
|
this.transformable = true; // enable GUI day/night button
|
||||||
this.secondCardFace = new CardView(meldsToCard, game);
|
this.secondCardFace = new CardView(meldsToCard, game);
|
||||||
this.alternateName = meldsToCard.getName();
|
this.alternateName = meldsToCard.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (card instanceof PermanentToken && card.isTransformable()) {
|
if (card instanceof PermanentToken && card.isTransformable()) {
|
||||||
Token backFace = (Token) ((PermanentToken) card).getOtherFace();
|
Token backFace = (Token) ((PermanentToken) card).getOtherFace();
|
||||||
this.secondCardFace = new CardView(backFace, game);
|
this.secondCardFace = new CardView(backFace, game);
|
||||||
this.alternateName = backFace.getName();
|
this.alternateName = backFace.getName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shared info - targets
|
||||||
if (card instanceof Spell) {
|
if (card instanceof Spell) {
|
||||||
this.mageObjectType = MageObjectType.SPELL;
|
this.mageObjectType = MageObjectType.SPELL;
|
||||||
Spell spell = (Spell) card;
|
Spell spell = (Spell) card;
|
||||||
|
|
@ -533,31 +575,6 @@ public class CardView extends SimpleCardView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine what part of the art to slice out for spells on the stack which originate
|
|
||||||
// from a split, fuse, or aftermath split card.
|
|
||||||
// Modal double faces cards draws as normal cards
|
|
||||||
SpellAbilityType ty = spell.getSpellAbility().getSpellAbilityType();
|
|
||||||
if (ty == SpellAbilityType.SPLIT_RIGHT || ty == SpellAbilityType.SPLIT_LEFT || ty == SpellAbilityType.SPLIT_FUSED) {
|
|
||||||
// Needs a special art rect
|
|
||||||
if (ty == SpellAbilityType.SPLIT_FUSED) {
|
|
||||||
artRect = ArtRect.SPLIT_FUSED;
|
|
||||||
} else if (spell.getCard() != null) {
|
|
||||||
SplitCard wholeCard = ((SplitCardHalf) spell.getCard()).getParentCard();
|
|
||||||
Abilities<Ability> aftermathHalfAbilities = wholeCard.getRightHalfCard().getAbilities(game);
|
|
||||||
if (aftermathHalfAbilities.stream().anyMatch(AftermathAbility.class::isInstance)) {
|
|
||||||
if (ty == SpellAbilityType.SPLIT_RIGHT) {
|
|
||||||
artRect = ArtRect.AFTERMATH_BOTTOM;
|
|
||||||
} else {
|
|
||||||
artRect = ArtRect.AFTERMATH_TOP;
|
|
||||||
}
|
|
||||||
} else if (ty == SpellAbilityType.SPLIT_RIGHT) {
|
|
||||||
artRect = ArtRect.SPLIT_RIGHT;
|
|
||||||
} else {
|
|
||||||
artRect = ArtRect.SPLIT_LEFT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// show for modal spell, which mode was chosen
|
// show for modal spell, which mode was chosen
|
||||||
if (spell.getSpellAbility().isModal()) {
|
if (spell.getSpellAbility().isModal()) {
|
||||||
for (UUID modeId : spell.getSpellAbility().getModes().getSelectedModes()) {
|
for (UUID modeId : spell.getSpellAbility().getModes().getSelectedModes()) {
|
||||||
|
|
@ -580,32 +597,62 @@ public class CardView extends SimpleCardView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cases, classes and sagas have portrait art
|
// render info
|
||||||
if (card.getSubtype().contains(SubType.CASE) ||
|
if (showFaceUp) {
|
||||||
card.getSubtype().contains(SubType.CLASS)) {
|
if (card instanceof Spell) {
|
||||||
artRect = ArtRect.FULL_LENGTH_LEFT;
|
Spell spell = (Spell) card;
|
||||||
} else if (card.getSubtype().contains(SubType.SAGA)) {
|
// Determine what part of the art to slice out for spells on the stack which originate
|
||||||
artRect = ArtRect.FULL_LENGTH_RIGHT;
|
// from a split, fuse, or aftermath split card.
|
||||||
|
// Modal double faces cards draws as normal cards
|
||||||
|
SpellAbilityType ty = spell.getSpellAbility().getSpellAbilityType();
|
||||||
|
if (ty == SpellAbilityType.SPLIT_RIGHT || ty == SpellAbilityType.SPLIT_LEFT || ty == SpellAbilityType.SPLIT_FUSED) {
|
||||||
|
// Needs a special art rect
|
||||||
|
if (ty == SpellAbilityType.SPLIT_FUSED) {
|
||||||
|
artRect = ArtRect.SPLIT_FUSED;
|
||||||
|
} else if (spell.getCard() != null) {
|
||||||
|
SplitCard wholeCard = ((SplitCardHalf) spell.getCard()).getParentCard();
|
||||||
|
Abilities<Ability> aftermathHalfAbilities = wholeCard.getRightHalfCard().getAbilities(game);
|
||||||
|
if (aftermathHalfAbilities.stream().anyMatch(AftermathAbility.class::isInstance)) {
|
||||||
|
if (ty == SpellAbilityType.SPLIT_RIGHT) {
|
||||||
|
artRect = ArtRect.AFTERMATH_BOTTOM;
|
||||||
|
} else {
|
||||||
|
artRect = ArtRect.AFTERMATH_TOP;
|
||||||
|
}
|
||||||
|
} else if (ty == SpellAbilityType.SPLIT_RIGHT) {
|
||||||
|
artRect = ArtRect.SPLIT_RIGHT;
|
||||||
|
} else {
|
||||||
|
artRect = ArtRect.SPLIT_LEFT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cases, classes and sagas have portrait art
|
||||||
|
if (card.getSubtype().contains(SubType.CASE) ||
|
||||||
|
card.getSubtype().contains(SubType.CLASS)) {
|
||||||
|
artRect = ArtRect.FULL_LENGTH_LEFT;
|
||||||
|
} else if (card.getSubtype().contains(SubType.SAGA)) {
|
||||||
|
artRect = ArtRect.FULL_LENGTH_RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame color
|
||||||
|
this.frameColor = card.getFrameColor(game).copy();
|
||||||
|
|
||||||
|
// Frame style
|
||||||
|
this.frameStyle = card.getFrameStyle();
|
||||||
|
|
||||||
|
// Get starting loyalty
|
||||||
|
this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty());
|
||||||
|
|
||||||
|
// Get starting defense
|
||||||
|
this.startingDefense = CardUtil.convertLoyaltyOrDefense(card.getStartingDefense());
|
||||||
|
|
||||||
|
// add card icons at the end, so it will have full card view data
|
||||||
|
this.generateCardIcons(null, card, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame color
|
|
||||||
this.frameColor = card.getFrameColor(game).copy();
|
|
||||||
|
|
||||||
// Frame style
|
|
||||||
this.frameStyle = card.getFrameStyle();
|
|
||||||
|
|
||||||
// Get starting loyalty
|
|
||||||
this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty());
|
|
||||||
|
|
||||||
// Get starting defense
|
|
||||||
this.startingDefense = CardUtil.convertLoyaltyOrDefense(card.getStartingDefense());
|
|
||||||
|
|
||||||
// add card icons at the end, so it will have full card view data
|
|
||||||
this.generateCardIcons(null, card, game);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -740,11 +787,14 @@ public class CardView extends SimpleCardView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated // TODO: research and raplace all usages to normal calls, see constructors for EmblemView and other
|
||||||
public CardView(MageObject object, Game game) {
|
public CardView(MageObject object, Game game) {
|
||||||
super(object.getId(), object.getExpansionSetCode(), object.getCardNumber(), false, true);
|
super(object.getId(), object.getExpansionSetCode(), object.getCardNumber(), false, true);
|
||||||
this.setOriginalValues(object);
|
this.setOriginalValues(object);
|
||||||
|
|
||||||
|
this.imageFileName = object.getImageFileName();
|
||||||
this.imageNumber = object.getImageNumber();
|
this.imageNumber = object.getImageNumber();
|
||||||
|
|
||||||
this.name = object.getName();
|
this.name = object.getName();
|
||||||
this.displayName = object.getName();
|
this.displayName = object.getName();
|
||||||
this.displayFullName = object.getName();
|
this.displayFullName = object.getName();
|
||||||
|
|
@ -770,7 +820,7 @@ public class CardView extends SimpleCardView {
|
||||||
if (object instanceof PermanentToken) {
|
if (object instanceof PermanentToken) {
|
||||||
this.mageObjectType = MageObjectType.TOKEN;
|
this.mageObjectType = MageObjectType.TOKEN;
|
||||||
PermanentToken permanentToken = (PermanentToken) object;
|
PermanentToken permanentToken = (PermanentToken) object;
|
||||||
this.rarity = Rarity.COMMON;
|
this.rarity = Rarity.SPECIAL;
|
||||||
this.rules = new ArrayList<>(permanentToken.getRules(game));
|
this.rules = new ArrayList<>(permanentToken.getRules(game));
|
||||||
} else if (object instanceof Emblem) {
|
} else if (object instanceof Emblem) {
|
||||||
this.mageObjectType = MageObjectType.EMBLEM;
|
this.mageObjectType = MageObjectType.EMBLEM;
|
||||||
|
|
@ -837,9 +887,10 @@ public class CardView extends SimpleCardView {
|
||||||
this.frameStyle = FrameStyle.M15_NORMAL;
|
this.frameStyle = FrameStyle.M15_NORMAL;
|
||||||
this.expansionSetCode = emblem.getExpansionSetCode();
|
this.expansionSetCode = emblem.getExpansionSetCode();
|
||||||
this.cardNumber = emblem.getCardNumber();
|
this.cardNumber = emblem.getCardNumber();
|
||||||
|
this.imageFileName = emblem.getImageFileName();
|
||||||
this.imageNumber = emblem.getImageNumber();
|
this.imageNumber = emblem.getImageNumber();
|
||||||
this.usesVariousArt = emblem.getUsesVariousArt();
|
this.usesVariousArt = emblem.getUsesVariousArt();
|
||||||
this.rarity = Rarity.COMMON;
|
this.rarity = Rarity.SPECIAL;
|
||||||
|
|
||||||
this.playableStats = emblem.playableStats.copy();
|
this.playableStats = emblem.playableStats.copy();
|
||||||
this.isChoosable = emblem.isChoosable();
|
this.isChoosable = emblem.isChoosable();
|
||||||
|
|
@ -859,8 +910,9 @@ public class CardView extends SimpleCardView {
|
||||||
this.frameStyle = FrameStyle.M15_NORMAL;
|
this.frameStyle = FrameStyle.M15_NORMAL;
|
||||||
this.expansionSetCode = dungeon.getExpansionSetCode();
|
this.expansionSetCode = dungeon.getExpansionSetCode();
|
||||||
this.cardNumber = "";
|
this.cardNumber = "";
|
||||||
|
this.imageFileName = "";
|
||||||
this.imageNumber = 0;
|
this.imageNumber = 0;
|
||||||
this.rarity = Rarity.COMMON;
|
this.rarity = Rarity.SPECIAL;
|
||||||
|
|
||||||
this.playableStats = dungeon.playableStats.copy();
|
this.playableStats = dungeon.playableStats.copy();
|
||||||
this.isChoosable = dungeon.isChoosable();
|
this.isChoosable = dungeon.isChoosable();
|
||||||
|
|
@ -881,8 +933,9 @@ public class CardView extends SimpleCardView {
|
||||||
this.frameStyle = FrameStyle.M15_NORMAL;
|
this.frameStyle = FrameStyle.M15_NORMAL;
|
||||||
this.expansionSetCode = plane.getExpansionSetCode();
|
this.expansionSetCode = plane.getExpansionSetCode();
|
||||||
this.cardNumber = "";
|
this.cardNumber = "";
|
||||||
|
this.imageFileName = "";
|
||||||
this.imageNumber = 0;
|
this.imageNumber = 0;
|
||||||
this.rarity = Rarity.COMMON;
|
this.rarity = Rarity.SPECIAL;
|
||||||
|
|
||||||
this.playableStats = plane.playableStats.copy();
|
this.playableStats = plane.playableStats.copy();
|
||||||
this.isChoosable = plane.isChoosable();
|
this.isChoosable = plane.isChoosable();
|
||||||
|
|
@ -903,8 +956,9 @@ public class CardView extends SimpleCardView {
|
||||||
this.cardNumber = designation.getCardNumber();
|
this.cardNumber = designation.getCardNumber();
|
||||||
this.expansionSetCode = designation.getExpansionSetCode();
|
this.expansionSetCode = designation.getExpansionSetCode();
|
||||||
this.cardNumber = "";
|
this.cardNumber = "";
|
||||||
|
this.imageFileName = "";
|
||||||
this.imageNumber = 0;
|
this.imageNumber = 0;
|
||||||
this.rarity = Rarity.COMMON;
|
this.rarity = Rarity.SPECIAL;
|
||||||
// no playable/chooseable marks for designations
|
// no playable/chooseable marks for designations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -913,7 +967,7 @@ public class CardView extends SimpleCardView {
|
||||||
if (!empty) {
|
if (!empty) {
|
||||||
throw new IllegalArgumentException("Not supported.");
|
throw new IllegalArgumentException("Not supported.");
|
||||||
}
|
}
|
||||||
fillEmpty(null, false);
|
fillEmptyWithImageInfo(null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean cardViewEquals(CardView a, CardView b) { // TODO: This belongs in CardView
|
public static boolean cardViewEquals(CardView a, CardView b) { // TODO: This belongs in CardView
|
||||||
|
|
@ -936,14 +990,21 @@ public class CardView extends SimpleCardView {
|
||||||
&& a.getManaCostStr().equals(b.getManaCostStr())
|
&& a.getManaCostStr().equals(b.getManaCostStr())
|
||||||
&& a.getRules().equals(b.getRules())
|
&& a.getRules().equals(b.getRules())
|
||||||
&& Objects.equals(a.getRarity(), b.getRarity())
|
&& Objects.equals(a.getRarity(), b.getRarity())
|
||||||
&& Objects.equals(a.getCardNumber(), b.getCardNumber())
|
|
||||||
&& Objects.equals(a.getExpansionSetCode(), b.getExpansionSetCode())
|
|
||||||
&& a.getFrameStyle() == b.getFrameStyle()
|
&& a.getFrameStyle() == b.getFrameStyle()
|
||||||
&& Objects.equals(a.getCounters(), b.getCounters())
|
&& Objects.equals(a.getCounters(), b.getCounters())
|
||||||
&& a.isFaceDown() == b.isFaceDown())) {
|
&& a.isFaceDown() == b.isFaceDown())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(Objects.equals(a.getExpansionSetCode(), b.getExpansionSetCode())
|
||||||
|
&& Objects.equals(a.getCardNumber(), b.getCardNumber())
|
||||||
|
&& Objects.equals(a.getImageNumber(), b.getImageNumber())
|
||||||
|
&& Objects.equals(a.getImageFileName(), b.getImageFileName())
|
||||||
|
)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(a instanceof PermanentView)) {
|
if (!(a instanceof PermanentView)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -953,10 +1014,16 @@ public class CardView extends SimpleCardView {
|
||||||
&& aa.getDamage() == bb.getDamage();
|
&& aa.getDamage() == bb.getDamage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillEmpty(Card card, boolean controlled) {
|
private void fillEmptyWithImageInfo(Game game, Card imageSourceCard, boolean isFaceDown) {
|
||||||
this.name = "Face Down";
|
this.name = "";
|
||||||
this.displayName = name;
|
this.displayName = "";
|
||||||
this.displayFullName = name;
|
this.displayFullName = "";
|
||||||
|
this.expansionSetCode = "";
|
||||||
|
this.cardNumber = "0";
|
||||||
|
this.imageFileName = "";
|
||||||
|
this.imageNumber = 0;
|
||||||
|
this.usesVariousArt = false;
|
||||||
|
|
||||||
this.rules = new ArrayList<>();
|
this.rules = new ArrayList<>();
|
||||||
this.power = "";
|
this.power = "";
|
||||||
this.toughness = "";
|
this.toughness = "";
|
||||||
|
|
@ -973,33 +1040,68 @@ public class CardView extends SimpleCardView {
|
||||||
this.manaCostLeftStr = new ArrayList<>();
|
this.manaCostLeftStr = new ArrayList<>();
|
||||||
this.manaCostRightStr = new ArrayList<>();
|
this.manaCostRightStr = new ArrayList<>();
|
||||||
this.manaValue = 0;
|
this.manaValue = 0;
|
||||||
|
this.rarity = Rarity.SPECIAL; // hide rarity info
|
||||||
|
|
||||||
// the controller can see more information (e.g. enlarged image) than other players for face down cards (e.g. Morph played face down)
|
if (imageSourceCard != null) {
|
||||||
if (!controlled) {
|
// keep inner images info (server side card already contain actual info)
|
||||||
this.rarity = Rarity.COMMON;
|
String imageSetCode = imageSourceCard.getExpansionSetCode();
|
||||||
this.expansionSetCode = "";
|
String imageCardNumber = imageSourceCard.getCardNumber();
|
||||||
this.cardNumber = "0";
|
String imageFileName = imageSourceCard.getImageFileName();
|
||||||
this.imageNumber = 0;
|
Integer imageNumber = imageSourceCard.getImageNumber();
|
||||||
} else {
|
boolean imageUsesVariousArt = imageSourceCard.getUsesVariousArt();
|
||||||
this.rarity = card.getRarity();
|
if (imageSetCode.equals(TokenRepository.XMAGE_TOKENS_SET_CODE)) {
|
||||||
}
|
this.expansionSetCode = imageSetCode;
|
||||||
|
this.cardNumber = imageCardNumber;
|
||||||
|
this.imageFileName = imageFileName;
|
||||||
|
this.imageNumber = imageNumber;
|
||||||
|
this.usesVariousArt = imageUsesVariousArt;
|
||||||
|
}
|
||||||
|
|
||||||
if (card != null) {
|
if (imageSourceCard instanceof PermanentToken) {
|
||||||
if (card instanceof Permanent) {
|
this.mageObjectType = MageObjectType.TOKEN;
|
||||||
|
} else if (imageSourceCard instanceof Permanent) {
|
||||||
this.mageObjectType = MageObjectType.PERMANENT;
|
this.mageObjectType = MageObjectType.PERMANENT;
|
||||||
} else if (card.isCopy()) {
|
} else if (imageSourceCard.isCopy()) {
|
||||||
this.mageObjectType = MageObjectType.COPY_CARD;
|
this.mageObjectType = MageObjectType.COPY_CARD;
|
||||||
|
} else if (imageSourceCard instanceof Spell) {
|
||||||
|
this.mageObjectType = MageObjectType.SPELL;
|
||||||
} else {
|
} else {
|
||||||
this.mageObjectType = MageObjectType.CARD;
|
this.mageObjectType = MageObjectType.CARD;
|
||||||
}
|
}
|
||||||
if (card instanceof PermanentToken) {
|
|
||||||
this.mageObjectType = MageObjectType.TOKEN;
|
|
||||||
}
|
|
||||||
if (card instanceof Spell) {
|
|
||||||
this.mageObjectType = MageObjectType.SPELL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make default face down image
|
||||||
|
// TODO: implement diff backface images someday and insert here (user data + card owner)
|
||||||
|
if (isFaceDown && this.imageFileName.isEmpty()) {
|
||||||
|
this.name = "";
|
||||||
|
this.displayName = this.name;
|
||||||
|
this.displayFullName = this.name;
|
||||||
|
|
||||||
|
// as foretell face down
|
||||||
|
// TODO: it's not ok to use that code - server side objects must has all data, see BecomesFaceDownCreatureEffect.makeFaceDownObject
|
||||||
|
// it must be a more global bug for card characteristics, not client side viewer
|
||||||
|
if (game != null && imageSourceCard != null && ForetellAbility.isCardInForetell(imageSourceCard, game)) {
|
||||||
|
TokenInfo tokenInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL, this.getId());
|
||||||
|
if (tokenInfo != null) {
|
||||||
|
this.expansionSetCode = tokenInfo.getSetCode();
|
||||||
|
this.cardNumber = "0";
|
||||||
|
this.imageFileName = tokenInfo.getName();
|
||||||
|
this.imageNumber = tokenInfo.getImageNumber();
|
||||||
|
this.usesVariousArt = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// as normal face down
|
||||||
|
TokenInfo tokenInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL, this.getId());
|
||||||
|
if (tokenInfo != null) {
|
||||||
|
this.expansionSetCode = tokenInfo.getSetCode();
|
||||||
|
this.cardNumber = "0";
|
||||||
|
this.imageFileName = tokenInfo.getName();
|
||||||
|
this.imageNumber = tokenInfo.getImageNumber();
|
||||||
|
this.usesVariousArt = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CardView(Token token, Game game) {
|
CardView(Token token, Game game) {
|
||||||
|
|
@ -1026,9 +1128,9 @@ public class CardView extends SimpleCardView {
|
||||||
this.manaCostRightStr = new ArrayList<>();
|
this.manaCostRightStr = new ArrayList<>();
|
||||||
this.rarity = Rarity.SPECIAL;
|
this.rarity = Rarity.SPECIAL;
|
||||||
|
|
||||||
// source object is a token, so no card number
|
|
||||||
this.expansionSetCode = token.getExpansionSetCode();
|
this.expansionSetCode = token.getExpansionSetCode();
|
||||||
this.cardNumber = token.getCardNumber();
|
this.cardNumber = token.getCardNumber();
|
||||||
|
this.imageFileName = token.getImageFileName();
|
||||||
this.imageNumber = token.getImageNumber();
|
this.imageNumber = token.getImageNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1063,18 +1165,15 @@ public class CardView extends SimpleCardView {
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Only valid objects to transfer original values are Card and Token
|
// only valid objects to transfer original values are Card and Token
|
||||||
if (object instanceof Card || object instanceof Token) {
|
if (object instanceof Card || object instanceof Token) {
|
||||||
this.originalPower = object.getPower();
|
this.originalPower = object.getPower();
|
||||||
this.originalToughness = object.getToughness();
|
this.originalToughness = object.getToughness();
|
||||||
this.originalIsCopy = object.isCopy();
|
this.originalIsCopy = object.isCopy();
|
||||||
this.originalId = object.getId();
|
|
||||||
|
|
||||||
if (object instanceof Card) {
|
if (object instanceof Card) {
|
||||||
this.originalColorIdentity = ((Card) object).getColorIdentity();
|
this.originalColorIdentity = findColorIdentityStr(((Card) object).getColorIdentity());
|
||||||
this.originalIsCard = true;
|
} else {
|
||||||
} else if (object instanceof Token) {
|
this.originalColorIdentity = findColorIdentityStr(ManaUtil.getColorIdentity((Token) object));
|
||||||
this.originalColorIdentity = ManaUtil.getColorIdentity((Token) object);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1190,9 +1289,8 @@ public class CardView extends SimpleCardView {
|
||||||
return rarity;
|
return rarity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getColorIdentityStr() {
|
public String findColorIdentityStr(FilterMana colorInfo) {
|
||||||
FilterMana colorInfo = this.originalColorIdentity;
|
if (colorInfo == null) {
|
||||||
if (colorInfo != null) {
|
|
||||||
colorInfo = new FilterMana();
|
colorInfo = new FilterMana();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1350,6 +1448,10 @@ public class CardView extends SimpleCardView {
|
||||||
return bandedCards;
|
return bandedCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getImageFileName() {
|
||||||
|
return imageFileName;
|
||||||
|
}
|
||||||
|
|
||||||
public int getImageNumber() {
|
public int getImageNumber() {
|
||||||
return imageNumber;
|
return imageNumber;
|
||||||
}
|
}
|
||||||
|
|
@ -1488,18 +1590,14 @@ public class CardView extends SimpleCardView {
|
||||||
return this.originalToughness;
|
return this.originalToughness;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getOriginalId() {
|
public String getOriginalColorIdentity() {
|
||||||
return this.originalId;
|
return this.originalColorIdentity != null ? this.originalColorIdentity : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOriginalACopy() {
|
public boolean isOriginalACopy() {
|
||||||
return this.originalIsCopy;
|
return this.originalIsCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOriginalACard() {
|
|
||||||
return this.originalIsCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CardIcon> getCardIcons() {
|
public List<CardIcon> getCardIcons() {
|
||||||
return this.cardIcons;
|
return this.cardIcons;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import mage.game.command.Plane;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.game.permanent.PermanentToken;
|
import mage.game.permanent.PermanentToken;
|
||||||
import mage.target.targetpointer.TargetPointer;
|
import mage.target.targetpointer.TargetPointer;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import mage.util.GameLog;
|
import mage.util.GameLog;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
|
@ -50,15 +51,15 @@ public class CardsView extends LinkedHashMap<UUID, CardView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardsView(Game game, Collection<? extends Card> cards) {
|
public CardsView(Game game, Collection<? extends Card> cards, UUID createdForPlayerId) {
|
||||||
for (Card card : cards) {
|
for (Card card : cards) {
|
||||||
this.put(card.getId(), new CardView(card, game, false));
|
this.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardsView(Game game, Collection<? extends Card> cards, boolean showFaceDown, boolean storeZone) {
|
public CardsView(Game game, Collection<? extends Card> cards, UUID createdForPlayerId, boolean storeZone) {
|
||||||
for (Card card : cards) {
|
for (Card card : cards) {
|
||||||
this.put(card.getId(), new CardView(card, game, false, showFaceDown, storeZone));
|
this.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId), storeZone));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ public interface CommandObjectView extends SelectableObjectView {
|
||||||
|
|
||||||
UUID getId();
|
UUID getId();
|
||||||
|
|
||||||
|
String getImageFileName();
|
||||||
|
|
||||||
int getImageNumber();
|
int getImageNumber();
|
||||||
|
|
||||||
List<String> getRules();
|
List<String> getRules();
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@
|
||||||
package mage.view;
|
package mage.view;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.constants.MageObjectType;
|
import mage.constants.MageObjectType;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.command.Commander;
|
import mage.game.command.Commander;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -13,8 +16,8 @@ import mage.game.command.Commander;
|
||||||
*/
|
*/
|
||||||
public class CommanderView extends CardView implements CommandObjectView, Serializable{
|
public class CommanderView extends CardView implements CommandObjectView, Serializable{
|
||||||
|
|
||||||
public CommanderView(Commander commander, Card sourceCard, Game game) {
|
public CommanderView(Commander commander, Card sourceCard, Game game, UUID createdForPlayerId) {
|
||||||
super(sourceCard, game, false);
|
super(sourceCard, game, CardUtil.canShowAsControlled(sourceCard, createdForPlayerId));
|
||||||
this.mageObjectType = MageObjectType.COMMANDER;
|
this.mageObjectType = MageObjectType.COMMANDER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,17 @@ public class DungeonView implements CommandObjectView, Serializable {
|
||||||
|
|
||||||
protected UUID id;
|
protected UUID id;
|
||||||
protected String name;
|
protected String name;
|
||||||
protected int imageNum;
|
protected String imageFileName = "";
|
||||||
protected String expansionSetCode;
|
protected int imageNumber = 0;
|
||||||
|
protected String expansionSetCode = "";
|
||||||
protected List<String> rules;
|
protected List<String> rules;
|
||||||
protected PlayableObjectStats playableStats = new PlayableObjectStats();
|
protected PlayableObjectStats playableStats = new PlayableObjectStats();
|
||||||
|
|
||||||
public DungeonView(Dungeon dungeon) {
|
public DungeonView(Dungeon dungeon) {
|
||||||
this.id = dungeon.getId();
|
this.id = dungeon.getId();
|
||||||
this.name = dungeon.getName();
|
this.name = dungeon.getName();
|
||||||
this.imageNum = dungeon.getImageNumber();
|
this.imageFileName = dungeon.getImageFileName();
|
||||||
|
this.imageNumber = dungeon.getImageNumber();
|
||||||
this.expansionSetCode = dungeon.getExpansionSetCode();
|
this.expansionSetCode = dungeon.getExpansionSetCode();
|
||||||
this.rules = dungeon.getRules();
|
this.rules = dungeon.getRules();
|
||||||
}
|
}
|
||||||
|
|
@ -42,9 +44,14 @@ public class DungeonView implements CommandObjectView, Serializable {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getImageFileName() {
|
||||||
|
return imageFileName;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getImageNumber() {
|
public int getImageNumber() {
|
||||||
return imageNum;
|
return imageNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ public class EmblemView implements CommandObjectView, Serializable {
|
||||||
protected UUID id;
|
protected UUID id;
|
||||||
protected String name;
|
protected String name;
|
||||||
protected String cardNumber = "";
|
protected String cardNumber = "";
|
||||||
protected int imageNum;
|
protected String imageFileName = "";
|
||||||
|
protected int imageNumber;
|
||||||
protected boolean usesVariousArt = false;
|
protected boolean usesVariousArt = false;
|
||||||
protected String expansionSetCode;
|
protected String expansionSetCode;
|
||||||
protected List<String> rules;
|
protected List<String> rules;
|
||||||
|
|
@ -25,7 +26,8 @@ public class EmblemView implements CommandObjectView, Serializable {
|
||||||
public EmblemView(Emblem emblem) {
|
public EmblemView(Emblem emblem) {
|
||||||
this.id = emblem.getId();
|
this.id = emblem.getId();
|
||||||
this.name = emblem.getName();
|
this.name = emblem.getName();
|
||||||
this.imageNum = emblem.getImageNumber();
|
this.imageFileName = emblem.getImageFileName();
|
||||||
|
this.imageNumber = emblem.getImageNumber();
|
||||||
this.expansionSetCode = emblem.getExpansionSetCode();
|
this.expansionSetCode = emblem.getExpansionSetCode();
|
||||||
this.rules = emblem.getAbilities().getRules(emblem.getName());
|
this.rules = emblem.getAbilities().getRules(emblem.getName());
|
||||||
if (emblem instanceof EmblemOfCard) {
|
if (emblem instanceof EmblemOfCard) {
|
||||||
|
|
@ -54,9 +56,15 @@ public class EmblemView implements CommandObjectView, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getImageNumber() {
|
public String getImageFileName() {
|
||||||
return imageNum;
|
return imageFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageNumber() {
|
||||||
|
return imageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getUsesVariousArt() {
|
public boolean getUsesVariousArt() {
|
||||||
return this.usesVariousArt;
|
return this.usesVariousArt;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import java.util.UUID;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.game.ExileZone;
|
import mage.game.ExileZone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -17,11 +18,11 @@ public class ExileView extends CardsView {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final UUID id;
|
private final UUID id;
|
||||||
|
|
||||||
public ExileView(ExileZone exileZone, Game game) {
|
public ExileView(ExileZone exileZone, Game game, UUID createdForPlayerId) {
|
||||||
this.name = exileZone.getName();
|
this.name = exileZone.getName();
|
||||||
this.id = exileZone.getId();
|
this.id = exileZone.getId();
|
||||||
for (Card card: exileZone.getCards(game)) {
|
for (Card card: exileZone.getCards(game)) {
|
||||||
this.put(card.getId(), new CardView(card, game, false));
|
this.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import mage.game.stack.StackAbility;
|
||||||
import mage.game.stack.StackObject;
|
import mage.game.stack.StackObject;
|
||||||
import mage.players.PlayableObjectsList;
|
import mage.players.PlayableObjectsList;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
@ -73,15 +74,16 @@ public class GameView implements Serializable {
|
||||||
if (player.getId().equals(createdForPlayerId)) {
|
if (player.getId().equals(createdForPlayerId)) {
|
||||||
createdForPlayer = player;
|
createdForPlayer = player;
|
||||||
this.myPlayerId = player.getId();
|
this.myPlayerId = player.getId();
|
||||||
this.myHand.putAll(new CardsView(game, player.getHand().getCards(game)));
|
this.myHand.putAll(new CardsView(game, player.getHand().getCards(game), createdForPlayerId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (StackObject stackObject : state.getStack()) {
|
for (StackObject stackObject : state.getStack()) {
|
||||||
if (stackObject instanceof Spell) {
|
if (stackObject instanceof Spell) {
|
||||||
// Spell
|
// Spell
|
||||||
CardView spellView = new CardView((Spell) stackObject, game, stackObject.getControllerId().equals(createdForPlayerId));
|
Spell spell = (Spell) stackObject;
|
||||||
spellView.paid = ((Spell) stackObject).getSpellAbility().getManaCostsToPay().isPaid();
|
CardView spellView = new CardView(spell, game, CardUtil.canShowAsControlled(spell, createdForPlayerId));
|
||||||
stack.put(stackObject.getId(), spellView);
|
spellView.paid = spell.getSpellAbility().getManaCostsToPay().isPaid();
|
||||||
|
stack.put(spell.getId(), spellView);
|
||||||
} else if (stackObject instanceof StackAbility) {
|
} else if (stackObject instanceof StackAbility) {
|
||||||
// Stack Ability
|
// Stack Ability
|
||||||
MageObject object = game.getObject(stackObject.getSourceId());
|
MageObject object = game.getObject(stackObject.getSourceId());
|
||||||
|
|
@ -93,9 +95,9 @@ public class GameView implements Serializable {
|
||||||
if (object != null) {
|
if (object != null) {
|
||||||
if (object instanceof Permanent) {
|
if (object instanceof Permanent) {
|
||||||
boolean controlled = ((Permanent) object).getControllerId().equals(createdForPlayerId);
|
boolean controlled = ((Permanent) object).getControllerId().equals(createdForPlayerId);
|
||||||
stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, object.getName(), object, new CardView(((Permanent) object), game, controlled, false, false)));
|
stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, object.getName(), object, new CardView(((Permanent) object), game, controlled, false)));
|
||||||
} else {
|
} else {
|
||||||
stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, card.getName(), card, new CardView(card, game, false, false, false)));
|
stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, card.getName(), card, new CardView(card, game, false, false)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, "", card, new CardView(card, game)));
|
stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, "", card, new CardView(card, game)));
|
||||||
|
|
@ -132,9 +134,9 @@ public class GameView implements Serializable {
|
||||||
} else if (object instanceof Designation) {
|
} else if (object instanceof Designation) {
|
||||||
Designation designation = (Designation) game.getObject(object.getId());
|
Designation designation = (Designation) game.getObject(object.getId());
|
||||||
if (designation != null) {
|
if (designation != null) {
|
||||||
stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, designation.getName(), designation, new CardView(designation, game)));
|
stack.put(stackObject.getId(), new StackAbilityView(game, (StackAbility) stackObject, designation.getName(), designation, new CardView(designation, (StackAbility) stackObject)));
|
||||||
} else {
|
} else {
|
||||||
LOGGER.fatal("Designation object not found: " + object.getName() + ' ' + object.toString() + ' ' + object.getClass().toString());
|
throw new IllegalArgumentException("Designation object not found: " + object + " - " + object.getClass().toString());
|
||||||
}
|
}
|
||||||
} else if (object instanceof StackAbility) {
|
} else if (object instanceof StackAbility) {
|
||||||
StackAbility stackAbility = ((StackAbility) object);
|
StackAbility stackAbility = ((StackAbility) object);
|
||||||
|
|
@ -142,20 +144,19 @@ public class GameView implements Serializable {
|
||||||
stack.put(stackObject.getId(), new CardView(stackObject, game));
|
stack.put(stackObject.getId(), new CardView(stackObject, game));
|
||||||
checkPaid(stackObject.getId(), ((StackAbility) stackObject));
|
checkPaid(stackObject.getId(), ((StackAbility) stackObject));
|
||||||
} else {
|
} else {
|
||||||
LOGGER.fatal("Object can't be cast to StackAbility: " + object.getName() + ' ' + object.toString() + ' ' + object.getClass().toString());
|
throw new IllegalArgumentException("Object can't be cast to StackAbility: " + object + " - " + object.getClass().toString());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// can happen if a player times out while ability is on the stack
|
// can happen if a player times out while ability is on the stack
|
||||||
LOGGER.debug("Stack Object for stack ability not found: " + stackObject.getStackAbility().getRule());
|
LOGGER.debug("Stack Object for stack ability not found: " + stackObject.getStackAbility().getRule());
|
||||||
}
|
}
|
||||||
} else if (stackObject != null) {
|
} else if (stackObject != null) {
|
||||||
LOGGER.fatal("Unknown type of StackObject: " + stackObject.getName() + ' ' + stackObject.toString() + ' ' + stackObject.getClass().toString());
|
throw new IllegalArgumentException("Unknown type of StackObject: " + stackObject + " - " + stackObject.getClass().toString());
|
||||||
}
|
}
|
||||||
//stackOrder.add(stackObject.getId());
|
|
||||||
}
|
}
|
||||||
//Collections.reverse(stackOrder);
|
|
||||||
for (ExileZone exileZone : state.getExile().getExileZones()) {
|
for (ExileZone exileZone : state.getExile().getExileZones()) {
|
||||||
exiles.add(new ExileView(exileZone, game));
|
exiles.add(new ExileView(exileZone, game, createdForPlayerId));
|
||||||
}
|
}
|
||||||
for (String name : state.getRevealed().keySet()) {
|
for (String name : state.getRevealed().keySet()) {
|
||||||
revealed.add(new RevealedView(name, state.getRevealed().get(name), game));
|
revealed.add(new RevealedView(name, state.getRevealed().get(name), game));
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
package mage.view;
|
package mage.view;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
|
||||||
import mage.abilities.common.TurnFaceUpAbility;
|
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.game.permanent.PermanentToken;
|
import mage.game.permanent.PermanentToken;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
|
|
@ -39,9 +37,8 @@ public class PermanentView extends CardView {
|
||||||
private final boolean attachedControllerDiffers;
|
private final boolean attachedControllerDiffers;
|
||||||
|
|
||||||
public PermanentView(Permanent permanent, Card card, UUID createdForPlayerId, Game game) {
|
public PermanentView(Permanent permanent, Card card, UUID createdForPlayerId, Game game) {
|
||||||
super(permanent, game, permanent.getControllerId() != null && permanent.getControllerId().equals(createdForPlayerId));
|
super(permanent, game, CardUtil.canShowAsControlled(permanent, createdForPlayerId));
|
||||||
this.controlled = permanent.getControllerId() != null && permanent.getControllerId().equals(createdForPlayerId);
|
this.controlled = permanent.getControllerId() != null && permanent.getControllerId().equals(createdForPlayerId);
|
||||||
this.rules = permanent.getRules(game);
|
|
||||||
this.tapped = permanent.isTapped();
|
this.tapped = permanent.isTapped();
|
||||||
this.flipped = permanent.isFlipped();
|
this.flipped = permanent.isFlipped();
|
||||||
this.phasedIn = permanent.isPhasedIn();
|
this.phasedIn = permanent.isPhasedIn();
|
||||||
|
|
@ -52,26 +49,26 @@ public class PermanentView extends CardView {
|
||||||
this.attachments = new ArrayList<>(permanent.getAttachments());
|
this.attachments = new ArrayList<>(permanent.getAttachments());
|
||||||
this.attachedTo = permanent.getAttachedTo();
|
this.attachedTo = permanent.getAttachedTo();
|
||||||
|
|
||||||
// show face down cards to all players at the game end
|
// store original card, e.g. for sides switch in GUI
|
||||||
boolean showFaceDownInfo = controlled || (game != null && game.hasEnded());
|
|
||||||
|
|
||||||
if (isToken()) {
|
if (isToken()) {
|
||||||
original = new CardView(((PermanentToken) permanent).getToken().copy(), (Game) null);
|
original = new CardView(((PermanentToken) permanent).getToken().copy(), (Game) null);
|
||||||
original.expansionSetCode = permanent.getExpansionSetCode();
|
original.expansionSetCode = permanent.getExpansionSetCode(); // TODO: miss card number and other?
|
||||||
expansionSetCode = permanent.getExpansionSetCode();
|
expansionSetCode = permanent.getExpansionSetCode();
|
||||||
} else {
|
} else {
|
||||||
|
// face down card must be hidden from opponent, but shown on game end for all
|
||||||
|
boolean showFaceDownInfo = controlled || (game != null && game.hasEnded());
|
||||||
if (card != null && showFaceDownInfo) {
|
if (card != null && showFaceDownInfo) {
|
||||||
// face down card must be hidden from opponent, but shown on game end for all
|
|
||||||
original = new CardView(card.copy(), (Game) null);
|
original = new CardView(card.copy(), (Game) null);
|
||||||
} else {
|
} else {
|
||||||
original = null;
|
original = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.transformed = permanent.isTransformed();
|
//this.transformed = permanent.isTransformed();
|
||||||
this.copy = permanent.isCopy();
|
this.copy = permanent.isCopy();
|
||||||
|
|
||||||
// for fipped, transformed or copied cards, switch the names
|
// for fipped, transformed or copied cards, switch the names
|
||||||
if (original != null && !original.getName().equals(this.getName())) {
|
if (original != null && !original.getName().equals(this.getName())) {
|
||||||
|
// TODO: wtf, why copy check here?! Need research
|
||||||
if (permanent.isCopy() && permanent.isFlipCard()) {
|
if (permanent.isCopy() && permanent.isFlipCard()) {
|
||||||
this.alternateName = permanent.getFlipCardName();
|
this.alternateName = permanent.getFlipCardName();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -98,31 +95,16 @@ public class PermanentView extends CardView {
|
||||||
}
|
}
|
||||||
this.nameController = nameController;
|
this.nameController = nameController;
|
||||||
|
|
||||||
// add info for face down permanents
|
// add additional info for face down permanents
|
||||||
if (permanent.isFaceDown(game) && card != null) {
|
if (permanent.isFaceDown(game)) {
|
||||||
if (showFaceDownInfo) {
|
//if (permanent.isManifested()) {
|
||||||
// must be a morphed or manifested card
|
// this.rules.add("A manifested creature card can be turned face up any time for it's mana cost."
|
||||||
for (Ability permanentAbility : permanent.getAbilities(game)) {
|
// + " A face-down card can also be turned face up for its morph cost.");
|
||||||
if (permanentAbility.getWorksFaceDown()) {
|
//} else if (permanent.isMorphed()) {
|
||||||
this.rules.add(permanentAbility.getRule(true));
|
// this.rules.add("If the controller has priority, they may turn this permanent face up."
|
||||||
} else if (permanentAbility instanceof TurnFaceUpAbility && !permanentAbility.getRuleVisible()) {
|
// + " This is a special action; it doesn't use the stack. To do this they pay the morph costs,"
|
||||||
this.rules.add(permanentAbility.getRule());
|
// + " then turns this permanent face up.");
|
||||||
}
|
//}
|
||||||
}
|
|
||||||
this.name = card.getName();
|
|
||||||
this.displayName = card.getName();
|
|
||||||
this.expansionSetCode = card.getExpansionSetCode();
|
|
||||||
this.cardNumber = card.getCardNumber();
|
|
||||||
} else {
|
|
||||||
if (permanent.isManifested()) {
|
|
||||||
this.rules.add("A manifested creature card can be turned face up any time for it's mana cost."
|
|
||||||
+ " A face-down card can also be turned face up for its morph cost.");
|
|
||||||
} else if (permanent.isMorphed()) {
|
|
||||||
this.rules.add("If the controller has priority, they may turn this permanent face up."
|
|
||||||
+ " This is a special action; it doesn't use the stack. To do this they pay the morph costs,"
|
|
||||||
+ " then turns this permanent face up.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// determines if shown in it's own column
|
// determines if shown in it's own column
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package mage.view;
|
package mage.view;
|
||||||
|
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.game.command.Plane;
|
import mage.game.command.Plane;
|
||||||
import mage.players.PlayableObjectStats;
|
import mage.players.PlayableObjectStats;
|
||||||
|
|
||||||
|
|
@ -15,15 +14,17 @@ public class PlaneView implements CommandObjectView, Serializable {
|
||||||
|
|
||||||
protected UUID id;
|
protected UUID id;
|
||||||
protected String name;
|
protected String name;
|
||||||
protected int imageNum;
|
protected String imageFileName = "";
|
||||||
protected String expansionSetCode;
|
protected int imageNumber = 0;
|
||||||
|
protected String expansionSetCode = "";
|
||||||
protected List<String> rules;
|
protected List<String> rules;
|
||||||
protected PlayableObjectStats playableStats = new PlayableObjectStats();
|
protected PlayableObjectStats playableStats = new PlayableObjectStats();
|
||||||
|
|
||||||
public PlaneView(Plane plane) {
|
public PlaneView(Plane plane) {
|
||||||
this.id = plane.getId();
|
this.id = plane.getId();
|
||||||
this.name = plane.getName();
|
this.name = plane.getName();
|
||||||
this.imageNum = plane.getImageNumber();
|
this.imageFileName = plane.getImageFileName();
|
||||||
|
this.imageNumber = plane.getImageNumber();
|
||||||
this.expansionSetCode = plane.getExpansionSetCode();
|
this.expansionSetCode = plane.getExpansionSetCode();
|
||||||
this.rules = plane.getAbilities().getRules(plane.getName());
|
this.rules = plane.getAbilities().getRules(plane.getName());
|
||||||
}
|
}
|
||||||
|
|
@ -43,9 +44,14 @@ public class PlaneView implements CommandObjectView, Serializable {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getImageFileName() {
|
||||||
|
return imageFileName;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getImageNumber() {
|
public int getImageNumber() {
|
||||||
return imageNum;
|
return imageNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import mage.game.command.*;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.players.net.UserData;
|
import mage.players.net.UserData;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
@ -82,19 +83,19 @@ public class PlayerView implements Serializable {
|
||||||
|
|
||||||
this.hasLeft = player.hasLeft();
|
this.hasLeft = player.hasLeft();
|
||||||
for (Card card : player.getGraveyard().getCards(game)) {
|
for (Card card : player.getGraveyard().getCards(game)) {
|
||||||
graveyard.put(card.getId(), new CardView(card, game, false));
|
graveyard.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId)));
|
||||||
}
|
}
|
||||||
for (ExileZone exileZone : game.getExile().getExileZones()) {
|
for (ExileZone exileZone : game.getExile().getExileZones()) {
|
||||||
for (Card card : exileZone.getCards(game)) {
|
for (Card card : exileZone.getCards(game)) {
|
||||||
if (player.getId().equals(card.getOwnerId())) {
|
if (player.getId().equals(card.getOwnerId())) {
|
||||||
exile.put(card.getId(), new CardView(card, game, false)); // unnown if it's allowed to look under a face down card
|
exile.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.controlled || !player.isHuman()) {
|
if (this.controlled || !player.isHuman()) {
|
||||||
// sideboard available for itself or for computer only
|
// sideboard available for itself or for computer only
|
||||||
for (Card card : player.getSideboard().getCards(game)) {
|
for (Card card : player.getSideboard().getCards(game)) {
|
||||||
sideboard.put(card.getId(), new CardView(card, game, false));
|
sideboard.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,7 +138,7 @@ public class PlayerView implements Serializable {
|
||||||
if (commander.getControllerId().equals(this.playerId)) {
|
if (commander.getControllerId().equals(this.playerId)) {
|
||||||
Card sourceCard = game.getCard(commander.getSourceId());
|
Card sourceCard = game.getCard(commander.getSourceId());
|
||||||
if (sourceCard != null) {
|
if (sourceCard != null) {
|
||||||
commandList.add(new CommanderView(commander, sourceCard, game));
|
commandList.add(new CommanderView(commander, sourceCard, game, createdForPlayerId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public class RevealedView implements Serializable {
|
||||||
public RevealedView(String name, Cards cards, Game game) {
|
public RevealedView(String name, Cards cards, Game game) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
for (Card card : cards.getCards(game)) {
|
for (Card card : cards.getCards(game)) {
|
||||||
this.cards.put(card.getId(), new CardView(card, game, card.getId()));
|
this.cards.put(card.getId(), new CardView(card, game));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ public final class ArtificialScoringSystem {
|
||||||
MageObject object = game.getObject(uuid);
|
MageObject object = game.getObject(uuid);
|
||||||
if (object instanceof Card) {
|
if (object instanceof Card) {
|
||||||
Card card = (Card) object;
|
Card card = (Card) object;
|
||||||
|
// TODO: implement getOutcomeTotal for permanents and cards too (not only attachments)
|
||||||
int outcomeScore = card.getAbilities(game).getOutcomeTotal();
|
int outcomeScore = card.getAbilities(game).getOutcomeTotal();
|
||||||
if (card.getCardType(game).contains(CardType.ENCHANTMENT)) {
|
if (card.getCardType(game).contains(CardType.ENCHANTMENT)) {
|
||||||
enchantments = enchantments + outcomeScore * 100;
|
enchantments = enchantments + outcomeScore * 100;
|
||||||
|
|
|
||||||
|
|
@ -822,7 +822,7 @@ public class GameController implements GameCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void choosePile(UUID playerId, final String message, final List<? extends Card> pile1, final List<? extends Card> pile2) throws MageException {
|
private synchronized void choosePile(UUID playerId, final String message, final List<? extends Card> pile1, final List<? extends Card> pile2) throws MageException {
|
||||||
perform(playerId, playerId1 -> getGameSession(playerId1).choosePile(message, new CardsView(game, pile1), new CardsView(game, pile2)));
|
perform(playerId, playerId1 -> getGameSession(playerId1).choosePile(message, new CardsView(game, pile1, playerId), new CardsView(game, pile2, playerId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void chooseMode(UUID playerId, final Map<UUID, String> modes, final String message) throws MageException {
|
private synchronized void chooseMode(UUID playerId, final Map<UUID, String> modes, final String message) throws MageException {
|
||||||
|
|
@ -836,12 +836,7 @@ public class GameController implements GameCallback {
|
||||||
private synchronized void target(UUID playerId, final String question, final Cards cards, final List<Permanent> perms, final Set<UUID> targets, final boolean required, final Map<String, Serializable> options) throws MageException {
|
private synchronized void target(UUID playerId, final String question, final Cards cards, final List<Permanent> perms, final Set<UUID> targets, final boolean required, final Map<String, Serializable> options) throws MageException {
|
||||||
perform(playerId, playerId1 -> {
|
perform(playerId, playerId1 -> {
|
||||||
if (cards != null) {
|
if (cards != null) {
|
||||||
// Zone targetZone = (Zone) options.get("targetZone");
|
getGameSession(playerId1).target(question, new CardsView(game, cards.getCards(game), playerId, true), targets, required, options);
|
||||||
// Are there really situations where a player selects from a list of face down cards?
|
|
||||||
// So always show face up for selection
|
|
||||||
// boolean showFaceDown = targetZone != null && targetZone.equals(Zone.PICK);
|
|
||||||
boolean showFaceDown = true;
|
|
||||||
getGameSession(playerId1).target(question, new CardsView(game, cards.getCards(game), showFaceDown, true), targets, required, options);
|
|
||||||
} else if (perms != null) {
|
} else if (perms != null) {
|
||||||
CardsView permsView = new CardsView();
|
CardsView permsView = new CardsView();
|
||||||
for (Permanent perm : perms) {
|
for (Permanent perm : perms) {
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@ public final class AerieBowmasters extends CardImpl {
|
||||||
|
|
||||||
// Megamorph {5}{G} <i>(You may cast this card face down as a 2/2 creature for {3}. Turn it face up at any time for its megamorph cost and put a +1/+1 counter on it.)</i>)
|
// Megamorph {5}{G} <i>(You may cast this card face down as a 2/2 creature for {3}. Turn it face up at any time for its megamorph cost and put a +1/+1 counter on it.)</i>)
|
||||||
this.addAbility(new MorphAbility(this, new ManaCostsImpl<>("{5}{G}"), true));
|
this.addAbility(new MorphAbility(this, new ManaCostsImpl<>("{5}{G}"), true));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AerieBowmasters(final AerieBowmasters card) {
|
private AerieBowmasters(final AerieBowmasters card) {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ public final class BaneAlleyBroker extends CardImpl {
|
||||||
this.power = new MageInt(0);
|
this.power = new MageInt(0);
|
||||||
this.toughness = new MageInt(3);
|
this.toughness = new MageInt(3);
|
||||||
|
|
||||||
// {tap}: Draw a card, then exile a card from your hand face down.
|
// {T}: Draw a card, then exile a card from your hand face down.
|
||||||
this.addAbility(new SimpleActivatedAbility(new BaneAlleyBrokerDrawExileEffect(), new TapSourceCost()));
|
this.addAbility(new SimpleActivatedAbility(new BaneAlleyBrokerDrawExileEffect(), new TapSourceCost()));
|
||||||
|
|
||||||
// You may look at cards exiled with Bane Alley Broker.
|
// You may look at cards exiled with Bane Alley Broker.
|
||||||
|
|
|
||||||
|
|
@ -103,9 +103,20 @@ class TurnOverEffect extends OneShotEffect {
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
Permanent creature = game.getPermanent(source.getFirstTarget());
|
Permanent creature = game.getPermanent(source.getFirstTarget());
|
||||||
if (creature != null) {
|
if (creature != null) {
|
||||||
|
// To turn over a creature means to physically turn the card over. If the result is a face-down card, it’s
|
||||||
|
// a colorless 2/2 creature, the same as one would get from using the morph ability. Turning a face-down
|
||||||
|
// card over results in it being turned face up. Any abilities that trigger when it’s turned face up will
|
||||||
|
// work. Turning a double-faced card over is the same as transforming it. Any abilities that trigger when
|
||||||
|
// you transform it will work. Turning a combined host/augment creature over will result in a big colorless
|
||||||
|
// 2/2 creature represented by two cards. Turning a melded creature over will result in the two cards
|
||||||
|
// breaking apart and forming two separate creatures, but they’ll probably just get right back together.
|
||||||
|
// Turning B.F.M. (Big Furry Monster) over is the same as turning a combined creature over.
|
||||||
|
// (2018-01-19)
|
||||||
if (creature.isFaceDown(game)) {
|
if (creature.isFaceDown(game)) {
|
||||||
|
// face down -> face up
|
||||||
creature.turnFaceUp(source, game, source.getControllerId());
|
creature.turnFaceUp(source, game, source.getControllerId());
|
||||||
} else {
|
} else {
|
||||||
|
// face up -> face down without face up ability
|
||||||
creature.turnFaceDown(source, game, source.getControllerId());
|
creature.turnFaceDown(source, game, source.getControllerId());
|
||||||
MageObjectReference objectReference = new MageObjectReference(creature.getId(), creature.getZoneChangeCounter(game), game);
|
MageObjectReference objectReference = new MageObjectReference(creature.getId(), creature.getZoneChangeCounter(game), game);
|
||||||
game.addEffect(new BecomesFaceDownCreatureEffect(null, objectReference, Duration.Custom, FaceDownType.MANUAL), source);
|
game.addEffect(new BecomesFaceDownCreatureEffect(null, objectReference, Duration.Custom, FaceDownType.MANUAL), source);
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ public class BloodMoonTest extends CardTestPlayerBase {
|
||||||
// which replacement effects apply and how they apply, check the characteristics of the permanent as it
|
// which replacement effects apply and how they apply, check the characteristics of the permanent as it
|
||||||
// would exist on the battlefield, taking into account replacement effects that have already modified how
|
// would exist on the battlefield, taking into account replacement effects that have already modified how
|
||||||
// it enters the battlefield (see rule 616.1), continuous effects generated by the resolution of spells
|
// it enters the battlefield (see rule 616.1), continuous effects generated by the resolution of spells
|
||||||
// or abilities that changed the permanents characteristics on the stack (see rule 400.7a), and continuous
|
// or abilities that changed the permanent's characteristics on the stack (see rule 400.7a), and continuous
|
||||||
// effects from the permanents own static abilities, but ignoring continuous effects from any other source
|
// effects from the permanent's own static abilities, but ignoring continuous effects from any other source
|
||||||
// that would affect it.
|
// that would affect it.
|
||||||
// Grassland has to enter the battlefield tapped, because
|
// Grassland has to enter the battlefield tapped, because
|
||||||
// the Blood Moon does not prevent ETB Replacement Effects
|
// the Blood Moon does not prevent ETB Replacement Effects
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
package org.mage.test.cards.abilities.keywords;
|
package org.mage.test.cards.abilities.keywords;
|
||||||
|
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
|
import mage.cards.repository.TokenRepository;
|
||||||
import mage.constants.EmptyNames;
|
import mage.constants.EmptyNames;
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.game.permanent.PermanentCard;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import mage.view.GameView;
|
import mage.view.GameView;
|
||||||
import mage.view.PermanentView;
|
import mage.view.PermanentView;
|
||||||
|
|
@ -535,9 +538,14 @@ public class ManifestTest extends CardTestPlayerBase {
|
||||||
Permanent perm = game.getBattlefield().getAllPermanents()
|
Permanent perm = game.getBattlefield().getAllPermanents()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(permanent -> permanent.isFaceDown(game))
|
.filter(permanent -> permanent.isFaceDown(game))
|
||||||
|
.filter(permanent -> {
|
||||||
|
Assert.assertEquals("face down permanent must have not name", "", permanent.getName());
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
Assert.assertNotNull(perm);
|
Assert.assertNotNull(perm);
|
||||||
|
Assert.assertEquals("server side face down permanent must have empty name", EmptyNames.FACE_DOWN_CREATURE.toString(), perm.getName());
|
||||||
GameView gameView = new GameView(game.getState(), game, viewFromPlayer.getId(), null);
|
GameView gameView = new GameView(game.getState(), game, viewFromPlayer.getId(), null);
|
||||||
PlayerView playerView = gameView.getPlayers()
|
PlayerView playerView = gameView.getPlayers()
|
||||||
.stream()
|
.stream()
|
||||||
|
|
@ -548,29 +556,34 @@ public class ManifestTest extends CardTestPlayerBase {
|
||||||
PermanentView permanentView = playerView.getBattlefield().values()
|
PermanentView permanentView = playerView.getBattlefield().values()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(CardView::isFaceDown)
|
.filter(CardView::isFaceDown)
|
||||||
|
.filter(p -> {
|
||||||
|
CardView debugView = new CardView((PermanentCard) currentGame.getPermanent(p.getId()), currentGame, false, false);
|
||||||
|
Assert.assertNotEquals("face down view must have name", "", p.getName());
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
Assert.assertNotNull(permanentView);
|
Assert.assertNotNull(permanentView);
|
||||||
return permanentView;
|
return permanentView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertFaceDown(String info, PermanentView faceDownPermanent, String realPermanentName, boolean realInfoMustBeVisible) {
|
private void assertFaceDown(String checkInfo, PermanentView faceDownPermanentView, String needRealName, String needFaceDownStatus, boolean needShowRealInfo) {
|
||||||
if (realInfoMustBeVisible) {
|
String info = checkInfo + " - " + faceDownPermanentView;
|
||||||
// show all info
|
String needName = CardUtil.getCardNameForGUI(needShowRealInfo ? needRealName : "", needFaceDownStatus);
|
||||||
Assert.assertEquals(realPermanentName, faceDownPermanent.getName()); // show real name
|
|
||||||
Assert.assertEquals("2", faceDownPermanent.getPower());
|
|
||||||
Assert.assertEquals("2", faceDownPermanent.getToughness());
|
|
||||||
//
|
|
||||||
Assert.assertNotNull(faceDownPermanent.getOriginal());
|
|
||||||
Assert.assertEquals(realPermanentName, faceDownPermanent.getOriginal().getName());
|
|
||||||
} else {
|
|
||||||
// hide original info
|
|
||||||
Assert.assertEquals(info, "", faceDownPermanent.getName());
|
|
||||||
Assert.assertEquals(info, "2", faceDownPermanent.getPower());
|
|
||||||
Assert.assertEquals(info, "2", faceDownPermanent.getToughness());
|
|
||||||
Assert.assertNull(info, faceDownPermanent.getOriginal());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// check view
|
||||||
|
Assert.assertTrue(info + " - wrong face down status", faceDownPermanentView.isFaceDown());
|
||||||
|
Assert.assertEquals(info + " - wrong name", needName, faceDownPermanentView.getName()); // show real name
|
||||||
|
Assert.assertEquals(info + " - wrong power", "2", faceDownPermanentView.getPower());
|
||||||
|
Assert.assertEquals(info + " - wrong toughness", "2", faceDownPermanentView.getToughness());
|
||||||
|
|
||||||
|
// check original info
|
||||||
|
if (needShowRealInfo) {
|
||||||
|
Assert.assertNotNull(info + " - miss original card data", faceDownPermanentView.getOriginal());
|
||||||
|
Assert.assertEquals(info + " - wrong original card name", needRealName, faceDownPermanentView.getOriginal().getName());
|
||||||
|
} else {
|
||||||
|
Assert.assertNull(info + " - original data must be hidden", faceDownPermanentView.getOriginal());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -587,27 +600,26 @@ public class ManifestTest extends CardTestPlayerBase {
|
||||||
runCode("on active game", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
runCode("on active game", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
// hide from opponent
|
// hide from opponent
|
||||||
PermanentView permanent = findFaceDownPermanent(game, playerA, playerB);
|
PermanentView permanent = findFaceDownPermanent(game, playerA, playerB);
|
||||||
assertFaceDown("in game: must hide from opponent", permanent, "Mountain", false);
|
assertFaceDown("in game: must hide from opponent", permanent, "Mountain", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, false);
|
||||||
|
|
||||||
// show for yourself
|
// show for yourself
|
||||||
permanent = findFaceDownPermanent(game, playerB, playerB);
|
permanent = findFaceDownPermanent(game, playerB, playerB);
|
||||||
assertFaceDown("in game: must show for yourself", permanent, "Mountain", true);
|
assertFaceDown("in game: must show for yourself", permanent, "Mountain", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
setStrictChooseMode(true);
|
setStrictChooseMode(true);
|
||||||
setStopAt(1, PhaseStep.END_TURN);
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
|
|
||||||
// workaround to force end game (can't use other test commands after that)
|
// workaround to force end game (can't use other test commands after that)
|
||||||
playerA.won(currentGame);
|
playerA.won(currentGame);
|
||||||
Assert.assertTrue(currentGame.hasEnded());
|
Assert.assertTrue(currentGame.hasEnded());
|
||||||
|
|
||||||
// show all after game end
|
// show all after game end
|
||||||
PermanentView permanent = findFaceDownPermanent(currentGame, playerA, playerB);
|
PermanentView permanent = findFaceDownPermanent(currentGame, playerA, playerB);
|
||||||
assertFaceDown("end game: must show for opponent", permanent, "Mountain", true);
|
assertFaceDown("end game: must show for opponent", permanent, "Mountain", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, true);
|
||||||
//
|
//
|
||||||
permanent = findFaceDownPermanent(currentGame, playerB, playerB);
|
permanent = findFaceDownPermanent(currentGame, playerB, playerB);
|
||||||
assertFaceDown("end game: must show for yourself", permanent, "Mountain", true);
|
assertFaceDown("end game: must show for yourself", permanent, "Mountain", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public class MegamorphTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.HAND, playerA, "Aerie Bowmasters", 1);
|
addCard(Zone.HAND, playerA, "Aerie Bowmasters", 1);
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 6);
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 6);
|
||||||
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aerie Bowmasters using Morph");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aerie Bowmasters using Megamorph");
|
||||||
|
|
||||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}: Turn");
|
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}: Turn");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -521,14 +521,10 @@ public class MorphTest extends CardTestPlayerBase {
|
||||||
setStrictChooseMode(true);
|
setStrictChooseMode(true);
|
||||||
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury using Morph");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury using Morph");
|
||||||
// showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
|
|
||||||
// showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB);
|
|
||||||
|
|
||||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Supplant Form");
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Supplant Form");
|
||||||
addTarget(playerB, EmptyNames.FACE_DOWN_CREATURE.toString());
|
addTarget(playerB, EmptyNames.FACE_DOWN_CREATURE.toString());
|
||||||
|
|
||||||
// showBattlefield("A battle end", 1, PhaseStep.END_TURN, playerA);
|
|
||||||
// showBattlefield("B battle end", 1, PhaseStep.END_TURN, playerB);
|
|
||||||
setStopAt(1, PhaseStep.END_TURN);
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
|
|
@ -1103,10 +1099,17 @@ public class MorphTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_MorphIsColorlessFlash() {
|
public void test_MorphIsColorlessFlash() {
|
||||||
|
// creature
|
||||||
|
// Morph {4}{G}
|
||||||
addCard(Zone.HAND, playerA, "Pine Walker", 1);
|
addCard(Zone.HAND, playerA, "Pine Walker", 1);
|
||||||
|
// land
|
||||||
|
// Morph {2}
|
||||||
addCard(Zone.HAND, playerA, "Zoetic Cavern", 1);
|
addCard(Zone.HAND, playerA, "Zoetic Cavern", 1);
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Liberator, Urza's Battlethopter", 1);
|
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
|
||||||
|
//
|
||||||
|
// You may cast colorless spells and artifact spells as though they had flash.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Liberator, Urza's Battlethopter", 1);
|
||||||
|
|
||||||
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Pine Walker using Morph");
|
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Pine Walker using Morph");
|
||||||
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Zoetic Cavern using Morph");
|
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Zoetic Cavern using Morph");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,11 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
*/
|
*/
|
||||||
public class PrototypeTest extends CardTestPlayerBase {
|
public class PrototypeTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
// Prototype {2}{R} - 3/2
|
||||||
private static final String automaton = "Blitz Automaton";
|
private static final String automaton = "Blitz Automaton";
|
||||||
private static final String withPrototype = " using Prototype";
|
private static final String withPrototype = " using Prototype";
|
||||||
private static final String automatonWithPrototype = automaton+withPrototype;
|
private static final String automatonWithPrototype = automaton+withPrototype;
|
||||||
|
|
||||||
private static final String bolt = "Lightning Bolt";
|
private static final String bolt = "Lightning Bolt";
|
||||||
private static final String cloudshift = "Cloudshift";
|
private static final String cloudshift = "Cloudshift";
|
||||||
private static final String clone = "Clone";
|
private static final String clone = "Clone";
|
||||||
|
|
@ -89,6 +91,7 @@ public class PrototypeTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||||
addCard(Zone.HAND, playerA, automaton);
|
addCard(Zone.HAND, playerA, automaton);
|
||||||
|
|
||||||
|
showAvailableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||||
|
|
||||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package org.mage.test.cards.cost.sacrifice;
|
||||||
|
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
|
import mage.util.RandomUtil;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mage.test.sba.PlaneswalkerRuleTest;
|
import org.mage.test.sba.PlaneswalkerRuleTest;
|
||||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
@ -22,16 +23,15 @@ public class SacrificeLandTest extends CardTestPlayerBase {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testRollback() {
|
public void testRollback() {
|
||||||
// If Soldevi Excavations would enter the battlefield, sacrifice an untapped Island instead.
|
// If Soldevi Excavations entered the battlefield, sacrifice an untapped Island instead.
|
||||||
// If you do, put Soldevi Excavations onto the battlefield. If you don't, put it into its owner's graveyard.
|
// If you do, put Soldevi Excavations onto the battlefield. If you don't, put it into its owner's graveyard.
|
||||||
String soldeviExcavations = "Soldevi Excavations";
|
String soldeviExcavations = "Soldevi Excavations";
|
||||||
|
|
||||||
addCard(Zone.HAND, playerA, soldeviExcavations);
|
addCard(Zone.HAND, playerA, soldeviExcavations);
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Island");
|
addCard(Zone.BATTLEFIELD, playerA, "Island");
|
||||||
|
|
||||||
Random random = new Random();
|
boolean sacFirstLand = RandomUtil.nextBoolean();
|
||||||
boolean sacFirstLand = random.nextBoolean();
|
boolean sacSecondLand = RandomUtil.nextBoolean();
|
||||||
boolean sacSecondLand = random.nextBoolean();
|
|
||||||
|
|
||||||
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, soldeviExcavations);
|
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, soldeviExcavations);
|
||||||
setChoice(playerA, sacFirstLand);
|
setChoice(playerA, sacFirstLand);
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,26 @@ package org.mage.test.serverside;
|
||||||
|
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
import mage.MageObjectImpl;
|
import mage.MageObjectImpl;
|
||||||
|
import mage.ObjectColor;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.SimpleActivatedAbility;
|
import mage.abilities.common.SimpleActivatedAbility;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.abilities.effects.common.CreateTokenEffect;
|
import mage.abilities.effects.common.CreateTokenEffect;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.repository.TokenRepository;
|
import mage.cards.repository.TokenRepository;
|
||||||
|
import mage.constants.EmptyNames;
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
import mage.game.permanent.PermanentToken;
|
import mage.game.permanent.PermanentToken;
|
||||||
import mage.game.permanent.token.HumanToken;
|
import mage.game.permanent.token.HumanToken;
|
||||||
import mage.game.permanent.token.SoldierToken;
|
import mage.game.permanent.token.SoldierToken;
|
||||||
import mage.game.permanent.token.Token;
|
import mage.game.permanent.token.Token;
|
||||||
import mage.game.permanent.token.TokenImpl;
|
import mage.game.permanent.token.TokenImpl;
|
||||||
import mage.game.permanent.token.custom.CreatureToken;
|
import mage.game.permanent.token.custom.CreatureToken;
|
||||||
|
import mage.game.stack.Spell;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import mage.view.GameView;
|
import mage.view.GameView;
|
||||||
|
|
@ -261,13 +266,13 @@ public class TokenImagesTest extends CardTestPlayerBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assert_TokenImageNumber(String tokenName, List<Integer> needUniqueImages) {
|
private void assert_TokenOrCardImageNumber(String tokenOrCardName, List<Integer> needUniqueImages) {
|
||||||
Set<Integer> serverStats = currentGame.getBattlefield().getAllPermanents()
|
Set<Integer> serverStats = currentGame.getBattlefield().getAllPermanents()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(card -> card.getName().equals(tokenName))
|
.filter(card -> card.getName().equals(tokenOrCardName))
|
||||||
.filter(card -> card instanceof PermanentToken)
|
.filter(card -> card instanceof MageObjectImpl)
|
||||||
.sorted(Comparator.comparing(Card::getExpansionSetCode))
|
.sorted(Comparator.comparing(Card::getExpansionSetCode))
|
||||||
.map(card -> (PermanentToken) card)
|
.map(card -> (MageObjectImpl) card)
|
||||||
.map(MageObjectImpl::getImageNumber)
|
.map(MageObjectImpl::getImageNumber)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
|
@ -280,7 +285,7 @@ public class TokenImagesTest extends CardTestPlayerBase {
|
||||||
Assert.assertNotNull(playerView);
|
Assert.assertNotNull(playerView);
|
||||||
Set<Integer> clientStats = playerView.getBattlefield().values()
|
Set<Integer> clientStats = playerView.getBattlefield().values()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(card -> card.getName().equals(tokenName))
|
.filter(card -> card.getName().equals(tokenOrCardName))
|
||||||
.sorted(Comparator.comparing(CardView::getExpansionSetCode))
|
.sorted(Comparator.comparing(CardView::getExpansionSetCode))
|
||||||
.map(CardView::getImageNumber)
|
.map(CardView::getImageNumber)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
@ -289,8 +294,79 @@ public class TokenImagesTest extends CardTestPlayerBase {
|
||||||
String imagesNeed = needUniqueImages.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
|
String imagesNeed = needUniqueImages.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
|
||||||
String imagesServer = serverStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
|
String imagesServer = serverStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
|
||||||
String imagesClient = clientStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
|
String imagesClient = clientStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
|
||||||
Assert.assertEquals(imagesNeed, imagesServer);
|
Assert.assertEquals("server side", imagesNeed, imagesServer);
|
||||||
Assert.assertEquals(imagesNeed, imagesClient);
|
Assert.assertEquals("client side", imagesNeed, imagesClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertFaceDownCharacteristics(String info, MageObject object, String faceDownTypeName) {
|
||||||
|
String prefix = info + " - " + object;
|
||||||
|
|
||||||
|
// image info
|
||||||
|
Assert.assertEquals(prefix + " - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, object.getExpansionSetCode());
|
||||||
|
Assert.assertEquals(prefix + " - wrong card number", "0", object.getCardNumber());
|
||||||
|
Assert.assertEquals(prefix + " - wrong image file name", faceDownTypeName, object.getImageFileName());
|
||||||
|
Assert.assertNotEquals(prefix + " - wrong image number", Integer.valueOf(0), object.getImageNumber());
|
||||||
|
|
||||||
|
// characteristic checks instead new test
|
||||||
|
Assert.assertEquals(prefix + " - wrong name", EmptyNames.FACE_DOWN_CREATURE.toString(), object.getName());
|
||||||
|
Assert.assertEquals(prefix + " - wrong power", 2, object.getPower().getValue());
|
||||||
|
Assert.assertEquals(prefix + " - wrong toughness", 2, object.getToughness().getValue());
|
||||||
|
Assert.assertEquals(prefix + " - wrong color", "", object.getColor(currentGame).toString());
|
||||||
|
Assert.assertEquals(prefix + " - wrong supertypes", "[]", object.getSuperType(currentGame).toString());
|
||||||
|
Assert.assertEquals(prefix + " - wrong types", "[Creature]", object.getCardType(currentGame).toString());
|
||||||
|
Assert.assertEquals(prefix + " - wrong subtypes", "[]", object.getSubtype(currentGame).toString());
|
||||||
|
Assert.assertEquals(prefix + " - wrong abilities", 2, object.getAbilities().size()); // become face down + face up abilities only
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOriginalData(String info, CardView cardView, int needPower, int needToughness, String needColor) {
|
||||||
|
String prefix = info + " - " + cardView;
|
||||||
|
int currentPower = cardView.getOriginalPower() == null ? 0 : cardView.getOriginalPower().getValue();
|
||||||
|
int currentToughness = cardView.getOriginalToughness() == null ? 0 : cardView.getOriginalToughness().getValue();
|
||||||
|
Assert.assertEquals(prefix + " - wrong power", needPower, currentPower);
|
||||||
|
Assert.assertEquals(prefix + " - wrong toughness", needToughness, currentToughness);
|
||||||
|
if (needColor != null) {
|
||||||
|
Assert.assertEquals(prefix + " - wrong color", needColor, cardView.getOriginalColorIdentity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assert_FaceDownMorphImageNumber(List<Integer> needUniqueImages) {
|
||||||
|
Set<Integer> serverStats = currentGame.getBattlefield().getAllPermanents()
|
||||||
|
.stream()
|
||||||
|
.filter(card -> card.isFaceDown(currentGame))
|
||||||
|
.filter(card -> {
|
||||||
|
Assert.assertEquals("server side - wrong set code - " + card, TokenRepository.XMAGE_TOKENS_SET_CODE, card.getExpansionSetCode());
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.sorted(Comparator.comparing(Card::getExpansionSetCode))
|
||||||
|
.map(card -> (MageObjectImpl) card)
|
||||||
|
.map(MageObjectImpl::getImageNumber)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// use another player to hide card view names in face down
|
||||||
|
GameView gameView = new GameView(currentGame.getState(), currentGame, playerB.getId(), null);
|
||||||
|
PlayerView playerView = gameView.getPlayers()
|
||||||
|
.stream()
|
||||||
|
.filter(p -> p.getName().equals(playerA.getName()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
Assert.assertNotNull(playerView);
|
||||||
|
Set<Integer> clientStats = playerView.getBattlefield().values()
|
||||||
|
.stream()
|
||||||
|
.filter(CardView::isFaceDown)
|
||||||
|
.filter(card -> {
|
||||||
|
Assert.assertEquals("client side - wrong set code - " + card, TokenRepository.XMAGE_TOKENS_SET_CODE, card.getExpansionSetCode());
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.sorted(Comparator.comparing(CardView::getExpansionSetCode))
|
||||||
|
.map(CardView::getImageNumber)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// server and client sides must have same data
|
||||||
|
String imagesNeed = needUniqueImages.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
|
||||||
|
String imagesServer = serverStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
|
||||||
|
String imagesClient = clientStats.stream().sorted().map(Object::toString).collect(Collectors.joining(", "));
|
||||||
|
Assert.assertEquals("server side", imagesNeed, imagesServer);
|
||||||
|
Assert.assertEquals("client side", imagesNeed, imagesClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -317,7 +393,7 @@ public class TokenImagesTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
// x2 tokens
|
// x2 tokens
|
||||||
assert_MemorialToGlory(20, "40K=40");
|
assert_MemorialToGlory(20, "40K=40");
|
||||||
assert_TokenImageNumber("Soldier Token", Arrays.asList(1, 2, 3)); // 40K set contains 3 diffrent soldiers
|
assert_TokenOrCardImageNumber("Soldier Token", Arrays.asList(1, 2, 3)); // 40K set contains 3 diffrent soldiers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -381,7 +457,7 @@ public class TokenImagesTest extends CardTestPlayerBase {
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
assertPermanentCount(playerA, 1 + 10); // 1 test card + 10 tokens
|
assertPermanentCount(playerA, 1 + 10); // 1 test card + 10 tokens
|
||||||
assert_TokenImageNumber("Soldier Token", Arrays.asList(realImageNumber.get())); // one ability's call must generate tokens with same image
|
assert_TokenOrCardImageNumber("Soldier Token", Arrays.asList(realImageNumber.get())); // one ability's call must generate tokens with same image
|
||||||
assert_Inner("test", 0, 0, 1,
|
assert_Inner("test", 0, 0, 1,
|
||||||
"Soldier Token", 10, false, "40K=10");
|
"Soldier Token", 10, false, "40K=10");
|
||||||
}
|
}
|
||||||
|
|
@ -479,7 +555,7 @@ public class TokenImagesTest extends CardTestPlayerBase {
|
||||||
setStopAt(1, PhaseStep.END_TURN);
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
assert_TokenImageNumber("Human Token", Arrays.asList(2)); // one ability's call must generate tokens with same image
|
assert_TokenOrCardImageNumber("Human Token", Arrays.asList(2)); // one ability's call must generate tokens with same image
|
||||||
assert_Inner("test", 0, 0, 1,
|
assert_Inner("test", 0, 0, 1,
|
||||||
"Human Token", 10, false, "MOC=10");
|
"Human Token", 10, false, "MOC=10");
|
||||||
}
|
}
|
||||||
|
|
@ -614,9 +690,9 @@ public class TokenImagesTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
@Test // it's ok for fail in 1 of 50
|
@Test // it's ok for fail in 1 of 50
|
||||||
// TODO: implement mock or test command to setup "random" images in TokenImpl.generateTokenInfo
|
// TODO: implement mock or test command to setup "random" images in TokenImpl.generateTokenInfo
|
||||||
// (see setFlipCoinResult and setDieRollResult), so no needs in big amout
|
// (see setFlipCoinResult and setDieRollResult), so no needs in big amount
|
||||||
public void test_Abilities_Incubator_MustTransformWithSameSettings() {
|
public void test_Abilities_Incubator_MustTransformWithSameSettings() {
|
||||||
// bug with miss image data in tranformed incubator token: https://github.com/magefree/mage/issues/11535
|
// bug with miss image data in transformed incubator token: https://github.com/magefree/mage/issues/11535
|
||||||
|
|
||||||
// make sure random images take all 3 diff images
|
// make sure random images take all 3 diff images
|
||||||
int needIncubatorTokens = 30;
|
int needIncubatorTokens = 30;
|
||||||
|
|
@ -656,8 +732,261 @@ public class TokenImagesTest extends CardTestPlayerBase {
|
||||||
"Phyrexian Token", needPhyrexianTokens, false, "MOM=" + needPhyrexianTokens);
|
"Phyrexian Token", needPhyrexianTokens, false, "MOM=" + needPhyrexianTokens);
|
||||||
|
|
||||||
// MOM-Incubator has 1 image (number is 0)
|
// MOM-Incubator has 1 image (number is 0)
|
||||||
assert_TokenImageNumber("Incubator Token", Arrays.asList(0));
|
assert_TokenOrCardImageNumber("Incubator Token", Arrays.asList(0));
|
||||||
// MOM-Phyrexian has 3 images
|
// MOM-Phyrexian has 3 images
|
||||||
assert_TokenImageNumber("Phyrexian Token", Arrays.asList(1, 2, 3));
|
assert_TokenOrCardImageNumber("Phyrexian Token", Arrays.asList(1, 2, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // it's ok for fail in very rare random
|
||||||
|
// TODO: implement mock or test command to setup "random" images in TokenImpl.generateTokenInfo
|
||||||
|
// (see setFlipCoinResult and setDieRollResult), so no needs in big amount
|
||||||
|
public void test_FaceDown_CardWithMorph_MustGetDefaultImage() {
|
||||||
|
int faceDownAmount = 15;
|
||||||
|
addCard(Zone.HAND, playerA, "Ainok Tracker", faceDownAmount); // {5}{R}, Morph {4}{R}, face up {3}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5 * faceDownAmount);
|
||||||
|
|
||||||
|
IntStream.range(0, faceDownAmount).forEach(i -> {
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ainok Tracker using Morph");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), faceDownAmount);
|
||||||
|
assert_FaceDownMorphImageNumber(Arrays.asList(1, 2, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // it's ok for fail in very rare random
|
||||||
|
public void test_FaceDown_LandWithMorph_MustGetDefaultImage() {
|
||||||
|
int faceDownAmount = 15;
|
||||||
|
addCard(Zone.HAND, playerA, "Zoetic Cavern", faceDownAmount);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 * faceDownAmount);
|
||||||
|
|
||||||
|
IntStream.range(0, faceDownAmount).forEach(i -> {
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zoetic Cavern using Morph");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), faceDownAmount);
|
||||||
|
assert_FaceDownMorphImageNumber(Arrays.asList(1, 2, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_FaceDown_Spell() {
|
||||||
|
addCard(Zone.HAND, playerA, "Zoetic Cavern", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zoetic Cavern using Morph");
|
||||||
|
runCode("stack check", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
Assert.assertEquals("stack must be active", 1, game.getState().getStack().size());
|
||||||
|
|
||||||
|
// server side spell before resolve contains full info, not empty
|
||||||
|
// so real data will be full, but view data will be hidden by face down status
|
||||||
|
String cardName = "Zoetic Cavern";
|
||||||
|
String needClientControllerName = CardUtil.getCardNameForGUI(cardName, TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH);
|
||||||
|
String needClientOpponentName = CardUtil.getCardNameForGUI("", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH);
|
||||||
|
|
||||||
|
Spell spell = (Spell) game.getState().getStack().stream().findFirst().orElse(null);
|
||||||
|
Assert.assertNotNull("server - spell must exists", spell);
|
||||||
|
|
||||||
|
// make sure image from object's id works fine
|
||||||
|
IntStream.of(5).forEach(i -> {
|
||||||
|
UUID objectId = UUID.randomUUID();
|
||||||
|
int objectImageNumber = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, objectId).getImageNumber();
|
||||||
|
Assert.assertNotEquals("wrong image number", 0, objectImageNumber);
|
||||||
|
IntStream.of(5).forEach(j -> {
|
||||||
|
int newImageNumber = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, objectId).getImageNumber();
|
||||||
|
Assert.assertEquals("generated image numbers must be same for same id", objectImageNumber, newImageNumber);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// debug
|
||||||
|
//CardView debugViewOpponent = new CardView(spell, currentGame, false, false);
|
||||||
|
//CardView debugViewController = new CardView(spell, currentGame, true, false);
|
||||||
|
|
||||||
|
// server side (full data)
|
||||||
|
Assert.assertTrue("server - wrong face down status", spell.isFaceDown(game));
|
||||||
|
Assert.assertEquals("server - wrong color", spell.getColor(game), new ObjectColor());
|
||||||
|
Assert.assertEquals("server - wrong name", cardName, spell.getName());
|
||||||
|
//
|
||||||
|
// workaround to find image number (from id) - it must be same on each generate
|
||||||
|
int serverImageNumber = spell.getSpellAbility().getCharacteristics(game).getImageNumber();
|
||||||
|
Assert.assertNotEquals("server - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, spell.getExpansionSetCode());
|
||||||
|
Assert.assertNotEquals("server - wrong image number", 0, serverImageNumber);
|
||||||
|
|
||||||
|
// client side - controller (hidden + card name)
|
||||||
|
GameView gameView = getGameView(playerA);
|
||||||
|
CardView spellView = gameView.getStack().values().stream().findFirst().orElse(null);
|
||||||
|
Assert.assertNotNull("client, controller - spell must exists", spellView);
|
||||||
|
Assert.assertTrue("client, controller - wrong face down status", spellView.isFaceDown());
|
||||||
|
Assert.assertEquals("client, controller - wrong color", spellView.getColor(), new ObjectColor());
|
||||||
|
Assert.assertEquals("client, controller - wrong spell name", needClientControllerName, spellView.getName());
|
||||||
|
//
|
||||||
|
Assert.assertEquals("client, controller - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, spellView.getExpansionSetCode());
|
||||||
|
Assert.assertEquals("client, controller - wrong card number", "0", spellView.getCardNumber());
|
||||||
|
Assert.assertEquals("client, controller - wrong image file", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, spellView.getImageFileName());
|
||||||
|
Assert.assertEquals("client, controller - wrong image number", serverImageNumber, spellView.getImageNumber());
|
||||||
|
|
||||||
|
// client side - opponent (hidden)
|
||||||
|
gameView = getGameView(playerB);
|
||||||
|
spellView = gameView.getStack().values().stream().findFirst().orElse(null);
|
||||||
|
Assert.assertNotNull("client, opponent - spell must exists", spellView);
|
||||||
|
Assert.assertTrue("client, opponent - wrong face down status", spellView.isFaceDown());
|
||||||
|
Assert.assertEquals("client, opponent - wrong color", spellView.getColor(), new ObjectColor());
|
||||||
|
Assert.assertEquals("client, opponent - wrong spell name", needClientOpponentName, spellView.getName());
|
||||||
|
//
|
||||||
|
Assert.assertEquals("client, opponent - wrong set code", TokenRepository.XMAGE_TOKENS_SET_CODE, spellView.getExpansionSetCode());
|
||||||
|
Assert.assertEquals("client, opponent - wrong card number", "0", spellView.getCardNumber());
|
||||||
|
Assert.assertEquals("client, opponent - wrong image file", TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, spellView.getImageFileName());
|
||||||
|
Assert.assertEquals("client, opponent - wrong image number", serverImageNumber, spellView.getImageNumber());
|
||||||
|
});
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_FaceDown_Megamorph_MustGetDefaultImage() {
|
||||||
|
addCard(Zone.HAND, playerA, "Aerie Bowmasters", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 6 + 3);
|
||||||
|
|
||||||
|
// prepare face down permanent
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aerie Bowmasters using Megamorph");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1);
|
||||||
|
assertPermanentCount(playerA, "Aerie Bowmasters", 0);
|
||||||
|
Permanent permanent = getPermanent(EmptyNames.FACE_DOWN_CREATURE.toString(), playerA);
|
||||||
|
assertFaceDownCharacteristics("permanent", permanent, TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MEGAMORPH);
|
||||||
|
});
|
||||||
|
|
||||||
|
// face up it and find counter
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}: Turn this");
|
||||||
|
runCode("on face up", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0);
|
||||||
|
assertPermanentCount(playerA, "Aerie Bowmasters", 1);
|
||||||
|
assertCounterCount(playerA, "Aerie Bowmasters", CounterType.P1P1, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_FaceDown_ExileZone_MustGetDefaultImage() {
|
||||||
|
// {T}: Draw a card, then exile a card from your hand face down.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Bane Alley Broker", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Forest", 1);
|
||||||
|
|
||||||
|
// exile face down
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw a card");
|
||||||
|
addTarget(playerA, "Forest");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
|
||||||
|
// check face down card in exile
|
||||||
|
runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
Card card = currentGame.getExile().getAllCards(currentGame, playerA.getId()).get(0);
|
||||||
|
GameView gameView = getGameView(playerA);
|
||||||
|
CardView controllerCardView = gameView.getExile()
|
||||||
|
.stream()
|
||||||
|
.flatMap(e -> e.values().stream())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
gameView = getGameView(playerB);
|
||||||
|
CardView opponentCardView = gameView.getExile()
|
||||||
|
.stream()
|
||||||
|
.flatMap(e -> e.values().stream())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
// server side (full data)
|
||||||
|
// TODO: possible bugged?! Other abilities must not see faced-down card as real on server side!
|
||||||
|
String needName = "Forest";
|
||||||
|
Assert.assertTrue("server side - must be face down", card.isFaceDown(currentGame));
|
||||||
|
Assert.assertEquals("server side - wrong name", needName, card.getName());
|
||||||
|
Assert.assertEquals("server side - wrong abilities", 2, card.getAbilities(currentGame).size()); // play + add mana
|
||||||
|
|
||||||
|
// client side - controller (hidden data + original name)
|
||||||
|
needName = "Face Down: Forest";
|
||||||
|
Assert.assertEquals("controller - wrong name", needName, controllerCardView.getName());
|
||||||
|
Assert.assertTrue("controller - must be face down", controllerCardView.isFaceDown());
|
||||||
|
Assert.assertEquals("controller - must not have abilities", 0, controllerCardView.getRules().size());
|
||||||
|
assertOriginalData("controller, original data", controllerCardView, 0, 0, "");
|
||||||
|
|
||||||
|
// client side - opponent (hidden data)
|
||||||
|
needName = "Face Down";
|
||||||
|
Assert.assertTrue("opponent - must be face down", opponentCardView.isFaceDown());
|
||||||
|
Assert.assertEquals("opponent - wrong name", needName, opponentCardView.getName());
|
||||||
|
Assert.assertEquals("opponent - must not have abilities", 0, opponentCardView.getRules().size());
|
||||||
|
assertOriginalData("opponent, original data", opponentCardView, 0, 0, "");
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_FaceDown_ForetellInExile_MustGetDefaultImage() {
|
||||||
|
// Foretell {1}{U}
|
||||||
|
addCard(Zone.HAND, playerA, "Behold the Multiverse", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||||
|
|
||||||
|
// exile face down as foretell
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell {1}{U}");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
|
||||||
|
// check face down card
|
||||||
|
runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
Card card = currentGame.getExile().getAllCards(currentGame, playerA.getId()).get(0);
|
||||||
|
GameView gameView = getGameView(playerA);
|
||||||
|
CardView controllerCardView = gameView.getExile()
|
||||||
|
.stream()
|
||||||
|
.flatMap(e -> e.values().stream())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
gameView = getGameView(playerB);
|
||||||
|
CardView opponentCardView = gameView.getExile()
|
||||||
|
.stream()
|
||||||
|
.flatMap(e -> e.values().stream())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
// server side (full data)
|
||||||
|
// TODO: possible bugged?! Other abilities must not see faced-down card as real on server side!
|
||||||
|
String needName = "Behold the Multiverse";
|
||||||
|
Assert.assertTrue("server side - must be face down", card.isFaceDown(currentGame));
|
||||||
|
Assert.assertEquals("server side - wrong name", needName, card.getName());
|
||||||
|
Assert.assertTrue("server side - wrong abilities", card.getAbilities(currentGame).size() > 0);
|
||||||
|
|
||||||
|
// client side - controller (hidden data + original name)
|
||||||
|
needName = "Foretell: Behold the Multiverse";
|
||||||
|
Assert.assertEquals("controller - wrong name", needName, controllerCardView.getName());
|
||||||
|
Assert.assertTrue("controller - must be face down", controllerCardView.isFaceDown());
|
||||||
|
Assert.assertEquals("controller - must not have abilities", 0, controllerCardView.getRules().size());
|
||||||
|
assertOriginalData("controller, original data", controllerCardView, 0, 0, "");
|
||||||
|
|
||||||
|
// client side - opponent (hidden data)
|
||||||
|
needName = "Foretell";
|
||||||
|
Assert.assertTrue("opponent - must be face down", opponentCardView.isFaceDown());
|
||||||
|
Assert.assertEquals("opponent - wrong name", needName, opponentCardView.getName());
|
||||||
|
Assert.assertEquals("opponent - must not have abilities", 0, opponentCardView.getRules().size());
|
||||||
|
assertOriginalData("opponent, original data", opponentCardView, 0, 0, "");
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ package org.mage.test.utils;
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
import mage.abilities.keyword.FlyingAbility;
|
import mage.abilities.keyword.FlyingAbility;
|
||||||
import mage.constants.CommanderCardType;
|
import mage.constants.CommanderCardType;
|
||||||
|
import mage.constants.EmptyNames;
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.util.GameLog;
|
import mage.util.GameLog;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
|
@ -29,6 +31,11 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
|
||||||
// * client side: inject additional elements for popup support (e.g. "a" with "href")
|
// * client side: inject additional elements for popup support (e.g. "a" with "href")
|
||||||
// * client side: process mouse move over a href and show object data like a card popup
|
// * client side: process mouse move over a href and show object data like a card popup
|
||||||
|
|
||||||
|
private Document parseHtmlLog(String originalLog) {
|
||||||
|
// replace log's face down info by real empty name (need for compatibility)
|
||||||
|
return Jsoup.parse(originalLog.replace(EmptyNames.EMPTY_NAME_IN_LOGS, ""));
|
||||||
|
}
|
||||||
|
|
||||||
private void assertObjectHtmlLog(String originalLog, String needVisibleColorPart, String needVisibleNormalPart, String needId) {
|
private void assertObjectHtmlLog(String originalLog, String needVisibleColorPart, String needVisibleNormalPart, String needId) {
|
||||||
String needVisibleFull = needVisibleColorPart;
|
String needVisibleFull = needVisibleColorPart;
|
||||||
if (!needVisibleNormalPart.isEmpty()) {
|
if (!needVisibleNormalPart.isEmpty()) {
|
||||||
|
|
@ -44,7 +51,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
|
||||||
Assert.assertTrue(mesPrefix + "can't find id" + mesPostfix, originalLog.contains(needId));
|
Assert.assertTrue(mesPrefix + "can't find id" + mesPostfix, originalLog.contains(needId));
|
||||||
|
|
||||||
// html check
|
// html check
|
||||||
Element html = Jsoup.parse(originalLog);
|
Element html = parseHtmlLog(originalLog);
|
||||||
Assert.assertEquals(mesPrefix + "can't find full text" + mesPostfix, needVisibleFull, html.text());
|
Assert.assertEquals(mesPrefix + "can't find full text" + mesPostfix, needVisibleFull, html.text());
|
||||||
Element htmlFont = html.getElementsByTag("font").stream().findFirst().orElse(null);
|
Element htmlFont = html.getElementsByTag("font").stream().findFirst().orElse(null);
|
||||||
Assert.assertNotNull(mesPrefix + "can't find tag [font]" + mesPostfix, htmlFont);
|
Assert.assertNotNull(mesPrefix + "can't find tag [font]" + mesPostfix, htmlFont);
|
||||||
|
|
@ -53,7 +60,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
|
||||||
|
|
||||||
// improved log from client (with href and popup support)
|
// improved log from client (with href and popup support)
|
||||||
String popupLog = GameLog.injectPopupSupport(originalLog);
|
String popupLog = GameLog.injectPopupSupport(originalLog);
|
||||||
html = Jsoup.parse(popupLog);
|
html = parseHtmlLog(popupLog);
|
||||||
Assert.assertEquals(mesPrefix + "injected, can't find full text" + mesPostfix, needVisibleFull, html.text());
|
Assert.assertEquals(mesPrefix + "injected, can't find full text" + mesPostfix, needVisibleFull, html.text());
|
||||||
// href
|
// href
|
||||||
Element htmlA = html.getElementsByTag("a").stream().findFirst().orElse(null);
|
Element htmlA = html.getElementsByTag("a").stream().findFirst().orElse(null);
|
||||||
|
|
@ -99,7 +106,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore // TODO: Fix test failure related to e264457
|
@Ignore
|
||||||
public void test_ObjectNamesInHtml() {
|
public void test_ObjectNamesInHtml() {
|
||||||
skipInitShuffling();
|
skipInitShuffling();
|
||||||
|
|
||||||
|
|
@ -127,7 +134,7 @@ public class CardHintsTest extends CardTestCommanderDuelBase {
|
||||||
.stream()
|
.stream()
|
||||||
.map(c -> currentGame.getObject(c))
|
.map(c -> currentGame.getObject(c))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
Assert.assertEquals(3 + 7 + 1, sampleObjects.size()); // defaul commander game already contains +1 commander
|
Assert.assertEquals(3 + 7 + 1, sampleObjects.size()); // default commander game already contains +1 commander
|
||||||
|
|
||||||
sampleObjects.forEach(this::assertObjectSupport);
|
sampleObjects.forEach(this::assertObjectSupport);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
|
|
||||||
|
|
||||||
package mage;
|
package mage;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface MageItem extends Serializable {
|
public interface MageItem extends Serializable {
|
||||||
|
|
||||||
UUID getId();
|
UUID getId();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,15 @@ public interface MageObject extends MageItem, Serializable, Copyable<MageObject>
|
||||||
|
|
||||||
void setImageNumber(Integer imageNumber);
|
void setImageNumber(Integer imageNumber);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get image file name
|
||||||
|
* - empty for default name from a card
|
||||||
|
* - non-empty for face down objects like Morph (GUI show empty name, but image must show some image)
|
||||||
|
*/
|
||||||
|
String getImageFileName();
|
||||||
|
|
||||||
|
void setImageFileName(String imageFile);
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ public abstract class MageObjectImpl implements MageObject {
|
||||||
|
|
||||||
private String expansionSetCode = "";
|
private String expansionSetCode = "";
|
||||||
private String cardNumber = "";
|
private String cardNumber = "";
|
||||||
|
private String imageFileName = "";
|
||||||
private int imageNumber = 0;
|
private int imageNumber = 0;
|
||||||
|
|
||||||
protected List<SuperType> supertype = new ArrayList<>();
|
protected List<SuperType> supertype = new ArrayList<>();
|
||||||
|
|
@ -77,6 +78,7 @@ public abstract class MageObjectImpl implements MageObject {
|
||||||
frameStyle = object.frameStyle;
|
frameStyle = object.frameStyle;
|
||||||
expansionSetCode = object.expansionSetCode;
|
expansionSetCode = object.expansionSetCode;
|
||||||
cardNumber = object.cardNumber;
|
cardNumber = object.cardNumber;
|
||||||
|
imageFileName = object.imageFileName;
|
||||||
imageNumber = object.imageNumber;
|
imageNumber = object.imageNumber;
|
||||||
power = object.power.copy();
|
power = object.power.copy();
|
||||||
toughness = object.toughness.copy();
|
toughness = object.toughness.copy();
|
||||||
|
|
@ -266,6 +268,16 @@ public abstract class MageObjectImpl implements MageObject {
|
||||||
this.cardNumber = cardNumber;
|
this.cardNumber = cardNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getImageFileName() {
|
||||||
|
return imageFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageFileName(String imageFileName) {
|
||||||
|
this.imageFileName = imageFileName;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getImageNumber() {
|
public Integer getImageNumber() {
|
||||||
return imageNumber;
|
return imageNumber;
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,6 @@ import java.util.UUID;
|
||||||
*/
|
*/
|
||||||
public interface Ability extends Controllable, Serializable {
|
public interface Ability extends Controllable, Serializable {
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the globally unique id of the ability contained within the game.
|
|
||||||
*
|
|
||||||
* @return A {@link java.util.UUID} which the game will use to store and
|
|
||||||
* retrieve the exact instance of this ability.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
UUID getId();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assigns a new {@link java.util.UUID}
|
* Assigns a new {@link java.util.UUID}
|
||||||
*
|
*
|
||||||
|
|
@ -71,14 +62,6 @@ public interface Ability extends Controllable, Serializable {
|
||||||
*/
|
*/
|
||||||
AbilityType getAbilityType();
|
AbilityType getAbilityType();
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the id of the player in control of this ability.
|
|
||||||
*
|
|
||||||
* @return The {@link java.util.UUID} of the controlling player.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
UUID getControllerId();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the id of the controller of this ability.
|
* Sets the id of the controller of this ability.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -428,6 +428,7 @@ public abstract class AbilityImpl implements Ability {
|
||||||
case MORE_THAN_MEETS_THE_EYE:
|
case MORE_THAN_MEETS_THE_EYE:
|
||||||
case BESTOW:
|
case BESTOW:
|
||||||
case MORPH:
|
case MORPH:
|
||||||
|
case MEGAMORPH:
|
||||||
// from Snapcaster Mage:
|
// from Snapcaster Mage:
|
||||||
// If you cast a spell from a graveyard using its flashback ability, you can't pay other alternative costs
|
// If you cast a spell from a graveyard using its flashback ability, you can't pay other alternative costs
|
||||||
// (such as that of Foil). (2018-12-07)
|
// (such as that of Foil). (2018-12-07)
|
||||||
|
|
@ -649,6 +650,11 @@ public abstract class AbilityImpl implements Ability {
|
||||||
return controllerId;
|
return controllerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getControllerOrOwnerId() {
|
||||||
|
return getControllerId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setControllerId(UUID controllerId) {
|
public void setControllerId(UUID controllerId) {
|
||||||
this.controllerId = controllerId;
|
this.controllerId = controllerId;
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
||||||
if (!approvingObjects.isEmpty()) {
|
if (!approvingObjects.isEmpty()) {
|
||||||
Card card = game.getCard(sourceId);
|
Card card = game.getCard(sourceId);
|
||||||
Zone zone = game.getState().getZone(sourceId);
|
Zone zone = game.getState().getZone(sourceId);
|
||||||
if(card != null && card.isOwnedBy(playerId) && Zone.HAND.match(zone)) {
|
if (card != null && card.isOwnedBy(playerId) && Zone.HAND.match(zone)) {
|
||||||
// Regular casting, to be an alternative to the AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE from hand (e.g. One with the Multiverse):
|
// Regular casting, to be an alternative to the AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE from hand (e.g. One with the Multiverse):
|
||||||
approvingObjects.add(new ApprovingObject(this, game));
|
approvingObjects.add(new ApprovingObject(this, game));
|
||||||
}
|
}
|
||||||
|
|
@ -160,8 +160,8 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
||||||
Player player = game.getPlayer(playerId);
|
Player player = game.getPlayer(playerId);
|
||||||
if (player != null
|
if (player != null
|
||||||
&& player.getCastSourceIdWithAlternateMana()
|
&& player.getCastSourceIdWithAlternateMana()
|
||||||
.getOrDefault(getSourceId(), Collections.emptySet())
|
.getOrDefault(getSourceId(), Collections.emptySet())
|
||||||
.contains(MageIdentifier.Default)
|
.contains(MageIdentifier.Default)
|
||||||
) {
|
) {
|
||||||
return ActivationStatus.getFalse();
|
return ActivationStatus.getFalse();
|
||||||
}
|
}
|
||||||
|
|
@ -181,11 +181,10 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
||||||
}
|
}
|
||||||
return ActivationStatus.getFalse();
|
return ActivationStatus.getFalse();
|
||||||
} else {
|
} else {
|
||||||
if(canChooseTarget(game, playerId)) {
|
if (canChooseTarget(game, playerId)) {
|
||||||
if(approvingObjects == null || approvingObjects.isEmpty()) {
|
if (approvingObjects == null || approvingObjects.isEmpty()) {
|
||||||
return ActivationStatus.withoutApprovingObject(true);
|
return ActivationStatus.withoutApprovingObject(true);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return new ActivationStatus(approvingObjects);
|
return new ActivationStatus(approvingObjects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -308,22 +307,27 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a card object with the spell characteristics like color, types,
|
* Returns combined card object with the spell characteristics like color, types,
|
||||||
* subtypes etc. E.g. if you cast a Bestow card as enchantment, the
|
* subtypes etc. E.g. if you cast a Bestow card as enchantment, the
|
||||||
* characteristics don't include the creature type.
|
* characteristics don't include the creature type.
|
||||||
|
* <p>
|
||||||
|
* Warning, it's not a real card - use it as a blueprint or characteristics searching
|
||||||
*
|
*
|
||||||
* @param game
|
|
||||||
* @return card object with the spell characteristics
|
* @return card object with the spell characteristics
|
||||||
*/
|
*/
|
||||||
public Card getCharacteristics(Game game) {
|
public Card getCharacteristics(Game game) {
|
||||||
Card spellCharacteristics = game.getSpell(this.getId());
|
Card spellCharacteristics = game.getSpell(this.getId());
|
||||||
if (spellCharacteristics == null) {
|
if (spellCharacteristics == null) {
|
||||||
|
// playable check (without put to stack)
|
||||||
spellCharacteristics = game.getCard(this.getSourceId());
|
spellCharacteristics = game.getCard(this.getSourceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spellCharacteristics != null) {
|
if (spellCharacteristics != null) {
|
||||||
if (getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) {
|
if (getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) {
|
||||||
spellCharacteristics = getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(spellCharacteristics, this);
|
// transform characteristics (morph, transform, bestow, etc)
|
||||||
|
spellCharacteristics = getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(spellCharacteristics, this, game);
|
||||||
}
|
}
|
||||||
|
spellCharacteristics = spellCharacteristics.copy();
|
||||||
}
|
}
|
||||||
return spellCharacteristics;
|
return spellCharacteristics;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl {
|
||||||
@Override
|
@Override
|
||||||
public void init(Ability source, Game game) {
|
public void init(Ability source, Game game) {
|
||||||
super.init(source, game);
|
super.init(source, game);
|
||||||
|
|
||||||
|
// save permanents to become face down (one time usage on resolve)
|
||||||
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
|
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
|
||||||
if (!perm.isFaceDown(game) && !perm.isTransformable()) {
|
if (!perm.isFaceDown(game) && !perm.isTransformable()) {
|
||||||
affectedObjectList.add(new MageObjectReference(perm, game));
|
affectedObjectList.add(new MageObjectReference(perm, game));
|
||||||
|
|
@ -66,6 +68,7 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl {
|
||||||
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
|
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
|
||||||
boolean targetExists = false;
|
boolean targetExists = false;
|
||||||
for (MageObjectReference mor : affectedObjectList) {
|
for (MageObjectReference mor : affectedObjectList) {
|
||||||
|
// TODO: wtf, why it not use a BecomesFaceDownCreatureEffect.makeFaceDownObject and applied by layers?! Looks buggy
|
||||||
Permanent permanent = mor.getPermanent(game);
|
Permanent permanent = mor.getPermanent(game);
|
||||||
if (permanent != null && permanent.isFaceDown(game)) {
|
if (permanent != null && permanent.isFaceDown(game)) {
|
||||||
targetExists = true;
|
targetExists = true;
|
||||||
|
|
@ -119,7 +122,6 @@ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl {
|
||||||
permanent.getPower().setModifiedBaseValue(2);
|
permanent.getPower().setModifiedBaseValue(2);
|
||||||
permanent.getToughness().setModifiedBaseValue(2);
|
permanent.getToughness().setModifiedBaseValue(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package mage.abilities.effects.common.continuous;
|
package mage.abilities.effects.common.continuous;
|
||||||
|
|
||||||
|
import mage.MageObject;
|
||||||
import mage.MageObjectReference;
|
import mage.MageObjectReference;
|
||||||
import mage.ObjectColor;
|
import mage.ObjectColor;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
|
|
@ -9,27 +10,42 @@ import mage.abilities.costs.Costs;
|
||||||
import mage.abilities.costs.CostsImpl;
|
import mage.abilities.costs.CostsImpl;
|
||||||
import mage.abilities.effects.ContinuousEffectImpl;
|
import mage.abilities.effects.ContinuousEffectImpl;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.repository.TokenInfo;
|
||||||
|
import mage.cards.repository.TokenRepository;
|
||||||
import mage.constants.*;
|
import mage.constants.*;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.game.permanent.token.EmptyToken;
|
||||||
|
import mage.game.permanent.token.Token;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This effect lets the card be a 2/2 face-down creature, with no text, no name,
|
* This effect lets the card be a 2/2 face-down creature, with no text, no name,
|
||||||
* no subtypes, and no mana cost, if it's face down on the battlefield. And it
|
* no subtypes, and no mana cost, if it's face down on the battlefield. And it
|
||||||
* adds the a TurnFaceUpAbility ability.
|
* adds the a TurnFaceUpAbility ability.
|
||||||
|
* <p>
|
||||||
|
* Warning, if a card has multiple face down abilities then keep only one face up cost
|
||||||
|
* Example: Mischievous Quanar
|
||||||
|
* - a. Turn Mischievous Quanar face down - BecomesFaceDownCreatureEffect without turn up cost
|
||||||
|
* - b. Morph - BecomesFaceDownCreatureEffect with turn up cost inside
|
||||||
*
|
*
|
||||||
* @author LevelX2
|
* @author LevelX2, JayDi85
|
||||||
*/
|
*/
|
||||||
public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
|
public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(BecomesFaceDownCreatureEffect.class);
|
||||||
|
|
||||||
public enum FaceDownType {
|
public enum FaceDownType {
|
||||||
MANIFESTED,
|
MANIFESTED,
|
||||||
MANUAL,
|
MANUAL,
|
||||||
MEGAMORPHED,
|
|
||||||
MORPHED,
|
MORPHED,
|
||||||
|
MEGAMORPHED,
|
||||||
DISGUISED,
|
DISGUISED,
|
||||||
CLOAKED
|
CLOAKED
|
||||||
}
|
}
|
||||||
|
|
@ -134,51 +150,137 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
|
||||||
throw new UnsupportedOperationException("FaceDownType not yet supported: " + faceDownType);
|
throw new UnsupportedOperationException("FaceDownType not yet supported: " + faceDownType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
makeFaceDownObject(game, source.getSourceId(), permanent, faceDownType, this.turnFaceUpAbility);
|
||||||
permanent.setName(EmptyNames.FACE_DOWN_CREATURE.toString());
|
|
||||||
permanent.removeAllSuperTypes(game);
|
|
||||||
permanent.removeAllCardTypes(game);
|
|
||||||
permanent.addCardType(game, CardType.CREATURE);
|
|
||||||
permanent.removeAllSubTypes(game);
|
|
||||||
permanent.getColor(game).setColor(ObjectColor.COLORLESS);
|
|
||||||
Card card = game.getCard(permanent.getId());
|
|
||||||
|
|
||||||
List<Ability> abilitiesToRemove = new ArrayList<>();
|
|
||||||
for (Ability ability : permanent.getAbilities()) {
|
|
||||||
|
|
||||||
// keep gained abilities from other sources, removes only own (card text)
|
|
||||||
if (card != null && !card.getAbilities().contains(ability)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 701.33c
|
|
||||||
// If a card with morph is manifested, its controller may turn that card face up using
|
|
||||||
// either the procedure described in rule 702.36e to turn a face-down permanent with morph face up
|
|
||||||
// or the procedure described above to turn a manifested permanent face up.
|
|
||||||
//
|
|
||||||
// so keep all tune face up abilities and other face down compatible
|
|
||||||
if (ability.getWorksFaceDown()) {
|
|
||||||
ability.setRuleVisible(false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) {
|
|
||||||
if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureEffect) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
abilitiesToRemove.add(ability);
|
|
||||||
}
|
|
||||||
permanent.removeAbilities(abilitiesToRemove, source.getSourceId(), game);
|
|
||||||
if (turnFaceUpAbility != null) { // TODO: shouldn't be added by this effect, but separately
|
|
||||||
permanent.addAbility(turnFaceUpAbility, source.getSourceId(), game);
|
|
||||||
}
|
|
||||||
permanent.getPower().setModifiedBaseValue(2);
|
|
||||||
permanent.getToughness().setModifiedBaseValue(2);
|
|
||||||
} else if (duration == Duration.Custom && foundPermanent) {
|
} else if (duration == Duration.Custom && foundPermanent) {
|
||||||
discard();
|
discard();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static FaceDownType findFaceDownType(Game game, Permanent permanent) {
|
||||||
|
if (permanent.isMorphed()) {
|
||||||
|
return BecomesFaceDownCreatureEffect.FaceDownType.MORPHED;
|
||||||
|
} else if (permanent.isManifested()) {
|
||||||
|
return BecomesFaceDownCreatureEffect.FaceDownType.MANIFESTED;
|
||||||
|
} else if (permanent.isFaceDown(game)) {
|
||||||
|
return BecomesFaceDownCreatureEffect.FaceDownType.MANUAL;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert any object (card, token) to face down (remove/hide all face up information and make it a 2/2 creature)
|
||||||
|
*/
|
||||||
|
public static void makeFaceDownObject(Game game, UUID sourceId, MageObject object, FaceDownType faceDownType, Ability turnFaceUpAbility) {
|
||||||
|
String originalObjectInfo = object.toString();
|
||||||
|
|
||||||
|
// warning, it's a direct changes to the object (without game state, so no game param here)
|
||||||
|
object.setName(EmptyNames.FACE_DOWN_CREATURE.toString());
|
||||||
|
object.removeAllSuperTypes();
|
||||||
|
object.getSubtype().clear();
|
||||||
|
object.removeAllCardTypes();
|
||||||
|
object.addCardType(CardType.CREATURE);
|
||||||
|
object.getColor().setColor(ObjectColor.COLORLESS);
|
||||||
|
|
||||||
|
// remove wrong abilities
|
||||||
|
Card card = game.getCard(object.getId());
|
||||||
|
List<Ability> abilitiesToRemove = new ArrayList<>();
|
||||||
|
for (Ability ability : object.getAbilities()) {
|
||||||
|
|
||||||
|
// keep gained abilities from other sources, removes only own (card text)
|
||||||
|
if (card != null && !card.getAbilities().contains(ability)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 701.33c
|
||||||
|
// If a card with morph is manifested, its controller may turn that card face up using
|
||||||
|
// either the procedure described in rule 702.36e to turn a face-down permanent with morph face up
|
||||||
|
// or the procedure described above to turn a manifested permanent face up.
|
||||||
|
//
|
||||||
|
// so keep all tune face up abilities and other face down compatible
|
||||||
|
if (ability.getWorksFaceDown()) {
|
||||||
|
// only face up abilities hidden by default (see below), so no needs in setRuleVisible
|
||||||
|
//ability.setRuleVisible(true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) {
|
||||||
|
if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureEffect) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
abilitiesToRemove.add(ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add face up abilities
|
||||||
|
// TODO: add here all possible face up like morph/disguis, manifest/cloak?
|
||||||
|
if (object instanceof Permanent) {
|
||||||
|
// as permanent
|
||||||
|
Permanent permanentObject = (Permanent) object;
|
||||||
|
permanentObject.removeAbilities(abilitiesToRemove, sourceId, game);
|
||||||
|
if (turnFaceUpAbility != null) {
|
||||||
|
Ability faceUp = turnFaceUpAbility.copy();
|
||||||
|
faceUp.setRuleVisible(true);
|
||||||
|
permanentObject.addAbility(faceUp, sourceId, game);
|
||||||
|
}
|
||||||
|
} else if (object instanceof CardImpl) {
|
||||||
|
// as card
|
||||||
|
CardImpl cardObject = (CardImpl) object;
|
||||||
|
cardObject.getAbilities().removeAll(abilitiesToRemove);
|
||||||
|
if (turnFaceUpAbility != null) {
|
||||||
|
Ability faceUp = turnFaceUpAbility.copy();
|
||||||
|
faceUp.setRuleVisible(true);
|
||||||
|
cardObject.addAbility(faceUp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object.getPower().setModifiedBaseValue(2);
|
||||||
|
object.getToughness().setModifiedBaseValue(2);
|
||||||
|
|
||||||
|
// image
|
||||||
|
String tokenName;
|
||||||
|
switch (faceDownType) {
|
||||||
|
case MORPHED:
|
||||||
|
tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH;
|
||||||
|
break;
|
||||||
|
case MEGAMORPHED:
|
||||||
|
tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MEGAMORPH;
|
||||||
|
break;
|
||||||
|
case MANIFESTED:
|
||||||
|
tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST;
|
||||||
|
break;
|
||||||
|
case CLOAKED:
|
||||||
|
tokenName = "TODO-CLOAKED";
|
||||||
|
break;
|
||||||
|
case DISGUISED:
|
||||||
|
tokenName = "TODO-DISGUISED";
|
||||||
|
break;
|
||||||
|
case MANUAL:
|
||||||
|
tokenName = TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Un-supported face down type for image: " + faceDownType);
|
||||||
|
}
|
||||||
|
|
||||||
|
Token faceDownToken = new EmptyToken();
|
||||||
|
TokenInfo faceDownInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(tokenName, object.getId());
|
||||||
|
if (faceDownInfo != null) {
|
||||||
|
faceDownToken.setExpansionSetCode(faceDownInfo.getSetCode());
|
||||||
|
faceDownToken.setCardNumber("0");
|
||||||
|
faceDownToken.setImageFileName(faceDownInfo.getName());
|
||||||
|
faceDownToken.setImageNumber(faceDownInfo.getImageNumber());
|
||||||
|
} else {
|
||||||
|
logger.error("Can't find face down image for " + tokenName + ": " + originalObjectInfo);
|
||||||
|
// TODO: add default image like backface (warning, missing image info must be visible in card popup)?
|
||||||
|
}
|
||||||
|
|
||||||
|
CardUtil.copySetAndCardNumber(object, faceDownToken);
|
||||||
|
|
||||||
|
// hide rarity info
|
||||||
|
if (object instanceof Card) {
|
||||||
|
((Card) object).setRarity(Rarity.SPECIAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ public class BestowAbility extends SpellAbility {
|
||||||
public static void becomeCreature(Permanent permanent, Game game) {
|
public static void becomeCreature(Permanent permanent, Game game) {
|
||||||
// permanently changes to the object
|
// permanently changes to the object
|
||||||
if (permanent != null) {
|
if (permanent != null) {
|
||||||
MageObject basicObject = permanent.getBasicMageObject(game);
|
MageObject basicObject = permanent.getBasicMageObject();
|
||||||
if (basicObject != null) {
|
if (basicObject != null) {
|
||||||
game.checkStateAndTriggered(); // Bug #8157
|
game.checkStateAndTriggered(); // Bug #8157
|
||||||
basicObject.getSubtype().remove(SubType.AURA);
|
basicObject.getSubtype().remove(SubType.AURA);
|
||||||
|
|
@ -164,7 +164,7 @@ class BestowEntersBattlefieldEffect extends ReplacementEffectImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
// change types permanently
|
// change types permanently
|
||||||
MageObject basicObject = bestowPermanent.getBasicMageObject(game);
|
MageObject basicObject = bestowPermanent.getBasicMageObject();
|
||||||
if (basicObject != null && !basicObject.getSubtype().contains(SubType.AURA)) {
|
if (basicObject != null && !basicObject.getSubtype().contains(SubType.AURA)) {
|
||||||
basicObject.addSubType(SubType.AURA);
|
basicObject.addSubType(SubType.AURA);
|
||||||
basicObject.removeCardType(CardType.CREATURE);
|
basicObject.removeCardType(CardType.CREATURE);
|
||||||
|
|
|
||||||
|
|
@ -463,4 +463,9 @@ public class ForetellAbility extends SpecialAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isCardInForetell(Card card, Game game) {
|
||||||
|
// searching ForetellCostAbility - it adds for foretelled cards only after exile
|
||||||
|
return card.getAbilities(game).containsClass(ForetellCostAbility.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import mage.MageObject;
|
|
||||||
import mage.ObjectColor;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.SpellAbility;
|
import mage.abilities.SpellAbility;
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
|
@ -13,18 +11,17 @@ import mage.abilities.costs.mana.ManaCost;
|
||||||
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
||||||
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType;
|
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.constants.*;
|
import mage.constants.SpellAbilityCastMode;
|
||||||
import mage.game.Game;
|
import mage.constants.SpellAbilityType;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.constants.TimingRule;
|
||||||
import mage.game.permanent.token.EmptyToken;
|
|
||||||
import mage.game.permanent.token.Token;
|
|
||||||
import mage.util.CardUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Morph and Megamorph
|
||||||
|
* <p>
|
||||||
* 702.36. Morph
|
* 702.36. Morph
|
||||||
* <p>
|
* <p>
|
||||||
* 702.36a Morph is a static ability that functions in any zone from which you
|
* 702.36a Morph is a static ability that functions in any zone from which you
|
||||||
* could play the card its on, and the morph effect works any time the card is
|
* could play the card it's on, and the morph effect works any time the card is
|
||||||
* face down. "Morph [cost]" means "You may cast this card as a 2/2 face-down
|
* face down. "Morph [cost]" means "You may cast this card as a 2/2 face-down
|
||||||
* creature, with no text, no name, no subtypes, and no mana cost by paying {3}
|
* creature, with no text, no name, no subtypes, and no mana cost by paying {3}
|
||||||
* rather than paying its mana cost." (See rule 707, "Face-Down Spells and
|
* rather than paying its mana cost." (See rule 707, "Face-Down Spells and
|
||||||
|
|
@ -33,7 +30,7 @@ import mage.util.CardUtil;
|
||||||
* 702.36b To cast a card using its morph ability, turn it face down. It becomes
|
* 702.36b To cast a card using its morph ability, turn it face down. It becomes
|
||||||
* a 2/2 face-down creature card, with no text, no name, no subtypes, and no
|
* a 2/2 face-down creature card, with no text, no name, no subtypes, and no
|
||||||
* mana cost. Any effects or prohibitions that would apply to casting a card
|
* mana cost. Any effects or prohibitions that would apply to casting a card
|
||||||
* with these characteristics (and not the face-up cards characteristics) are
|
* with these characteristics (and not the face-up card's characteristics) are
|
||||||
* applied to casting this card. These values are the copiable values of that
|
* applied to casting this card. These values are the copiable values of that
|
||||||
* object's characteristics. (See rule 613, "Interaction of Continuous Effects,"
|
* object's characteristics. (See rule 613, "Interaction of Continuous Effects,"
|
||||||
* and rule 706, "Copying Objects.") Put it onto the stack (as a face-down spell
|
* and rule 706, "Copying Objects.") Put it onto the stack (as a face-down spell
|
||||||
|
|
@ -48,54 +45,54 @@ import mage.util.CardUtil;
|
||||||
* <p>
|
* <p>
|
||||||
* 702.36d If you have priority, you may turn a face-down permanent you control
|
* 702.36d If you have priority, you may turn a face-down permanent you control
|
||||||
* face up. This is a special action; it doesn't use the stack (see rule 115).
|
* face up. This is a special action; it doesn't use the stack (see rule 115).
|
||||||
* To do this, show all players what the permanents morph cost would be if it
|
* To do this, show all players what the permanent's morph cost would be if it
|
||||||
* were face up, pay that cost, then turn the permanent face up. (If the
|
* were face up, pay that cost, then turn the permanent face up. (If the
|
||||||
* permanent wouldn't have a morph cost if it were face up, it cant be turned
|
* permanent wouldn't have a morph cost if it were face up, it can't be turned
|
||||||
* face up this way.) The morph effect on it ends, and it regains its normal
|
* face up this way.) The morph effect on it ends, and it regains its normal
|
||||||
* characteristics. Any abilities relating to the permanent entering the
|
* characteristics. Any abilities relating to the permanent entering the
|
||||||
* battlefield dont trigger when its turned face up and dont have any effect,
|
* battlefield don't trigger when it's turned face up and don't have any effect,
|
||||||
* because the permanent has already entered the battlefield.
|
* because the permanent has already entered the battlefield.
|
||||||
* <p>
|
* <p>
|
||||||
* 702.36e See rule 707, "Face-Down Spells and Permanents," for more information
|
* 702.36e See rule 707, "Face-Down Spells and Permanents," for more information
|
||||||
* on how to cast cards with morph.
|
* on how to cast cards with morph.
|
||||||
*
|
*
|
||||||
* @author LevelX2
|
* @author LevelX2, JayDi85
|
||||||
*/
|
*/
|
||||||
public class MorphAbility extends SpellAbility {
|
public class MorphAbility extends SpellAbility {
|
||||||
|
|
||||||
protected static final String ABILITY_KEYWORD = "Morph";
|
protected static final String ABILITY_KEYWORD = "Morph";
|
||||||
protected static final String ABILITY_KEYWORD_MEGA = "Megamorph";
|
|
||||||
protected static final String REMINDER_TEXT = "You may cast this card face down as a "
|
protected static final String REMINDER_TEXT = "You may cast this card face down as a "
|
||||||
+ "2/2 creature for {3}. Turn it face up any time for its morph cost.";
|
+ "2/2 creature for {3}. Turn it face up any time for its morph cost.";
|
||||||
|
|
||||||
|
protected static final String ABILITY_KEYWORD_MEGA = "Megamorph";
|
||||||
protected static final String REMINDER_TEXT_MEGA = "You may cast this card face down "
|
protected static final String REMINDER_TEXT_MEGA = "You may cast this card face down "
|
||||||
+ "as a 2/2 creature for {3}. Turn it face up any time for its megamorph "
|
+ "as a 2/2 creature for {3}. Turn it face up any time for its megamorph "
|
||||||
+ "cost and put a +1/+1 counter on it.";
|
+ "cost and put a +1/+1 counter on it.";
|
||||||
protected Costs<Cost> morphCosts;
|
protected Costs<Cost> morphCosts;
|
||||||
// needed to check activation status, if card changes zone after casting it
|
|
||||||
private final boolean megamorph;
|
|
||||||
|
|
||||||
public MorphAbility(Card card, Cost morphCost) {
|
public MorphAbility(Card card, Cost morphCost) {
|
||||||
this(card, morphCost, false);
|
this(card, morphCost, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MorphAbility(Card card, Cost morphCost, boolean megamorph) {
|
public MorphAbility(Card card, Cost morphCost, boolean useMegamorph) {
|
||||||
super(new GenericManaCost(3), card.getName());
|
super(new GenericManaCost(3), card.getName());
|
||||||
|
this.timing = TimingRule.SORCERY;
|
||||||
this.morphCosts = new CostsImpl<>();
|
this.morphCosts = new CostsImpl<>();
|
||||||
this.morphCosts.add(morphCost);
|
this.morphCosts.add(morphCost);
|
||||||
this.megamorph = megamorph;
|
this.setSpellAbilityCastMode(useMegamorph ? SpellAbilityCastMode.MEGAMORPH : SpellAbilityCastMode.MORPH);
|
||||||
this.setSpellAbilityCastMode(SpellAbilityCastMode.MORPH);
|
|
||||||
this.setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE);
|
this.setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE);
|
||||||
|
|
||||||
|
// face down effect (hidden by default, visible in face down objects)
|
||||||
Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect(
|
Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect(
|
||||||
morphCosts, (megamorph ? FaceDownType.MEGAMORPHED : FaceDownType.MORPHED)));
|
morphCosts, (useMegamorph ? FaceDownType.MEGAMORPHED : FaceDownType.MORPHED)));
|
||||||
ability.setWorksFaceDown(true);
|
ability.setWorksFaceDown(true);
|
||||||
ability.setRuleVisible(false);
|
ability.setRuleVisible(false);
|
||||||
this.timing = TimingRule.SORCERY;
|
|
||||||
addSubAbility(ability);
|
addSubAbility(ability);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MorphAbility(final MorphAbility ability) {
|
protected MorphAbility(final MorphAbility ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.morphCosts = ability.morphCosts; // can't be changed
|
this.morphCosts = ability.morphCosts; // can't be changed
|
||||||
this.megamorph = ability.megamorph;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -110,55 +107,20 @@ public class MorphAbility extends SpellAbility {
|
||||||
@Override
|
@Override
|
||||||
public String getRule() {
|
public String getRule() {
|
||||||
boolean isMana = morphCosts.get(0) instanceof ManaCost;
|
boolean isMana = morphCosts.get(0) instanceof ManaCost;
|
||||||
String name = megamorph ? ABILITY_KEYWORD_MEGA : ABILITY_KEYWORD;
|
String text;
|
||||||
String reminder = " <i>(" + (megamorph ? REMINDER_TEXT_MEGA : REMINDER_TEXT) + ")</i>";
|
String reminder;
|
||||||
return name + (isMana ? " " : "—") +
|
switch (this.getSpellAbilityCastMode()) {
|
||||||
morphCosts.getText() + (isMana ? ' ' : ". ") + reminder;
|
case MORPH:
|
||||||
}
|
text = ABILITY_KEYWORD;
|
||||||
|
reminder = REMINDER_TEXT;
|
||||||
/**
|
break;
|
||||||
* Hide all info and make it a 2/2 creature
|
case MEGAMORPH:
|
||||||
*
|
text = ABILITY_KEYWORD_MEGA;
|
||||||
* @param targetObject
|
reminder = REMINDER_TEXT_MEGA;
|
||||||
* @param sourcePermanent source of the face down status
|
break;
|
||||||
* @param game
|
default:
|
||||||
*/
|
throw new IllegalArgumentException("Un-supported spell ability cast mode for morph: " + this.getSpellAbilityCastMode());
|
||||||
public static void setPermanentToFaceDownCreature(MageObject targetObject, Permanent sourcePermanent, Game game) {
|
|
||||||
targetObject.getPower().setModifiedBaseValue(2);
|
|
||||||
targetObject.getToughness().setModifiedBaseValue(2);
|
|
||||||
targetObject.getAbilities().clear();
|
|
||||||
targetObject.getColor(game).setColor(new ObjectColor());
|
|
||||||
targetObject.setName("");
|
|
||||||
targetObject.removeAllCardTypes(game);
|
|
||||||
targetObject.addCardType(game, CardType.CREATURE);
|
|
||||||
targetObject.removeAllSubTypes(game);
|
|
||||||
targetObject.removeAllSuperTypes(game);
|
|
||||||
targetObject.getManaCost().clear();
|
|
||||||
|
|
||||||
Token emptyImage = new EmptyToken();
|
|
||||||
|
|
||||||
// TODO: add morph image here?
|
|
||||||
if (targetObject instanceof Permanent) {
|
|
||||||
// hide image info
|
|
||||||
CardUtil.copySetAndCardNumber(targetObject, emptyImage);
|
|
||||||
// hide rarity info
|
|
||||||
((Permanent) targetObject).setRarity(Rarity.SPECIAL);
|
|
||||||
} else if (targetObject instanceof Token) {
|
|
||||||
CardUtil.copySetAndCardNumber(targetObject, emptyImage);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Wrong code usage: un-supported targetObject in face down method: " + targetObject.getClass().getSimpleName());
|
|
||||||
}
|
}
|
||||||
}
|
return text + (isMana ? " " : "—") + morphCosts.getText() + (isMana ? ' ' : ". ") + " <i>(" + reminder + ")</i>";
|
||||||
public static void setCardToFaceDownCreature(Card targetCard) {
|
|
||||||
targetCard.getPower().setModifiedBaseValue(2);
|
|
||||||
targetCard.getToughness().setModifiedBaseValue(2);
|
|
||||||
targetCard.getAbilities().clear();
|
|
||||||
targetCard.getColor().setColor(new ObjectColor());
|
|
||||||
targetCard.setName("");
|
|
||||||
targetCard.removeAllCardTypes();
|
|
||||||
targetCard.addCardType(CardType.CREATURE);
|
|
||||||
targetCard.getSubtype().clear();
|
|
||||||
targetCard.removeAllSuperTypes();
|
|
||||||
targetCard.getManaCost().clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,27 +80,34 @@ public class ProtectionAbility extends StaticAbility {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canTarget(MageObject source, Game game) {
|
public boolean canTarget(MageObject source, Game game) {
|
||||||
|
// TODO: need research, protection ability can be bugged with aura and aura permanents, spells (see below)
|
||||||
|
|
||||||
|
// permanent restriction
|
||||||
if (filter instanceof FilterPermanent) {
|
if (filter instanceof FilterPermanent) {
|
||||||
if (source instanceof Permanent) {
|
if (source instanceof Permanent) {
|
||||||
return !filter.match(source, game);
|
return !((FilterPermanent) filter).match((Permanent) source, game);
|
||||||
}
|
}
|
||||||
|
// TODO: possible bugged, need token too?
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// card restriction
|
||||||
if (filter instanceof FilterCard) {
|
if (filter instanceof FilterCard) {
|
||||||
if (source instanceof Permanent) {
|
if (source instanceof Card) {
|
||||||
return !((FilterCard) filter).match((Card) source, ((Permanent) source).getControllerId(), this, game);
|
return !((FilterCard) filter).match((Card) source, ((Card) source).getControllerOrOwnerId(), this, game);
|
||||||
} else if (source instanceof Card) {
|
|
||||||
return !((FilterCard) filter).match((Card) source, ((Card) source).getOwnerId(), this, game);
|
|
||||||
} else if (source instanceof Token) {
|
} else if (source instanceof Token) {
|
||||||
// Fake a permanent with the Token info.
|
// make fake permanent cause it checked before real permanent create
|
||||||
PermanentToken token = new PermanentToken((Token) source, null, game);
|
// warning, Token don't have controllerId info, so it can be a problem here
|
||||||
return !((FilterCard) filter).match((Card) token, game);
|
// TODO: wtf, possible bugged for filters that checking controller/player (if so then use with controllerId param)
|
||||||
|
PermanentToken fakePermanent = new PermanentToken((Token) source, UUID.randomUUID(), game);
|
||||||
|
return !((FilterCard) filter).match(fakePermanent, game);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// spell restriction
|
||||||
if (filter instanceof FilterSpell) {
|
if (filter instanceof FilterSpell) {
|
||||||
|
// TODO: need research, possible bugged
|
||||||
// Problem here is that for the check if a player can play a Spell, the source
|
// Problem here is that for the check if a player can play a Spell, the source
|
||||||
// object is still a card and not a spell yet.
|
// object is still a card and not a spell yet.
|
||||||
if (source instanceof Spell || game.inCheckPlayableState() && source.isInstantOrSorcery(game)) {
|
if (source instanceof Spell || game.inCheckPlayableState() && source.isInstantOrSorcery(game)) {
|
||||||
|
|
@ -109,16 +116,20 @@ public class ProtectionAbility extends StaticAbility {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unknown restriction
|
||||||
if (filter instanceof FilterObject) {
|
if (filter instanceof FilterObject) {
|
||||||
return !filter.match(source, game);
|
return !((FilterObject) filter).match(source, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// player restriction
|
||||||
if (filter instanceof FilterPlayer) {
|
if (filter instanceof FilterPlayer) {
|
||||||
Player player = null;
|
Player player = null;
|
||||||
if (source instanceof Permanent) {
|
if (source instanceof Card) {
|
||||||
player = game.getPlayer(((Permanent) source).getControllerId());
|
player = game.getPlayer(((Card) source).getControllerOrOwnerId());
|
||||||
} else if (source instanceof Card) {
|
} else if (source instanceof Token) {
|
||||||
player = game.getPlayer(((Card) source).getOwnerId());
|
// TODO: fakePermanent will not work here like above, so try to rework whole logic
|
||||||
|
throw new IllegalArgumentException("Wrong code usage: token can't be checked in player restriction filter");
|
||||||
|
|
||||||
}
|
}
|
||||||
return !((FilterPlayer) filter).match(player, this.getControllerId(), this, game);
|
return !((FilterPlayer) filter).match(player, this.getControllerId(), this, game);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import mage.counters.Counters;
|
||||||
import mage.filter.FilterMana;
|
import mage.filter.FilterMana;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.GameState;
|
import mage.game.GameState;
|
||||||
|
import mage.game.Ownerable;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.util.ManaUtil;
|
import mage.util.ManaUtil;
|
||||||
import mage.watchers.common.CommanderPlaysCountWatcher;
|
import mage.watchers.common.CommanderPlaysCountWatcher;
|
||||||
|
|
@ -21,12 +22,12 @@ import mage.watchers.common.CommanderPlaysCountWatcher;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public interface Card extends MageObject {
|
public interface Card extends MageObject, Ownerable {
|
||||||
|
|
||||||
UUID getOwnerId();
|
|
||||||
|
|
||||||
Rarity getRarity(); // null for tokens
|
Rarity getRarity(); // null for tokens
|
||||||
|
|
||||||
|
void setRarity(Rarity rarity);
|
||||||
|
|
||||||
void setOwnerId(UUID ownerId);
|
void setOwnerId(UUID ownerId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,7 +47,11 @@ public interface Card extends MageObject {
|
||||||
|
|
||||||
List<String> getRules(Game game); // gets card rules + in game modifications
|
List<String> getRules(Game game); // gets card rules + in game modifications
|
||||||
|
|
||||||
void checkForCountersToAdd(Permanent permanent, Ability source, Game game);
|
/**
|
||||||
|
* Find ETB counters and apply it to permanent.
|
||||||
|
* Warning, it's one time action, use it before a put to battlefield only.
|
||||||
|
*/
|
||||||
|
void applyEnterWithCounters(Permanent permanent, Ability source, Game game);
|
||||||
|
|
||||||
void setFaceDown(boolean value, Game game);
|
void setFaceDown(boolean value, Game game);
|
||||||
|
|
||||||
|
|
@ -143,10 +148,12 @@ public interface Card extends MageObject {
|
||||||
List<Mana> getMana();
|
List<Mana> getMana();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if there exists various art images for this card
|
* Set contains multiple cards with same card name but different images. Used for image path generation.
|
||||||
*/
|
*/
|
||||||
boolean getUsesVariousArt();
|
boolean getUsesVariousArt();
|
||||||
|
|
||||||
|
void setUsesVariousArt(boolean usesVariousArt);
|
||||||
|
|
||||||
Counters getCounters(Game game);
|
Counters getCounters(Game game);
|
||||||
|
|
||||||
Counters getCounters(GameState state);
|
Counters getCounters(GameState state);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import mage.abilities.keyword.FlashbackAbility;
|
||||||
import mage.abilities.keyword.ReconfigureAbility;
|
import mage.abilities.keyword.ReconfigureAbility;
|
||||||
import mage.abilities.keyword.SunburstAbility;
|
import mage.abilities.keyword.SunburstAbility;
|
||||||
import mage.abilities.mana.ActivatedManaAbilityImpl;
|
import mage.abilities.mana.ActivatedManaAbilityImpl;
|
||||||
|
import mage.cards.mock.MockableCard;
|
||||||
import mage.cards.repository.PluginClassloaderRegistery;
|
import mage.cards.repository.PluginClassloaderRegistery;
|
||||||
import mage.constants.*;
|
import mage.constants.*;
|
||||||
import mage.counters.Counter;
|
import mage.counters.Counter;
|
||||||
|
|
@ -58,12 +59,15 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
protected CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs) {
|
protected CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs) {
|
||||||
this(ownerId, setInfo, cardTypes, costs, SpellAbilityType.BASE);
|
this(ownerId, setInfo, cardTypes, costs, SpellAbilityType.BASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs, SpellAbilityType spellAbilityType) {
|
protected CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs, SpellAbilityType spellAbilityType) {
|
||||||
this(ownerId, setInfo.getName());
|
this(ownerId, setInfo.getName());
|
||||||
|
|
||||||
this.rarity = setInfo.getRarity();
|
this.rarity = setInfo.getRarity();
|
||||||
this.setExpansionSetCode(setInfo.getExpansionSetCode());
|
this.setExpansionSetCode(setInfo.getExpansionSetCode());
|
||||||
this.setCardNumber(setInfo.getCardNumber());
|
this.setCardNumber(setInfo.getCardNumber());
|
||||||
|
this.setImageFileName(""); // use default
|
||||||
|
this.setImageNumber(0);
|
||||||
this.cardType.addAll(Arrays.asList(cardTypes));
|
this.cardType.addAll(Arrays.asList(cardTypes));
|
||||||
this.manaCost.load(costs);
|
this.manaCost.load(costs);
|
||||||
setDefaultColor();
|
setDefaultColor();
|
||||||
|
|
@ -119,12 +123,24 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
ownerId = card.ownerId;
|
ownerId = card.ownerId;
|
||||||
rarity = card.rarity;
|
rarity = card.rarity;
|
||||||
|
|
||||||
|
// TODO: wtf, do not copy card sides cause it must be re-created each time (see details in getSecondCardFace)
|
||||||
|
// must be reworked to normal copy and workable transform without such magic
|
||||||
|
|
||||||
|
nightCard = card.nightCard;
|
||||||
secondSideCardClazz = card.secondSideCardClazz;
|
secondSideCardClazz = card.secondSideCardClazz;
|
||||||
secondSideCard = null; // will be set on first getSecondCardFace call if card has one
|
secondSideCard = null; // will be set on first getSecondCardFace call if card has one
|
||||||
nightCard = card.nightCard;
|
if (card.secondSideCard instanceof MockableCard) {
|
||||||
|
// workaround to support gui's mock cards
|
||||||
|
secondSideCard = card.secondSideCard.copy();
|
||||||
|
}
|
||||||
|
|
||||||
meldsWithClazz = card.meldsWithClazz;
|
meldsWithClazz = card.meldsWithClazz;
|
||||||
meldsToClazz = card.meldsToClazz;
|
meldsToClazz = card.meldsToClazz;
|
||||||
meldsToCard = null; // will be set on first getMeldsToCard call if card has one
|
meldsToCard = null; // will be set on first getMeldsToCard call if card has one
|
||||||
|
if (card.meldsToCard instanceof MockableCard) {
|
||||||
|
// workaround to support gui's mock cards
|
||||||
|
meldsToCard = card.meldsToCard.copy();
|
||||||
|
}
|
||||||
|
|
||||||
spellAbility = null; // will be set on first getSpellAbility call if card has one
|
spellAbility = null; // will be set on first getSpellAbility call if card has one
|
||||||
flipCard = card.flipCard;
|
flipCard = card.flipCard;
|
||||||
|
|
@ -203,6 +219,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
return rarity;
|
return rarity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRarity(Rarity rarity) {
|
||||||
|
this.rarity = rarity;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInfo(String key, String value, Game game) {
|
public void addInfo(String key, String value, Game game) {
|
||||||
game.getState().getCardState(objectId).addInfo(key, value);
|
game.getState().getCardState(objectId).addInfo(key, value);
|
||||||
|
|
@ -360,7 +381,12 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
@Override
|
@Override
|
||||||
public void setOwnerId(UUID ownerId) {
|
public void setOwnerId(UUID ownerId) {
|
||||||
this.ownerId = ownerId;
|
this.ownerId = ownerId;
|
||||||
abilities.setControllerId(ownerId);
|
this.abilities.setControllerId(ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getControllerOrOwnerId() {
|
||||||
|
return getOwnerId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -549,7 +575,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkForCountersToAdd(Permanent permanent, Ability source, Game game) {
|
public void applyEnterWithCounters(Permanent permanent, Ability source, Game game) {
|
||||||
Counters countersToAdd = game.getEnterWithCounters(permanent.getId());
|
Counters countersToAdd = game.getEnterWithCounters(permanent.getId());
|
||||||
if (countersToAdd != null) {
|
if (countersToAdd != null) {
|
||||||
for (Counter counter : countersToAdd.values()) {
|
for (Counter counter : countersToAdd.values()) {
|
||||||
|
|
@ -620,6 +646,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
if (secondSideCard == null) {
|
if (secondSideCard == null) {
|
||||||
secondSideCard = initSecondSideCard(secondSideCardClazz);
|
secondSideCard = initSecondSideCard(secondSideCardClazz);
|
||||||
if (secondSideCard != null && secondSideCard.getSpellAbility() != null) {
|
if (secondSideCard != null && secondSideCard.getSpellAbility() != null) {
|
||||||
|
// TODO: wtf, why it set cast mode here?! Transform tests fails without it
|
||||||
|
// must be reworked without that magic, also see CardImpl'constructor for copy code
|
||||||
secondSideCard.getSpellAbility().setSourceId(this.getId());
|
secondSideCard.getSpellAbility().setSourceId(this.getId());
|
||||||
secondSideCard.getSpellAbility().setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE);
|
secondSideCard.getSpellAbility().setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE);
|
||||||
secondSideCard.getSpellAbility().setSpellAbilityCastMode(SpellAbilityCastMode.TRANSFORMED);
|
secondSideCard.getSpellAbility().setSpellAbilityCastMode(SpellAbilityCastMode.TRANSFORMED);
|
||||||
|
|
@ -693,6 +721,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
return usesVariousArt;
|
return usesVariousArt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUsesVariousArt(boolean usesVariousArt) {
|
||||||
|
this.usesVariousArt = usesVariousArt;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Counters getCounters(Game game) {
|
public Counters getCounters(Game game) {
|
||||||
return getCounters(game.getState());
|
return getCounters(game.getState());
|
||||||
|
|
@ -703,13 +736,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
return state.getCardState(this.objectId).getCounters();
|
return state.getCardState(this.objectId).getCounters();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The controller if available otherwise the owner.
|
|
||||||
*/
|
|
||||||
protected UUID getControllerOrOwner() {
|
|
||||||
return ownerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addCounters(Counter counter, Ability source, Game game) {
|
public boolean addCounters(Counter counter, Ability source, Game game) {
|
||||||
return addCounters(counter, source.getControllerId(), source, game);
|
return addCounters(counter, source.getControllerId(), source, game);
|
||||||
|
|
@ -787,7 +813,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
if (!getCounters(game).removeCounter(name, 1)) {
|
if (!getCounters(game).removeCounter(name, 1)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, source, getControllerOrOwner());
|
GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, source, getControllerOrOwnerId());
|
||||||
if (source != null
|
if (source != null
|
||||||
&& source.getControllerId() != null) {
|
&& source.getControllerId() != null) {
|
||||||
event.setPlayerId(source.getControllerId()); // player who controls the source ability that removed the counter
|
event.setPlayerId(source.getControllerId()); // player who controls the source ability that removed the counter
|
||||||
|
|
@ -796,7 +822,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||||
game.fireEvent(event);
|
game.fireEvent(event);
|
||||||
finalAmount++;
|
finalAmount++;
|
||||||
}
|
}
|
||||||
GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTERS_REMOVED, objectId, source, getControllerOrOwner());
|
GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTERS_REMOVED, objectId, source, getControllerOrOwnerId());
|
||||||
if (source != null
|
if (source != null
|
||||||
&& source.getControllerId() != null) {
|
&& source.getControllerId() != null) {
|
||||||
event.setPlayerId(source.getControllerId()); // player who controls the source ability that removed the counter
|
event.setPlayerId(source.getControllerId()); // player who controls the source ability that removed the counter
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import java.util.List;
|
||||||
*
|
*
|
||||||
* @author North
|
* @author North
|
||||||
*/
|
*/
|
||||||
public class MockCard extends CardImpl {
|
public class MockCard extends CardImpl implements MockableCard {
|
||||||
|
|
||||||
static public String ADVENTURE_NAME_SEPARATOR = " // ";
|
static public String ADVENTURE_NAME_SEPARATOR = " // ";
|
||||||
static public String MODAL_DOUBLE_FACES_NAME_SEPARATOR = " // ";
|
static public String MODAL_DOUBLE_FACES_NAME_SEPARATOR = " // ";
|
||||||
|
|
@ -42,6 +42,8 @@ public class MockCard extends CardImpl {
|
||||||
super(null, card.getName());
|
super(null, card.getName());
|
||||||
this.setExpansionSetCode(card.getSetCode());
|
this.setExpansionSetCode(card.getSetCode());
|
||||||
this.setCardNumber(card.getCardNumber());
|
this.setCardNumber(card.getCardNumber());
|
||||||
|
this.setImageFileName(""); // use default
|
||||||
|
this.setImageNumber(0);
|
||||||
this.power = mageIntFromString(card.getPower());
|
this.power = mageIntFromString(card.getPower());
|
||||||
this.toughness = mageIntFromString(card.getToughness());
|
this.toughness = mageIntFromString(card.getToughness());
|
||||||
this.rarity = card.getRarity();
|
this.rarity = card.getRarity();
|
||||||
|
|
@ -157,7 +159,7 @@ public class MockCard extends CardImpl {
|
||||||
if (adventureSpellName != null) {
|
if (adventureSpellName != null) {
|
||||||
return getName() + ADVENTURE_NAME_SEPARATOR + adventureSpellName;
|
return getName() + ADVENTURE_NAME_SEPARATOR + adventureSpellName;
|
||||||
} else if (isModalDoubleFacedCard) {
|
} else if (isModalDoubleFacedCard) {
|
||||||
return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.secondSideCard.getName();
|
return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.getSecondCardFace().getName();
|
||||||
} else {
|
} else {
|
||||||
return getName();
|
return getName();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* @author North
|
* @author North
|
||||||
*/
|
*/
|
||||||
public class MockSplitCard extends SplitCard {
|
public class MockSplitCard extends SplitCard implements MockableCard {
|
||||||
|
|
||||||
public MockSplitCard(CardInfo card) {
|
public MockSplitCard(CardInfo card) {
|
||||||
super(null, new CardSetInfo(card.getName(), card.getSetCode(), card.getCardNumber(), card.getRarity()),
|
super(null, new CardSetInfo(card.getName(), card.getSetCode(), card.getCardNumber(), card.getRarity()),
|
||||||
card.getTypes().toArray(new CardType[0]),
|
card.getTypes().toArray(new CardType[0]),
|
||||||
|
|
|
||||||
9
Mage/src/main/java/mage/cards/mock/MockableCard.java
Normal file
9
Mage/src/main/java/mage/cards/mock/MockableCard.java
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
package mage.cards.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Card is mock, e.g. used in GUI only like deck editor
|
||||||
|
*
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public interface MockableCard {
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,20 @@ public enum TokenRepository {
|
||||||
|
|
||||||
public static final String XMAGE_TOKENS_SET_CODE = "XMAGE";
|
public static final String XMAGE_TOKENS_SET_CODE = "XMAGE";
|
||||||
|
|
||||||
|
// All possible image names. Used for:
|
||||||
|
// - image name from tok/xmage folder
|
||||||
|
// - additional card name for controller like "Morph: face up name"
|
||||||
|
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL = "Face Down";
|
||||||
|
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST = "Manifest";
|
||||||
|
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MORPH = "Morph";
|
||||||
|
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_MEGAMORPH = "Megamorph";
|
||||||
|
public static final String XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL = "Foretell";
|
||||||
|
public static final String XMAGE_IMAGE_NAME_COPY = "Copy";
|
||||||
|
public static final String XMAGE_IMAGE_NAME_CITY_BLESSING = "City's Blessing";
|
||||||
|
public static final String XMAGE_IMAGE_NAME_DAY = "Day";
|
||||||
|
public static final String XMAGE_IMAGE_NAME_NIGHT = "Night";
|
||||||
|
public static final String XMAGE_IMAGE_NAME_THE_MONARCH = "The Monarch";
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(TokenRepository.class);
|
private static final Logger logger = Logger.getLogger(TokenRepository.class);
|
||||||
|
|
||||||
private ArrayList<TokenInfo> allTokens = new ArrayList<>();
|
private ArrayList<TokenInfo> allTokens = new ArrayList<>();
|
||||||
|
|
@ -229,44 +243,60 @@ public enum TokenRepository {
|
||||||
// Search by
|
// Search by
|
||||||
// - https://tagger.scryfall.com/tags/card/assistant-cards
|
// - https://tagger.scryfall.com/tags/card/assistant-cards
|
||||||
// - https://scryfall.com/search?q=otag%3Aassistant-cards&unique=cards&as=grid&order=name
|
// - https://scryfall.com/search?q=otag%3Aassistant-cards&unique=cards&as=grid&order=name
|
||||||
// Must add only unique prints
|
// Must add only unique images/prints
|
||||||
// TODO: add custom set in download window to download a custom tokens only
|
// TODO: add custom set in download window to download a custom tokens only
|
||||||
// TODO: add custom set in card viewer to view a custom tokens only
|
|
||||||
ArrayList<TokenInfo> res = new ArrayList<>();
|
ArrayList<TokenInfo> res = new ArrayList<>();
|
||||||
|
|
||||||
|
// Backface
|
||||||
|
// TODO: can't find backface's api url so use direct link from third party site instead (must be replaced to scryfall someday)
|
||||||
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL, 1, "https://upload.wikimedia.org/wikipedia/en/a/aa/Magic_the_gathering-card_back.jpg"));
|
||||||
|
|
||||||
// Copy
|
// Copy
|
||||||
// https://scryfall.com/search?q=include%3Aextras+unique%3Aprints+type%3Atoken+copy&unique=cards&as=grid&order=name
|
// https://scryfall.com/search?q=include%3Aextras+unique%3Aprints+type%3Atoken+copy&unique=cards&as=grid&order=name
|
||||||
res.add(createXmageToken("Copy", 1, "https://api.scryfall.com/cards/tclb/19/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 1, "https://api.scryfall.com/cards/tclb/19/en?format=image"));
|
||||||
res.add(createXmageToken("Copy", 2, "https://api.scryfall.com/cards/tsnc/1/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 2, "https://api.scryfall.com/cards/tsnc/1/en?format=image"));
|
||||||
res.add(createXmageToken("Copy", 3, "https://api.scryfall.com/cards/tvow/19/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 3, "https://api.scryfall.com/cards/tvow/19/en?format=image"));
|
||||||
res.add(createXmageToken("Copy", 4, "https://api.scryfall.com/cards/tznr/12/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 4, "https://api.scryfall.com/cards/tznr/12/en?format=image"));
|
||||||
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 5, "https://api.scryfall.com/cards/twho/1/en?format=image"));
|
||||||
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 6, "https://api.scryfall.com/cards/tlci/1/en?format=image"));
|
||||||
|
|
||||||
// City's Blessing
|
// City's Blessing
|
||||||
// https://scryfall.com/search?q=type%3Atoken+include%3Aextras+unique%3Aprints+City%27s+Blessing+&unique=cards&as=grid&order=name
|
// https://scryfall.com/search?q=type%3Atoken+include%3Aextras+unique%3Aprints+City%27s+Blessing+&unique=cards&as=grid&order=name
|
||||||
res.add(createXmageToken("City's Blessing", 1, "https://api.scryfall.com/cards/f18/2/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_CITY_BLESSING, 1, "https://api.scryfall.com/cards/f18/2/en?format=image"));
|
||||||
|
|
||||||
// Day // Night
|
// Day // Night
|
||||||
// https://scryfall.com/search?q=include%3Aextras+unique%3Aprints+%22Day+%2F%2F+Night%22&unique=cards&as=grid&order=name
|
// https://scryfall.com/search?q=include%3Aextras+unique%3Aprints+%22Day+%2F%2F+Night%22&unique=cards&as=grid&order=name
|
||||||
res.add(createXmageToken("Day", 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=front"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_DAY, 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=front"));
|
||||||
res.add(createXmageToken("Night", 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=back"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_NIGHT, 1, "https://api.scryfall.com/cards/tvow/21/en?format=image&face=back"));
|
||||||
|
|
||||||
// Manifest
|
// Manifest
|
||||||
// https://scryfall.com/search?q=Manifest+include%3Aextras+unique%3Aprints&unique=cards&as=grid&order=name
|
// https://scryfall.com/search?q=Manifest+include%3Aextras+unique%3Aprints&unique=cards&as=grid&order=name
|
||||||
res.add(createXmageToken("Manifest", 1, "https://api.scryfall.com/cards/tc19/28/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, 1, "https://api.scryfall.com/cards/tc19/28/en?format=image"));
|
||||||
res.add(createXmageToken("Manifest", 2, "https://api.scryfall.com/cards/tc18/1/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, 2, "https://api.scryfall.com/cards/tc18/1/en?format=image"));
|
||||||
res.add(createXmageToken("Manifest", 3, "https://api.scryfall.com/cards/tfrf/4/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, 3, "https://api.scryfall.com/cards/tfrf/4/en?format=image"));
|
||||||
res.add(createXmageToken("Manifest", 4, "https://api.scryfall.com/cards/tncc/3/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MANIFEST, 4, "https://api.scryfall.com/cards/tncc/3/en?format=image"));
|
||||||
|
|
||||||
// Morph
|
// Morph
|
||||||
// https://scryfall.com/search?q=Morph+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name
|
// https://scryfall.com/search?q=Morph+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name
|
||||||
res.add(createXmageToken("Morph", 1, "https://api.scryfall.com/cards/tktk/11/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, 1, "https://api.scryfall.com/cards/tktk/11/en?format=image"));
|
||||||
res.add(createXmageToken("Morph", 2, "https://api.scryfall.com/cards/ta25/15/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, 2, "https://api.scryfall.com/cards/ta25/15/en?format=image"));
|
||||||
res.add(createXmageToken("Morph", 3, "https://api.scryfall.com/cards/tc19/27/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MORPH, 3, "https://api.scryfall.com/cards/tc19/27/en?format=image"));
|
||||||
|
|
||||||
|
// Megamorph
|
||||||
|
// warning, mtg don't have megamorph tokens yet so use morph instead (users will see the diff by card name and face up ability text)
|
||||||
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MEGAMORPH, 1, "https://api.scryfall.com/cards/tktk/11/en?format=image"));
|
||||||
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MEGAMORPH, 2, "https://api.scryfall.com/cards/ta25/15/en?format=image"));
|
||||||
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_MEGAMORPH, 3, "https://api.scryfall.com/cards/tc19/27/en?format=image"));
|
||||||
|
|
||||||
|
// Foretell
|
||||||
|
// https://scryfall.com/search?q=Foretell+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name
|
||||||
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_FACE_DOWN_FORETELL, 1, "https://api.scryfall.com/cards/tkhm/23/en?format=image"));
|
||||||
|
|
||||||
// The Monarch
|
// The Monarch
|
||||||
// https://scryfall.com/search?q=Monarch+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name
|
// https://scryfall.com/search?q=Monarch+unique%3Aprints+otag%3Aassistant-cards&unique=cards&as=grid&order=name
|
||||||
res.add(createXmageToken("The Monarch", 1, "https://api.scryfall.com/cards/tonc/22/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_THE_MONARCH, 1, "https://api.scryfall.com/cards/tonc/22/en?format=image"));
|
||||||
res.add(createXmageToken("The Monarch", 2, "https://api.scryfall.com/cards/tcn2/1/en?format=image"));
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_THE_MONARCH, 2, "https://api.scryfall.com/cards/tcn2/1/en?format=image"));
|
||||||
|
res.add(createXmageToken(XMAGE_IMAGE_NAME_THE_MONARCH, 3, "https://api.scryfall.com/cards/tltc/15/en?format=image"));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
@ -307,4 +337,33 @@ public enum TokenRepository {
|
||||||
public TokenInfo findPreferredTokenInfoForClass(String className, String preferredSetCode) {
|
public TokenInfo findPreferredTokenInfoForClass(String className, String preferredSetCode) {
|
||||||
return findPreferredTokenInfo(TokenRepository.instance.getByClassName(className), preferredSetCode);
|
return findPreferredTokenInfo(TokenRepository.instance.getByClassName(className), preferredSetCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to find random image info by related set code (use for inner tokens like copy, morph, etc)
|
||||||
|
* <p>
|
||||||
|
* Allow to generate "random" image number from an object's UUID (workaround to keep same image after each update)
|
||||||
|
*
|
||||||
|
* @param randomFromId object's UUID for image number generation
|
||||||
|
*/
|
||||||
|
public TokenInfo findPreferredTokenInfoForXmage(String name, UUID randomFromId) {
|
||||||
|
List<TokenInfo> needList = TokenRepository.instance.getByType(TokenType.XMAGE)
|
||||||
|
.stream()
|
||||||
|
.filter(info -> info.getName().equals(name))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (needList.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (needList.size() == 1) {
|
||||||
|
return needList.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// workaround to find stable image from object's id (need for face down image generation)
|
||||||
|
if (randomFromId == null) {
|
||||||
|
return RandomUtil.randomFromCollection(needList);
|
||||||
|
} else {
|
||||||
|
// warning, do not use global random here (it can break it with same seed)
|
||||||
|
int itemIndex = new Random(randomFromId.getLeastSignificantBits()).nextInt(needList.size());
|
||||||
|
return needList.get(itemIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
package mage.constants;
|
package mage.constants;
|
||||||
|
|
||||||
import mage.abilities.SpellAbility;
|
import mage.abilities.SpellAbility;
|
||||||
|
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
||||||
import mage.abilities.keyword.BestowAbility;
|
import mage.abilities.keyword.BestowAbility;
|
||||||
import mage.abilities.keyword.PrototypeAbility;
|
import mage.abilities.keyword.PrototypeAbility;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.abilities.keyword.MorphAbility;
|
import mage.game.Game;
|
||||||
import mage.game.stack.Spell;
|
import mage.game.stack.Spell;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
|
|
@ -16,16 +22,24 @@ public enum SpellAbilityCastMode {
|
||||||
FLASHBACK("Flashback"),
|
FLASHBACK("Flashback"),
|
||||||
BESTOW("Bestow"),
|
BESTOW("Bestow"),
|
||||||
PROTOTYPE("Prototype"),
|
PROTOTYPE("Prototype"),
|
||||||
MORPH("Morph"),
|
MORPH("Morph", false, SpellAbilityCastMode.MORPH_ADDITIONAL_RULE),
|
||||||
|
MEGAMORPH("Megamorph", false, SpellAbilityCastMode.MORPH_ADDITIONAL_RULE),
|
||||||
TRANSFORMED("Transformed", true),
|
TRANSFORMED("Transformed", true),
|
||||||
DISTURB("Disturb", true),
|
DISTURB("Disturb", true),
|
||||||
MORE_THAN_MEETS_THE_EYE("More than Meets the Eye", true);
|
MORE_THAN_MEETS_THE_EYE("More than Meets the Eye", true);
|
||||||
|
|
||||||
|
private static final String MORPH_ADDITIONAL_RULE = "You may cast this card as a 2/2 face-down creature, with no text,"
|
||||||
|
+ " no name, no subtypes, and no mana cost by paying {3} rather than paying its mana cost.";
|
||||||
|
|
||||||
private final String text;
|
private final String text;
|
||||||
|
|
||||||
// Should the cast mode use the second face?
|
// should the cast mode use the second face?
|
||||||
private final boolean isTransformed;
|
private final boolean isTransformed;
|
||||||
|
|
||||||
|
// use it to add additional info in stack object cause face down has nothing
|
||||||
|
// TODO: is it possible to use InfoEffect or CardHint instead that?
|
||||||
|
private final List<String> additionalRulesOnStack;
|
||||||
|
|
||||||
public boolean isTransformed() {
|
public boolean isTransformed() {
|
||||||
return this.isTransformed;
|
return this.isTransformed;
|
||||||
}
|
}
|
||||||
|
|
@ -35,8 +49,17 @@ public enum SpellAbilityCastMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbilityCastMode(String text, boolean isTransformed) {
|
SpellAbilityCastMode(String text, boolean isTransformed) {
|
||||||
|
this(text, isTransformed, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellAbilityCastMode(String text, boolean isTransformed, String additionalRulesOnStack) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.isTransformed = isTransformed;
|
this.isTransformed = isTransformed;
|
||||||
|
this.additionalRulesOnStack = additionalRulesOnStack == null ? null : Collections.singletonList(additionalRulesOnStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAdditionalRulesOnStack() {
|
||||||
|
return additionalRulesOnStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -44,27 +67,46 @@ public enum SpellAbilityCastMode {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Card getTypeModifiedCardObjectCopy(Card card, SpellAbility spellAbility) {
|
public Card getTypeModifiedCardObjectCopy(Card card, SpellAbility spellAbility, Game game) {
|
||||||
Card cardCopy = card.copy();
|
Card cardCopy = card.copy();
|
||||||
if (this.equals(BESTOW)) {
|
|
||||||
BestowAbility.becomeAura(cardCopy);
|
|
||||||
}
|
|
||||||
if (this.isTransformed) {
|
if (this.isTransformed) {
|
||||||
Card tmp = card.getSecondCardFace();
|
Card tmp = card.getSecondCardFace();
|
||||||
if (tmp != null) {
|
if (tmp != null) {
|
||||||
cardCopy = tmp.copy();
|
cardCopy = tmp.copy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.equals(PROTOTYPE)) {
|
|
||||||
cardCopy = ((PrototypeAbility) spellAbility).prototypeCardSpell(cardCopy);
|
switch (this) {
|
||||||
}
|
case BESTOW:
|
||||||
if (this.equals(MORPH)) {
|
BestowAbility.becomeAura(cardCopy);
|
||||||
if (cardCopy instanceof Spell) {
|
break;
|
||||||
//Spell doesn't support setName, so make a copy of the card (we're blowing it away anyway)
|
case PROTOTYPE:
|
||||||
cardCopy = ((Spell) cardCopy).getCard().copy();
|
cardCopy = ((PrototypeAbility) spellAbility).prototypeCardSpell(cardCopy);
|
||||||
}
|
break;
|
||||||
MorphAbility.setCardToFaceDownCreature(cardCopy);
|
case MORPH:
|
||||||
|
case MEGAMORPH:
|
||||||
|
if (cardCopy instanceof Spell) {
|
||||||
|
//Spell doesn't support setName, so make a copy of the card (we're blowing it away anyway)
|
||||||
|
// TODO: research - is it possible to apply face down code to spell instead workaround with card
|
||||||
|
cardCopy = ((Spell) cardCopy).getCard().copy();
|
||||||
|
}
|
||||||
|
BecomesFaceDownCreatureEffect.makeFaceDownObject(game, null, cardCopy, BecomesFaceDownCreatureEffect.FaceDownType.MORPHED, null);
|
||||||
|
break;
|
||||||
|
case NORMAL:
|
||||||
|
case MADNESS:
|
||||||
|
case FLASHBACK:
|
||||||
|
case DISTURB:
|
||||||
|
case MORE_THAN_MEETS_THE_EYE:
|
||||||
|
// it changes only cost, so keep other characteristics
|
||||||
|
// TODO: research - why TRANSFORMED here - is it used in this.isTransformed code?!
|
||||||
|
break;
|
||||||
|
case TRANSFORMED:
|
||||||
|
// TODO: research - why TRANSFORMED here - is it used in this.isTransformed code?!
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Un-supported ability cast mode: " + this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cardCopy;
|
return cardCopy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,10 @@ import java.util.UUID;
|
||||||
/**
|
/**
|
||||||
* @author magenoxx_at_gmail.com
|
* @author magenoxx_at_gmail.com
|
||||||
*/
|
*/
|
||||||
public interface Controllable {
|
public interface Controllable extends ControllableOrOwnerable {
|
||||||
|
|
||||||
UUID getControllerId();
|
UUID getControllerId();
|
||||||
|
|
||||||
UUID getId();
|
|
||||||
|
|
||||||
default boolean isControlledBy(UUID controllerID) {
|
default boolean isControlledBy(UUID controllerID) {
|
||||||
if (getControllerId() == null) {
|
if (getControllerId() == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
16
Mage/src/main/java/mage/game/ControllableOrOwnerable.java
Normal file
16
Mage/src/main/java/mage/game/ControllableOrOwnerable.java
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package mage.game;
|
||||||
|
|
||||||
|
import mage.MageItem;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public interface ControllableOrOwnerable extends MageItem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the controller if available otherwise the owner
|
||||||
|
*/
|
||||||
|
UUID getControllerOrOwnerId();
|
||||||
|
}
|
||||||
|
|
@ -503,6 +503,7 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
||||||
*/
|
*/
|
||||||
void applyEffects();
|
void applyEffects();
|
||||||
|
|
||||||
|
@Deprecated // TODO: must research usage and remove it from all non engine code (example: Bestow ability, ProcessActions must be used instead)
|
||||||
boolean checkStateAndTriggered();
|
boolean checkStateAndTriggered();
|
||||||
|
|
||||||
void playPriority(UUID activePlayerId, boolean resuming);
|
void playPriority(UUID activePlayerId, boolean resuming);
|
||||||
|
|
@ -589,6 +590,9 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
||||||
|
|
||||||
boolean executingRollback();
|
boolean executingRollback();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add counters to permanent before ETB. Use it before put real permanent to battlefield.
|
||||||
|
*/
|
||||||
void setEnterWithCounters(UUID sourceId, Counters counters);
|
void setEnterWithCounters(UUID sourceId, Counters counters);
|
||||||
|
|
||||||
Counters getEnterWithCounters(UUID sourceId);
|
Counters getEnterWithCounters(UUID sourceId);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import mage.abilities.effects.Effect;
|
||||||
import mage.abilities.effects.PreventionEffectData;
|
import mage.abilities.effects.PreventionEffectData;
|
||||||
import mage.abilities.effects.common.CopyEffect;
|
import mage.abilities.effects.common.CopyEffect;
|
||||||
import mage.abilities.effects.common.InfoEffect;
|
import mage.abilities.effects.common.InfoEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
||||||
import mage.abilities.effects.keyword.FinalityCounterEffect;
|
import mage.abilities.effects.keyword.FinalityCounterEffect;
|
||||||
import mage.abilities.effects.keyword.ShieldCounterEffect;
|
import mage.abilities.effects.keyword.ShieldCounterEffect;
|
||||||
import mage.abilities.effects.keyword.StunCounterEffect;
|
import mage.abilities.effects.keyword.StunCounterEffect;
|
||||||
|
|
@ -1996,10 +1997,9 @@ public abstract class GameImpl implements Game {
|
||||||
|
|
||||||
// workaround to find real copyable characteristics of transformed/facedown/etc permanents
|
// workaround to find real copyable characteristics of transformed/facedown/etc permanents
|
||||||
|
|
||||||
if (copyFromPermanent.isMorphed()
|
BecomesFaceDownCreatureEffect.FaceDownType faceDownType = BecomesFaceDownCreatureEffect.findFaceDownType(this, copyFromPermanent);
|
||||||
|| copyFromPermanent.isManifested()
|
if (faceDownType != null) {
|
||||||
|| copyFromPermanent.isFaceDown(this)) {
|
BecomesFaceDownCreatureEffect.makeFaceDownObject(this, null, newBluePrint, faceDownType, null);
|
||||||
MorphAbility.setPermanentToFaceDownCreature(newBluePrint, copyFromPermanent, this);
|
|
||||||
}
|
}
|
||||||
newBluePrint.assignNewId();
|
newBluePrint.assignNewId();
|
||||||
if (copyFromPermanent.isTransformed()) {
|
if (copyFromPermanent.isTransformed()) {
|
||||||
|
|
|
||||||
10
Mage/src/main/java/mage/game/Ownerable.java
Normal file
10
Mage/src/main/java/mage/game/Ownerable.java
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package mage.game;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public interface Ownerable extends ControllableOrOwnerable {
|
||||||
|
UUID getOwnerId();
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package mage.game;
|
package mage.game;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.SpellAbility;
|
|
||||||
import mage.abilities.keyword.TransformAbility;
|
import mage.abilities.keyword.TransformAbility;
|
||||||
import mage.cards.*;
|
import mage.cards.*;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
|
|
@ -27,7 +26,7 @@ public final class ZonesHandler {
|
||||||
|
|
||||||
public static boolean cast(ZoneChangeInfo info, Ability source, Game game) {
|
public static boolean cast(ZoneChangeInfo info, Ability source, Game game) {
|
||||||
if (maybeRemoveFromSourceZone(info, game, source)) {
|
if (maybeRemoveFromSourceZone(info, game, source)) {
|
||||||
placeInDestinationZone(info,0, source, game);
|
placeInDestinationZone(info, 0, source, game);
|
||||||
// create a group zone change event if a card is moved to stack for casting (it's always only one card, but some effects check for group events (one or more xxx))
|
// create a group zone change event if a card is moved to stack for casting (it's always only one card, but some effects check for group events (one or more xxx))
|
||||||
Set<Card> cards = new HashSet<>();
|
Set<Card> cards = new HashSet<>();
|
||||||
Set<PermanentToken> tokens = new HashSet<>();
|
Set<PermanentToken> tokens = new HashSet<>();
|
||||||
|
|
@ -325,33 +324,50 @@ public final class ZonesHandler {
|
||||||
// Handle all normal cases
|
// Handle all normal cases
|
||||||
Card card = getTargetCard(game, event.getTargetId());
|
Card card = getTargetCard(game, event.getTargetId());
|
||||||
if (card == null) {
|
if (card == null) {
|
||||||
// If we can't find the card we can't remove it.
|
// if we can't find the card we can't remove it.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean success = false;
|
boolean isGoodToMove = false;
|
||||||
if (info.faceDown) {
|
if (info.faceDown) {
|
||||||
card.setFaceDown(true, game);
|
// any card can be moved as face down (doubled faced cards also support face down)
|
||||||
|
isGoodToMove = true;
|
||||||
} else if (event.getToZone().equals(Zone.BATTLEFIELD)) {
|
} else if (event.getToZone().equals(Zone.BATTLEFIELD)) {
|
||||||
if (!card.isPermanent(game)
|
// non-permanents can't move to battlefield
|
||||||
&& (!card.isTransformable() || Boolean.FALSE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId())))) {
|
// "return to battlefield transformed" abilities uses game state value instead "info.transformed", so check it too
|
||||||
// Non permanents (Instants, Sorceries, ... stay in the zone they are if an abilty/effect tries to move it to the battlefield
|
// TODO: possible bug with non permanent on second side like Life // Death, see https://github.com/magefree/mage/issues/11573
|
||||||
return false;
|
// need to check second side here, not status only
|
||||||
}
|
// TODO: possible bug with Nightbound, search all usage of getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED and insert additional check Ability.checkCard
|
||||||
|
boolean wantToPutTransformed = card.isTransformable()
|
||||||
|
&& Boolean.TRUE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId()));
|
||||||
|
isGoodToMove = card.isPermanent(game) || wantToPutTransformed;
|
||||||
|
} else {
|
||||||
|
// other zones allows to move
|
||||||
|
isGoodToMove = true;
|
||||||
|
}
|
||||||
|
if (!isGoodToMove) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: is it buggy? Card characteristics are global - if you change face down then it will be
|
||||||
|
// changed in original card too, not in blueprint only
|
||||||
|
card.setFaceDown(info.faceDown, game);
|
||||||
|
|
||||||
|
boolean success = false;
|
||||||
if (!game.replaceEvent(event)) {
|
if (!game.replaceEvent(event)) {
|
||||||
Zone fromZone = event.getFromZone();
|
Zone fromZone = event.getFromZone();
|
||||||
if (event.getToZone() == Zone.BATTLEFIELD) {
|
if (event.getToZone() == Zone.BATTLEFIELD) {
|
||||||
// prepare card and permanent
|
// PUT TO BATTLEFIELD AS PERMANENT
|
||||||
// If needed take attributes from the spell (e.g. color of spell was changed)
|
// prepare card and permanent (card must contain full data, even for face down)
|
||||||
card = takeAttributesFromSpell(card, event, game);
|
// if needed to take attributes from the spell (e.g. color of spell was changed)
|
||||||
|
card = prepareBlueprintCardFromSpell(card, event, game);
|
||||||
|
|
||||||
// controlling player can be replaced so use event player now
|
// controlling player can be replaced so use event player now
|
||||||
Permanent permanent;
|
Permanent permanent;
|
||||||
if (card instanceof MeldCard) {
|
if (card instanceof MeldCard) {
|
||||||
permanent = new PermanentMeld(card, event.getPlayerId(), game);
|
permanent = new PermanentMeld(card, event.getPlayerId(), game);
|
||||||
} else if (card instanceof ModalDoubleFacedCard) {
|
} else if (card instanceof ModalDoubleFacedCard) {
|
||||||
// main mdf card must be processed before that call (e.g. only halfes can be moved to battlefield)
|
// main mdf card must be processed before that call (e.g. only halves can be moved to battlefield)
|
||||||
throw new IllegalStateException("Unexpected trying of move mdf card to battlefield instead half");
|
throw new IllegalStateException("Unexpected trying of move mdf card to battlefield instead half");
|
||||||
} else if (card instanceof Permanent) {
|
} else if (card instanceof Permanent) {
|
||||||
throw new IllegalStateException("Unexpected trying of move permanent to battlefield instead card");
|
throw new IllegalStateException("Unexpected trying of move permanent to battlefield instead card");
|
||||||
|
|
@ -361,11 +377,12 @@ public final class ZonesHandler {
|
||||||
|
|
||||||
// put onto battlefield with possible counters
|
// put onto battlefield with possible counters
|
||||||
game.getPermanentsEntering().put(permanent.getId(), permanent);
|
game.getPermanentsEntering().put(permanent.getId(), permanent);
|
||||||
card.checkForCountersToAdd(permanent, source, game);
|
card.applyEnterWithCounters(permanent, source, game);
|
||||||
|
|
||||||
permanent.setTapped(info instanceof ZoneChangeInfo.Battlefield
|
permanent.setTapped(info instanceof ZoneChangeInfo.Battlefield
|
||||||
&& ((ZoneChangeInfo.Battlefield) info).tapped);
|
&& ((ZoneChangeInfo.Battlefield) info).tapped);
|
||||||
|
|
||||||
|
// if need prototyped version
|
||||||
if (Zone.STACK == event.getFromZone()) {
|
if (Zone.STACK == event.getFromZone()) {
|
||||||
Spell spell = game.getStack().getSpell(event.getTargetId());
|
Spell spell = game.getStack().getSpell(event.getTargetId());
|
||||||
if (spell != null) {
|
if (spell != null) {
|
||||||
|
|
@ -375,29 +392,34 @@ public final class ZonesHandler {
|
||||||
|
|
||||||
permanent.setFaceDown(info.faceDown, game);
|
permanent.setFaceDown(info.faceDown, game);
|
||||||
if (info.faceDown) {
|
if (info.faceDown) {
|
||||||
card.setFaceDown(false, game);
|
// TODO: need research cards with "setFaceDown(false"
|
||||||
|
// TODO: delete after new release and new face down bugs (old code remove face down status from a card for unknown reason), 2024-02-20
|
||||||
|
//card.setFaceDown(false, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the controller of all continuous effects of this card are switched to the current controller
|
// make sure the controller of all continuous effects of this card are switched to the current controller
|
||||||
game.setScopeRelevant(true);
|
game.setScopeRelevant(true);
|
||||||
game.getContinuousEffects().setController(permanent.getId(), permanent.getControllerId());
|
try {
|
||||||
if (permanent.entersBattlefield(source, game, fromZone, true)
|
game.getContinuousEffects().setController(permanent.getId(), permanent.getControllerId());
|
||||||
&& card.removeFromZone(game, fromZone, source)) {
|
if (permanent.entersBattlefield(source, game, fromZone, true)
|
||||||
success = true;
|
&& card.removeFromZone(game, fromZone, source)) {
|
||||||
event.setTarget(permanent);
|
success = true;
|
||||||
} else {
|
event.setTarget(permanent);
|
||||||
// revert controller to owner if permanent does not enter
|
} else {
|
||||||
game.getContinuousEffects().setController(permanent.getId(), permanent.getOwnerId());
|
// revert controller to owner if permanent does not enter
|
||||||
game.getPermanentsEntering().remove(permanent.getId());
|
game.getContinuousEffects().setController(permanent.getId(), permanent.getOwnerId());
|
||||||
|
game.getPermanentsEntering().remove(permanent.getId());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
game.setScopeRelevant(false);
|
||||||
}
|
}
|
||||||
game.setScopeRelevant(false);
|
|
||||||
} else if (event.getTarget() != null) {
|
} else if (event.getTarget() != null) {
|
||||||
card.setFaceDown(info.faceDown, game);
|
// PUT PERMANENT TO OTHER ZONE (e.g. remove only)
|
||||||
Permanent target = event.getTarget();
|
Permanent target = event.getTarget();
|
||||||
success = target.removeFromZone(game, fromZone, source)
|
success = target.removeFromZone(game, fromZone, source)
|
||||||
&& game.getPlayer(target.getControllerId()).removeFromBattlefield(target, source, game);
|
&& game.getPlayer(target.getControllerId()).removeFromBattlefield(target, source, game);
|
||||||
} else {
|
} else {
|
||||||
card.setFaceDown(info.faceDown, game);
|
// PUT CARD TO OTHER ZONE
|
||||||
success = card.removeFromZone(game, fromZone, source);
|
success = card.removeFromZone(game, fromZone, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -434,17 +456,30 @@ public final class ZonesHandler {
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Card takeAttributesFromSpell(Card card, ZoneChangeEvent event, Game game) {
|
private static Card prepareBlueprintCardFromSpell(Card card, ZoneChangeEvent event, Game game) {
|
||||||
card = card.copy();
|
card = card.copy();
|
||||||
if (Zone.STACK == event.getFromZone()) {
|
if (Zone.STACK == event.getFromZone()) {
|
||||||
|
// TODO: wtf, why only colors!? Must research and remove colors workaround or add all other data like types too
|
||||||
Spell spell = game.getStack().getSpell(event.getTargetId());
|
Spell spell = game.getStack().getSpell(event.getTargetId());
|
||||||
if (spell != null && !spell.isFaceDown(game)) {
|
|
||||||
// TODO: wtf, why only colors!? Must research and remove colors workaround
|
// old version
|
||||||
|
if (false && spell != null && !spell.isFaceDown(game)) {
|
||||||
if (!card.getColor(game).equals(spell.getColor(game))) {
|
if (!card.getColor(game).equals(spell.getColor(game))) {
|
||||||
// the card that is referenced to in the permanent is copied and the spell attributes are set to this copied card
|
// the card that is referenced to in the permanent is copied and the spell attributes are set to this copied card
|
||||||
card.getColor(game).setColor(spell.getColor(game));
|
card.getColor(game).setColor(spell.getColor(game));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new version
|
||||||
|
if (true && spell != null && spell.getSpellAbility() != null) {
|
||||||
|
Card characteristics = spell.getSpellAbility().getCharacteristics(game);
|
||||||
|
if (!characteristics.isFaceDown(game)) {
|
||||||
|
if (!card.getColor(game).equals(characteristics.getColor(game))) {
|
||||||
|
// TODO: don't work with prototyped spells (setColor can't set colorless color)
|
||||||
|
card.getColor(game).setColor(characteristics.getColor(game));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,9 @@ public abstract class CommandObjectImpl implements CommandObject {
|
||||||
private UUID id;
|
private UUID id;
|
||||||
private String name = "";
|
private String name = "";
|
||||||
|
|
||||||
private String expansionSetCode;
|
private String expansionSetCode = "";
|
||||||
private String cardNumber;
|
private String cardNumber = "";
|
||||||
|
private String imageFileName = "";
|
||||||
private int imageNumber;
|
private int imageNumber;
|
||||||
|
|
||||||
public CommandObjectImpl(String name) {
|
public CommandObjectImpl(String name) {
|
||||||
|
|
@ -27,6 +28,7 @@ public abstract class CommandObjectImpl implements CommandObject {
|
||||||
this.name = object.name;
|
this.name = object.name;
|
||||||
this.expansionSetCode = object.expansionSetCode;
|
this.expansionSetCode = object.expansionSetCode;
|
||||||
this.cardNumber = object.cardNumber;
|
this.cardNumber = object.cardNumber;
|
||||||
|
this.imageFileName = object.imageFileName;
|
||||||
this.imageNumber = object.imageNumber;
|
this.imageNumber = object.imageNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +57,16 @@ public abstract class CommandObjectImpl implements CommandObject {
|
||||||
this.cardNumber = cardNumber;
|
this.cardNumber = cardNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getImageFileName() {
|
||||||
|
return imageFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageFileName(String imageFileName) {
|
||||||
|
this.imageFileName = imageFileName;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getImageNumber() {
|
public Integer getImageNumber() {
|
||||||
return imageNumber;
|
return imageNumber;
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,11 @@ public class Commander extends CommandObjectImpl {
|
||||||
return sourceObject.getOwnerId();
|
return sourceObject.getOwnerId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getControllerOrOwnerId() {
|
||||||
|
return getControllerId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandObject copy() {
|
public CommandObject copy() {
|
||||||
return new Commander(this);
|
return new Commander(this);
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,11 @@ public class Dungeon extends CommandObjectImpl {
|
||||||
this.abilites.setControllerId(controllerId);
|
this.abilites.setControllerId(controllerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getControllerOrOwnerId() {
|
||||||
|
return getControllerId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCopy(boolean isCopy, MageObject copyFrom) {
|
public void setCopy(boolean isCopy, MageObject copyFrom) {
|
||||||
this.copy = isCopy;
|
this.copy = isCopy;
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ public abstract class Emblem extends CommandObjectImpl {
|
||||||
if (foundInfo != null) {
|
if (foundInfo != null) {
|
||||||
this.setExpansionSetCode(foundInfo.getSetCode());
|
this.setExpansionSetCode(foundInfo.getSetCode());
|
||||||
this.setCardNumber("");
|
this.setCardNumber("");
|
||||||
|
this.setImageFileName(""); // use default
|
||||||
this.setImageNumber(foundInfo.getImageNumber());
|
this.setImageNumber(foundInfo.getImageNumber());
|
||||||
} else {
|
} else {
|
||||||
// how-to fix: add emblem to the tokens-database
|
// how-to fix: add emblem to the tokens-database
|
||||||
|
|
@ -99,6 +100,11 @@ public abstract class Emblem extends CommandObjectImpl {
|
||||||
this.abilites.setControllerId(controllerId);
|
this.abilites.setControllerId(controllerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getControllerOrOwnerId() {
|
||||||
|
return getControllerId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
abstract public Emblem copy();
|
abstract public Emblem copy();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ public abstract class Plane extends CommandObjectImpl {
|
||||||
if (foundInfo != null) {
|
if (foundInfo != null) {
|
||||||
this.setExpansionSetCode(foundInfo.getSetCode());
|
this.setExpansionSetCode(foundInfo.getSetCode());
|
||||||
this.setCardNumber("");
|
this.setCardNumber("");
|
||||||
|
this.setImageFileName(""); // use default
|
||||||
this.setImageNumber(foundInfo.getImageNumber());
|
this.setImageNumber(foundInfo.getImageNumber());
|
||||||
} else {
|
} else {
|
||||||
// how-to fix: add plane to the tokens-database
|
// how-to fix: add plane to the tokens-database
|
||||||
|
|
@ -103,6 +104,11 @@ public abstract class Plane extends CommandObjectImpl {
|
||||||
this.abilites.setControllerId(controllerId);
|
this.abilites.setControllerId(controllerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getControllerOrOwnerId() {
|
||||||
|
return getControllerId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
abstract public Plane copy();
|
abstract public Plane copy();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ import java.util.stream.Collectors;
|
||||||
* mana burn with Yurlok of Scorch Thrash, and anything else players might think of.
|
* mana burn with Yurlok of Scorch Thrash, and anything else players might think of.
|
||||||
*/
|
*/
|
||||||
public final class EmblemOfCard extends Emblem {
|
public final class EmblemOfCard extends Emblem {
|
||||||
|
|
||||||
private final boolean usesVariousArt;
|
private final boolean usesVariousArt;
|
||||||
private static final Logger logger = Logger.getLogger(EmblemOfCard.class);
|
|
||||||
|
|
||||||
public static Card lookupCard(
|
public static Card lookupCard(
|
||||||
String cardName,
|
String cardName,
|
||||||
|
|
@ -75,8 +75,10 @@ public final class EmblemOfCard extends Emblem {
|
||||||
return ability;
|
return ability;
|
||||||
}).collect(Collectors.toList()));
|
}).collect(Collectors.toList()));
|
||||||
this.getAbilities().setSourceId(this.getId());
|
this.getAbilities().setSourceId(this.getId());
|
||||||
|
|
||||||
this.setExpansionSetCode(card.getExpansionSetCode());
|
this.setExpansionSetCode(card.getExpansionSetCode());
|
||||||
this.setCardNumber(card.getCardNumber());
|
this.setCardNumber(card.getCardNumber());
|
||||||
|
this.setImageFileName(card.getImageFileName());
|
||||||
this.setImageNumber(card.getImageNumber());
|
this.setImageNumber(card.getImageNumber());
|
||||||
this.usesVariousArt = card.getUsesVariousArt();
|
this.usesVariousArt = card.getUsesVariousArt();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,4 +103,8 @@ public class ZoneChangeEvent extends GameEvent {
|
||||||
return this.source;
|
return this.source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + ", from " + getFromZone() + " to " + getToZone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import mage.MageObject;
|
||||||
import mage.MageObjectReference;
|
import mage.MageObjectReference;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.constants.Rarity;
|
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Controllable;
|
import mage.game.Controllable;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
@ -113,8 +112,6 @@ public interface Permanent extends Card, Controllable {
|
||||||
|
|
||||||
void setExpansionSetCode(String expansionSetCode);
|
void setExpansionSetCode(String expansionSetCode);
|
||||||
|
|
||||||
void setRarity(Rarity rarity);
|
|
||||||
|
|
||||||
void setFlipCard(boolean flipCard);
|
void setFlipCard(boolean flipCard);
|
||||||
|
|
||||||
void setFlipCardName(String flipCardName);
|
void setFlipCardName(String flipCardName);
|
||||||
|
|
@ -136,7 +133,7 @@ public interface Permanent extends Card, Controllable {
|
||||||
boolean hasProtectionFrom(MageObject source, Game game);
|
boolean hasProtectionFrom(MageObject source, Game game);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param attachment
|
* @param attachment can be any object: card, permanent, token
|
||||||
* @param source can be null for default checks like state base
|
* @param source can be null for default checks like state base
|
||||||
* @param game
|
* @param game
|
||||||
* @param silentMode - use it to ignore warning message for users (e.g. for
|
* @param silentMode - use it to ignore warning message for users (e.g. for
|
||||||
|
|
@ -194,7 +191,14 @@ public interface Permanent extends Card, Controllable {
|
||||||
|
|
||||||
void reset(Game game);
|
void reset(Game game);
|
||||||
|
|
||||||
MageObject getBasicMageObject(Game game);
|
/**
|
||||||
|
* Return original/blueprint/printable object (token or card)
|
||||||
|
* <p>
|
||||||
|
* Original object used on each game cycle for permanent reset and apply all active effects
|
||||||
|
* <p>
|
||||||
|
* Warning, all changes to the original object will be applied forever
|
||||||
|
*/
|
||||||
|
MageObject getBasicMageObject();
|
||||||
|
|
||||||
boolean destroy(Ability source, Game game);
|
boolean destroy(Ability source, Game game);
|
||||||
|
|
||||||
|
|
@ -221,7 +225,7 @@ public interface Permanent extends Card, Controllable {
|
||||||
* Add abilities to the permanent, can be used in effects
|
* Add abilities to the permanent, can be used in effects
|
||||||
*
|
*
|
||||||
* @param ability
|
* @param ability
|
||||||
* @param sourceId
|
* @param sourceId can be null
|
||||||
* @param game
|
* @param game
|
||||||
* @return can be null for exists abilities
|
* @return can be null for exists abilities
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -14,28 +14,30 @@ import mage.cards.SplitCard;
|
||||||
import mage.constants.SpellAbilityType;
|
import mage.constants.SpellAbilityType;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.events.ZoneChangeEvent;
|
import mage.game.events.ZoneChangeEvent;
|
||||||
import mage.game.permanent.token.Token;
|
|
||||||
|
|
||||||
import javax.annotation.processing.SupportedSourceVersion;
|
|
||||||
import javax.lang.model.SourceVersion;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static permanent on the battlefield. There are possible multiple permanents per one card,
|
* Static permanent on the battlefield. There are possible multiple permanents per one card,
|
||||||
* so be carefull for targets (ids are different) and ZCC (zcc is static for permanent).
|
* so be carefully for targets (ids are different) and ZCC (zcc is static for permanent).
|
||||||
*
|
*
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
*/
|
*/
|
||||||
public class PermanentCard extends PermanentImpl {
|
public class PermanentCard extends PermanentImpl {
|
||||||
|
|
||||||
|
// blueprint e.g. a copy of the original card that was cast
|
||||||
|
// (this is not the original card, so it's possible to change some attribute before it enters the battlefield)
|
||||||
|
// TODO: wtf, it modified on getCard/getBasicMageObject/getMainCard() and other places, e.g. on bestow -- must be fixed!
|
||||||
|
protected Card card;
|
||||||
|
|
||||||
protected int maxLevelCounters;
|
protected int maxLevelCounters;
|
||||||
// A copy of the original card that was cast (this is not the original card, so it's possible to change some attribute to this blueprint to change attributes to the permanent if it enters the battlefield with e.g. a subtype)
|
|
||||||
protected Card card; // TODO: wtf, it modified on getCard and other places, e.g. on bestow -- must be fixed!
|
|
||||||
// the number this permanent instance had
|
|
||||||
protected int zoneChangeCounter;
|
protected int zoneChangeCounter;
|
||||||
|
|
||||||
public PermanentCard(Card card, UUID controllerId, Game game) {
|
public PermanentCard(Card card, UUID controllerId, Game game) {
|
||||||
super(card.getId(), card.getOwnerId(), controllerId, card.getName());
|
super(card.getId(), card.getOwnerId(), controllerId, card.getName()); // card id
|
||||||
|
// TODO: wtf, must research - is it possible to have diff ids for same card id?!
|
||||||
|
// ETB with counters depends on card id, not permanent id
|
||||||
|
// TODO: ETB with counters works with tokens?! Must research
|
||||||
|
|
||||||
// runtime check: must use real card only inside
|
// runtime check: must use real card only inside
|
||||||
if (card instanceof PermanentCard) {
|
if (card instanceof PermanentCard) {
|
||||||
|
|
@ -124,7 +126,7 @@ public class PermanentCard extends PermanentImpl {
|
||||||
this.abilities.setSourceId(objectId);
|
this.abilities.setSourceId(objectId);
|
||||||
this.cardType.clear();
|
this.cardType.clear();
|
||||||
this.cardType.addAll(card.getCardType());
|
this.cardType.addAll(card.getCardType());
|
||||||
this.color = card.getColor(null).copy();
|
this.color = card.getColor(null).copy(); // TODO: need research - why it null
|
||||||
this.frameColor = card.getFrameColor(game).copy();
|
this.frameColor = card.getFrameColor(game).copy();
|
||||||
this.frameStyle = card.getFrameStyle();
|
this.frameStyle = card.getFrameStyle();
|
||||||
this.manaCost = card.getManaCost().copy();
|
this.manaCost = card.getManaCost().copy();
|
||||||
|
|
@ -134,10 +136,12 @@ public class PermanentCard extends PermanentImpl {
|
||||||
this.subtype.copyFrom(card.getSubtype());
|
this.subtype.copyFrom(card.getSubtype());
|
||||||
this.supertype.clear();
|
this.supertype.clear();
|
||||||
this.supertype.addAll(card.getSuperType());
|
this.supertype.addAll(card.getSuperType());
|
||||||
|
this.rarity = card.getRarity();
|
||||||
|
|
||||||
this.setExpansionSetCode(card.getExpansionSetCode());
|
this.setExpansionSetCode(card.getExpansionSetCode());
|
||||||
this.setCardNumber(card.getCardNumber());
|
this.setCardNumber(card.getCardNumber());
|
||||||
this.rarity = card.getRarity();
|
this.setImageFileName(card.getImageFileName());
|
||||||
|
this.setImageNumber(card.getImageNumber());
|
||||||
this.usesVariousArt = card.getUsesVariousArt();
|
this.usesVariousArt = card.getUsesVariousArt();
|
||||||
|
|
||||||
if (card.getSecondCardFace() != null) {
|
if (card.getSecondCardFace() != null) {
|
||||||
|
|
@ -152,7 +156,7 @@ public class PermanentCard extends PermanentImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MageObject getBasicMageObject(Game game) {
|
public MageObject getBasicMageObject() {
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,12 +218,15 @@ public class PermanentCard extends PermanentImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) {
|
public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) {
|
||||||
|
// TODO: wtf, permanent must not change ZCC at all, is it buggy here?!
|
||||||
card.updateZoneChangeCounter(game, event);
|
card.updateZoneChangeCounter(game, event);
|
||||||
zoneChangeCounter = card.getZoneChangeCounter(game);
|
zoneChangeCounter = card.getZoneChangeCounter(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setZoneChangeCounter(int value, Game game) {
|
public void setZoneChangeCounter(int value, Game game) {
|
||||||
|
// TODO: wtf, why it sync card only without permanent zcc, is it buggy here?!
|
||||||
|
// TODO: miss zoneChangeCounter = card.getZoneChangeCounter(game); ?
|
||||||
card.setZoneChangeCounter(value, game);
|
card.setZoneChangeCounter(value, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,13 +234,4 @@ public class PermanentCard extends PermanentImpl {
|
||||||
public Card getMainCard() {
|
public Card getMainCard() {
|
||||||
return card.getMainCard();
|
return card.getMainCard();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return card.toString()
|
|
||||||
+ ", " + ((this instanceof Token) ? "T" : "C")
|
|
||||||
+ (this.isCopy() ? ", copy" : "")
|
|
||||||
+ ", " + this.getPower() + "/" + this.getToughness()
|
|
||||||
+ (this.isTapped() ? ", tapped" : "");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import mage.abilities.effects.Effect;
|
||||||
import mage.abilities.effects.RequirementEffect;
|
import mage.abilities.effects.RequirementEffect;
|
||||||
import mage.abilities.effects.RestrictionEffect;
|
import mage.abilities.effects.RestrictionEffect;
|
||||||
import mage.abilities.effects.common.RegenerateSourceEffect;
|
import mage.abilities.effects.common.RegenerateSourceEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
||||||
import mage.abilities.hint.HintUtils;
|
import mage.abilities.hint.HintUtils;
|
||||||
import mage.abilities.keyword.*;
|
import mage.abilities.keyword.*;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
|
|
@ -30,6 +31,7 @@ import mage.game.command.CommandObject;
|
||||||
import mage.game.events.*;
|
import mage.game.events.*;
|
||||||
import mage.game.events.GameEvent.EventType;
|
import mage.game.events.GameEvent.EventType;
|
||||||
import mage.game.permanent.token.SquirrelToken;
|
import mage.game.permanent.token.SquirrelToken;
|
||||||
|
import mage.game.permanent.token.Token;
|
||||||
import mage.game.stack.Spell;
|
import mage.game.stack.Spell;
|
||||||
import mage.game.stack.StackObject;
|
import mage.game.stack.StackObject;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
|
@ -118,6 +120,12 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
||||||
|
|
||||||
protected PermanentImpl(UUID ownerId, UUID controllerId, String name) {
|
protected PermanentImpl(UUID ownerId, UUID controllerId, String name) {
|
||||||
super(ownerId, name);
|
super(ownerId, name);
|
||||||
|
|
||||||
|
// runtime check: need controller (if you catch it in non-game then use random uuid)
|
||||||
|
if (controllerId == null) {
|
||||||
|
throw new IllegalArgumentException("Wrong code usage: controllerId can't be null - " + name, new Throwable());
|
||||||
|
}
|
||||||
|
|
||||||
this.originalControllerId = controllerId;
|
this.originalControllerId = controllerId;
|
||||||
this.controllerId = controllerId;
|
this.controllerId = controllerId;
|
||||||
this.counters = new Counters();
|
this.counters = new Counters();
|
||||||
|
|
@ -186,12 +194,20 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = threadLocalBuilder.get();
|
String name = getName().isEmpty()
|
||||||
sb.append(this.getName()).append('-').append(this.getExpansionSetCode());
|
? "face down" + " [" + getId().toString().substring(0, 3) + "]"
|
||||||
if (copy) {
|
: getIdName();
|
||||||
sb.append(" [Copy]");
|
String imageInfo = getExpansionSetCode()
|
||||||
}
|
+ ":" + getCardNumber()
|
||||||
return sb.toString();
|
+ ":" + getImageFileName()
|
||||||
|
+ ":" + getImageNumber();
|
||||||
|
return name
|
||||||
|
+ ", " + (getBasicMageObject() instanceof Token ? "T" : "C")
|
||||||
|
+ ", " + getBasicMageObject().getClass().getSimpleName()
|
||||||
|
+ ", " + imageInfo
|
||||||
|
+ ", " + this.getPower() + "/" + this.getToughness()
|
||||||
|
+ (this.isCopy() ? ", copy" : "")
|
||||||
|
+ (this.isTapped() ? ", tapped" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -483,7 +499,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected UUID getControllerOrOwner() {
|
public UUID getControllerOrOwnerId() {
|
||||||
return controllerId;
|
return controllerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1222,18 +1238,24 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
||||||
@Override
|
@Override
|
||||||
public boolean entersBattlefield(Ability source, Game game, Zone fromZone, boolean fireEvent) {
|
public boolean entersBattlefield(Ability source, Game game, Zone fromZone, boolean fireEvent) {
|
||||||
controlledFromStartOfControllerTurn = false;
|
controlledFromStartOfControllerTurn = false;
|
||||||
if (this.isFaceDown(game)) { // ?? add morphed/manifested here ???
|
|
||||||
|
BecomesFaceDownCreatureEffect.FaceDownType faceDownType = BecomesFaceDownCreatureEffect.findFaceDownType(game, this);
|
||||||
|
if (faceDownType != null) {
|
||||||
// remove some attributes here, because first apply effects comes later otherwise abilities (e.g. color related) will unintended trigger
|
// remove some attributes here, because first apply effects comes later otherwise abilities (e.g. color related) will unintended trigger
|
||||||
MorphAbility.setPermanentToFaceDownCreature(this, this, game);
|
BecomesFaceDownCreatureEffect.makeFaceDownObject(game, null, this, faceDownType, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// own etb event
|
||||||
if (game.replaceEvent(new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone, EnterEventType.SELF))) {
|
if (game.replaceEvent(new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone, EnterEventType.SELF))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normal etb event
|
||||||
EntersTheBattlefieldEvent event = new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone);
|
EntersTheBattlefieldEvent event = new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone);
|
||||||
if (game.replaceEvent(event)) {
|
if (game.replaceEvent(event)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isPlaneswalker(game)) {
|
if (this.isPlaneswalker(game)) {
|
||||||
int loyalty;
|
int loyalty;
|
||||||
if (this.getStartingLoyalty() == -2) {
|
if (this.getStartingLoyalty() == -2) {
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,11 @@ public class PermanentToken extends PermanentImpl {
|
||||||
|
|
||||||
// non-modifyable container with token characteristics
|
// non-modifyable container with token characteristics
|
||||||
// this PermanentToken resets to it on each game cycle
|
// this PermanentToken resets to it on each game cycle
|
||||||
|
// TODO: see PermanentCard.card for usage research and fixes
|
||||||
protected Token token;
|
protected Token token;
|
||||||
|
|
||||||
public PermanentToken(Token token, UUID controllerId, Game game) {
|
public PermanentToken(Token token, UUID controllerId, Game game) {
|
||||||
super(controllerId, controllerId, token.getName());
|
super(controllerId, controllerId, token.getName()); // random id
|
||||||
this.token = token.copy();
|
this.token = token.copy();
|
||||||
this.token.getAbilities().newOriginalId(); // neccessary if token has ability like DevourAbility()
|
this.token.getAbilities().newOriginalId(); // neccessary if token has ability like DevourAbility()
|
||||||
this.token.getAbilities().setSourceId(objectId);
|
this.token.getAbilities().setSourceId(objectId);
|
||||||
|
|
@ -76,11 +77,6 @@ public class PermanentToken extends PermanentImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("%s - %s", getExpansionSetCode(), getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyFromToken(Token token, Game game, boolean reset) {
|
private void copyFromToken(Token token, Game game, boolean reset) {
|
||||||
// modify all attributes permanently (without game usage)
|
// modify all attributes permanently (without game usage)
|
||||||
this.name = token.getName();
|
this.name = token.getName();
|
||||||
|
|
@ -119,7 +115,7 @@ public class PermanentToken extends PermanentImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MageObject getBasicMageObject(Game game) {
|
public MageObject getBasicMageObject() {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import mage.game.permanent.PermanentToken;
|
||||||
import mage.game.permanent.token.custom.CreatureToken;
|
import mage.game.permanent.token.custom.CreatureToken;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.target.Target;
|
import mage.target.Target;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
@ -30,6 +31,8 @@ import java.util.*;
|
||||||
*/
|
*/
|
||||||
public abstract class TokenImpl extends MageObjectImpl implements Token {
|
public abstract class TokenImpl extends MageObjectImpl implements Token {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(MageObjectImpl.class);
|
||||||
|
|
||||||
protected String description;
|
protected String description;
|
||||||
private final ArrayList<UUID> lastAddedTokenIds = new ArrayList<>();
|
private final ArrayList<UUID> lastAddedTokenIds = new ArrayList<>();
|
||||||
|
|
||||||
|
|
@ -142,6 +145,14 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
||||||
return putOntoBattlefield(amount, game, source, controllerId, tapped, attacking, null);
|
return putOntoBattlefield(amount, game, source, controllerId, tapped, attacking, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find random token image from a database
|
||||||
|
*
|
||||||
|
* @param token
|
||||||
|
* @param game
|
||||||
|
* @param sourceId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public static TokenInfo generateTokenInfo(TokenImpl token, Game game, UUID sourceId) {
|
public static TokenInfo generateTokenInfo(TokenImpl token, Game game, UUID sourceId) {
|
||||||
// Choose a token image by priority:
|
// Choose a token image by priority:
|
||||||
// - use source's set code
|
// - use source's set code
|
||||||
|
|
@ -190,11 +201,10 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
||||||
// TODO: return default creature token image
|
// TODO: return default creature token image
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement Copy image
|
// unknown tokens:
|
||||||
// TODO: implement Manifest image
|
// - without official token sets;
|
||||||
// TODO: implement Morph image
|
// - un-implemented token set (must add missing images to tokens database);
|
||||||
|
// - another use cases with unknown tokens
|
||||||
// unknown tokens
|
|
||||||
return new TokenInfo(TokenType.TOKEN, "Unknown", TokenRepository.XMAGE_TOKENS_SET_CODE, 0);
|
return new TokenInfo(TokenType.TOKEN, "Unknown", TokenRepository.XMAGE_TOKENS_SET_CODE, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ public class Spell extends StackObjectImpl implements Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.card = affectedCard;
|
this.card = affectedCard;
|
||||||
this.manaCost = this.card.getManaCost().copy();
|
this.manaCost = affectedCard.getManaCost().copy();
|
||||||
this.color = affectedCard.getColor(null).copy();
|
this.color = affectedCard.getColor(null).copy();
|
||||||
this.frameColor = affectedCard.getFrameColor(null).copy();
|
this.frameColor = affectedCard.getFrameColor(null).copy();
|
||||||
this.frameStyle = affectedCard.getFrameStyle();
|
this.frameStyle = affectedCard.getFrameStyle();
|
||||||
|
|
@ -100,7 +100,11 @@ public class Spell extends StackObjectImpl implements Card {
|
||||||
this.ability = ability;
|
this.ability = ability;
|
||||||
this.ability.setControllerId(controllerId);
|
this.ability.setControllerId(controllerId);
|
||||||
|
|
||||||
if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.MORPH){
|
if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.MORPH
|
||||||
|
|| ability.getSpellAbilityCastMode() == SpellAbilityCastMode.MEGAMORPH){
|
||||||
|
// TODO: need research:
|
||||||
|
// - why it use game param for color and subtype (possible bug?)
|
||||||
|
// - is it possible to use BecomesFaceDownCreatureEffect.makeFaceDownObject or like that?
|
||||||
this.faceDown = true;
|
this.faceDown = true;
|
||||||
this.getColor(game).setColor(null);
|
this.getColor(game).setColor(null);
|
||||||
game.getState().getCreateMageObjectAttribute(this.getCard(), game).getSubtype().clear();
|
game.getState().getCreateMageObjectAttribute(this.getCard(), game).getSubtype().clear();
|
||||||
|
|
@ -238,6 +242,16 @@ public class Spell extends StackObjectImpl implements Card {
|
||||||
throw new IllegalStateException("Wrong code usage: you can't change card number for the spell");
|
throw new IllegalStateException("Wrong code usage: you can't change card number for the spell");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getImageFileName() {
|
||||||
|
return card.getImageFileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageFileName(String imageFile) {
|
||||||
|
throw new IllegalStateException("Wrong code usage: you can't change image file name for the spell");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getImageNumber() {
|
public Integer getImageNumber() {
|
||||||
return card.getImageNumber();
|
return card.getImageNumber();
|
||||||
|
|
@ -509,6 +523,11 @@ public class Spell extends StackObjectImpl implements Card {
|
||||||
return this.controllerId;
|
return this.controllerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getControllerOrOwnerId() {
|
||||||
|
return getControllerId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return card.getName();
|
return card.getName();
|
||||||
|
|
@ -546,6 +565,11 @@ public class Spell extends StackObjectImpl implements Card {
|
||||||
return card.getRarity();
|
return card.getRarity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRarity(Rarity rarity) {
|
||||||
|
throw new IllegalArgumentException("Un-supported operation: " + this, new Throwable());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<CardType> getCardType(Game game) {
|
public List<CardType> getCardType(Game game) {
|
||||||
if (faceDown) {
|
if (faceDown) {
|
||||||
|
|
@ -933,6 +957,11 @@ public class Spell extends StackObjectImpl implements Card {
|
||||||
return card.getUsesVariousArt();
|
return card.getUsesVariousArt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUsesVariousArt(boolean usesVariousArt) {
|
||||||
|
card.setUsesVariousArt(usesVariousArt);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Mana> getMana() {
|
public List<Mana> getMana() {
|
||||||
return card.getMana();
|
return card.getMana();
|
||||||
|
|
@ -1104,8 +1133,8 @@ public class Spell extends StackObjectImpl implements Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkForCountersToAdd(Permanent permanent, Ability source, Game game) {
|
public void applyEnterWithCounters(Permanent permanent, Ability source, Game game) {
|
||||||
card.checkForCountersToAdd(permanent, source, game);
|
card.applyEnterWithCounters(permanent, source, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,16 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
||||||
throw new IllegalStateException("Wrong code usage: you can't change card number for the stack ability");
|
throw new IllegalStateException("Wrong code usage: you can't change card number for the stack ability");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getImageFileName() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageFileName(String imageFile) {
|
||||||
|
throw new IllegalStateException("Wrong code usage: you can't change image file name for the stack ability");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getImageNumber() {
|
public Integer getImageNumber() {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -301,6 +311,11 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
||||||
return this.controllerId;
|
return this.controllerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getControllerOrOwnerId() {
|
||||||
|
return getControllerId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Costs<Cost> getCosts() {
|
public Costs<Cost> getCosts() {
|
||||||
return emptyCosts;
|
return emptyCosts;
|
||||||
|
|
|
||||||
|
|
@ -842,13 +842,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
private boolean doDiscard(Card card, Ability source, Game game, boolean payForCost, boolean fireFinalEvent) {
|
private boolean doDiscard(Card card, Ability source, Game game, boolean payForCost, boolean fireFinalEvent) {
|
||||||
//20100716 - 701.7
|
//20100716 - 701.7
|
||||||
/* 701.7. Discard #
|
/* 701.7. Discard #
|
||||||
701.7a To discard a card, move it from its owners hand to that players graveyard.
|
701.7a To discard a card, move it from its owner's hand to that player's graveyard.
|
||||||
701.7b By default, effects that cause a player to discard a card allow the affected
|
701.7b By default, effects that cause a player to discard a card allow the affected
|
||||||
player to choose which card to discard. Some effects, however, require a random
|
player to choose which card to discard. Some effects, however, require a random
|
||||||
discard or allow another player to choose which card is discarded.
|
discard or allow another player to choose which card is discarded.
|
||||||
701.7c If a card is discarded, but an effect causes it to be put into a hidden zone
|
701.7c If a card is discarded, but an effect causes it to be put into a hidden zone
|
||||||
instead of into its owners graveyard without being revealed, all values of that
|
instead of into its owner's graveyard without being revealed, all values of that
|
||||||
cards characteristics are considered to be undefined.
|
card's characteristics are considered to be undefined.
|
||||||
TODO:
|
TODO:
|
||||||
If a card is discarded this way to pay a cost that specifies a characteristic
|
If a card is discarded this way to pay a cost that specifies a characteristic
|
||||||
about the discarded card, that cost payment is illegal; the game returns to
|
about the discarded card, that cost payment is illegal; the game returns to
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue