From 743143acde2715c7c6da953bfe44dd86656b55aa Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 9 Mar 2023 21:36:39 +0400 Subject: [PATCH 1/8] * 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(); From d67376641acb015d8edaa77beb281b0e770a25cd Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 9 Mar 2023 22:07:21 +0400 Subject: [PATCH 2/8] GUI: fixed wrong card icon rendering after theme change --- Mage.Client/src/main/java/mage/client/themes/ThemeType.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mage.Client/src/main/java/mage/client/themes/ThemeType.java b/Mage.Client/src/main/java/mage/client/themes/ThemeType.java index 327defdf2b..5122baf1ab 100644 --- a/Mage.Client/src/main/java/mage/client/themes/ThemeType.java +++ b/Mage.Client/src/main/java/mage/client/themes/ThemeType.java @@ -3,6 +3,7 @@ package mage.client.themes; import mage.abilities.hint.HintUtils; import mage.abilities.icon.CardIconColor; import org.mage.card.arcane.SvgUtils; +import org.mage.plugins.card.images.ImageCache; import java.awt.*; @@ -347,5 +348,8 @@ public enum ThemeType { for (CardIconColor cardIconColor : CardIconColor.values()) { SvgUtils.prepareCss(this.getCardIconsCssFile(cardIconColor), this.getCardIconsCssSettings(cardIconColor), true); } + + // reload card icons and other rendering things from cache - it can depend on current theme + ImageCache.clearCache(); } } \ No newline at end of file From 29e1c9b318fc4760ca3d935ba4fec9fafcc24c57 Mon Sep 17 00:00:00 2001 From: chesse20 Date: Thu, 9 Mar 2023 16:16:06 -0800 Subject: [PATCH 3/8] [USG] Fix Shower of Sparks (#10106) Co-authored-by: Alex W. Jackson --- Mage.Sets/src/mage/cards/s/ShowerOfSparks.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/ShowerOfSparks.java b/Mage.Sets/src/mage/cards/s/ShowerOfSparks.java index 6ae115872e..db144515b3 100644 --- a/Mage.Sets/src/mage/cards/s/ShowerOfSparks.java +++ b/Mage.Sets/src/mage/cards/s/ShowerOfSparks.java @@ -1,15 +1,12 @@ - package mage.cards.s; import java.util.UUID; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetPlayerOrPlaneswalker; -import mage.target.targetpointer.SecondTargetPointer; /** * @@ -19,15 +16,10 @@ public final class ShowerOfSparks extends CardImpl { public ShowerOfSparks(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); - - // Shower of Sparks deals 1 damage to target creature and 1 damage to target player. - this.getSpellAbility().addEffect(new DamageTargetEffect(1)); + + // Shower of sparks deals 1 damage to target creature and 1 damage to target player. + this.getSpellAbility().addEffect(new DamageTargetEffect(1, true, "target creature and 1 damage to target player or planeswalker")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); - - Effect effect = new DamageTargetEffect(1); - effect.setTargetPointer(new SecondTargetPointer()); - effect.setText("and 1 damage to target player"); - this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetPlayerOrPlaneswalker()); } From 18a3ec52474003a9b4c6045d12b4f02832e66112 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 11 Mar 2023 12:28:58 +0400 Subject: [PATCH 4/8] DB: fixed wrong result for night cards search (fixed double faced cards in test render dialog) --- .../client/dialog/TestCardRenderDialog.java | 13 ++++- .../mage/test/utils/CardRepositoryTest.java | 58 +++++++++++++------ .../java/mage/verify/VerifyCardDataTest.java | 16 ++++- .../mage/cards/repository/CardRepository.java | 4 ++ 4 files changed, 71 insertions(+), 20 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 8bcdaf1216..17f9d05e8e 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java @@ -316,7 +316,7 @@ public class TestCardRenderDialog extends MageDialog { //cardViews.add(createHandCard(game, playerYou.getId(), "ZNR", "134")); // Akoum Warrior //*/ - // test meld cards in hands and battlefield + /*// 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 @@ -325,7 +325,16 @@ public class TestCardRenderDialog extends MageDialog { cardViews.add(createPermanentCard(game, playerYou.getId(), "EMN", "130b", 1, 1, 0, false, false, null)); // Hanweir, the Writhing Township //*/ - /* //test card icons + // test variant double faced cards (main and second sides must be same pair) + cardViews.add(createHandCard(game, playerYou.getId(), "VOW", "65")); // Jacob Hauken, Inspector + cardViews.add(createHandCard(game, playerYou.getId(), "VOW", "320")); // Jacob Hauken, Inspector + cardViews.add(createHandCard(game, playerYou.getId(), "VOW", "332")); // Jacob Hauken, Inspector + cardViews.add(createPermanentCard(game, playerYou.getId(), "VOW", "65", 1, 1, 0, false, false, null)); // Jacob Hauken, Inspector + cardViews.add(createPermanentCard(game, playerYou.getId(), "VOW", "320", 1, 1, 0, false, false, null)); // Jacob Hauken, Inspector + cardViews.add(createPermanentCard(game, playerYou.getId(), "VOW", "332", 1, 1, 0, false, false, null)); // Jacob Hauken, Inspector + //*/ + + /*//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.Tests/src/test/java/org/mage/test/utils/CardRepositoryTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/CardRepositoryTest.java index c047431553..b2ac202ff5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/CardRepositoryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/CardRepositoryTest.java @@ -2,7 +2,9 @@ package org.mage.test.utils; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; +import mage.cards.repository.CardScanner; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import java.util.List; @@ -10,17 +12,22 @@ import java.util.List; /** * Testing of CardRepository functionality. * - * @author Alex-Vasile + * @author Alex-Vasile, JayDi85 */ public class CardRepositoryTest { + @Before + public void setUp() { + CardScanner.scan(); + } + /** * Test CardRepository.findCards for the difficult cases when provided with the full card name. - * + *

* CardRepository.findCards is used only for testing purposes. */ @Test - public void testFindSplitCardsByFullName() { + public void test_FindSplitCardsByFullName() { // Modal double-faced assertFindCard("Malakir Rebirth // Malakir Mire"); // Transform double-faced @@ -35,11 +42,11 @@ public class CardRepositoryTest { /** * Test CardRepository.findCards for the difficult cases. - * + *

* CardRepository.findCards is used only for testing purposes. */ @Test - public void testFindSplitCardsByMainName() { + public void test_FindSplitCardsByMainName() { // Modal double-faced assertFindCard("Malakir Rebirth"); // Transform double-faced @@ -54,11 +61,11 @@ public class CardRepositoryTest { /** * Test CardRepository.findCards for the difficult cases. - * + *

* CardRepository.findCards is used only for testing purposes. */ @Test - public void testFindSplitCardsBySecondName() { + public void test_FindSplitCardsBySecondName() { // Modal double-faced assertFindCard("Malakir Mire"); // Transform double-faced @@ -73,11 +80,11 @@ public class CardRepositoryTest { /** * Test CardRepository.findCardsCaseInsensitive for the difficult cases. - * + *

* CardRepository.findCardsCaseInsensitive is used for actual game */ @Test - public void testFindSplitCardsByFullNameCaseInsensitive() { + public void test_FindSplitCardsByFullNameCaseInsensitive() { // Modal double-faced assertFindCard("malakIR rebirTH // maLAkir mIRe"); // Transform double-faced @@ -92,11 +99,11 @@ public class CardRepositoryTest { /** * Test CardRepository.findCards for the difficult cases. - * + *

* CardRepository.findCards is used only for testing purposes. */ @Test - public void testFindSplitCardsByMainNameCaseInsensitive() { + public void test_FindSplitCardsByMainNameCaseInsensitive() { // Modal double-faced assertFindCard("malakIR rebirTH"); // Transform double-faced @@ -111,11 +118,11 @@ public class CardRepositoryTest { /** * Test CardRepository.findCards for the difficult cases. - * + *

* CardRepository.findCards is used only for testing purposes. */ @Test - public void testFindSplitCardsBySecondNameCaseInsensitive() { + public void test_FindSplitCardsBySecondNameCaseInsensitive() { // Modal double-faced assertFindCard("maLAkir mIRe"); // Transform double-faced @@ -130,13 +137,13 @@ public class CardRepositoryTest { /** * Reported bug: https://github.com/magefree/mage/issues/9533 - * + *

* Each half of a split card displays the combined information of both halves in the deck editor. - * + *

* `findCards`'s `returnSplitCardHalf` parameter should handle this issue */ @Test - public void splitCardInfoIsntDoubled() { + public void test_splitCardInfoIsntDoubled() { // Consecrate // Consume // {1}{W/B} // {2}{W}{B} List fullCard1 = CardRepository.instance.findCards("Consecrate", 1, false); @@ -146,7 +153,7 @@ public class CardRepositoryTest { Assert.assertTrue(fullCard2.get(0).isSplitCard()); Assert.assertEquals("Consecrate // Consume", fullCard2.get(0).getName()); - List splitHalfCardLeft = CardRepository.instance.findCards("Consecrate", 1, true); + List splitHalfCardLeft = CardRepository.instance.findCards("Consecrate", 1, true); Assert.assertTrue(splitHalfCardLeft.get(0).isSplitCardHalf()); Assert.assertEquals("Consecrate", splitHalfCardLeft.get(0).getName()); List splitHalfCardRight = CardRepository.instance.findCards("Consume", 1, true); @@ -167,4 +174,21 @@ public class CardRepositoryTest { foundCards.isEmpty() ); } + + /** + * Some set can contain both main and second side cards with same card numbers, + * so search result must return main side first + */ + @Test + public void test_SearchSetWithSecondSides() { + // XLN - Ixalan - Arguel's Blood Fast -> Temple of Aclazotz - 90 + Assert.assertEquals("Arguel's Blood Fast", CardRepository.instance.findCard("XLN", "90").getName()); + Assert.assertEquals("Arguel's Blood Fast", CardRepository.instance.findCard("XLN", "90", true).getName()); + Assert.assertEquals("Arguel's Blood Fast", CardRepository.instance.findCard("XLN", "90", false).getName()); + + // VOW - Innistrad: Crimson Vow - Jacob Hauken, Inspector -> Hauken's Insight - 320 + Assert.assertEquals("Jacob Hauken, Inspector", CardRepository.instance.findCard("VOW", "320").getName()); + Assert.assertEquals("Jacob Hauken, Inspector", CardRepository.instance.findCard("VOW", "320", true).getName()); + Assert.assertEquals("Jacob Hauken, Inspector", CardRepository.instance.findCard("VOW", "320", false).getName()); + } } diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 9362816956..12830dd267 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -942,6 +942,7 @@ public class VerifyCardDataTest { } boolean containsDoubleSideCards = false; + Map cardNumbers = new HashMap<>(); for (ExpansionSet.SetCardInfo cardInfo : set.getSetCardInfo()) { Card card = CardImpl.createCard(cardInfo.getCardClass(), new CardSetInfo(cardInfo.getName(), set.getCode(), cardInfo.getCardNumber(), cardInfo.getRarity(), cardInfo.getGraphicInfo())); @@ -973,7 +974,20 @@ public class VerifyCardDataTest { + " - " + card.getName() + " - " + card.getCardNumber() + " - " + card.getSecondCardFace().getName() + " - " + card.getSecondCardFace().getCardNumber()); } - */ + //*/ + + // CHECK: set contains both card sides + // related to second side cards usage + /* + String existedCardName = cardNumbers.getOrDefault(card.getCardNumber(), null); + if (existedCardName != null && !existedCardName.equals(card.getName())) { + String info = card.isNightCard() ? existedCardName + " -> " + card.getName() : card.getName() + " -> " + existedCardName; + errorsList.add("Error: set contains both card sides instead main only: " + + set.getCode() + " - " + set.getName() + " - " + info + " - " + card.getCardNumber()); + } else { + cardNumbers.put(card.getCardNumber(), card.getName()); + } + //*/ } // CHECK: double side cards must be in boosters diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 22fe740560..fd8733be28 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -287,6 +287,10 @@ public enum CardRepository { queryBuilder.limit(1L).where() .eq("setCode", new SelectArg(setCode)) .and().eq("cardNumber", new SelectArg(cardNumber)); + + // some double faced cards can use second side card with same number as main side + // (example: vow - 65 - Jacob Hauken, Inspector), so make priority for main side first + queryBuilder.orderBy("nightCard", true); } List result = cardDao.query(queryBuilder.prepare()); if (!result.isEmpty()) { From 4e9ffdfaf95521ad402e2d87904d7869fe4e7a35 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 11 Mar 2023 17:35:18 +0400 Subject: [PATCH 5/8] * GUI: double faced cards improves: * fixed images download for alternative prints (#9826, #9701); * fixed wrong pair of main and second side arts for alternative prints; --- .../client/dialog/TestCardRenderDialog.java | 13 +++++----- .../card/images/DownloadPicturesService.java | 4 +-- Mage.Sets/src/mage/sets/Battlebond.java | 2 +- Mage.Sets/src/mage/sets/MysteryBooster.java | 2 +- .../mage/test/utils/CardRepositoryTest.java | 16 +++++++++++- .../java/mage/verify/VerifyCardDataTest.java | 2 +- Mage/src/main/java/mage/cards/CardImpl.java | 2 +- Mage/src/main/java/mage/cards/Sets.java | 14 +++++++--- .../main/java/mage/cards/mock/MockCard.java | 2 +- .../java/mage/cards/mock/MockSplitCard.java | 6 ++--- .../mage/cards/repository/CardRepository.java | 26 +++++++++++-------- .../main/java/mage/game/draft/DraftCube.java | 2 +- 12 files changed, 59 insertions(+), 32 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 17f9d05e8e..9b5c916cea 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java @@ -326,12 +326,13 @@ public class TestCardRenderDialog extends MageDialog { //*/ // test variant double faced cards (main and second sides must be same pair) - cardViews.add(createHandCard(game, playerYou.getId(), "VOW", "65")); // Jacob Hauken, Inspector - cardViews.add(createHandCard(game, playerYou.getId(), "VOW", "320")); // Jacob Hauken, Inspector - cardViews.add(createHandCard(game, playerYou.getId(), "VOW", "332")); // Jacob Hauken, Inspector - cardViews.add(createPermanentCard(game, playerYou.getId(), "VOW", "65", 1, 1, 0, false, false, null)); // Jacob Hauken, Inspector - cardViews.add(createPermanentCard(game, playerYou.getId(), "VOW", "320", 1, 1, 0, false, false, null)); // Jacob Hauken, Inspector - cardViews.add(createPermanentCard(game, playerYou.getId(), "VOW", "332", 1, 1, 0, false, false, null)); // Jacob Hauken, Inspector + // Jacob Hauken, Inspector -> Hauken's Insight + cardViews.add(createHandCard(game, playerYou.getId(), "VOW", "65")); + cardViews.add(createHandCard(game, playerYou.getId(), "VOW", "320")); + cardViews.add(createHandCard(game, playerYou.getId(), "VOW", "332")); + cardViews.add(createPermanentCard(game, playerYou.getId(), "VOW", "65", 1, 1, 0, false, false, null)); + cardViews.add(createPermanentCard(game, playerYou.getId(), "VOW", "320", 1, 1, 0, false, false, null)); + cardViews.add(createPermanentCard(game, playerYou.getId(), "VOW", "332", 1, 1, 0, false, false, null)); //*/ /*//test card icons 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 cfab3fac27..d47ec13041 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 @@ -456,7 +456,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements throw new IllegalStateException("Second side card can't have empty name."); } - CardInfo secondSideCard = CardRepository.instance.findCardWPreferredSet(card.getSecondSideName(), card.getSetCode()); + CardInfo secondSideCard = CardRepository.instance.findCardWithPreferredSetAndNumber(card.getSecondSideName(), card.getSetCode(), card.getCardNumber()); if (secondSideCard == null) { throw new IllegalStateException("Can''t find second side card in database: " + card.getSecondSideName()); } @@ -490,7 +490,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements throw new IllegalStateException("MeldsToCardName can't be empty in " + card.getName()); } - CardInfo meldsToCard = CardRepository.instance.findCardWPreferredSet(card.getMeldsToCardName(), card.getSetCode()); + CardInfo meldsToCard = CardRepository.instance.findCardWithPreferredSetAndNumber(card.getMeldsToCardName(), card.getSetCode(), card.getCardNumber()); if (meldsToCard == null) { throw new IllegalStateException("Can''t find meldsToCard in database: " + card.getMeldsToCardName()); } diff --git a/Mage.Sets/src/mage/sets/Battlebond.java b/Mage.Sets/src/mage/sets/Battlebond.java index 24fc08ad38..fc2fa54e79 100644 --- a/Mage.Sets/src/mage/sets/Battlebond.java +++ b/Mage.Sets/src/mage/sets/Battlebond.java @@ -358,7 +358,7 @@ public final class Battlebond extends ExpansionSet { //Check if the pack already contains a partner pair if (partnerAllowed) { //Added card always replaces an uncommon card - Card card = CardRepository.instance.findCardWPreferredSet(partnerName, sourceCard.getExpansionSetCode()).getCard(); + Card card = CardRepository.instance.findCardWithPreferredSetAndNumber(partnerName, sourceCard.getExpansionSetCode(), null).getCard(); if (i < max) { booster.add(card); } else { diff --git a/Mage.Sets/src/mage/sets/MysteryBooster.java b/Mage.Sets/src/mage/sets/MysteryBooster.java index 86f213208d..61d51ac77c 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster.java @@ -3482,7 +3482,7 @@ public class MysteryBooster extends ExpansionSet { private void populateSlot(int slotNumber, List cardNames) { final List cardInfoList = this.possibleCardsPerBoosterSlot.get(slotNumber); for (String name : cardNames) { - final CardInfo cardWithGivenName = CardRepository.instance.findCardWPreferredSet(name, this.code); + final CardInfo cardWithGivenName = CardRepository.instance.findCardWithPreferredSetAndNumber(name, this.code, null); cardInfoList.add(cardWithGivenName); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/CardRepositoryTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/CardRepositoryTest.java index b2ac202ff5..784f74bccb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/CardRepositoryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/CardRepositoryTest.java @@ -180,7 +180,7 @@ public class CardRepositoryTest { * so search result must return main side first */ @Test - public void test_SearchSetWithSecondSides() { + public void test_SearchSecondSides_FindCard() { // XLN - Ixalan - Arguel's Blood Fast -> Temple of Aclazotz - 90 Assert.assertEquals("Arguel's Blood Fast", CardRepository.instance.findCard("XLN", "90").getName()); Assert.assertEquals("Arguel's Blood Fast", CardRepository.instance.findCard("XLN", "90", true).getName()); @@ -191,4 +191,18 @@ public class CardRepositoryTest { Assert.assertEquals("Jacob Hauken, Inspector", CardRepository.instance.findCard("VOW", "320", true).getName()); Assert.assertEquals("Jacob Hauken, Inspector", CardRepository.instance.findCard("VOW", "320", false).getName()); } + + @Test + public void test_SearchSecondSides_FindCardWithPreferredSetAndNumber() { + // VOW - Innistrad: Crimson Vow - Jacob Hauken, Inspector -> Hauken's Insight - 65 + // VOW - Innistrad: Crimson Vow - Jacob Hauken, Inspector -> Hauken's Insight - 320 + // VOW - Innistrad: Crimson Vow - Jacob Hauken, Inspector -> Hauken's Insight - 332 + Assert.assertEquals("65", CardRepository.instance.findCardWithPreferredSetAndNumber("Jacob Hauken, Inspector", "VOW", "65").getCardNumber()); + Assert.assertEquals("320", CardRepository.instance.findCardWithPreferredSetAndNumber("Jacob Hauken, Inspector", "VOW", "320").getCardNumber()); + Assert.assertEquals("332", CardRepository.instance.findCardWithPreferredSetAndNumber("Jacob Hauken, Inspector", "VOW", "332").getCardNumber()); + + Assert.assertEquals("65", CardRepository.instance.findCardWithPreferredSetAndNumber("Hauken's Insight", "VOW", "65").getCardNumber()); + Assert.assertEquals("320", CardRepository.instance.findCardWithPreferredSetAndNumber("Hauken's Insight", "VOW", "320").getCardNumber()); + Assert.assertEquals("332", CardRepository.instance.findCardWithPreferredSetAndNumber("Hauken's Insight", "VOW", "332").getCardNumber()); + } } diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 12830dd267..aa19194490 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -1968,7 +1968,7 @@ public class VerifyCardDataTest { // same find code as original cube CardInfo cardInfo; if (!cardId.getExtension().isEmpty()) { - cardInfo = CardRepository.instance.findCardWPreferredSet(cardId.getName(), cardId.getExtension()); + cardInfo = CardRepository.instance.findCardWithPreferredSetAndNumber(cardId.getName(), cardId.getExtension(), null); } else { cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardId.getName()); } diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 2d279d09d7..9e98afb524 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -643,7 +643,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { // 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(cardClazz, expansionSetCode); + ExpansionSet.SetCardInfo info = Sets.findCardByClass(cardClazz, expansionSetCode, cardNumber); if (info == null) { return null; } diff --git a/Mage/src/main/java/mage/cards/Sets.java b/Mage/src/main/java/mage/cards/Sets.java index 1435da7c90..4c85557794 100644 --- a/Mage/src/main/java/mage/cards/Sets.java +++ b/Mage/src/main/java/mage/cards/Sets.java @@ -199,15 +199,23 @@ public class Sets extends HashMap { return null; } - public static ExpansionSet.SetCardInfo findCardByClass(Class clazz, String preferredSetCode) { + public static ExpansionSet.SetCardInfo findCardByClass(Class clazz, String preferredSetCode, String preferredCardNumber) { ExpansionSet.SetCardInfo info = null; if (instance.containsKey(preferredSetCode)) { - info = instance.get(preferredSetCode).findCardInfoByClass(clazz).stream().findFirst().orElse(null); + info = instance.get(preferredSetCode).findCardInfoByClass(clazz) + .stream() + .filter(card -> preferredCardNumber == null || card.getCardNumber().equals(preferredCardNumber)) + .findFirst() + .orElse(null); } if (info == null) { for (Map.Entry entry : instance.entrySet()) { - info = entry.getValue().findCardInfoByClass(clazz).stream().findFirst().orElse(null); + info = entry.getValue().findCardInfoByClass(clazz) + .stream() + .filter(card -> preferredCardNumber == null || card.getCardNumber().equals(preferredCardNumber)) + .findFirst() + .orElse(null); if (info != null) { break; } diff --git a/Mage/src/main/java/mage/cards/mock/MockCard.java b/Mage/src/main/java/mage/cards/mock/MockCard.java index f4b8239487..dd767762b8 100644 --- a/Mage/src/main/java/mage/cards/mock/MockCard.java +++ b/Mage/src/main/java/mage/cards/mock/MockCard.java @@ -65,7 +65,7 @@ public class MockCard extends CardImpl { this.nightCard = card.isNightCard(); if (card.getSecondSideName() != null && !card.getSecondSideName().isEmpty()) { - this.secondSideCard = new MockCard(CardRepository.instance.findCardWPreferredSet(card.getSecondSideName(), card.getSetCode())); + this.secondSideCard = new MockCard(CardRepository.instance.findCardWithPreferredSetAndNumber(card.getSecondSideName(), card.getSetCode(), card.getCardNumber())); } if (card.isAdventureCard()) { diff --git a/Mage/src/main/java/mage/cards/mock/MockSplitCard.java b/Mage/src/main/java/mage/cards/mock/MockSplitCard.java index 8c2512d236..b677d8aa8a 100644 --- a/Mage/src/main/java/mage/cards/mock/MockSplitCard.java +++ b/Mage/src/main/java/mage/cards/mock/MockSplitCard.java @@ -40,7 +40,7 @@ public class MockSplitCard extends SplitCard { this.nightCard = card.isNightCard(); if (card.getSecondSideName() != null && !card.getSecondSideName().isEmpty()) { - this.secondSideCard = new MockCard(CardRepository.instance.findCardWPreferredSet(card.getSecondSideName(), card.getSetCode())); + this.secondSideCard = new MockCard(CardRepository.instance.findCardWithPreferredSetAndNumber(card.getSecondSideName(), card.getSetCode(), card.getCardNumber())); } this.flipCardName = card.getFlipCardName(); @@ -49,13 +49,13 @@ public class MockSplitCard extends SplitCard { this.addAbility(textAbilityFromString(ruleText)); } - CardInfo leftHalf = CardRepository.instance.findCardWPreferredSet(getLeftHalfName(card), card.getSetCode(), true); + CardInfo leftHalf = CardRepository.instance.findCardWithPreferredSetAndNumber(getLeftHalfName(card), card.getSetCode(), card.getCardNumber(), true); if (leftHalf != null) { this.leftHalfCard = new MockSplitCardHalf(leftHalf); ((SplitCardHalf) this.leftHalfCard).setParentCard(this); } - CardInfo rightHalf = CardRepository.instance.findCardWPreferredSet(getRightHalfName(card), card.getSetCode(), true); + CardInfo rightHalf = CardRepository.instance.findCardWithPreferredSetAndNumber(getRightHalfName(card), card.getSetCode(), card.getCardNumber(), true); if (rightHalf != null) { this.rightHalfCard = new MockSplitCardHalf(rightHalf); ((SplitCardHalf) this.rightHalfCard).setParentCard(this); diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index fd8733be28..da8ffff847 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -394,32 +394,36 @@ public enum CardRepository { /** * Function to find a card by name from a specific set. - * Used for building cubes, packs, and for ensuring that dual faces and split cards have sides/halves from the same set. + * Used for building cubes, packs, and for ensuring that dual faces and split cards have sides/halves from + * the same set and variant art. * * @param name name of the card, or side of the card, to find * @param expansion the set name from which to find the card + * @param cardNumber the card number for variant arts in one set * @param returnSplitCardHalf whether to return a half of a split card or the corresponding full card. * Want this `false` when user is searching by either names in a split card so that * the full card can be found by either name. * @return */ - public CardInfo findCardWPreferredSet(String name, String expansion, boolean returnSplitCardHalf) { + public CardInfo findCardWithPreferredSetAndNumber(String name, String expansion, String cardNumber, boolean returnSplitCardHalf) { List cards; cards = findCards(name, 0, returnSplitCardHalf); + CardInfo bestCard = cards.stream() + .filter(card -> expansion == null || expansion.equalsIgnoreCase(card.getSetCode())) + .filter(card -> cardNumber == null || cardNumber.equals(card.getCardNumber())) + .findFirst() + .orElse(null); - if (!cards.isEmpty()) { - for (CardInfo cardinfo : cards) { - if (cardinfo.getSetCode() != null && expansion != null && expansion.equalsIgnoreCase(cardinfo.getSetCode())) { - return cardinfo; - } - } + if (bestCard != null) { + return bestCard; + } else { + return findPreferredCoreExpansionCard(name); } - return findPreferredCoreExpansionCard(name); } - public CardInfo findCardWPreferredSet(String name, String expansion) { - return findCardWPreferredSet(name, expansion, false); + public CardInfo findCardWithPreferredSetAndNumber(String name, String expansion, String cardNumber) { + return findCardWithPreferredSetAndNumber(name, expansion, cardNumber, false); } public List findCards(String name) { diff --git a/Mage/src/main/java/mage/game/draft/DraftCube.java b/Mage/src/main/java/mage/game/draft/DraftCube.java index 7db7239a68..2ed8dc9e2a 100644 --- a/Mage/src/main/java/mage/game/draft/DraftCube.java +++ b/Mage/src/main/java/mage/game/draft/DraftCube.java @@ -84,7 +84,7 @@ public abstract class DraftCube { if (!cardId.getName().isEmpty()) { CardInfo cardInfo = null; if (!cardId.getExtension().isEmpty()) { - cardInfo = CardRepository.instance.findCardWPreferredSet(cardId.getName(), cardId.getExtension()); + cardInfo = CardRepository.instance.findCardWithPreferredSetAndNumber(cardId.getName(), cardId.getExtension(), null); } else { cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardId.getName()); } From 7880d6cc43a30d68eb1c46721c6a9b0f15098508 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 11 Mar 2023 21:20:16 +0400 Subject: [PATCH 6/8] Refactor: removed outdated files, added actual license and readme to releases --- Mage.Client/release/how to start.txt | 5 -- Mage.Client/release/readme.txt | 60 ------------------- .../src/main/assembly/distribution.xml | 9 +++ .../src/main/assembly/distribution.xml | 9 +++ Mage.Server/catalog.xml | 14 ----- Mage.Server/license.txt | 25 -------- Mage.Server/release/how to start.txt | 4 -- Mage.Server/release/license.txt | 25 -------- Mage.Server/release/readme.txt | 60 ------------------- Mage.Server/server.msg.txt | 6 -- .../src/main/assembly/distribution.xml | 9 +++ 11 files changed, 27 insertions(+), 199 deletions(-) delete mode 100644 Mage.Client/release/how to start.txt delete mode 100644 Mage.Client/release/readme.txt delete mode 100644 Mage.Server/catalog.xml delete mode 100644 Mage.Server/license.txt delete mode 100644 Mage.Server/release/how to start.txt delete mode 100644 Mage.Server/release/license.txt delete mode 100644 Mage.Server/release/readme.txt delete mode 100644 Mage.Server/server.msg.txt diff --git a/Mage.Client/release/how to start.txt b/Mage.Client/release/how to start.txt deleted file mode 100644 index 14c3ee711f..0000000000 --- a/Mage.Client/release/how to start.txt +++ /dev/null @@ -1,5 +0,0 @@ -use .exe or one of the scripts: - startClient.bat - for Windows - startClientWin7.bat - for Windows 7 - startClient.sh - for Linux - startClient.command - for MacOS \ No newline at end of file diff --git a/Mage.Client/release/readme.txt b/Mage.Client/release/readme.txt deleted file mode 100644 index cb4c055c01..0000000000 --- a/Mage.Client/release/readme.txt +++ /dev/null @@ -1,60 +0,0 @@ -XMage - is an acronym for Extended - Magic, Another Game Engine - -XMage is a client/server implementation of a popular CCG without the collecting part. -The server hosts games and enforces the rules. The client creates or joins games, -displays the current state of a game in progress and sends user events to the server. - -You will need to have the Java Runtime Environment Version 7 or greater. -You can download this from: http://java.com/ - ------------------------------------------------------------------------------------ -Installing and running XMage - -You will need to download both the client and the server applications. These can be -obtained from HTTP://XMage.de. -Extact the client and the server to separate folders. - -To play a game you can either connect to a server or start your own server. To -connect to a server you will need to know the server name or IP address and the port. -To start a server run the startServer.bat command. If you want to use a different -port or change the timeout setting then modify the config.xml file in the -config folder. - -To launch the client run the startClient.bat command. Click on the connect button on -the toolbar and enter the server name/IP address and port. Then click on the tables -button. This will bring up a list of active and completed games. Click on join to -join an existing game that hasn't started yet or you can create a new table by -clicking the New button. - ------------------------------------------------------------------------------------ -Playing a game - -Playing a game should be fairly self evident. Your hand is displayed at the bottom -of the screen. The battlefield is the central area. Click on cards in your hand to -play them. Click on cards in the battlefield to activate abilities. A popup menu -will be presented if you have more than one choice. To pass priority for the turn -hold down the ctrl key while clicking done. You will still receive priority if -your opponent casts a spell or activates an ability. Target cards by clicking on -them. To target a player click on the player name. You can see the cards in any -graveyard by clicking on the graveyard count. - ------------------------------------------------------------------------------------ -Deck editor - -A simple deck editor is available by clicking on the Deck Editor button on the -toolbar. All the available cards are displayed in the top section. Your deck -and sideboard are displayed at the bottom. To add a card to your deck double -click on the card in the top section. To remove it from your deck double click -on the card in the deck area. The sideboard section is not ready yet (don't -worry it's coming soon). - -You can save or load a deck using the Save or Load buttons. - ------------------------------------------------------------------------------------ -Notes - -XMage is still in the testing phase so there can be bugs and/or -missing functionality. Please be patient. If you notice anything or want to -make suggestions please visit our board at - http://www.slightlymagic.net/forum/viewforum.php?f=70 -and give us some feedback. diff --git a/Mage.Client/src/main/assembly/distribution.xml b/Mage.Client/src/main/assembly/distribution.xml index 2ce3f69aaf..9b7e36f761 100644 --- a/Mage.Client/src/main/assembly/distribution.xml +++ b/Mage.Client/src/main/assembly/distribution.xml @@ -33,6 +33,15 @@ + + true + ../ + + readme.md + LICENSE.txt + + / + false release/ diff --git a/Mage.Server.Console/src/main/assembly/distribution.xml b/Mage.Server.Console/src/main/assembly/distribution.xml index 892d57c6cc..835b3b3936 100644 --- a/Mage.Server.Console/src/main/assembly/distribution.xml +++ b/Mage.Server.Console/src/main/assembly/distribution.xml @@ -24,6 +24,15 @@ + + true + ../ + + readme.md + LICENSE.txt + + / + true release/ diff --git a/Mage.Server/catalog.xml b/Mage.Server/catalog.xml deleted file mode 100644 index 2db64342c3..0000000000 --- a/Mage.Server/catalog.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Mage.Server/license.txt b/Mage.Server/license.txt deleted file mode 100644 index dc1ab47fb1..0000000000 --- a/Mage.Server/license.txt +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are -permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of - conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list - of conditions and the following disclaimer in the documentation and/or other materials - provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the -authors and should not be interpreted as representing official policies, either expressed -or implied, of BetaSteward_at_googlemail.com. \ No newline at end of file diff --git a/Mage.Server/release/how to start.txt b/Mage.Server/release/how to start.txt deleted file mode 100644 index 9d4a6e7ea5..0000000000 --- a/Mage.Server/release/how to start.txt +++ /dev/null @@ -1,4 +0,0 @@ -use .exe or one of the scripts: - startServer.bat - for Windows - startServer.sh - for Linux - startServer.command - for MacOS \ No newline at end of file diff --git a/Mage.Server/release/license.txt b/Mage.Server/release/license.txt deleted file mode 100644 index dc1ab47fb1..0000000000 --- a/Mage.Server/release/license.txt +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are -permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of - conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list - of conditions and the following disclaimer in the documentation and/or other materials - provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the -authors and should not be interpreted as representing official policies, either expressed -or implied, of BetaSteward_at_googlemail.com. \ No newline at end of file diff --git a/Mage.Server/release/readme.txt b/Mage.Server/release/readme.txt deleted file mode 100644 index 5e9e90c4f9..0000000000 --- a/Mage.Server/release/readme.txt +++ /dev/null @@ -1,60 +0,0 @@ -XMage - is an acronym for Extended - Magic, Another Game Engine - -XMage is a client/server implementation of a popular CCG without the collecting part. -The server hosts games and enforces the rules. The client creates or joins games, -displays the current state of a game in progress and sends user events to the server. - -You will need to have the Java Runtime Environment Version 7 or greater. -You can download this from: http://java.com/ - ------------------------------------------------------------------------------------ -Installing and running XMage - -You will need to download both the client and the server applications. These can be -obtained from HTTP://XMage.de. -Extact the client and the server to separate folders. - -To play a game you can either connect to a server or start your own server. To -connect to a server you will need to know the server name or IP address and the port. -To start a server run the startServer.bat command. If you want to use a different -port or change the timeout setting then modify the config.xml file in the -config folder. - -To launch the client run the startClient.bat command. Click on the connect button on -the toolbar and enter the server name/IP address and port. Then click on the tables -button. This will bring up a list of active and completed games. Click on join to -join an existing game that hasn't started yet or you can create a new table by -clicking the New button. - ------------------------------------------------------------------------------------ -Playing a game - -Playing a game should be fairly self evident. Your hand is displayed at the bottom -of the screen. The battlefield is the central area. Click on cards in your hand to -play them. Click on cards in the battlefield to activate abilities. A popup menu -will be presented if you have more than one choice. To pass priority for the turn -hold down the ctrl key while clicking done. You will still receive priority if -your opponent casts a spell or activates an ability. Target cards by clicking on -them. To target a player click on the player name. You can see the cards in any -graveyard by clicking on the graveyard count. - ------------------------------------------------------------------------------------ -Deck editor - -A simple deck editor is available by clicking on the Deck Editor button on the -toolbar. All the available cards are displayed in the top section. Your deck -and sideboard are displayed at the bottom. To add a card to your deck double -click on the card in the top section. To remove it from your deck double click -on the card in the deck area. The sideboard section is not ready yet (don't -worry it's coming soon). - -You can save or load a deck using the Save or Load buttons. - ------------------------------------------------------------------------------------ -Notes - -XMage is still in the testing phase so there can be bugs and/or -missing functionality. Please be patient. If you notice anything or want to -make suggestions please visit our board at - http://www.slightlymagic.net/forum/viewforum.php?f=70 -and give us some feedback. diff --git a/Mage.Server/server.msg.txt b/Mage.Server/server.msg.txt deleted file mode 100644 index e0375adafb..0000000000 --- a/Mage.Server/server.msg.txt +++ /dev/null @@ -1,6 +0,0 @@ -HotKeys: Alt+E - Enlarge card image -Wheel zoom in/out - Enlarge card image -F4 - end current turn, response to stack -F9 - skip all opponents' turns, no response to stack -Welcome! You are playing Mage version 1.4.9 -Contact us on www.slightlymagic.net \ No newline at end of file diff --git a/Mage.Server/src/main/assembly/distribution.xml b/Mage.Server/src/main/assembly/distribution.xml index 20bab51d57..f21baff9fd 100644 --- a/Mage.Server/src/main/assembly/distribution.xml +++ b/Mage.Server/src/main/assembly/distribution.xml @@ -50,6 +50,15 @@ + + true + ../ + + readme.md + LICENSE.txt + + / + true release/ From e2271d79fb39d5e84bdc8ea3fe8c25a1215fa387 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 12 Mar 2023 13:56:29 +0400 Subject: [PATCH 7/8] Refactor: added auto-generated readable readme file to releases --- Mage.Client/pom.xml | 4 ++++ .../src/main/assembly/distribution.xml | 10 +++++++- Mage.Server.Console/pom.xml | 4 ++++ .../src/main/assembly/distribution.xml | 10 +++++++- Mage.Server/pom.xml | 4 ++++ .../src/main/assembly/distribution.xml | 10 +++++++- pom.xml | 23 +++++++++++++++++++ 7 files changed, 62 insertions(+), 3 deletions(-) diff --git a/Mage.Client/pom.xml b/Mage.Client/pom.xml index 929e7931ca..8fe1db8efc 100644 --- a/Mage.Client/pom.xml +++ b/Mage.Client/pom.xml @@ -215,6 +215,10 @@ + + com.ruleoftech + markdown-page-generator-plugin + mage-client diff --git a/Mage.Client/src/main/assembly/distribution.xml b/Mage.Client/src/main/assembly/distribution.xml index 9b7e36f761..ddf248c1b1 100644 --- a/Mage.Client/src/main/assembly/distribution.xml +++ b/Mage.Client/src/main/assembly/distribution.xml @@ -37,8 +37,16 @@ true ../ - readme.md LICENSE.txt + readme.md + + / + + + true + ${project.build.directory}/docs/ + + readme.html / diff --git a/Mage.Server.Console/pom.xml b/Mage.Server.Console/pom.xml index f67ab5e777..4d9a46a3ec 100644 --- a/Mage.Server.Console/pom.xml +++ b/Mage.Server.Console/pom.xml @@ -62,6 +62,10 @@ + + com.ruleoftech + markdown-page-generator-plugin + mage-serverconsole diff --git a/Mage.Server.Console/src/main/assembly/distribution.xml b/Mage.Server.Console/src/main/assembly/distribution.xml index 835b3b3936..d63170b63c 100644 --- a/Mage.Server.Console/src/main/assembly/distribution.xml +++ b/Mage.Server.Console/src/main/assembly/distribution.xml @@ -28,8 +28,16 @@ true ../ - readme.md LICENSE.txt + readme.md + + / + + + true + ${project.build.directory}/docs/ + + readme.html / diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index 0576da6a36..bd4636a455 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -352,6 +352,10 @@ org.apache.maven.plugins maven-surefire-plugin + + com.ruleoftech + markdown-page-generator-plugin + mage-server diff --git a/Mage.Server/src/main/assembly/distribution.xml b/Mage.Server/src/main/assembly/distribution.xml index f21baff9fd..62ccfe1034 100644 --- a/Mage.Server/src/main/assembly/distribution.xml +++ b/Mage.Server/src/main/assembly/distribution.xml @@ -54,8 +54,16 @@ true ../ - readme.md LICENSE.txt + readme.md + + / + + + true + ${project.build.directory}/docs/ + + readme.html / diff --git a/pom.xml b/pom.xml index f53baf3db2..5364e32922 100644 --- a/pom.xml +++ b/pom.xml @@ -116,11 +116,34 @@ 2.7 + org.codehaus.mojo exec-maven-plugin 3.0.0 + + + + + + com.ruleoftech + markdown-page-generator-plugin + 2.4.0 + + + process-resources + + generate + + + + + ../ + md + ${project.build.directory}/docs + + From 03b82ff7ee8c920f68ffdd8033d06e6fccf92803 Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Sun, 12 Mar 2023 22:01:11 +0000 Subject: [PATCH 8/8] Fix multiple cards not saying which revealed cards were chosen to put into hand. Closes #9949 --- Mage.Sets/src/mage/cards/a/AtraxaGrandUnifier.java | 3 +-- Mage.Sets/src/mage/cards/b/BountyOfSkemfar.java | 2 +- Mage.Sets/src/mage/cards/h/HurkylMasterWizard.java | 2 +- Mage.Sets/src/mage/cards/i/InscribedTablet.java | 2 +- Mage.Sets/src/mage/cards/n/NivMizzetReborn.java | 5 +---- Mage.Sets/src/mage/cards/t/TajuruParagon.java | 2 +- .../test/java/org/mage/test/player/TestPlayer.java | 5 +++++ .../src/test/java/org/mage/test/stub/PlayerStub.java | 5 +++++ Mage/src/main/java/mage/players/Player.java | 11 +++++++++++ Mage/src/main/java/mage/players/PlayerImpl.java | 9 +++++++++ 10 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AtraxaGrandUnifier.java b/Mage.Sets/src/mage/cards/a/AtraxaGrandUnifier.java index 3c6526b262..73da1e6530 100644 --- a/Mage.Sets/src/mage/cards/a/AtraxaGrandUnifier.java +++ b/Mage.Sets/src/mage/cards/a/AtraxaGrandUnifier.java @@ -89,8 +89,7 @@ class AtraxaGrandUnifierEffect extends OneShotEffect { TargetCard target = new AtraxaGrandUnifierTarget(); player.choose(outcome, cards, target, game); Cards toHand = new CardsImpl(target.getTargets()); - player.revealCards(source, toHand, game); - player.moveCards(toHand, Zone.HAND, source, game); + player.moveCardsToHandWithInfo(toHand, source, game, true); cards.retainZone(Zone.LIBRARY, game); player.putCardsOnBottomOfLibrary(cards, game, source, false); return true; diff --git a/Mage.Sets/src/mage/cards/b/BountyOfSkemfar.java b/Mage.Sets/src/mage/cards/b/BountyOfSkemfar.java index f433f8d2fc..ed088be589 100644 --- a/Mage.Sets/src/mage/cards/b/BountyOfSkemfar.java +++ b/Mage.Sets/src/mage/cards/b/BountyOfSkemfar.java @@ -84,7 +84,7 @@ class BountyOfSkemfarEffect extends OneShotEffect { player.choose(outcome, cards, target, game); Card elf = cards.get(target.getFirstTarget(), game); if (elf != null) { - player.moveCards(elf, Zone.HAND, source, game); + player.moveCardToHandWithInfo(elf, source, game, true); } cards.removeIf(uuid -> game.getState().getZone(uuid) != Zone.LIBRARY); player.putCardsOnBottomOfLibrary(cards, game, source, false); diff --git a/Mage.Sets/src/mage/cards/h/HurkylMasterWizard.java b/Mage.Sets/src/mage/cards/h/HurkylMasterWizard.java index db49e104b8..ec5e2d1dd4 100644 --- a/Mage.Sets/src/mage/cards/h/HurkylMasterWizard.java +++ b/Mage.Sets/src/mage/cards/h/HurkylMasterWizard.java @@ -94,7 +94,7 @@ class HurkylMasterWizardEffect extends OneShotEffect { TargetCard target = new HurkylMasterWizardTarget(source, game); player.choose(outcome, cards, target, game); Cards toHand = new CardsImpl(target.getTargets()); - player.moveCards(toHand, Zone.HAND, source, game); + player.moveCardsToHandWithInfo(toHand, source, game, true); cards.retainZone(Zone.LIBRARY, game); player.putCardsOnBottomOfLibrary(cards, game, source, false); return true; diff --git a/Mage.Sets/src/mage/cards/i/InscribedTablet.java b/Mage.Sets/src/mage/cards/i/InscribedTablet.java index ccf642d56b..541b893c2e 100644 --- a/Mage.Sets/src/mage/cards/i/InscribedTablet.java +++ b/Mage.Sets/src/mage/cards/i/InscribedTablet.java @@ -78,7 +78,7 @@ class InscribedTabletEffect extends OneShotEffect { Card land = game.getCard(target.getFirstTarget()); if (land != null) { cards.remove(land); - landToHand = controller.moveCards(land, Zone.HAND, source, game); + landToHand = controller.moveCardToHandWithInfo(land, source, game, true); } } controller.putCardsOnBottomOfLibrary(cards, game, source, false); diff --git a/Mage.Sets/src/mage/cards/n/NivMizzetReborn.java b/Mage.Sets/src/mage/cards/n/NivMizzetReborn.java index b39ee01e09..6aefab1505 100644 --- a/Mage.Sets/src/mage/cards/n/NivMizzetReborn.java +++ b/Mage.Sets/src/mage/cards/n/NivMizzetReborn.java @@ -86,10 +86,7 @@ class NivMizzetRebornEffect extends OneShotEffect { TargetCard target = new NivMizzetRebornTarget(); player.choose(outcome, cards, target, game); Cards toHand = new CardsImpl(target.getTargets()); - player.moveCards(toHand, Zone.HAND, source, game); - game.informPlayers(player.getLogName() + " moves " + CardUtil.concatWithAnd( - toHand.getCards(game).stream().map(MageObject::getName).collect(Collectors.toList()) - ) + " to hand"); + player.moveCardsToHandWithInfo(toHand, source, game, true); cards.retainZone(Zone.LIBRARY, game); player.putCardsOnBottomOfLibrary(cards, game, source, false); return true; diff --git a/Mage.Sets/src/mage/cards/t/TajuruParagon.java b/Mage.Sets/src/mage/cards/t/TajuruParagon.java index 05210227a7..b283dedc60 100644 --- a/Mage.Sets/src/mage/cards/t/TajuruParagon.java +++ b/Mage.Sets/src/mage/cards/t/TajuruParagon.java @@ -95,7 +95,7 @@ class TajuruParagonEffect extends OneShotEffect { player.choose(outcome, cards, target, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { - player.moveCards(card, Zone.HAND, source, game); + player.moveCardToHandWithInfo(card, source, game, true); cards.remove(card); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 8cd9ac7073..7f59787625 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -3813,6 +3813,11 @@ public class TestPlayer implements Player { return computerPlayer.moveCardToHandWithInfo(card, source, game, withName); } + @Override + public boolean moveCardsToHandWithInfo(Cards cards, Ability source, Game game, boolean withName) { + return computerPlayer.moveCardsToHandWithInfo(cards, source, game, withName); + } + @Override public boolean moveCardsToExile(Card card, Ability source, Game game, boolean withName, UUID exileId, String exileZoneName) { return computerPlayer.moveCardsToExile(card, source, game, withName, exileId, exileZoneName); diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 5af173b539..56cf85952a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -1204,6 +1204,11 @@ public class PlayerStub implements Player { return false; } + @Override + public boolean moveCardsToHandWithInfo(Cards cards, Ability source, Game game, boolean withName) { + return false; + } + @Override public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, Ability source, Game game, Zone fromZone, boolean withName) { return false; diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index cafd9f0ff7..093f11c516 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -908,6 +908,17 @@ public interface Player extends MageItem, Copyable { */ boolean moveCardToHandWithInfo(Card card, Ability source, Game game, boolean withName); + /** + * Iterates through a set of cards and runs moveCardToHandWithInfo on each item + * + * @param cards + * @param source + * @param game + * @param withName show the card names in the log + * @return + */ + boolean moveCardsToHandWithInfo(Cards cards, Ability source, Game game, boolean withName); + /** * Uses card.moveToExile and posts a inform message about moving the card to * exile into the game log. Don't use this in replacement effects, because diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index ac1b7f4636..b92b82ba0a 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -4642,6 +4642,15 @@ public abstract class PlayerImpl implements Player, Serializable { return result; } + @Override + public boolean moveCardsToHandWithInfo(Cards cards, Ability source, Game game, boolean withName) { + Player player = this; + for (Card card : cards.getCards(game)) { + player.moveCardToHandWithInfo(card, source, game, withName); + } + return true; + } + @Override public boolean moveCardToHandWithInfo(Card card, Ability source, Game game, boolean withName) { boolean result = false;