From 743143acde2715c7c6da953bfe44dd86656b55aa Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 9 Mar 2023 21:36:39 +0400 Subject: [PATCH] * GUI: meld cards improves: * added images download for melds cards (#9660); * added switch card side button to view a meld part; * fixed NPE and other errors on card side switching; * added meld cards support in test render dialog; --- .../client/dialog/TestCardRenderDialog.java | 51 ++++++++++++------- .../plugins/adapters/MageActionCallback.java | 7 +-- .../java/org/mage/card/arcane/CardPanel.java | 18 +++++-- .../card/images/DownloadPicturesService.java | 30 ++++++++++- .../src/main/java/mage/view/CardView.java | 7 +++ .../mage/cards/a/ArgothSanctumOfNature.java | 1 + .../src/mage/cards/b/BrunaTheFadingLight.java | 2 + .../src/mage/cards/g/GarrukRelentless.java | 2 +- .../src/mage/cards/g/GatstafArsonists.java | 2 +- .../src/mage/cards/g/GatstafShepherd.java | 2 +- .../mage/cards/g/GiselaTheBrokenBlade.java | 2 + Mage.Sets/src/mage/cards/g/GrafRats.java | 2 + .../mage/cards/g/GrimlockDinobotLeader.java | 2 +- .../src/mage/cards/g/GrizzledAngler.java | 2 +- .../src/mage/cards/h/HanweirBattlements.java | 2 + .../src/mage/cards/h/HanweirGarrison.java | 2 + .../src/mage/cards/h/HinterlandHermit.java | 2 +- .../src/mage/cards/m/MidnightScavengers.java | 2 + .../src/mage/cards/m/MishraClaimedByGix.java | 2 + .../mage/cards/p/PhyrexianDragonEngine.java | 2 + .../cards/t/TheMightstoneAndWeakstone.java | 1 + .../src/mage/cards/t/TitaniaVoiceOfGaea.java | 2 + .../src/mage/cards/u/UrzaLordProtector.java | 2 + Mage/src/main/java/mage/cards/Card.java | 8 +++ Mage/src/main/java/mage/cards/CardImpl.java | 42 ++++++++++++--- Mage/src/main/java/mage/cards/MeldCard.java | 2 + .../java/mage/cards/repository/CardInfo.java | 13 ++++- .../mage/cards/repository/CardRepository.java | 5 ++ .../mage/game/permanent/PermanentCard.java | 5 +- 29 files changed, 180 insertions(+), 42 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java index 15c34e7c46..8bcdaf1216 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java @@ -33,6 +33,7 @@ import mage.game.mulligan.Mulligan; import mage.game.mulligan.MulliganType; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; +import mage.game.permanent.PermanentMeld; import mage.players.Player; import mage.players.StubPlayer; import mage.util.CardUtil; @@ -111,7 +112,7 @@ public class TestCardRenderDialog extends MageDialog { } private PermanentView createPermanentCard(Game game, UUID controllerId, String code, String cardNumber, int powerBoosted, int toughnessBoosted, int damage, boolean tapped, boolean transform, List extraAbilities) { - CardInfo cardInfo = CardRepository.instance.findCard(code, cardNumber); + CardInfo cardInfo = CardRepository.instance.findCard(code, cardNumber, false); ExpansionInfo setInfo = ExpansionRepository.instance.getSetByCode(code); CardSetInfo testSet = new CardSetInfo(cardInfo.getName(), setInfo.getCode(), cardNumber, cardInfo.getRarity(), new CardGraphicInfo(cardInfo.getFrameStyle(), cardInfo.usesVariousArt())); @@ -126,24 +127,31 @@ public class TestCardRenderDialog extends MageDialog { extraAbilities.forEach(ability -> permCard.addAbility(ability)); } - PermanentCard perm = new PermanentCard(permCard, controllerId, game); - if (transform) { - // need direct transform call to keep other side info (original) - TransformAbility.transformPermanent(perm, permCard.getSecondCardFace(), game, null); + // meld card must be a special class, see CardUtils.putCardOntoBattlefieldWithEffects + PermanentCard permanent; + if (permCard instanceof MeldCard) { + permanent = new PermanentMeld(permCard, controllerId, game); + } else { + permanent = new PermanentCard(permCard, controllerId, game); } - if (damage > 0) perm.damage(damage, controllerId, null, game); - if (powerBoosted > 0) perm.getPower().setBoostedValue(powerBoosted); - if (toughnessBoosted > 0) perm.getToughness().setBoostedValue(toughnessBoosted); - perm.removeSummoningSickness(); - perm.setTapped(tapped); - PermanentView cardView = new PermanentView(perm, permCard, controllerId, game); + if (transform) { + // need direct transform call to keep other side info (original) + TransformAbility.transformPermanent(permanent, permCard.getSecondCardFace(), game, null); + } + + if (damage > 0) permanent.damage(damage, controllerId, null, game); + if (powerBoosted > 0) permanent.getPower().setBoostedValue(powerBoosted); + if (toughnessBoosted > 0) permanent.getToughness().setBoostedValue(toughnessBoosted); + permanent.removeSummoningSickness(); + permanent.setTapped(tapped); + PermanentView cardView = new PermanentView(permanent, permCard, controllerId, game); return cardView; } private CardView createFaceDownCard(Game game, UUID controllerId, String code, String cardNumber, boolean isMorphed, boolean isManifested, boolean tapped) { - CardInfo cardInfo = CardRepository.instance.findCard(code, cardNumber); + CardInfo cardInfo = CardRepository.instance.findCard(code, cardNumber, false); ExpansionInfo setInfo = ExpansionRepository.instance.getSetByCode(code); CardSetInfo testSet = new CardSetInfo(cardInfo.getName(), setInfo.getCode(), cardNumber, cardInfo.getRarity(), new CardGraphicInfo(cardInfo.getFrameStyle(), cardInfo.usesVariousArt())); @@ -170,7 +178,7 @@ public class TestCardRenderDialog extends MageDialog { } private CardView createHandCard(Game game, UUID controllerId, String code, String cardNumber) { - CardInfo cardInfo = CardRepository.instance.findCard(code, cardNumber); + CardInfo cardInfo = CardRepository.instance.findCard(code, cardNumber, false); ExpansionInfo setInfo = ExpansionRepository.instance.getSetByCode(code); CardSetInfo testSet = new CardSetInfo(cardInfo.getName(), setInfo.getCode(), cardNumber, cardInfo.getRarity(), new CardGraphicInfo(cardInfo.getFrameStyle(), cardInfo.usesVariousArt())); @@ -303,12 +311,21 @@ public class TestCardRenderDialog extends MageDialog { //test split, transform and mdf in hands cardViews.add(createHandCard(game, playerYou.getId(), "SOI", "97")); // Accursed Witch - cardViews.add(createHandCard(game, playerYou.getId(), "UMA", "225")); // Fire // Ice - cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "14")); // Giant Killer - cardViews.add(createHandCard(game, playerYou.getId(), "ZNR", "134")); // Akoum Warrior + //cardViews.add(createHandCard(game, playerYou.getId(), "UMA", "225")); // Fire // Ice + //cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "14")); // Giant Killer + //cardViews.add(createHandCard(game, playerYou.getId(), "ZNR", "134")); // Akoum Warrior //*/ - //* //test card icons + // test meld cards in hands and battlefield + cardViews.add(createHandCard(game, playerYou.getId(), "EMN", "204")); // Hanweir Battlements + cardViews.add(createHandCard(game, playerYou.getId(), "EMN", "130a")); // Hanweir Garrison + cardViews.add(createHandCard(game, playerYou.getId(), "EMN", "130b")); // Hanweir, the Writhing Township + cardViews.add(createPermanentCard(game, playerYou.getId(), "EMN", "204", 1, 1, 0, false, false, null)); // Hanweir Battlements + cardViews.add(createPermanentCard(game, playerYou.getId(), "EMN", "130a", 1, 1, 0, false, false, null)); // Hanweir Garrison + cardViews.add(createPermanentCard(game, playerYou.getId(), "EMN", "130b", 1, 1, 0, false, false, null)); // Hanweir, the Writhing Township + //*/ + + /* //test card icons cardViews.add(createHandCard(game, playerYou.getId(), "POR", "169")); // Grizzly Bears cardViews.add(createHandCard(game, playerYou.getId(), "DKA", "140")); // Huntmaster of the Fells, transforms cardViews.add(createPermanentCard(game, playerYou.getId(), "DKA", "140", 3, 3, 1, false, true, additionalIcons)); // Huntmaster of the Fells, transforms diff --git a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java index 1268264ef1..a83ba5ebdb 100644 --- a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java +++ b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java @@ -672,7 +672,7 @@ public class MageActionCallback implements ActionCallback { popupContainer.setVisible(true); // popup hint mode - Image image = null; + Image image = cardPanel.getImage(); CardView displayCard = cardPanel.getOriginal(); switch (enlargeMode) { case COPY: @@ -696,12 +696,9 @@ public class MageActionCallback implements ActionCallback { default: break; } - if (image == null) { - image = cardPanel.getImage(); - } + // shows the card in the popup Container displayCardInfo(displayCard, image, (BigCard) cardPreviewPane); - } else { logger.warn("No Card preview Pane in Mage Frame defined. Card: " + cardView.getName()); } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 501ae1fcf2..53f67633b0 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -856,7 +856,7 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen if (this.guiTransformed) { // main side -> alternative side if (this.cardSideOther == null) { - logger.error("no second side for card to transform!"); + logger.error("can't find second side to toggle transform from main to second: " + this.getCard().getName()); return; } copySelections(this.cardSideMain, this.cardSideOther); @@ -864,6 +864,10 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen this.getGameCard().setAlternateName(this.cardSideMain.getName()); } else { // alternative side -> main side + if (this.cardSideOther == null) { + logger.error("can't find second side to toggle transform from second side to main: " + this.getCard().getName()); + return; + } copySelections(this.cardSideOther, this.cardSideMain); update(this.cardSideMain); this.getGameCard().setAlternateName(this.cardSideOther.getName()); @@ -945,10 +949,16 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen } } - // fix other side: if it's a night side permanent then the main side info must be extracted + // fix other side: if it's a night side permanent then the main side info can be extracted from original if (this.cardSideOther == null || this.cardSideOther.getName().equals(this.cardSideMain.getName())) { - if (this.cardSideMain instanceof PermanentView) { - this.cardSideOther = ((PermanentView) this.cardSideMain).getOriginal(); + if ((this.cardSideMain instanceof PermanentView)) { + // some "transformed" cards don't have info about main side + // (example: melded card have two main sides/cards), + // so it must be ignored until multiple hints implement like mtga + CardView original = ((PermanentView) this.cardSideMain).getOriginal(); + if (original != null && !original.getName().equals(this.getName())) { + this.cardSideOther = original; + } } } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index 33f6a1e509..cfab3fac27 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -449,7 +449,8 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements // main side allCardsUrls.add(url); - // second side (xmage's set doesn't have info about it, so generate it here) + // second side + // xmage doesn't search night cards by default, so add it and other types manually if (card.isDoubleFaced()) { if (card.getSecondSideName() == null || card.getSecondSideName().trim().isEmpty()) { throw new IllegalStateException("Second side card can't have empty name."); @@ -460,7 +461,12 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements throw new IllegalStateException("Can''t find second side card in database: " + card.getSecondSideName()); } - url = new CardDownloadData(card.getSecondSideName(), card.getSetCode(), secondSideCard.getCardNumber(), card.usesVariousArt(), 0, "", "", false, card.isDoubleFaced(), true); + url = new CardDownloadData( + card.getSecondSideName(), + card.getSetCode(), + secondSideCard.getCardNumber(), + card.usesVariousArt(), + 0, "", "", false, card.isDoubleFaced(), true); url.setType2(isType2); allCardsUrls.add(url); } @@ -479,6 +485,26 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements cardDownloadData.setType2(isType2); allCardsUrls.add(cardDownloadData); } + if (card.getMeldsToCardName() != null) { + if (card.getMeldsToCardName().trim().isEmpty()) { + throw new IllegalStateException("MeldsToCardName can't be empty in " + card.getName()); + } + + CardInfo meldsToCard = CardRepository.instance.findCardWPreferredSet(card.getMeldsToCardName(), card.getSetCode()); + if (meldsToCard == null) { + throw new IllegalStateException("Can''t find meldsToCard in database: " + card.getMeldsToCardName()); + } + + // meld cards are normal cards from the set, so no needs to set two faces/sides here + url = new CardDownloadData( + card.getMeldsToCardName(), + card.getSetCode(), + meldsToCard.getCardNumber(), + card.usesVariousArt(), + 0, "", "", false, false, false); + url.setType2(isType2); + allCardsUrls.add(url); + } if (card.isModalDoubleFacesCard()) { if (card.getModalDoubleFacesSecondSideName() == null || card.getModalDoubleFacesSecondSideName().trim().isEmpty()) { throw new IllegalStateException("MDF card can't have empty name."); diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index 513b7e29a6..fe792aff71 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -519,6 +519,13 @@ public class CardView extends SimpleCardView { this.alternateName = mdfCard.getRightHalfCard().getName(); } + Card meldsToCard = card.getMeldsToCard(); + if (meldsToCard != null) { + this.transformable = true; // enable GUI day/night button + this.secondCardFace = new CardView(meldsToCard, game); + this.alternateName = meldsToCard.getName(); + } + if (card instanceof Spell) { this.mageObjectType = MageObjectType.SPELL; Spell spell = (Spell) card; diff --git a/Mage.Sets/src/mage/cards/a/ArgothSanctumOfNature.java b/Mage.Sets/src/mage/cards/a/ArgothSanctumOfNature.java index f4321fb91f..553704268e 100644 --- a/Mage.Sets/src/mage/cards/a/ArgothSanctumOfNature.java +++ b/Mage.Sets/src/mage/cards/a/ArgothSanctumOfNature.java @@ -47,6 +47,7 @@ public final class ArgothSanctumOfNature extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); this.meldsWithClazz = mage.cards.t.TitaniaVoiceOfGaea.class; + this.meldsToClazz = mage.cards.h.HanweirTheWrithingTownship.class; // Argoth, Sanctum of Nature enters the battlefield tapped unless you control a legendary green creature. this.addAbility(new EntersBattlefieldAbility( diff --git a/Mage.Sets/src/mage/cards/b/BrunaTheFadingLight.java b/Mage.Sets/src/mage/cards/b/BrunaTheFadingLight.java index 5e438f6572..1f59d4a3b0 100644 --- a/Mage.Sets/src/mage/cards/b/BrunaTheFadingLight.java +++ b/Mage.Sets/src/mage/cards/b/BrunaTheFadingLight.java @@ -42,7 +42,9 @@ public final class BrunaTheFadingLight extends CardImpl { this.subtype.add(SubType.ANGEL, SubType.HORROR); this.power = new MageInt(5); this.toughness = new MageInt(7); + this.meldsWithClazz = mage.cards.g.GiselaTheBrokenBlade.class; + this.meldsToClazz = mage.cards.b.BriselaVoiceOfNightmares.class; // When you cast Bruna, the Fading Light, you may return target Angel or Human creature card from your graveyard to the battlefield. Effect effect = new ReturnFromGraveyardToBattlefieldTargetEffect(); diff --git a/Mage.Sets/src/mage/cards/g/GarrukRelentless.java b/Mage.Sets/src/mage/cards/g/GarrukRelentless.java index f81ee1874a..073f4d748b 100644 --- a/Mage.Sets/src/mage/cards/g/GarrukRelentless.java +++ b/Mage.Sets/src/mage/cards/g/GarrukRelentless.java @@ -30,7 +30,7 @@ public final class GarrukRelentless extends CardImpl { this.addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.GARRUK); - this.secondSideCardClazz = GarrukTheVeilCursed.class; + this.secondSideCardClazz = mage.cards.g.GarrukTheVeilCursed.class; this.setStartingLoyalty(3); diff --git a/Mage.Sets/src/mage/cards/g/GatstafArsonists.java b/Mage.Sets/src/mage/cards/g/GatstafArsonists.java index 198d47e1d7..95ee3a5336 100644 --- a/Mage.Sets/src/mage/cards/g/GatstafArsonists.java +++ b/Mage.Sets/src/mage/cards/g/GatstafArsonists.java @@ -22,7 +22,7 @@ public final class GatstafArsonists extends CardImpl { this.power = new MageInt(5); this.toughness = new MageInt(4); - this.secondSideCardClazz = GatstafRavagers.class; + this.secondSideCardClazz = mage.cards.g.GatstafRavagers.class; // At the beginning of each upkeep, if no spells were cast last turn, transform Gatstaf Arsonists. this.addAbility(new TransformAbility()); diff --git a/Mage.Sets/src/mage/cards/g/GatstafShepherd.java b/Mage.Sets/src/mage/cards/g/GatstafShepherd.java index 47d1bb9d28..c23426f489 100644 --- a/Mage.Sets/src/mage/cards/g/GatstafShepherd.java +++ b/Mage.Sets/src/mage/cards/g/GatstafShepherd.java @@ -20,7 +20,7 @@ public final class GatstafShepherd extends CardImpl { this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WEREWOLF); - this.secondSideCardClazz = GatstafHowler.class; + this.secondSideCardClazz = mage.cards.g.GatstafHowler.class; this.power = new MageInt(2); this.toughness = new MageInt(2); diff --git a/Mage.Sets/src/mage/cards/g/GiselaTheBrokenBlade.java b/Mage.Sets/src/mage/cards/g/GiselaTheBrokenBlade.java index 90038a94d1..d1b4597d1c 100644 --- a/Mage.Sets/src/mage/cards/g/GiselaTheBrokenBlade.java +++ b/Mage.Sets/src/mage/cards/g/GiselaTheBrokenBlade.java @@ -31,7 +31,9 @@ public final class GiselaTheBrokenBlade extends CardImpl { this.subtype.add(SubType.HORROR); this.power = new MageInt(4); this.toughness = new MageInt(3); + this.meldsWithClazz = mage.cards.b.BrunaTheFadingLight.class; + this.meldsToClazz = mage.cards.b.BriselaVoiceOfNightmares.class; // Flying this.addAbility(FlyingAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/g/GrafRats.java b/Mage.Sets/src/mage/cards/g/GrafRats.java index b3b3eb1078..93de703ed8 100644 --- a/Mage.Sets/src/mage/cards/g/GrafRats.java +++ b/Mage.Sets/src/mage/cards/g/GrafRats.java @@ -26,7 +26,9 @@ public final class GrafRats extends CardImpl { this.subtype.add(SubType.RAT); this.power = new MageInt(2); this.toughness = new MageInt(1); + this.meldsWithClazz = mage.cards.m.MidnightScavengers.class; + this.meldsToClazz = mage.cards.c.ChitteringHost.class; // At the beginning of combat on your turn, if you both own and control Graf Rats and a creature named Midnight Scavengers, exile them, then meld them into Chittering Host. this.addAbility(new ConditionalInterveningIfTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java b/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java index 74deb601ba..d0ab1d99f8 100644 --- a/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java +++ b/Mage.Sets/src/mage/cards/g/GrimlockDinobotLeader.java @@ -47,7 +47,7 @@ public final class GrimlockDinobotLeader extends CardImpl{ this.power = new MageInt(4); this.toughness = new MageInt(4); - this.secondSideCardClazz = GrimlockFerociousKing.class; + this.secondSideCardClazz = mage.cards.g.GrimlockFerociousKing.class; // Dinosaurs, Vehicles and other Transformers creatures you control get +2/+0. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(2, 0, Duration.WhileOnBattlefield, filter, false))); diff --git a/Mage.Sets/src/mage/cards/g/GrizzledAngler.java b/Mage.Sets/src/mage/cards/g/GrizzledAngler.java index 59d3015cdc..0eb8e58948 100644 --- a/Mage.Sets/src/mage/cards/g/GrizzledAngler.java +++ b/Mage.Sets/src/mage/cards/g/GrizzledAngler.java @@ -32,7 +32,7 @@ public final class GrizzledAngler extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(3); - this.secondSideCardClazz = GrislyAnglerfish.class; + this.secondSideCardClazz = mage.cards.g.GrislyAnglerfish.class; // {T}: Put the top two cards of your library into your graveyard. Then if there is a colorless creature card in your graveyard, transform Grizzled Angler. this.addAbility(new TransformAbility()); diff --git a/Mage.Sets/src/mage/cards/h/HanweirBattlements.java b/Mage.Sets/src/mage/cards/h/HanweirBattlements.java index d3e30ca1bd..bbb155ab7f 100644 --- a/Mage.Sets/src/mage/cards/h/HanweirBattlements.java +++ b/Mage.Sets/src/mage/cards/h/HanweirBattlements.java @@ -29,7 +29,9 @@ public final class HanweirBattlements extends CardImpl { public HanweirBattlements(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + this.meldsWithClazz = mage.cards.h.HanweirGarrison.class; + this.meldsToClazz = mage.cards.h.HanweirTheWrithingTownship.class; // {T}: Add {C}. this.addAbility(new ColorlessManaAbility()); diff --git a/Mage.Sets/src/mage/cards/h/HanweirGarrison.java b/Mage.Sets/src/mage/cards/h/HanweirGarrison.java index fd1c762372..4eb2f3b996 100644 --- a/Mage.Sets/src/mage/cards/h/HanweirGarrison.java +++ b/Mage.Sets/src/mage/cards/h/HanweirGarrison.java @@ -26,7 +26,9 @@ public final class HanweirGarrison extends CardImpl { this.subtype.add(SubType.SOLDIER); this.power = new MageInt(2); this.toughness = new MageInt(3); + this.meldsWithClazz = mage.cards.h.HanweirBattlements.class; + this.meldsToClazz = mage.cards.h.HanweirTheWrithingTownship.class; // Whenever Hanweir Garrison attacks, create two 1/1 red Human creature tokens tapped and attacking. this.addAbility(new AttacksTriggeredAbility(new CreateTokenEffect(new RedHumanToken(), 2, true, true), false)); diff --git a/Mage.Sets/src/mage/cards/h/HinterlandHermit.java b/Mage.Sets/src/mage/cards/h/HinterlandHermit.java index 549ec1ed1b..e376e0ebde 100644 --- a/Mage.Sets/src/mage/cards/h/HinterlandHermit.java +++ b/Mage.Sets/src/mage/cards/h/HinterlandHermit.java @@ -20,7 +20,7 @@ public final class HinterlandHermit extends CardImpl { this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WEREWOLF); - this.secondSideCardClazz = HinterlandScourge.class; + this.secondSideCardClazz = mage.cards.h.HinterlandScourge.class; this.power = new MageInt(2); this.toughness = new MageInt(1); diff --git a/Mage.Sets/src/mage/cards/m/MidnightScavengers.java b/Mage.Sets/src/mage/cards/m/MidnightScavengers.java index a1f2d7ff2b..6f0c3caf0e 100644 --- a/Mage.Sets/src/mage/cards/m/MidnightScavengers.java +++ b/Mage.Sets/src/mage/cards/m/MidnightScavengers.java @@ -37,7 +37,9 @@ public final class MidnightScavengers extends CardImpl { this.subtype.add(SubType.ROGUE); this.power = new MageInt(3); this.toughness = new MageInt(3); + this.meldsWithClazz = mage.cards.g.GrafRats.class; + this.meldsToClazz = mage.cards.c.ChitteringHost.class; // When Midnight Scavengers enters the battlefield, you may return target creature card with converted mana cost 3 or less from your graveyard to your hand. Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect(), true); diff --git a/Mage.Sets/src/mage/cards/m/MishraClaimedByGix.java b/Mage.Sets/src/mage/cards/m/MishraClaimedByGix.java index 25302639bd..70bfb2e9c9 100644 --- a/Mage.Sets/src/mage/cards/m/MishraClaimedByGix.java +++ b/Mage.Sets/src/mage/cards/m/MishraClaimedByGix.java @@ -38,7 +38,9 @@ public final class MishraClaimedByGix extends CardImpl { this.subtype.add(SubType.ARTIFICER); this.power = new MageInt(3); this.toughness = new MageInt(5); + this.meldsWithClazz = mage.cards.p.PhyrexianDragonEngine.class; + this.meldsToClazz = mage.cards.m.MishraLostToPhyrexia.class; // Whenever you attack, each opponent loses X life and you gain X life, where X is the number of attacking creatures. If Mishra, Claimed by Gix and a creature named Phyrexian Dragon Engine are attacking, and you both own and control them, exile them, then meld them into Mishra, Lost to Phyrexia. It enters the battlefield tapped and attacking. Ability ability = new AttacksWithCreaturesTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianDragonEngine.java b/Mage.Sets/src/mage/cards/p/PhyrexianDragonEngine.java index 13801c8287..a7ccec363a 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianDragonEngine.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianDragonEngine.java @@ -31,7 +31,9 @@ public final class PhyrexianDragonEngine extends CardImpl { this.subtype.add(SubType.DRAGON); this.power = new MageInt(2); this.toughness = new MageInt(2); + this.meldsWithClazz = mage.cards.m.MishraClaimedByGix.class; + this.meldsToClazz = mage.cards.m.MishraLostToPhyrexia.class; // Double strike this.addAbility(DoubleStrikeAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/t/TheMightstoneAndWeakstone.java b/Mage.Sets/src/mage/cards/t/TheMightstoneAndWeakstone.java index aa757a21b2..f2823578e2 100644 --- a/Mage.Sets/src/mage/cards/t/TheMightstoneAndWeakstone.java +++ b/Mage.Sets/src/mage/cards/t/TheMightstoneAndWeakstone.java @@ -31,6 +31,7 @@ public final class TheMightstoneAndWeakstone extends CardImpl { this.subtype.add(SubType.POWERSTONE); this.meldsWithClazz = mage.cards.u.UrzaLordProtector.class; + this.meldsToClazz = mage.cards.u.UrzaPlaneswalker.class; // When The Mightstone and Weakstone enters the battlefield, choose one -- // * Draw two cards. diff --git a/Mage.Sets/src/mage/cards/t/TitaniaVoiceOfGaea.java b/Mage.Sets/src/mage/cards/t/TitaniaVoiceOfGaea.java index 0a4c96edad..30035b2509 100644 --- a/Mage.Sets/src/mage/cards/t/TitaniaVoiceOfGaea.java +++ b/Mage.Sets/src/mage/cards/t/TitaniaVoiceOfGaea.java @@ -39,7 +39,9 @@ public final class TitaniaVoiceOfGaea extends CardImpl { this.subtype.add(SubType.ELEMENTAL); this.power = new MageInt(3); this.toughness = new MageInt(4); + this.meldsWithClazz = mage.cards.a.ArgothSanctumOfNature.class; + this.meldsToClazz = mage.cards.t.TitaniaGaeaIncarnate.class; // Reach this.addAbility(ReachAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/u/UrzaLordProtector.java b/Mage.Sets/src/mage/cards/u/UrzaLordProtector.java index d93d5fa610..3bef247e99 100644 --- a/Mage.Sets/src/mage/cards/u/UrzaLordProtector.java +++ b/Mage.Sets/src/mage/cards/u/UrzaLordProtector.java @@ -46,7 +46,9 @@ public final class UrzaLordProtector extends CardImpl { this.subtype.add(SubType.ARTIFICER); this.power = new MageInt(2); this.toughness = new MageInt(4); + this.meldsWithClazz = mage.cards.t.TheMightstoneAndWeakstone.class; + this.meldsToClazz = mage.cards.u.UrzaPlaneswalker.class; // Artifact, instant, and sorcery spells you cast cost {1} less to cast. this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); diff --git a/Mage/src/main/java/mage/cards/Card.java b/Mage/src/main/java/mage/cards/Card.java index a035a256fa..71ddf58ef0 100644 --- a/Mage/src/main/java/mage/cards/Card.java +++ b/Mage/src/main/java/mage/cards/Card.java @@ -80,6 +80,14 @@ public interface Card extends MageObject { return false; } + default Class getMeldsToClazz() { + return null; + } + + default Card getMeldsToCard() { + return null; + } + void assignNewId(); void addInfo(String key, String value, Game game); diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index b035daed60..2d279d09d7 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -47,6 +47,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { protected Rarity rarity; protected Class secondSideCardClazz; protected Class meldsWithClazz; + protected Class meldsToClazz; + protected Card meldsToCard; protected Card secondSideCard; protected boolean nightCard; protected SpellAbility spellAbility; @@ -127,6 +129,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { secondSideCard = null; // will be set on first getSecondCardFace call if card has one nightCard = card.nightCard; meldsWithClazz = card.meldsWithClazz; + meldsToClazz = card.meldsToClazz; + meldsToCard = null; // will be set on first getMeldsToCard call if card has one spellAbility = null; // will be set on first getSpellAbility call if card has one flipCard = card.flipCard; @@ -614,29 +618,36 @@ public abstract class CardImpl extends MageObjectImpl implements Card { @Override public boolean isTransformable() { + // warning, not all multifaces cards can be transformable (meld, mdfc) + // mtg rules method: here + // GUI related method: search "transformable = true" in CardView + // TODO: check and fix method usage in game engine, it's must be mtg rules logic, not GUI return this.secondSideCardClazz != null || this.nightCard; } @Override public final Card getSecondCardFace() { - // init second side card on first call + // init card side on first call if (secondSideCardClazz == null && secondSideCard == null) { return null; } - if (secondSideCard != null) { - return secondSideCard; + if (secondSideCard == null) { + secondSideCard = initSecondSideCard(secondSideCardClazz); } + return secondSideCard; + } + + private Card initSecondSideCard(Class cardClazz) { // must be non strict search in any sets, not one set // example: if set contains only one card side // method used in cards database creating, so can't use repository here - ExpansionSet.SetCardInfo info = Sets.findCardByClass(secondSideCardClazz, expansionSetCode); + ExpansionSet.SetCardInfo info = Sets.findCardByClass(cardClazz, expansionSetCode); if (info == null) { return null; } - secondSideCard = createCard(secondSideCardClazz, new CardSetInfo(info.getName(), expansionSetCode, info.getCardNumber(), info.getRarity(), info.getGraphicInfo())); - return secondSideCard; + return createCard(cardClazz, new CardSetInfo(info.getName(), expansionSetCode, info.getCardNumber(), info.getRarity(), info.getGraphicInfo())); } @Override @@ -653,6 +664,25 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return this.meldsWithClazz != null && this.meldsWithClazz.isInstance(card.getMainCard()); } + @Override + public Class getMeldsToClazz() { + return this.meldsToClazz; + } + + @Override + public Card getMeldsToCard() { + // init card on first call + if (meldsToClazz == null && meldsToCard == null) { + return null; + } + + if (meldsToCard == null) { + meldsToCard = initSecondSideCard(meldsToClazz); + } + + return meldsToCard; + } + @Override public boolean isNightCard() { return this.nightCard; diff --git a/Mage/src/main/java/mage/cards/MeldCard.java b/Mage/src/main/java/mage/cards/MeldCard.java index 75d3a49fdc..1ede54932b 100644 --- a/Mage/src/main/java/mage/cards/MeldCard.java +++ b/Mage/src/main/java/mage/cards/MeldCard.java @@ -109,6 +109,8 @@ public abstract class MeldCard extends CardImpl { @Override public boolean isTransformable() { + // there are multiple day cards for one meld card, so can't show it as second side + // TODO: can be fixed after mutiple sides implement, e.g. with Mutate support return false; } diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index 4c21b18d57..61eef41476 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -93,7 +93,7 @@ public class CardInfo { protected boolean flipCard; @DatabaseField protected boolean doubleFaced; - @DatabaseField(indexName = "name_index") + @DatabaseField(indexName = "nightCard_index") protected boolean nightCard; @DatabaseField protected String flipCardName; @@ -107,6 +107,8 @@ public class CardInfo { protected boolean modalDoubleFacesCard; @DatabaseField protected String modalDoubleFacesSecondSideName; + @DatabaseField + protected String meldsToCardName; // if you add new field with card side name then update CardRepository.addNewNames too @@ -134,6 +136,11 @@ public class CardInfo { this.flipCard = card.isFlipCard(); this.flipCardName = card.getFlipCardName(); + Card meldToCard = card.getMeldsToCard(); + if (meldToCard != null) { + this.meldsToCardName = meldToCard.getName(); + } + this.doubleFaced = card.isTransformable() && card.getSecondCardFace() != null; this.nightCard = card.isNightCard(); Card secondSide = card.getSecondCardFace(); @@ -437,6 +444,10 @@ public class CardInfo { return flipCardName; } + public String getMeldsToCardName() { + return meldsToCardName; + } + public boolean isDoubleFaced() { return doubleFaced; } diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index b912ed8c59..22fe740560 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -117,6 +117,9 @@ public enum CardRepository { if (card.getFlipCardName() != null && !card.getFlipCardName().isEmpty()) { namesList.add(card.getFlipCardName()); } + if (card.getMeldsToCardName() != null && !card.getMeldsToCardName().isEmpty()) { + namesList.add(card.getMeldsToCardName()); + } } public static Boolean haveSnowLands(String setCode) { @@ -528,6 +531,8 @@ public enum CardRepository { * Warning, don't use db functions in card's code - it generates heavy db loading in AI simulations. If you * need that feature then check for simulation mode. See https://github.com/magefree/mage/issues/7014 * + * Ignoring night cards by default + * * @param criteria * @return */ diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index f31b2a5e2f..0f24a33334 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -71,7 +71,7 @@ public class PermanentCard extends PermanentImpl { if (card instanceof LevelerCard) { maxLevelCounters = ((LevelerCard) card).getMaxLevelCounters(); } - if (isTransformable()) { + if (card.isTransformable()) { if (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId()) != null || NightboundAbility.checkCard(this, game)) { game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId(), null); @@ -133,6 +133,9 @@ public class PermanentCard extends PermanentImpl { if (card.getSecondCardFace() != null) { this.secondSideCardClazz = card.getSecondCardFace().getClass(); } + if (card.getMeldsToCard() != null) { + this.meldsToClazz = card.getMeldsToCard().getClass(); + } this.nightCard = card.isNightCard(); this.flipCard = card.isFlipCard(); this.flipCardName = card.getFlipCardName();