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 230c4e91ab70..7f22c23bc6d1 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 @@ -150,32 +150,7 @@ public void focusLost(FocusEvent e) { // Both card rendering implementations have a transform button if (this.getGameCard().canTransform()) { - // Create the day night button - dayNightButton = new JButton(""); - dayNightButton.setSize(32, 32); - dayNightButton.setToolTipText("This permanent is a double faced card. To see the card's other face, push this button or move mouse wheel down while hovering over it."); - BufferedImage day = ImageManagerImpl.instance.getDayImage(); - dayNightButton.setIcon(new ImageIcon(day)); - dayNightButton.addActionListener(e -> { - // if card is rotating then ignore it - if (animationInProgress) { - return; - } - - // if card is tapped then no visual transforming is possible, so switch it immediately - if (isTapped()) { - // toggle without animation - this.getTopPanelRef().toggleTransformed(); - this.getTopPanelRef().repaint(); - return; - } - - // normal animation - Animation.transformCard(this); - }); - - // Add it - buttonPanel.add(dayNightButton); + createDayNightButton(); } // Both card rendering implementations have a view copy source button @@ -217,6 +192,35 @@ public void focusLost(FocusEvent e) { setFlippedAngle(isFlipped() ? CardPanel.FLIPPED_ANGLE : 0); } + private void createDayNightButton() { + // Create the day night button + dayNightButton = new JButton(""); + dayNightButton.setSize(32, 32); + dayNightButton.setToolTipText("This permanent is a double faced card. To see the card's other face, push this button or move mouse wheel down while hovering over it."); + BufferedImage day = ImageManagerImpl.instance.getDayImage(); + dayNightButton.setIcon(new ImageIcon(day)); + dayNightButton.addActionListener(e -> { + // if card is rotating then ignore it + if (animationInProgress) { + return; + } + + // if card is tapped then no visual transforming is possible, so switch it immediately + if (isTapped()) { + // toggle without animation + this.getTopPanelRef().toggleTransformed(); + this.getTopPanelRef().repaint(); + return; + } + + // normal animation + Animation.transformCard(this); + }); + + // Add it + buttonPanel.add(dayNightButton); + } + @Override public void doLayout() { // Position transform and show source buttons @@ -585,6 +589,14 @@ public void update(CardView card) { dayNightButton.setVisible(true); // show T button for any cards and permanents dayNightButton.setIcon(new ImageIcon(transformIcon)); } + // Card became transformable + if (card.canTransform() && dayNightButton == null) { + createDayNightButton(); + } + // Card became non-transformable + if (!card.canTransform() && !this.cardSideMain.canTransform() && dayNightButton != null) { + dayNightButton.setVisible(false); + } } @Override @@ -944,7 +956,9 @@ private void setGameCardSides(CardView gameCard) { this.cardSideOther = gameCard.getSecondCardFace(); } else { // updated card - if (this.cardSideMain.getName().equals(gameCard.getName())) { + if (this.cardSideMain.getName().equals(gameCard.getName()) + || (!(this.cardSideMain instanceof PermanentView) && !gameCard.getName().equals(this.cardSideMain.getName()) && gameCard.getSecondCardFace() != null) + || (gameCard instanceof PermanentView && !(gameCard.getName().equals(this.cardSideMain.getName())))) { // from main side this.cardSideMain = gameCard; this.cardSideOther = gameCard.getSecondCardFace(); diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index 749488b8cf37..c257a91f4355 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -313,7 +313,7 @@ public CardView(Card sourceCard, Game game, boolean showAsControlled, boolean st // - face down status, face down image // find real name from original card, cause face down status can be applied to card/spell - String sourceName = sourceCard.getMainCard().getName(); + String sourceName = sourceCard.isCopy() ? sourceCard.getCopyFrom().getName() : sourceCard.getMainCard().getName(); // find real spell characteristics before resolve Card card = sourceCard.copy(); @@ -328,13 +328,6 @@ public CardView(Card sourceCard, Game game, boolean showAsControlled, boolean st // show real name and day/night button for controller or any player at the game's end boolean showHiddenFaceDownData = showAsControlled || (game != null && game.hasEnded()); - // default image info - this.expansionSetCode = card.getExpansionSetCode(); - this.cardNumber = card.getCardNumber(); - this.imageFileName = card.getImageFileName(); - this.imageNumber = card.getImageNumber(); - this.usesVariousArt = card.getUsesVariousArt(); - // permanent data if (showFaceUp) { this.setOriginalValues(card); @@ -348,80 +341,206 @@ public CardView(Card sourceCard, Game game, boolean showAsControlled, boolean st } } + this.setSharedInfo(game, card); + // FACE DOWN if (!showFaceUp) { - this.fillEmptyWithImageInfo(game, card, true); - - // can show face up card name for controller or game end - // TODO: add exception on non empty name of the faced-down card here - String visibleName = CardUtil.getCardNameForGUI(showHiddenFaceDownData ? sourceName : "", this.imageFileName); - this.name = visibleName; - this.displayName = visibleName; - this.displayFullName = visibleName; - this.alternateName = visibleName; - - // TODO: remove workaround - all actual characteristics must get from a card -- same as normal card do - // TODO: must use same code in all zones - // workaround to add PT, creature type and face up ability text (for stack and battlefield zones only) - // in other zones it has only face down status/name - if (sourceCard instanceof Spell - || card instanceof Permanent) { - this.power = Integer.toString(card.getPower().getValue()); - this.toughness = Integer.toString(card.getToughness().getValue()); - this.cardTypes = new ArrayList<>(card.getCardType()); - this.color = card.getColor(null).copy(); - this.superTypes = new ArrayList<>(card.getSuperType()); - this.subTypes = card.getSubtype().copy(); - this.rules = new ArrayList<>(card.getRules()); + this.setFaceDownInfo(game, card, sourceCard, showHiddenFaceDownData, sourceName); + } + + // FACE UP INFO + if (showFaceUp) { + this.setFaceUpName(game, card); + this.setFaceUpInfo(game, card); + this.setFaceUpFrameInfo(game, card); + this.manaValue = card.getManaValue(); + } + + // shared info - targets + if (card instanceof Spell) { + this.setSpellTargets(game, (Spell) card); + } + } + + private void setSpellTargets(Game game, Spell card) { + this.mageObjectType = MageObjectType.SPELL; + for (SpellAbility spellAbility : card.getSpellAbilities()) { + for (UUID modeId : spellAbility.getModes().getSelectedModes()) { + Mode mode = spellAbility.getModes().get(modeId); + if (!mode.getTargets().isEmpty()) { + addTargets(mode.getTargets(), mode.getEffects(), spellAbility, game); + } } + } - // GUI: enable day/night button to view original face up card - if (showHiddenFaceDownData) { - this.transformable = true; - this.secondCardFace = new CardView(sourceCard.getMainCard()); // do not use game param, so it will take default card - this.alternateName = sourceCard.getMainCard().getName(); + // show for modal spell, which mode was chosen + if (card.getSpellAbility().isModal()) { + for (UUID modeId : card.getSpellAbility().getModes().getSelectedModes()) { + Mode mode = card.getSpellAbility().getModes().get(modeId); + this.rules.add("Chosen mode: " + mode.getEffects().getText(mode) + ""); } } - // FACE UP and shared data like counters + // show target of a spell on the stack + if (!card.getSpellAbility().getTargets().isEmpty()) { + StackObject stackObjectTarget = null; + for (Target target : card.getSpellAbility().getTargets()) { + for (UUID targetId : target.getTargets()) { + MageObject mo = game.getObject(targetId); + if (mo instanceof StackObject) { + stackObjectTarget = (StackObject) mo; + } + if (stackObjectTarget != null) { + String idName = stackObjectTarget.getIdName(); + if (stackObjectTarget instanceof Spell && ((Spell) stackObjectTarget).isFaceDown(game) + && card.getControllerId() != stackObjectTarget.getControllerId()) { + idName = "face down spell " + "[" + stackObjectTarget.getId().toString().substring(0, 3) + "]"; + } + this.rules.add("Target on stack: " + idName + ""); + } + } + } + } + } - if (showFaceUp) { - SplitCard splitCard = null; - if (card instanceof SplitCard) { - splitCard = (SplitCard) card; - rotate = (card.getSpellAbility().getSpellAbilityType()) != SpellAbilityType.SPLIT_AFTERMATH; - } else if (card instanceof Spell) { - switch (card.getSpellAbility().getSpellAbilityType()) { - case SPLIT_FUSED: - splitCard = (SplitCard) ((Spell) card).getCard(); - rotate = true; - break; - case SPLIT_AFTERMATH: - splitCard = (SplitCard) ((Spell) card).getCard(); - rotate = false; - break; - case SPLIT_LEFT: - case SPLIT_RIGHT: - rotate = true; - break; - case MODAL_LEFT: - case MODAL_RIGHT: - rotate = false; - break; + private void setFaceUpFrameInfo(Game game, Card card) { + if (card instanceof Spell) { + Spell spell = (Spell) card; + // Determine what part of the art to slice out for spells on the stack which originate + // from a split, fuse, or aftermath split card. + // Modal double faces cards draws as normal cards + SpellAbilityType ty = spell.getSpellAbility().getSpellAbilityType(); + if (ty == SpellAbilityType.SPLIT_RIGHT || ty == SpellAbilityType.SPLIT_LEFT || ty == SpellAbilityType.SPLIT_FUSED) { + // Needs a special art rect + if (ty == SpellAbilityType.SPLIT_FUSED) { + artRect = ArtRect.SPLIT_FUSED; + } else if (spell.getCard() != null) { + SplitCard wholeCard = ((SplitCardHalf) spell.getCard()).getParentCard(); + Abilities aftermathHalfAbilities = wholeCard.getRightHalfCard().getAbilities(game); + if (aftermathHalfAbilities.stream().anyMatch(AftermathAbility.class::isInstance)) { + if (ty == SpellAbilityType.SPLIT_RIGHT) { + artRect = ArtRect.AFTERMATH_BOTTOM; + } else { + artRect = ArtRect.AFTERMATH_TOP; + } + } else if (ty == SpellAbilityType.SPLIT_RIGHT) { + artRect = ArtRect.SPLIT_RIGHT; + } else { + artRect = ArtRect.SPLIT_LEFT; + } } } + } - String fullCardName; - if (splitCard != null) { - this.isSplitCard = true; - leftSplitName = splitCard.getLeftHalfCard().getName(); - leftSplitCostsStr = String.join("", splitCard.getLeftHalfCard().getManaCostSymbols()); - leftSplitRules = splitCard.getLeftHalfCard().getRules(game); - leftSplitTypeLine = getCardTypeLine(game, splitCard.getLeftHalfCard()); - rightSplitName = splitCard.getRightHalfCard().getName(); - rightSplitCostsStr = String.join("", splitCard.getRightHalfCard().getManaCostSymbols()); - rightSplitRules = splitCard.getRightHalfCard().getRules(game); - rightSplitTypeLine = getCardTypeLine(game, splitCard.getRightHalfCard()); + // Cases, classes and sagas have portrait art + if (card.getSubtype(game).contains(SubType.CASE) || + card.getSubtype(game).contains(SubType.CLASS)) { + artRect = ArtRect.FULL_LENGTH_LEFT; + } else if (card.getSubtype(game).contains(SubType.SAGA)) { + artRect = ArtRect.FULL_LENGTH_RIGHT; + } + + // Frame color + this.frameColor = card.getFrameColor(game).copy(); + + // Frame style + this.frameStyle = card.getFrameStyle(); + + // Get starting loyalty + this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty()); + + // Get starting defense + this.startingDefense = CardUtil.convertLoyaltyOrDefense(card.getStartingDefense()); + + // add card icons at the end, so it will have full card view data + this.generateCardIcons(null, card, game); + } + + private void setFaceUpInfo(Game game, Card card) { + if (card instanceof PermanentToken) { + this.isToken = true; + this.mageObjectType = MageObjectType.TOKEN; + this.rarity = Rarity.SPECIAL; + this.rules = new ArrayList<>(card.getRules(game)); + } else { + this.rarity = card.getRarity(); + this.isToken = false; + } + + this.extraDeckCard = card.isExtraDeckCard(); + + // transformable, double faces cards + this.transformable = card.isTransformable(); + + Card secondSideCard = card.getSecondCardFace(); + if (secondSideCard != null) { + this.secondCardFace = new CardView(secondSideCard, game); + this.alternateName = secondCardFace.getName(); + } + + this.flipCard = card.isFlipCard(); + if (card.isFlipCard() && card.getFlipCardName() != null) { + this.alternateName = card.getFlipCardName(); + } + + if (card instanceof ModalDoubleFacedCard) { + this.transformable = true; // enable GUI day/night button + ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card; + this.secondCardFace = new CardView(mdfCard.getRightHalfCard(), game); + 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 PermanentToken && card.isTransformable()) { + Token backFace = (Token) ((PermanentToken) card).getOtherFace(); + this.secondCardFace = new CardView(backFace, game); + this.alternateName = backFace.getName(); + } + } + + private void setFaceUpName(Game game, Card card) { + SplitCard splitCard = null; + if (card instanceof SplitCard) { + splitCard = (SplitCard) card; + rotate = (card.getSpellAbility().getSpellAbilityType()) != SpellAbilityType.SPLIT_AFTERMATH; + } else if (card instanceof Spell) { + switch (card.getSpellAbility().getSpellAbilityType()) { + case SPLIT_FUSED: + splitCard = (SplitCard) ((Spell) card).getCard(); + rotate = true; + break; + case SPLIT_AFTERMATH: + splitCard = (SplitCard) ((Spell) card).getCard(); + rotate = false; + break; + case SPLIT_LEFT: + case SPLIT_RIGHT: + rotate = true; + break; + case MODAL_LEFT: + case MODAL_RIGHT: + rotate = false; + break; + } + } + + String fullCardName; + if (splitCard != null) { + this.isSplitCard = true; + leftSplitName = splitCard.getLeftHalfCard().getName(); + leftSplitCostsStr = String.join("", splitCard.getLeftHalfCard().getManaCostSymbols()); + leftSplitRules = splitCard.getLeftHalfCard().getRules(game); + leftSplitTypeLine = getCardTypeLine(game, splitCard.getLeftHalfCard()); + rightSplitName = splitCard.getRightHalfCard().getName(); + rightSplitCostsStr = String.join("", splitCard.getRightHalfCard().getManaCostSymbols()); + rightSplitRules = splitCard.getRightHalfCard().getRules(game); + rightSplitTypeLine = getCardTypeLine(game, splitCard.getRightHalfCard()); fullCardName = card.getName(); // split card contains full name as normal this.manaCostLeftStr = splitCard.getLeftHalfCard().getManaCostSymbols(); @@ -458,38 +577,100 @@ public CardView(Card sourceCard, Game game, boolean showAsControlled, boolean st this.manaCostRightStr = new ArrayList<>(); } - this.name = card.getName(); - this.displayName = card.getName(); - this.displayFullName = fullCardName; - this.rules = new ArrayList<>(card.getRules(game)); - this.manaValue = card.getManaValue(); + this.name = card.getName(); + this.displayName = card.getName(); + this.displayFullName = fullCardName; + } + + private void setFaceDownInfo(Game game, Card card, Card sourceCard, boolean showHiddenFaceDownData, String sourceName) { + setImageInfo(game, card); + boolean hideFaceDownInfo = !(sourceCard instanceof Spell) && !(card instanceof Permanent); + // TODO: replace hideFaceDownInfo workaround to have methods return proper values if face down + if (hideFaceDownInfo) { + this.power = ""; + this.toughness = ""; + this.cardTypes = new ArrayList<>(); + this.subTypes = new SubTypes(); + this.superTypes = new ArrayList<>(); + this.color = new ObjectColor(); + this.rules = new ArrayList<>(); + } + else if (sourceCard.isCopy()) { + this.subTypes = new SubTypes(); + this.superTypes = new ArrayList<>(); + this.color = new ObjectColor(); } - // shared info - counters and other - if (card instanceof Permanent) { - this.mageObjectType = MageObjectType.PERMANENT; - Permanent permanent = (Permanent) card; - if (game != null) { - if (permanent.getCounters(game) != null && !permanent.getCounters(game).isEmpty()) { - this.loyalty = Integer.toString(permanent.getCounters(game).getCount(CounterType.LOYALTY)); - this.defense = Integer.toString(permanent.getCounters(game).getCount(CounterType.DEFENSE)); - counters = new ArrayList<>(); - for (Counter counter : permanent.getCounters(game).values()) { - counters.add(new CounterView(counter)); - } - } - this.pairedCard = permanent.getPairedCard() != null ? permanent.getPairedCard().getSourceId() : null; - this.bandedCards = new ArrayList<>(); - for (UUID bandedCard : permanent.getBandedCards()) { - bandedCards.add(bandedCard); - } - if (!permanent.getControllerId().equals(permanent.getOwnerId())) { - controlledByOwner = false; - } - if (permanent.isTransformed()) { - transformed = true; + // can show face up card name for controller or game end + // TODO: add exception on non empty name of the faced-down card here + String visibleName = CardUtil.getCardNameForGUI(showHiddenFaceDownData ? sourceName : "", this.imageFileName); + this.name = visibleName; + this.displayName = visibleName; + this.displayFullName = visibleName; + this.alternateName = visibleName; + this.frameColor = new ObjectColor(); + this.frameStyle = FrameStyle.M15_NORMAL; + this.manaCostLeftStr = new ArrayList<>(); + this.manaCostRightStr = new ArrayList<>(); + this.manaValue = 0; + this.rarity = Rarity.SPECIAL; // hide rarity info + + // GUI: enable day/night button to view original face up card + if (showHiddenFaceDownData && !sourceCard.isCopy()) { + this.transformable = true; + this.secondCardFace = new CardView(sourceCard.getMainCard()); // do not use game param, so it will take default card + this.alternateName = sourceCard.getMainCard().getName(); + } + else if (showHiddenFaceDownData && sourceCard.getCopyFrom() instanceof Card) { + this.transformable = true; + this.secondCardFace = new CardView(((Card) sourceCard.getCopyFrom()).getMainCard()); + this.alternateName = sourceCard.getCopyFrom().getName(); + } + } + + private void setPermanentInfo(Game game, Permanent card) { + this.mageObjectType = MageObjectType.PERMANENT; + if (game != null) { + if (card.getCounters(game) != null && !card.getCounters(game).isEmpty()) { + this.loyalty = Integer.toString(card.getCounters(game).getCount(CounterType.LOYALTY)); + this.defense = Integer.toString(card.getCounters(game).getCount(CounterType.DEFENSE)); + counters = new ArrayList<>(); + for (Counter counter : card.getCounters(game).values()) { + counters.add(new CounterView(counter)); } } + this.pairedCard = card.getPairedCard() != null ? card.getPairedCard().getSourceId() : null; + this.bandedCards = new ArrayList<>(); + bandedCards.addAll(card.getBandedCards()); + if (!card.getControllerId().equals(card.getOwnerId())) { + controlledByOwner = false; + } + if (card.isTransformed()) { + transformed = true; + } + } + } + + private void setSharedInfo(Game game, Card card) { + this.power = Integer.toString(card.getPower().getValue()); + this.toughness = Integer.toString(card.getToughness().getValue()); + this.cardTypes = new ArrayList<>(card.getCardType(game)); + this.subTypes = card.getSubtype(game).copy(); + this.superTypes = new ArrayList<>(card.getSuperType(game)); + this.color = card.getColor(game).copy(); + this.rules = new ArrayList<>(card.getRules(game)); + this.flipCard = card.isFlipCard(); + + // default image info + this.expansionSetCode = card.getExpansionSetCode(); + this.cardNumber = card.getCardNumber(); + this.imageFileName = card.getImageFileName(); + this.imageNumber = card.getImageNumber(); + this.usesVariousArt = card.getUsesVariousArt(); + + // shared info - counters and other + if (card instanceof Permanent) { + setPermanentInfo(game, (Permanent) card); } else { if (card.isCopy()) { this.mageObjectType = MageObjectType.COPY_CARD; @@ -505,162 +686,6 @@ public CardView(Card sourceCard, Game game, boolean showAsControlled, boolean st } } } - - // FACE UP INFO - if (showFaceUp) { - // TODO: extract characteristics setup to shared code (same for face down and normal cards) - // PT, card types/subtypes/super/color/rules - this.power = Integer.toString(card.getPower().getValue()); - this.toughness = Integer.toString(card.getToughness().getValue()); - this.cardTypes = new ArrayList<>(card.getCardType(game)); - this.subTypes = card.getSubtype(game).copy(); - this.superTypes = card.getSuperType(game); - this.color = card.getColor(game).copy(); - this.flipCard = card.isFlipCard(); - - if (card instanceof PermanentToken) { - this.isToken = true; - this.mageObjectType = MageObjectType.TOKEN; - this.rarity = Rarity.SPECIAL; - this.rules = new ArrayList<>(card.getRules(game)); - } else { - this.rarity = card.getRarity(); - this.isToken = false; - } - - this.extraDeckCard = card.isExtraDeckCard(); - - // transformable, double faces cards - this.transformable = card.isTransformable(); - - Card secondSideCard = card.getSecondCardFace(); - if (secondSideCard != null) { - this.secondCardFace = new CardView(secondSideCard, game); - this.alternateName = secondCardFace.getName(); - } - - this.flipCard = card.isFlipCard(); - if (card.isFlipCard() && card.getFlipCardName() != null) { - this.alternateName = card.getFlipCardName(); - } - - if (card instanceof ModalDoubleFacedCard) { - this.transformable = true; // enable GUI day/night button - ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card; - this.secondCardFace = new CardView(mdfCard.getRightHalfCard(), game); - 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 PermanentToken && card.isTransformable()) { - Token backFace = (Token) ((PermanentToken) card).getOtherFace(); - this.secondCardFace = new CardView(backFace, game); - this.alternateName = backFace.getName(); - } - } - - // shared info - targets - if (card instanceof Spell) { - this.mageObjectType = MageObjectType.SPELL; - Spell spell = (Spell) card; - for (SpellAbility spellAbility : spell.getSpellAbilities()) { - for (UUID modeId : spellAbility.getModes().getSelectedModes()) { - Mode mode = spellAbility.getModes().get(modeId); - if (!mode.getTargets().isEmpty()) { - addTargets(mode.getTargets(), mode.getEffects(), spellAbility, game); - } - } - } - - // show for modal spell, which mode was chosen - if (spell.getSpellAbility().isModal()) { - for (UUID modeId : spell.getSpellAbility().getModes().getSelectedModes()) { - Mode mode = spell.getSpellAbility().getModes().get(modeId); - this.rules.add("Chosen mode: " + mode.getEffects().getText(mode) + ""); - } - } - - // show target of a spell on the stack - if (!spell.getSpellAbility().getTargets().isEmpty()) { - StackObject stackObjectTarget = null; - for (Target target : spell.getSpellAbility().getTargets()) { - for (UUID targetId : target.getTargets()) { - MageObject mo = game.getObject(targetId); - if (mo instanceof StackObject) { - stackObjectTarget = (StackObject) mo; - } - if (stackObjectTarget != null) { - this.rules.add("Target on stack: " + stackObjectTarget.getIdName()); - } - } - } - } - } - - // render info - if (showFaceUp) { - if (card instanceof Spell) { - Spell spell = (Spell) card; - // Determine what part of the art to slice out for spells on the stack which originate - // from a split, fuse, or aftermath split card. - // Modal double faces cards draws as normal cards - SpellAbilityType ty = spell.getSpellAbility().getSpellAbilityType(); - if (ty == SpellAbilityType.SPLIT_RIGHT || ty == SpellAbilityType.SPLIT_LEFT || ty == SpellAbilityType.SPLIT_FUSED) { - // Needs a special art rect - if (ty == SpellAbilityType.SPLIT_FUSED) { - artRect = ArtRect.SPLIT_FUSED; - } else if (spell.getCard() != null) { - SplitCard wholeCard = ((SplitCardHalf) spell.getCard()).getParentCard(); - Abilities aftermathHalfAbilities = wholeCard.getRightHalfCard().getAbilities(game); - if (aftermathHalfAbilities.stream().anyMatch(AftermathAbility.class::isInstance)) { - if (ty == SpellAbilityType.SPLIT_RIGHT) { - artRect = ArtRect.AFTERMATH_BOTTOM; - } else { - artRect = ArtRect.AFTERMATH_TOP; - } - } else if (ty == SpellAbilityType.SPLIT_RIGHT) { - artRect = ArtRect.SPLIT_RIGHT; - } else { - artRect = ArtRect.SPLIT_LEFT; - } - } - } - } - - // Cases, classes and sagas have portrait art - if (card.getSubtype(game).contains(SubType.CASE) || - card.getSubtype(game).contains(SubType.CLASS)) { - artRect = ArtRect.FULL_LENGTH_LEFT; - } else if (card.getSubtype(game).contains(SubType.SAGA)) { - artRect = ArtRect.FULL_LENGTH_RIGHT; - } - - // Retro border cards need different art cutout - if (card.getFrameStyle() == FrameStyle.RETRO) { - this.artRect = ArtRect.RETRO; - } - - // Frame color - this.frameColor = card.getFrameColor(game).copy(); - - // Frame style - this.frameStyle = card.getFrameStyle(); - - // Get starting loyalty - this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty()); - - // Get starting defense - this.startingDefense = CardUtil.convertLoyaltyOrDefense(card.getStartingDefense()); - - // add card icons at the end, so it will have full card view data - this.generateCardIcons(null, card, game); - } } /** @@ -972,7 +997,7 @@ public CardView(boolean empty) { if (!empty) { throw new IllegalArgumentException("Not supported."); } - fillEmptyWithImageInfo(null, null, false); + fillEmptyWithImageInfo(); } public static boolean cardViewEquals(CardView a, CardView b) { // TODO: This belongs in CardView @@ -1019,7 +1044,7 @@ public static boolean cardViewEquals(CardView a, CardView b) { // TODO: This bel && aa.getDamage() == bb.getDamage(); } - private void fillEmptyWithImageInfo(Game game, Card imageSourceCard, boolean isFaceDown) { + private void fillEmptyWithImageInfo() { this.name = ""; this.displayName = ""; this.displayFullName = ""; @@ -1046,7 +1071,9 @@ private void fillEmptyWithImageInfo(Game game, Card imageSourceCard, boolean isF this.manaCostRightStr = new ArrayList<>(); this.manaValue = 0; this.rarity = Rarity.SPECIAL; // hide rarity info + } + private void setImageInfo(Game game, Card imageSourceCard) { if (imageSourceCard != null) { // keep inner images info (server side card already contain actual info) String imageSetCode = imageSourceCard.getExpansionSetCode(); @@ -1075,13 +1102,7 @@ private void fillEmptyWithImageInfo(Game game, Card imageSourceCard, boolean isF } } - // make default face down image - // TODO: implement diff backface images someday and insert here (user data + card owner) - if (isFaceDown && this.imageFileName.isEmpty()) { - this.name = ""; - this.displayName = this.name; - this.displayFullName = this.name; - + if (this.imageFileName.isEmpty()) { // as foretell face down // TODO: it's not ok to use that code - server side objects must has all data, see BecomesFaceDownCreatureEffect.makeFaceDownObject // it must be a more global bug for card characteristics, not client side viewer @@ -1097,9 +1118,12 @@ private void fillEmptyWithImageInfo(Game game, Card imageSourceCard, boolean isF return; } + // make default face down image + // TODO: implement diff backface images someday and insert here (user data + card owner) + // as normal face down TokenInfo tokenInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MANUAL, this.getId()); - if (tokenInfo != null) { + if (this.imageFileName.isEmpty() && tokenInfo != null) { this.expansionSetCode = tokenInfo.getSetCode(); this.cardNumber = "0"; this.imageFileName = tokenInfo.getName(); diff --git a/Mage.Common/src/main/java/mage/view/ExileView.java b/Mage.Common/src/main/java/mage/view/ExileView.java index bfcafcfb5a92..e70dbd84f242 100644 --- a/Mage.Common/src/main/java/mage/view/ExileView.java +++ b/Mage.Common/src/main/java/mage/view/ExileView.java @@ -2,11 +2,10 @@ package mage.view; -import java.util.UUID; import mage.cards.Card; import mage.game.ExileZone; import mage.game.Game; -import mage.util.CardUtil; +import java.util.UUID; /** * @@ -22,7 +21,7 @@ public ExileView(ExileZone exileZone, Game game, UUID createdForPlayerId) { this.name = exileZone.getName(); this.id = exileZone.getId(); for (Card card: exileZone.getCards(game)) { - this.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId))); + this.put(card.getId(), new CardView(card, game, exileZone.isPlayerAllowedToSeeCard(createdForPlayerId, card))); } } diff --git a/Mage.Common/src/main/java/mage/view/PlayerView.java b/Mage.Common/src/main/java/mage/view/PlayerView.java index 21f8ad3d5b37..47ec13dc5f7d 100644 --- a/Mage.Common/src/main/java/mage/view/PlayerView.java +++ b/Mage.Common/src/main/java/mage/view/PlayerView.java @@ -87,7 +87,7 @@ public PlayerView(Player player, GameState state, Game game, UUID createdForPlay for (ExileZone exileZone : game.getExile().getExileZones()) { for (Card card : exileZone.getCards(game)) { if (player.getId().equals(card.getOwnerId())) { - exile.put(card.getId(), new CardView(card, game, CardUtil.canShowAsControlled(card, createdForPlayerId))); + exile.put(card.getId(), new CardView(card, game, exileZone.isPlayerAllowedToSeeCard(createdForPlayerId, card))); } } } diff --git a/Mage.Sets/src/mage/cards/a/ArvinoxTheMindFlail.java b/Mage.Sets/src/mage/cards/a/ArvinoxTheMindFlail.java index 1012742cb9ff..6b8efebfc03a 100644 --- a/Mage.Sets/src/mage/cards/a/ArvinoxTheMindFlail.java +++ b/Mage.Sets/src/mage/cards/a/ArvinoxTheMindFlail.java @@ -2,24 +2,20 @@ import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.AsThoughManaEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.LoseCreatureTypeSourceEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.*; import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.game.Game; -import mage.players.ManaPoolItem; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.UUID; @@ -100,144 +96,13 @@ public boolean apply(Game game, Ability source) { } cards.add(opponent.getLibrary().getFromBottom(game)); } - controller.moveCardsToExile( - cards.getCards(game), source, game, false, - CardUtil.getExileZoneId(game, source), - CardUtil.getSourceName(game, source) - ); - cards.getCards(game).stream().forEach(card -> card.setFaceDown(true, game)); - for (Card card : cards.getCards(game)) { - card.setFaceDown(true, game); - game.addEffect(new ArvinoxTheMindFlailCastFromExileEffect().setTargetPointer(new FixedTarget(card, game)), source); - game.addEffect(new ArvinoxTheMindFlailSpendAnyManaEffect().setTargetPointer(new FixedTarget(card, game)), source); - game.addEffect(new ArvinoxTheMindFlailLookEffect(source.getControllerId()).setTargetPointer(new FixedTarget(card, game)), source); - } - return true; - } -} - -class ArvinoxTheMindFlailCastFromExileEffect extends AsThoughEffectImpl { - - ArvinoxTheMindFlailCastFromExileEffect() { - super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); - } - - private ArvinoxTheMindFlailCastFromExileEffect(final ArvinoxTheMindFlailCastFromExileEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public ArvinoxTheMindFlailCastFromExileEffect copy() { - return new ArvinoxTheMindFlailCastFromExileEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID targetId = getTargetPointer().getFirst(game, source); - if (targetId == null) { - this.discard(); // card is no longer in the origin zone, effect can be discarded - return false; - } - Card theCard = game.getCard(objectId); - if (theCard == null - || theCard.isLand(game) - || !theCard.isPermanent(game)) { - return false; - } - objectId = theCard.getMainCard().getId(); // for split cards - - if (objectId.equals(targetId) - && affectedControllerId.equals(source.getControllerId())) { - Card card = game.getCard(objectId); - // TODO: Allow to cast Zoetic Cavern face down - return card != null; - } - return false; - } -} - -class ArvinoxTheMindFlailSpendAnyManaEffect extends AsThoughEffectImpl implements AsThoughManaEffect { - - public ArvinoxTheMindFlailSpendAnyManaEffect() { - super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.Custom, Outcome.Benefit); - staticText = "you may spend mana as though it were mana of any color to cast it"; - } - - private ArvinoxTheMindFlailSpendAnyManaEffect(final ArvinoxTheMindFlailSpendAnyManaEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public ArvinoxTheMindFlailSpendAnyManaEffect copy() { - return new ArvinoxTheMindFlailSpendAnyManaEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - Card theCard = game.getCard(objectId); - if (theCard == null) { - return false; - } - objectId = theCard.getMainCard().getId(); // for split cards - if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) - && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - // if the card moved from exile to spell the zone change counter is increased by 1 (effect must applies before and on stack, use isCheckPlayableMode?) - return source.isControlledBy(affectedControllerId); - } else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { - // object has moved zone so effect can be discarded - this.discard(); + if (CardUtil.moveCardsToExileFaceDown(game, source, controller, cards.getCards(game), true)) { + for (Card card : cards.getCards(game)) { + if (card.isPermanent(game)) { + CardUtil.makeCardPlayable(game, source, card, true, Duration.Custom, true); + } + } } - return false; - } - - @Override - public ManaType getAsThoughManaType(ManaType manaType, ManaPoolItem mana, UUID affectedControllerId, Ability source, Game game) { - return mana.getFirstAvailable(); - } -} - -class ArvinoxTheMindFlailLookEffect extends AsThoughEffectImpl { - - private final UUID authorizedPlayerId; - - public ArvinoxTheMindFlailLookEffect(UUID authorizedPlayerId) { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - this.authorizedPlayerId = authorizedPlayerId; - staticText = "You may look at the cards exiled with {this}"; - } - - private ArvinoxTheMindFlailLookEffect(final ArvinoxTheMindFlailLookEffect effect) { - super(effect); - this.authorizedPlayerId = effect.authorizedPlayerId; - } - - @Override - public boolean apply(Game game, Ability source) { return true; } - - @Override - public ArvinoxTheMindFlailLookEffect copy() { - return new ArvinoxTheMindFlailLookEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID cardId = getTargetPointer().getFirst(game, source); - if (cardId == null) { - this.discard(); // card is no longer in the origin zone, effect can be discarded - } - return affectedControllerId.equals(authorizedPlayerId) - && objectId.equals(cardId); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BaneAlleyBroker.java b/Mage.Sets/src/mage/cards/b/BaneAlleyBroker.java index 9223387c3ee8..8b858f17d353 100644 --- a/Mage.Sets/src/mage/cards/b/BaneAlleyBroker.java +++ b/Mage.Sets/src/mage/cards/b/BaneAlleyBroker.java @@ -6,12 +6,15 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.LookAtCardsExiledWithThisEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.ExileZone; import mage.game.Game; @@ -68,7 +71,7 @@ public BaneAlleyBroker(UUID ownerId, CardSetInfo setInfo) { this.addAbility(new SimpleActivatedAbility(new BaneAlleyBrokerDrawExileEffect(), new TapSourceCost())); // You may look at cards exiled with Bane Alley Broker. - this.addAbility(new SimpleStaticAbility(new BaneAlleyBrokerLookAtCardEffect())); + this.addAbility(new SimpleStaticAbility(new LookAtCardsExiledWithThisEffect())); // {U}{B}, {tap}: Return a card exiled with Bane Alley Broker to its owner's hand. Ability ability = new SimpleActivatedAbility(new BaneAlleyBrokerReturnToHandEffect(), new ManaCostsImpl<>("{U}{B}")); @@ -113,13 +116,7 @@ public boolean apply(Game game, Ability source) { if (card == null) { return false; } - if (!controller.moveCardsToExile( - card, source, game, false, CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source) - )) { - return false; - } - card.setFaceDown(true, game); - return true; + return CardUtil.moveCardsToExileFaceDown(game, source, controller, card, true); } @Override @@ -165,36 +162,3 @@ public boolean apply(Game game, Ability source) { return player.moveCards(card, Zone.HAND, source, game); } } - -class BaneAlleyBrokerLookAtCardEffect extends AsThoughEffectImpl { - - BaneAlleyBrokerLookAtCardEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - staticText = "You may look at cards exiled with {this}"; - } - - private BaneAlleyBrokerLookAtCardEffect(final BaneAlleyBrokerLookAtCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public BaneAlleyBrokerLookAtCardEffect copy() { - return new BaneAlleyBrokerLookAtCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (!source.isControlledBy(affectedControllerId)) { - return false; - } - Card card = game.getCard(objectId); - ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); - return card != null && exile != null && exile.getCards(game).contains(card); - } - -} diff --git a/Mage.Sets/src/mage/cards/c/ColfenorsPlans.java b/Mage.Sets/src/mage/cards/c/ColfenorsPlans.java index bcf6c27e2d73..5b0084d96d43 100644 --- a/Mage.Sets/src/mage/cards/c/ColfenorsPlans.java +++ b/Mage.Sets/src/mage/cards/c/ColfenorsPlans.java @@ -1,26 +1,23 @@ package mage.cards.c; -import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.SkipDrawStepEffect; import mage.abilities.effects.common.continuous.CantCastMoreThanOneSpellEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.abilities.effects.common.continuous.MayPlayCardsExiledWithThisEffect; +import mage.abilities.effects.common.continuous.LookAtCardsExiledWithThisEffect; +import mage.cards.*; import mage.constants.*; -import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; import mage.util.CardUtil; +import java.util.Set; +import java.util.UUID; + /** * * @author Styxo @@ -34,8 +31,8 @@ public ColfenorsPlans(UUID ownerId, CardSetInfo setInfo) { this.addAbility(new EntersBattlefieldTriggeredAbility(new ColfenorsPlansExileEffect(), false)); // You may look at and play cards exiled with Colfenor's Plans. - this.addAbility(new SimpleStaticAbility(new ColfenorsPlansPlayCardEffect())); - this.addAbility(new SimpleStaticAbility(Zone.ALL, new ColfenorsPlansLookAtCardEffect())); + this.addAbility(new SimpleStaticAbility(new MayPlayCardsExiledWithThisEffect())); + this.addAbility(new SimpleStaticAbility(new LookAtCardsExiledWithThisEffect())); // Skip your draw step. this.addAbility(new SimpleStaticAbility(new SkipDrawStepEffect())); @@ -70,18 +67,10 @@ private ColfenorsPlansExileEffect(final ColfenorsPlansExileEffect effect) { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Cards toExile = new CardsImpl(controller.getLibrary().getTopCards(game, 7)); + Set toExile = controller.getLibrary().getTopCards(game, 7); UUID exileId = CardUtil.getCardExileZoneId(game, source); - controller.moveCardsToExile(toExile.getCards(game), source, game, false, - exileId, CardUtil.createObjectRelatedWindowTitle(source, game, null)); - ExileZone exileZone = game.getExile().getExileZone(exileId); - if (exileZone != null) { - for (Card card : exileZone.getCards(game)) { - if (card != null) { - card.setFaceDown(true, game); - } - } - } + CardUtil.moveCardsToExileFaceDown(game, source, controller, toExile, exileId, + CardUtil.createObjectRelatedWindowTitle(source, game, null), true); return true; } return false; @@ -91,75 +80,4 @@ public boolean apply(Game game, Ability source) { public ColfenorsPlansExileEffect copy() { return new ColfenorsPlansExileEffect(this); } -} - -class ColfenorsPlansPlayCardEffect extends AsThoughEffectImpl { - - ColfenorsPlansPlayCardEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "You may play cards exiled with {this}"; - } - - private ColfenorsPlansPlayCardEffect(final ColfenorsPlansPlayCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public ColfenorsPlansPlayCardEffect copy() { - return new ColfenorsPlansPlayCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (affectedControllerId.equals(source.getControllerId()) && game.getState().getZone(objectId) == Zone.EXILED) { - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source)); - return exileZone != null && exileZone.contains(objectId); - } - return false; - } -} - -class ColfenorsPlansLookAtCardEffect extends AsThoughEffectImpl { - - ColfenorsPlansLookAtCardEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - staticText = "You may look at cards exiled with {this}"; - } - - private ColfenorsPlansLookAtCardEffect(final ColfenorsPlansLookAtCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public ColfenorsPlansLookAtCardEffect copy() { - return new ColfenorsPlansLookAtCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (affectedControllerId.equals(source.getControllerId())) { - Card card = game.getCard(objectId); - if (card != null) { - MageObject sourceObject = game.getObject(source); - if (sourceObject == null) { - return false; - } - UUID exileId = CardUtil.getCardExileZoneId(game, source); - ExileZone exile = game.getExile().getExileZone(exileId); - return exile != null && exile.contains(objectId); - } - } - return false; - } - -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GontiNightMinister.java b/Mage.Sets/src/mage/cards/g/GontiNightMinister.java index af7783794e03..60b915470047 100644 --- a/Mage.Sets/src/mage/cards/g/GontiNightMinister.java +++ b/Mage.Sets/src/mage/cards/g/GontiNightMinister.java @@ -1,17 +1,14 @@ package mage.cards.g; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenTargetEffect; -import mage.abilities.effects.common.asthought.MayLookAtTargetCardEffect; import mage.cards.Card; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.game.Game; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; @@ -22,6 +19,8 @@ import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.UUID; + /** * * @author Jmlundeen @@ -153,13 +152,11 @@ public boolean apply(Game game, Ability source) { } UUID exileZoneId = CardUtil.getExileZoneId(game, controller.getId(), source.getStackMomentSourceZCC()); String exileName = CardUtil.getSourceName(game, source) + " - " + controller.getName(); - if (controller.moveCardsToExile(card, source, game, false, exileZoneId, exileName)) { - card.setFaceDown(true, game); - CardUtil.makeCardPlayable(game, source, card, false, Duration.Custom, true, controller.getId(), null); + + if (!CardUtil.moveCardsToExileFaceDown(game, source, controller, card, exileZoneId, exileName, true)) { + return false; } - ContinuousEffect effect = new MayLookAtTargetCardEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card.getId())); - game.addEffect(effect, source); + CardUtil.makeCardPlayable(game, source, card, false, Duration.Custom, true, controller.getId(), null); return true; } } diff --git a/Mage.Sets/src/mage/cards/g/GrimoireThief.java b/Mage.Sets/src/mage/cards/g/GrimoireThief.java index 18e81ea55b2e..0d71360718d7 100644 --- a/Mage.Sets/src/mage/cards/g/GrimoireThief.java +++ b/Mage.Sets/src/mage/cards/g/GrimoireThief.java @@ -156,6 +156,7 @@ public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, if (!exileZones.contains(exileZone.getId())) { return false; } + exileZone.letPlayerSeeCards(controller.getId(), card); } } return true; diff --git a/Mage.Sets/src/mage/cards/g/GusthasScepter.java b/Mage.Sets/src/mage/cards/g/GusthasScepter.java index 1597f6bfa1f2..a1709784a80e 100644 --- a/Mage.Sets/src/mage/cards/g/GusthasScepter.java +++ b/Mage.Sets/src/mage/cards/g/GusthasScepter.java @@ -1,16 +1,16 @@ package mage.cards.g; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.ExileZone; import mage.game.Game; @@ -74,13 +74,7 @@ public boolean apply(Game game, Ability source) { if (card == null) { return false; } - controller.moveCardsToExile( - card, source, game, false, - CardUtil.getExileZoneId(game, source), - CardUtil.getSourceName(game, source) - ); - card.setFaceDown(true, game); - game.addEffect(new GusthasScepterLookAtCardEffect(card, game), source); + CardUtil.moveCardsToExileFaceDown(game, source, controller, card, true); return true; } @@ -125,46 +119,6 @@ public boolean apply(Game game, Ability source) { } } -class GusthasScepterLookAtCardEffect extends AsThoughEffectImpl { - - private final MageObjectReference mor; - - public GusthasScepterLookAtCardEffect(Card card, Game game) { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - this.mor = new MageObjectReference(card, game); - staticText = "You may look at it for as long as it remains exiled"; - } - - private GusthasScepterLookAtCardEffect(final GusthasScepterLookAtCardEffect effect) { - super(effect); - this.mor = effect.mor; - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public GusthasScepterLookAtCardEffect copy() { - return new GusthasScepterLookAtCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (!mor.zoneCounterIsCurrent(game)) { - discard(); - return false; - } - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); - if (exileZone == null || !exileZone.contains(mor.getSourceId())) { - discard(); - return false; - } - return mor.refersTo(objectId, game) && source.isControlledBy(affectedControllerId); - } -} - class GusthasScepterTriggeredAbility extends TriggeredAbilityImpl { public GusthasScepterTriggeredAbility() { diff --git a/Mage.Sets/src/mage/cards/h/HaukensInsight.java b/Mage.Sets/src/mage/cards/h/HaukensInsight.java index d2b944a5f9ef..4c96501415ce 100644 --- a/Mage.Sets/src/mage/cards/h/HaukensInsight.java +++ b/Mage.Sets/src/mage/cards/h/HaukensInsight.java @@ -1,30 +1,29 @@ package mage.cards.h; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - import mage.MageIdentifier; import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.Card; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.game.ExileZone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import mage.watchers.Watcher; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** * * @author weirddan455 @@ -83,56 +82,13 @@ public boolean apply(Game game, Ability source) { UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); MageObject sourceObject = source.getSourceObject(game); String exileName = sourceObject == null ? null : sourceObject.getIdName(); - card.setFaceDown(true, game); - controller.moveCardsToExile(card, source, game, false, exileId, exileName); - if (game.getState().getZone(card.getId()) == Zone.EXILED) { - card.setFaceDown(true, game); - HaukensInsightLookEffect effect = new HaukensInsightLookEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card, game)); - game.addEffect(effect, source); - return true; - } + CardUtil.moveCardsToExileFaceDown(game, source, controller, card, exileId, exileName, true); } } return false; } } -class HaukensInsightLookEffect extends AsThoughEffectImpl { - - private final UUID authorizedPlayerId; - - public HaukensInsightLookEffect(UUID authorizedPlayerId) { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - this.authorizedPlayerId = authorizedPlayerId; - } - - private HaukensInsightLookEffect(final HaukensInsightLookEffect effect) { - super(effect); - this.authorizedPlayerId = effect.authorizedPlayerId; - } - - @Override - public HaukensInsightLookEffect copy() { - return new HaukensInsightLookEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID cardId = getTargetPointer().getFirst(game, source); - if (cardId == null) { - this.discard(); // card is no longer in the origin zone, effect can be discarded - } - return affectedControllerId.equals(authorizedPlayerId) - && objectId.equals(cardId); - } -} - class HaukensInsightPlayEffect extends AsThoughEffectImpl { HaukensInsightPlayEffect() { diff --git a/Mage.Sets/src/mage/cards/h/HeadlinerScarlett.java b/Mage.Sets/src/mage/cards/h/HeadlinerScarlett.java index 6854e49e90e1..54bfae9b6cd8 100644 --- a/Mage.Sets/src/mage/cards/h/HeadlinerScarlett.java +++ b/Mage.Sets/src/mage/cards/h/HeadlinerScarlett.java @@ -2,23 +2,21 @@ import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.asthought.MayLookAtTargetCardEffect; import mage.abilities.effects.common.combat.CantBlockAllEffect; import mage.abilities.keyword.HasteAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.UUID; @@ -120,14 +118,12 @@ public boolean apply(Game game, Ability source) { UUID exileId = CardUtil.getExileZoneId("HeadlinerScarlett::" + source.getSourceId() + "::" + game.getTurn(), game); String exileName = CardUtil.getSourceIdName(game, source) + " turn:" + game.getTurnNum(); - card.setFaceDown(true, game); - controller.moveCardsToExile(card, source, game, false, exileId, exileName); - if (game.getState().getZone(card.getId()) == Zone.EXILED) { - card.setFaceDown(true, game); + if (CardUtil.moveCardsToExileFaceDown(game, source, controller, card, exileId, exileName, true)) { CardUtil.makeCardPlayable(game, source, card, false, Duration.EndOfTurn, false); - ContinuousEffect effect = new MayLookAtTargetCardEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card, game)); - game.addEffect(effect, source); + ExileZone exileZone = game.getExile().getExileZone(exileId); + if (exileZone != null) { + exileZone.setCleanupOnEndTurn(true); + } } return true; } diff --git a/Mage.Sets/src/mage/cards/h/HoardingBroodlord.java b/Mage.Sets/src/mage/cards/h/HoardingBroodlord.java index aad78e255120..a504004502e4 100644 --- a/Mage.Sets/src/mage/cards/h/HoardingBroodlord.java +++ b/Mage.Sets/src/mage/cards/h/HoardingBroodlord.java @@ -91,12 +91,13 @@ public boolean apply(Game game, Ability source) { TargetCardInLibrary target = new TargetCardInLibrary(); player.searchLibrary(target, source, game); Card card = player.getLibrary().getCard(target.getFirstTarget(), game); - player.shuffleLibrary(source, game); - if (card != null) { - player.moveCards(card, Zone.EXILED, source, game); - card.setFaceDown(true, game); + if (card == null) { + return false; + } + if (CardUtil.moveCardsToExileFaceDown(game, source, player, card, true)) { CardUtil.makeCardPlayable(game, source, card, false, Duration.Custom, false); } + player.shuffleLibrary(source, game); return true; } } diff --git a/Mage.Sets/src/mage/cards/i/IntetTheDreamer.java b/Mage.Sets/src/mage/cards/i/IntetTheDreamer.java index 091dfbc00c8b..5c711a033e10 100644 --- a/Mage.Sets/src/mage/cards/i/IntetTheDreamer.java +++ b/Mage.Sets/src/mage/cards/i/IntetTheDreamer.java @@ -10,12 +10,12 @@ import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DoIfCostPaid; -import mage.abilities.effects.common.asthought.MayLookAtTargetCardEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; import mage.target.targetpointer.FixedTarget; @@ -88,9 +88,10 @@ public boolean apply(Game game, Ability source) { ContinuousEffect effect = new IntetTheDreamerAsThoughEffect(); effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId()))); game.getState().addEffect(effect, source); - effect = new MayLookAtTargetCardEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card, game)); - game.addEffect(effect, source); + ExileZone exileZone = game.getExile().getExileZone(exileId); + if (exileZone != null) { + exileZone.letPlayerSeeCards(controller.getId(), card); + } } return true; } diff --git a/Mage.Sets/src/mage/cards/i/Ixidron.java b/Mage.Sets/src/mage/cards/i/Ixidron.java index b7a2a7096a68..4ee6af20bda8 100644 --- a/Mage.Sets/src/mage/cards/i/Ixidron.java +++ b/Mage.Sets/src/mage/cards/i/Ixidron.java @@ -1,7 +1,6 @@ package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.SimpleStaticAbility; @@ -13,19 +12,21 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.card.FaceDownPredicate; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.permanent.TokenPredicate; +import java.util.UUID; + + /** * * @author LevelX2 */ public final class Ixidron extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("face-down creatures on the battlefield"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("face-down creatures on the battlefield"); private static final FilterCreaturePermanent filterTurnFaceDown = new FilterCreaturePermanent("other nontoken creatures"); static { diff --git a/Mage.Sets/src/mage/cards/j/JacobHaukenInspector.java b/Mage.Sets/src/mage/cards/j/JacobHaukenInspector.java index c71aedd5f2cb..8b7eb1d7a94f 100644 --- a/Mage.Sets/src/mage/cards/j/JacobHaukenInspector.java +++ b/Mage.Sets/src/mage/cards/j/JacobHaukenInspector.java @@ -1,27 +1,25 @@ package mage.cards.j; -import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.Card; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInHand; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.UUID; + /** * * @author weirddan455 @@ -84,54 +82,9 @@ public boolean apply(Game game, Ability source) { controller.chooseTarget(outcome, controller.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); - MageObject sourceObject = source.getSourceObject(game); - String exileName = sourceObject == null ? null : sourceObject.getIdName(); - card.setFaceDown(true, game); - controller.moveCardsToExile(card, source, game, false, exileId, exileName); - if (game.getState().getZone(card.getId()) == Zone.EXILED) { - card.setFaceDown(true, game); - JacobHaukenInspectorLookEffect effect = new JacobHaukenInspectorLookEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card, game)); - game.addEffect(effect, source); - } + CardUtil.moveCardsToExileFaceDown(game, source , controller, card, true); } } return true; } -} - -class JacobHaukenInspectorLookEffect extends AsThoughEffectImpl { - - private final UUID authorizedPlayerId; - - public JacobHaukenInspectorLookEffect(UUID authorizedPlayerId) { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - this.authorizedPlayerId = authorizedPlayerId; - } - - private JacobHaukenInspectorLookEffect(final JacobHaukenInspectorLookEffect effect) { - super(effect); - this.authorizedPlayerId = effect.authorizedPlayerId; - } - - @Override - public JacobHaukenInspectorLookEffect copy() { - return new JacobHaukenInspectorLookEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID cardId = getTargetPointer().getFirst(game, source); - if (cardId == null) { - this.discard(); // card is no longer in the origin zone, effect can be discarded - } - return affectedControllerId.equals(authorizedPlayerId) - && objectId.equals(cardId); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/j/JestersScepter.java b/Mage.Sets/src/mage/cards/j/JestersScepter.java index bb34acdfccd1..bf77912ed829 100644 --- a/Mage.Sets/src/mage/cards/j/JestersScepter.java +++ b/Mage.Sets/src/mage/cards/j/JestersScepter.java @@ -4,17 +4,14 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.cards.*; import mage.constants.*; import mage.filter.FilterCard; -import mage.game.ExileZone; import mage.game.Game; import mage.game.stack.Spell; import mage.players.Player; @@ -34,14 +31,11 @@ public final class JestersScepter extends CardImpl { public JestersScepter(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); - // When Jester's Scepter enters the battlefield, exile the top five cards of target player's library face down. + // When Jester's Scepter enters the battlefield, exile the top five cards of target player's library face down. You may look at those cards for as long as they remain exiled. Ability ability = new EntersBattlefieldTriggeredAbility(new JestersScepterEffect(), false); ability.addTarget(new TargetPlayer()); this.addAbility(ability); - // You may look at those cards for as long as they remain exiled. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new JestersScepterLookAtCardEffect())); - // {2}, {tap}, Put a card exiled with Jester's Scepter into its owner's graveyard: Counter target spell if it has the same name as that card. Ability ability2 = new SimpleActivatedAbility(new JestersScepterCounterEffect(), new ManaCostsImpl<>("{2}")); ability2.addCost(new TapSourceCost()); @@ -82,11 +76,7 @@ public boolean apply(Game game, Ability source) { && sourceObject != null) { if (targetedPlayer.getLibrary().hasCards()) { Set cardsToExile = targetedPlayer.getLibrary().getTopCards(game, 5); - for (Card card : cardsToExile) { - if (card.moveToExile(CardUtil.getCardExileZoneId(game, source), sourceObject.getName(), source, game)) { - card.setFaceDown(true, game); - } - } + CardUtil.moveCardsToExileFaceDown(game, source, controller, cardsToExile, true); } return true; } @@ -99,46 +89,6 @@ public JestersScepterEffect copy() { } } -class JestersScepterLookAtCardEffect extends AsThoughEffectImpl { - - JestersScepterLookAtCardEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - staticText = "You may look at cards exiled with {this}"; - } - - private JestersScepterLookAtCardEffect(final JestersScepterLookAtCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public JestersScepterLookAtCardEffect copy() { - return new JestersScepterLookAtCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (affectedControllerId.equals(source.getControllerId())) { - Card card = game.getCard(objectId); - if (card != null) { - MageObject sourceObject = game.getObject(source); - if (sourceObject == null) { - return false; - } - UUID exileId = CardUtil.getCardExileZoneId(game, source); - ExileZone exile = game.getExile().getExileZone(exileId); - return exile != null - && exile.contains(objectId); - } - } - return false; - } -} - class JestersScepterCost extends CostImpl { public JestersScepterCost() { diff --git a/Mage.Sets/src/mage/cards/k/KaylasMusicBox.java b/Mage.Sets/src/mage/cards/k/KaylasMusicBox.java index 680d4e475cc8..2691d2bb7533 100644 --- a/Mage.Sets/src/mage/cards/k/KaylasMusicBox.java +++ b/Mage.Sets/src/mage/cards/k/KaylasMusicBox.java @@ -6,7 +6,6 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; @@ -15,7 +14,6 @@ import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.UUID; @@ -82,54 +80,10 @@ public boolean apply(Game game, Ability source) { card.setFaceDown(true, game); controller.lookAtCards(null, card, game); - if (controller.moveCardsToExile(card, source, game, true, CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source))) { - card.setFaceDown(true, game); - // No other player may look at the face-down cards you own exiled with Kayla’s Music Box, even if another player takes control of it. - // (2022-10-14) - ContinuousEffect effect = new KaylasMusicBoxLookEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(effect, source); - } - return true; - } - -} - -class KaylasMusicBoxLookEffect extends AsThoughEffectImpl { - - private final UUID authorizedPlayerId; - - public KaylasMusicBoxLookEffect(UUID authorizedPlayerId) { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - this.authorizedPlayerId = authorizedPlayerId; - staticText = "You may look at the cards exiled with {this}"; - } - - private KaylasMusicBoxLookEffect(final KaylasMusicBoxLookEffect effect) { - super(effect); - this.authorizedPlayerId = effect.authorizedPlayerId; - } - - @Override - public boolean apply(Game game, Ability source) { + CardUtil.moveCardsToExileFaceDown(game, source, controller, card, true); return true; } - @Override - public KaylasMusicBoxLookEffect copy() { - return new KaylasMusicBoxLookEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID cardId = getTargetPointer().getFirst(game, source); - if (cardId == null) { - this.discard(); // card is no longer in the origin zone, effect can be discarded - } - return affectedControllerId.equals(authorizedPlayerId) - && objectId.equals(cardId); - } - } class KaylasMusicBoxPlayFromExileEffect extends AsThoughEffectImpl { @@ -159,7 +113,11 @@ public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, if (exileZone == null || !exileZone.contains(sourceId)) { return false; } - CardUtil.makeCardPlayable(game, source, exileZone.get(sourceId, game), false, Duration.EndOfTurn, false); + Card card = exileZone.get(sourceId, game); + if (card != null && !card.getOwnerId().equals(affectedControllerId)) { + return false; + } + CardUtil.makeCardPlayable(game, source, card, false, Duration.EndOfTurn, false); return true; } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/k/KheruMindEater.java b/Mage.Sets/src/mage/cards/k/KheruMindEater.java index 08e33f61fda2..24af6d5525fb 100644 --- a/Mage.Sets/src/mage/cards/k/KheruMindEater.java +++ b/Mage.Sets/src/mage/cards/k/KheruMindEater.java @@ -1,32 +1,30 @@ package mage.cards.k; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.LookAtCardsExiledWithThisEffect; +import mage.abilities.effects.common.continuous.MayPlayCardsExiledWithThisEffect; import mage.abilities.keyword.MenaceAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AsThoughEffectType; import mage.constants.CardType; -import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterCard; -import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCardInHand; import mage.util.CardUtil; +import java.util.UUID; + /** * * @author TheElk801 @@ -47,8 +45,8 @@ public KheruMindEater(UUID ownerId, CardSetInfo setInfo) { this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new KheruMindEaterExileEffect(), false, true)); // You may look at and play cards exiled with Kheru Mind-Eater. - this.addAbility(new SimpleStaticAbility(new KheruMindEaterEffect())); - this.addAbility(new SimpleStaticAbility(Zone.ALL, new KheruMindEaterLookAtCardEffect())); + this.addAbility(new SimpleStaticAbility(new MayPlayCardsExiledWithThisEffect())); + this.addAbility(new SimpleStaticAbility(new LookAtCardsExiledWithThisEffect())); } private KheruMindEater(final KheruMindEater card) { @@ -75,16 +73,14 @@ private KheruMindEaterExileEffect(final KheruMindEaterExileEffect effect) { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + Player controller = game.getPlayer(source.getControllerId()); if (player != null && !player.getHand().isEmpty()) { Target target = new TargetCardInHand(1, new FilterCard()); target.chooseTarget(Outcome.Exile, player.getId(), source, game); Card card = game.getCard(target.getFirstTarget()); MageObject sourceObject = game.getObject(source); if (card != null && sourceObject != null) { - if (player.moveCardsToExile(card, source, game, false, CardUtil.getCardExileZoneId(game, source), sourceObject.getIdName())) { - card.setFaceDown(true, game); - } - return true; + return CardUtil.moveCardsToExileFaceDown(game, source, controller, card, true); } } return false; @@ -94,76 +90,4 @@ public boolean apply(Game game, Ability source) { public KheruMindEaterExileEffect copy() { return new KheruMindEaterExileEffect(this); } -} - -class KheruMindEaterEffect extends AsThoughEffectImpl { - - KheruMindEaterEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); - staticText = "You may play cards exiled with {this}"; - } - - private KheruMindEaterEffect(final KheruMindEaterEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public KheruMindEaterEffect copy() { - return new KheruMindEaterEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - Card card = game.getCard(objectId); - if (affectedControllerId.equals(source.getControllerId()) && card != null && game.getState().getZone(card.getId()) == Zone.EXILED) { - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source)); - return exileZone != null && exileZone.contains(card.getId()); - } - return false; - } -} - -class KheruMindEaterLookAtCardEffect extends AsThoughEffectImpl { - - KheruMindEaterLookAtCardEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - staticText = "You may look at cards exiled with {this}"; - } - - private KheruMindEaterLookAtCardEffect(final KheruMindEaterLookAtCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public KheruMindEaterLookAtCardEffect copy() { - return new KheruMindEaterLookAtCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (affectedControllerId.equals(source.getControllerId())) { - Card card = game.getCard(objectId); - if (card != null) { - MageObject sourceObject = game.getObject(source); - if (sourceObject == null) { - return false; - } - UUID exileId = CardUtil.getCardExileZoneId(game, source); - ExileZone exile = game.getExile().getExileZone(exileId); - return exile != null && exile.contains(objectId); - } - } - return false; - } - -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java b/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java index 866c5237622f..cbc14dd51179 100644 --- a/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java +++ b/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java @@ -10,7 +10,6 @@ import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; @@ -21,7 +20,6 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import mage.watchers.Watcher; @@ -106,69 +104,15 @@ public boolean apply(Game game, Ability source) { if (card == null) { continue; } - - card.setFaceDown(true, game); topCards.add(card); } controller.lookAtCards(source, null, topCards, game); Set cardSet = topCards.getCards(game); - if (controller.moveCardsToExile( - cardSet, source, game, true, - CardUtil.getExileZoneId(game, source), - CardUtil.getSourceName(game, source) - )) { - topCards.retainZone(Zone.EXILED, game); - topCards.getCards(game).forEach(c -> c.setFaceDown(true, game)); - - // You may look at these face-down exiled cards any time you wish. No other player may look at the face-down cards you exiled with Lobelia, Defender of Bag End, even if another player takes control of it. - // (2023-06-16) - for (Card card : cardSet) { - ContinuousEffect effect = new LobeliaDefenderOfBagLookEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(effect, source); - } - } - return true; - } - -} - -class LobeliaDefenderOfBagLookEffect extends AsThoughEffectImpl { - - private final UUID authorizedPlayerId; - - public LobeliaDefenderOfBagLookEffect(UUID authorizedPlayerId) { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - this.authorizedPlayerId = authorizedPlayerId; - staticText = "You may look at the cards exiled with {this}"; - } - - private LobeliaDefenderOfBagLookEffect(final LobeliaDefenderOfBagLookEffect effect) { - super(effect); - this.authorizedPlayerId = effect.authorizedPlayerId; - } - - @Override - public boolean apply(Game game, Ability source) { + CardUtil.moveCardsToExileFaceDown(game, source, controller, cardSet, true); return true; } - @Override - public LobeliaDefenderOfBagLookEffect copy() { - return new LobeliaDefenderOfBagLookEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID cardId = getTargetPointer().getFirst(game, source); - if (cardId == null) { - this.discard(); // card is no longer in the origin zone, effect can be discarded - } - return affectedControllerId.equals(authorizedPlayerId) - && objectId.equals(cardId); - } - } class LobeliaDefenderOfBagEndPlayFromExileEffect extends AsThoughEffectImpl { @@ -195,6 +139,10 @@ public boolean apply(Game game, Ability source) { @Override public void init(Ability source, Game game) { super.init(source, game); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + if (exileZone != null) { + exileZone.letPlayerSeeCards(source.getControllerId(), exileZone.getCards(game)); + } LobeliaDefenderOfBagEndWatcher.addPlayable(source, game); } diff --git a/Mage.Sets/src/mage/cards/r/RogueClass.java b/Mage.Sets/src/mage/cards/r/RogueClass.java index 3d86151a74c9..eba64bb10a64 100644 --- a/Mage.Sets/src/mage/cards/r/RogueClass.java +++ b/Mage.Sets/src/mage/cards/r/RogueClass.java @@ -1,5 +1,6 @@ package mage.cards.r; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -20,11 +21,9 @@ import mage.game.Game; import mage.players.ManaPoolItem; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; import java.util.UUID; -import mage.MageObject; -import mage.util.CardUtil; /** * @author TheElk801 @@ -108,11 +107,7 @@ public boolean apply(Game game, Ability source) { } // exileId must remain consistent among all checks UUID exileZoneId = CardUtil.getExileZoneId(game, sourceObject.getId(), sourceObject.getZoneChangeCounter(game)); - if (controller.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName())) { - card.setFaceDown(true, game); - game.addEffect(new RogueClassLookEffect().setTargetPointer(new FixedTarget(card, game)), source); - // store the exileId and the zcc of the card put into exile. this is used to verify the "rogueclassmanaeffect" if the card is cast from exile - // note that the rogueclassmanaeffect will check its validity only when the card is out of the exile zone, so it can't be checked directly + if (CardUtil.moveCardsToExileFaceDown(game, source, controller, card, exileZoneId, sourceObject.getIdName(), true)) { game.getState().setValue(card.getId().toString() + game.getState().getZoneChangeCounter(card.getId()), exileZoneId); return true; } @@ -120,39 +115,6 @@ public boolean apply(Game game, Ability source) { } } -class RogueClassLookEffect extends AsThoughEffectImpl { - - RogueClassLookEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - } - - private RogueClassLookEffect(final RogueClassLookEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public RogueClassLookEffect copy() { - return new RogueClassLookEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID cardId = getTargetPointer().getFirst(game, source); - if (cardId == null) { - discard(); - return false; - } - return source.isControlledBy(affectedControllerId) - && cardId.equals(objectId) - && game.getState().getZone(cardId) == Zone.EXILED; - } -} - class RogueClassPlayEffect extends AsThoughEffectImpl { RogueClassPlayEffect() { diff --git a/Mage.Sets/src/mage/cards/s/SharedFate.java b/Mage.Sets/src/mage/cards/s/SharedFate.java index c66c6ee93e87..fc31dd871240 100644 --- a/Mage.Sets/src/mage/cards/s/SharedFate.java +++ b/Mage.Sets/src/mage/cards/s/SharedFate.java @@ -32,7 +32,6 @@ public SharedFate(UUID ownerId, CardSetInfo setInfo) { // Each player may look at and play cards they exiled with Shared Fate. this.addAbility(new SimpleStaticAbility(new SharedFatePlayEffect())); - this.addAbility(new SimpleStaticAbility(new SharedFateLookEffect())); } private SharedFate(final SharedFate card) { @@ -108,8 +107,7 @@ public boolean replaceEvent(GameEvent event, Ability source, Game game) { UUID exileId = CardUtil.getExileZoneId(SharedFate.prepareExileKey(game, source, sourcePermanent, playerToDraw.getId()), game); String exileName = sourcePermanent.getIdName() + "-" + sourcePermanent.getZoneChangeCounter(game) + " (" + playerToDraw.getName() + ')'; - playerToDraw.moveCardsToExile(card, source, game, false, exileId, exileName); - card.setFaceDown(true, game); + CardUtil.moveCardsToExileFaceDown(game, source, playerToDraw, card, exileId, exileName, true); return true; } @@ -156,39 +154,3 @@ public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, return false; } } - -class SharedFateLookEffect extends AsThoughEffectImpl { - - SharedFateLookEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "Each player may look at the cards exiled with {this}"; - } - - private SharedFateLookEffect(final SharedFateLookEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public SharedFateLookEffect copy() { - return new SharedFateLookEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (game.getState().getZone(objectId) == Zone.EXILED) { - Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); - UUID exileId = CardUtil.getExileZoneId(SharedFate.prepareExileKey(game, source, sourcePermanent, affectedControllerId), game); - ExileZone exileZone = game.getExile().getExileZone(exileId); - if (exileZone != null && exileZone.contains(objectId)) { - Card card = game.getCard(objectId); - return card != null && game.getState().getZone(objectId) == Zone.EXILED; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/s/SummonersEgg.java b/Mage.Sets/src/mage/cards/s/SummonersEgg.java index 840fba1c29a5..6543e210857a 100644 --- a/Mage.Sets/src/mage/cards/s/SummonersEgg.java +++ b/Mage.Sets/src/mage/cards/s/SummonersEgg.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DiesSourceTriggeredAbility; @@ -18,6 +17,8 @@ import mage.target.TargetCard; import mage.util.CardUtil; +import java.util.UUID; + /** * * @author Plopman @@ -72,9 +73,8 @@ public boolean apply(Game game, Ability source) { && controller.choose(Outcome.Benefit, controller.getHand(), target, source, game)) { Card card = controller.getHand().get(target.getFirstTarget(), game); if (card != null) { - card.setFaceDown(true, game); - controller.moveCardsToExile(card, source, game, false, source.getSourceId(), sourcePermanent.getIdName() + " (Imprint)"); - card.setFaceDown(true, game); + CardUtil.moveCardsToExileFaceDown(game, source, controller, card, + source.getSourceId(), sourcePermanent.getIdName() + " (Imprint)", false); sourcePermanent.imprint(card.getId(), game); sourcePermanent.addInfo("imprint", CardUtil.addToolTipMarkTags("[Imprinted card]"), game); } diff --git a/Mage.Sets/src/mage/cards/t/ThreeWishes.java b/Mage.Sets/src/mage/cards/t/ThreeWishes.java index 1d627a0fee85..3e2cf9779773 100644 --- a/Mage.Sets/src/mage/cards/t/ThreeWishes.java +++ b/Mage.Sets/src/mage/cards/t/ThreeWishes.java @@ -1,30 +1,24 @@ package mage.cards.t; -import java.util.Set; -import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.delayed.AtTheBeginOfYourNextUpkeepDelayedTriggeredAbility; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AsThoughEffectType; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; -import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.Set; +import java.util.UUID; + /** * * @author jeffwadsworth @@ -36,7 +30,6 @@ public ThreeWishes(UUID ownerId, CardSetInfo setInfo) { // Exile the top three cards of your library face down. You may look at those cards for as long as they remain exiled. Until your next turn, you may play those cards. At the beginning of your next upkeep, put any of those cards you didn't play into your graveyard. this.getSpellAbility().addEffect(new ThreeWishesExileEffect()); - this.addAbility(new SimpleStaticAbility(Zone.ALL, new ThreeWishesLookAtCardEffect())); } @@ -68,11 +61,8 @@ public boolean apply(Game game, Ability source) { UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), 0); Set topThreeCards = controller.getLibrary().getTopCards(game, 3); for (Card card : topThreeCards) { - if (controller.moveCardsToExile(card, source, game, true, exileId, "Three Wishes")) { - card.setFaceDown(true, game); - ContinuousEffect effect = new ThreeWishesPlayFromExileEffect(); - effect.setTargetPointer(new FixedTarget(card.getId())); - game.addEffect(effect, source); + if (CardUtil.moveCardsToExileFaceDown(game, source, controller, card, exileId, "Three Wishes", true)) { + CardUtil.makeCardPlayable(game, source, card, false, Duration.UntilYourNextTurn, false); } } DelayedTriggeredAbility delayed = new AtTheBeginOfYourNextUpkeepDelayedTriggeredAbility(new ThreeWishesPutIntoGraveyardEffect()); @@ -118,77 +108,3 @@ public ThreeWishesPutIntoGraveyardEffect copy() { return new ThreeWishesPutIntoGraveyardEffect(this); } } - -class ThreeWishesLookAtCardEffect extends AsThoughEffectImpl { - - ThreeWishesLookAtCardEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.Custom, Outcome.Benefit); - staticText = "You may look at cards exiled with {this} as long as they remain exiled"; - } - - private ThreeWishesLookAtCardEffect(final ThreeWishesLookAtCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public ThreeWishesLookAtCardEffect copy() { - return new ThreeWishesLookAtCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (affectedControllerId.equals(source.getControllerId())) { - Card card = game.getCard(objectId); - if (card != null) { - MageObject sourceObject = game.getObject(source); - if (sourceObject == null) { - return false; - } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), 0); - ExileZone exile = game.getExile().getExileZone(exileId); - return exile != null - && exile.contains(card.getId()); - } - } - return false; - } -} - -class ThreeWishesPlayFromExileEffect extends AsThoughEffectImpl { - - ThreeWishesPlayFromExileEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.UntilYourNextTurn, Outcome.Benefit); - staticText = "Until your next turn, you may play those cards"; - } - - private ThreeWishesPlayFromExileEffect(final ThreeWishesPlayFromExileEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public ThreeWishesPlayFromExileEffect copy() { - return new ThreeWishesPlayFromExileEffect(this); - } - - @Override - public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), 0); - ExileZone exile = game.getExile().getExileZone(exileId); - return exile != null - && getTargetPointer().getFirst(game, source) != null - && getTargetPointer().getFirst(game, source).equals(sourceId) - && source.isControlledBy(affectedControllerId) - && game.getState().getZone(sourceId) == Zone.EXILED - && exile.contains(sourceId); - } -} diff --git a/Mage.Sets/src/mage/cards/u/UginTheIneffable.java b/Mage.Sets/src/mage/cards/u/UginTheIneffable.java index d18b19a832cb..54efb90f7cd7 100644 --- a/Mage.Sets/src/mage/cards/u/UginTheIneffable.java +++ b/Mage.Sets/src/mage/cards/u/UginTheIneffable.java @@ -6,7 +6,6 @@ import mage.abilities.DelayedTriggeredAbility; import mage.abilities.LoyaltyAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; @@ -111,11 +110,7 @@ public boolean apply(Game game, Ability source) { // exile and look UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()); - if (player.moveCardsToExile(card, source, game, false, exileZoneId, sourceObject.getIdName() + " (" + player.getName() + ")")) { - card.turnFaceDown(source, game, source.getControllerId()); - player.lookAtCards(player.getName() + " - " + card.getIdName() + " - " + CardUtil.sdf.format(System.currentTimeMillis()), card, game); - } - + CardUtil.moveCardsToExileFaceDown(game, source, player, card, exileZoneId, sourceObject.getIdName() + " (" + player.getName() + ")", true); // create token Set tokenObjs = new HashSet<>(); CreateTokenEffect effect = new CreateTokenEffect(new UginTheIneffableToken()); @@ -130,11 +125,6 @@ public boolean apply(Game game, Ability source) { gainAbilityEffect.setTargetPointer(new FixedTarget(addedTokenId)); game.addEffect(gainAbilityEffect, source); - // look at face-down card in exile - UginTheIneffableLookAtFaceDownEffect lookAtEffect = new UginTheIneffableLookAtFaceDownEffect(); - lookAtEffect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(lookAtEffect, source); - tokenObjs.add(new MageObjectReference(addedTokenId, game)); game.addDelayedTriggeredAbility(new UginTheIneffableDelayedTriggeredAbility( tokenObjs, new MageObjectReference(card, game) @@ -190,35 +180,3 @@ public String getRule() { return "When this token leaves the battlefield, put the exiled card into your hand."; } } - -class UginTheIneffableLookAtFaceDownEffect extends AsThoughEffectImpl { - - UginTheIneffableLookAtFaceDownEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - } - - private UginTheIneffableLookAtFaceDownEffect(final UginTheIneffableLookAtFaceDownEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public UginTheIneffableLookAtFaceDownEffect copy() { - return new UginTheIneffableLookAtFaceDownEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID cardId = getTargetPointer().getFirst(game, source); - if (cardId == null) { - this.discard(); - } - return affectedControllerId.equals(source.getControllerId()) - && objectId.equals(cardId) - && game.getState().getExile().containsId(cardId, game); - } -} diff --git a/Mage.Sets/src/mage/cards/v/VivienChampionOfTheWilds.java b/Mage.Sets/src/mage/cards/v/VivienChampionOfTheWilds.java index b0cdeddfd1e2..4a77e60aed39 100644 --- a/Mage.Sets/src/mage/cards/v/VivienChampionOfTheWilds.java +++ b/Mage.Sets/src/mage/cards/v/VivienChampionOfTheWilds.java @@ -3,8 +3,6 @@ import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashAllEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; @@ -18,7 +16,6 @@ import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCreaturePermanent; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.UUID; @@ -107,21 +104,14 @@ public boolean apply(Game game, Ability source) { // exile Card cardToExile = game.getCard(target.getFirstTarget()); - if (!player.moveCardsToExile(cardToExile, source, game, false, + if (!CardUtil.moveCardsToExileFaceDown(game, source, player, cardToExile, CardUtil.getCardExileZoneId(game, source), - CardUtil.createObjectRelatedWindowTitle(source, game, " (look and cast)"))) { + CardUtil.createObjectRelatedWindowTitle(source, game, " (look and cast)"), true)) { return false; } - cardToExile.setFaceDown(true, game); - - // look and cast - ContinuousEffect effect = new VivienChampionOfTheWildsLookEffect(player.getId()); - effect.setTargetPointer(new FixedTarget(cardToExile, game)); - game.addEffect(effect, source); + // cast if (cardToExile.isCreature(game)) { - effect = new VivienChampionOfTheWildsCastFromExileEffect(player.getId()); - effect.setTargetPointer(new FixedTarget(cardToExile, game)); - game.addEffect(effect, source); + CardUtil.makeCardPlayable(game, source, cardToExile, true, Duration.EndOfGame, false); } // put the rest on the bottom of your library in any order @@ -132,77 +122,3 @@ public boolean apply(Game game, Ability source) { return true; } } - -class VivienChampionOfTheWildsLookEffect extends AsThoughEffectImpl { - - private final UUID authorizedPlayerId; - - VivienChampionOfTheWildsLookEffect(UUID authorizedPlayerId) { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); - this.authorizedPlayerId = authorizedPlayerId; - } - - private VivienChampionOfTheWildsLookEffect(final VivienChampionOfTheWildsLookEffect effect) { - super(effect); - this.authorizedPlayerId = effect.authorizedPlayerId; - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public VivienChampionOfTheWildsLookEffect copy() { - return new VivienChampionOfTheWildsLookEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID cardId = getTargetPointer().getFirst(game, source); - if (cardId == null) { - this.discard(); // card is no longer in the origin zone, effect can be discarded - } - return affectedControllerId.equals(authorizedPlayerId) - && objectId.equals(cardId); - } -} - -class VivienChampionOfTheWildsCastFromExileEffect extends AsThoughEffectImpl { - - private final UUID authorizedPlayerId; - - VivienChampionOfTheWildsCastFromExileEffect(UUID authorizedPlayerId) { - super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); - this.authorizedPlayerId = authorizedPlayerId; - } - - private VivienChampionOfTheWildsCastFromExileEffect(final VivienChampionOfTheWildsCastFromExileEffect effect) { - super(effect); - this.authorizedPlayerId = effect.authorizedPlayerId; - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public VivienChampionOfTheWildsCastFromExileEffect copy() { - return new VivienChampionOfTheWildsCastFromExileEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - UUID cardId = getTargetPointer().getFirst(game, source); - if (cardId == null) { - this.discard(); // card is no longer in the origin zone, effect can be discarded - } else if (objectId.equals(cardId) - && affectedControllerId.equals(authorizedPlayerId)) { - Card card = game.getCard(objectId); - // TODO: Allow to cast Zoetic Cavern face down - return card != null && !card.isLand(game); - } - return false; - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/y/YedoraGraveGardener.java b/Mage.Sets/src/mage/cards/y/YedoraGraveGardener.java index 3f6f27eeb334..e2be36fd090f 100644 --- a/Mage.Sets/src/mage/cards/y/YedoraGraveGardener.java +++ b/Mage.Sets/src/mage/cards/y/YedoraGraveGardener.java @@ -2,6 +2,7 @@ import mage.MageInt; import mage.MageObjectReference; +import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.DiesCreatureTriggeredAbility; import mage.abilities.effects.ContinuousEffectImpl; @@ -121,7 +122,7 @@ public boolean apply(Game game, Ability source) { target.removeAllSubTypes(game); target.addCardType(game, CardType.LAND); target.addSubType(game, SubType.FOREST); - target.removeAllAbilities(source.getSourceId(), game); + target.getColor(game).setColor(ObjectColor.COLORLESS); target.addAbility(new GreenManaAbility(), source.getSourceId(), game); return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CascadeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CascadeTest.java index 5da3f77d6a5d..39bf0dd73871 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CascadeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CascadeTest.java @@ -1,5 +1,6 @@ package org.mage.test.cards.abilities.keywords; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Assert; @@ -226,7 +227,7 @@ public void testWithSplitSpell() { setStrictChooseMode(true); // When the spell is cast, you cascade, but the only spell you could find is "Breaking // Entering", - // but it can't be cast since the mana cost for a spliot it's mana cost of the card is the sum of both halves (8 here) + // but it can't be cast since the mana cost for a split is the sum of both halves (8 here) castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ardent Plea"); setStopAt(1, PhaseStep.BEGIN_COMBAT); @@ -236,4 +237,23 @@ public void testWithSplitSpell() { assertGraveyardCount(playerA, "Breaking // Entering", 0); assertLibraryCount(playerA, "Breaking // Entering", 1); } + + @Test + public void testFaceDownSpell() { + // Face down spell should have mana value of 0 and not allow casting. + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, "Den Protector"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, "Maelstrom Nexus"); + addCard(Zone.LIBRARY, playerA, "Think Twice"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Den Protector using Morph"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + // Think Twice should still remain in library. + assertLibraryCount(playerA, "Think Twice", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/LookAtAndPlayExiledWithThisTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/LookAtAndPlayExiledWithThisTest.java new file mode 100644 index 000000000000..7a4cea00b52b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/LookAtAndPlayExiledWithThisTest.java @@ -0,0 +1,80 @@ +package org.mage.test.cards.asthough; + +import mage.cards.Card; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.util.CardUtil; +import mage.view.CardView; +import mage.view.GameView; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class LookAtAndPlayExiledWithThisTest extends CardTestPlayerBase { + + @Test + public void KheruMindEater() { + addCard(Zone.BATTLEFIELD, playerA, "Kheru Mind-Eater"); + addCard(Zone.HAND, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); + addCard(Zone.HAND, playerB, "Act of Treason"); + + attack(1, playerA, "Kheru Mind-Eater"); + addTarget(playerB, "Swamp"); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + runCode("face down exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { + checkExiledCardView("Swamp", false); + }); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Act of Treason", "Kheru Mind-Eater"); + waitStackResolved(2, PhaseStep.POSTCOMBAT_MAIN); + runCode("face down exile", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, (info, player, game) -> { + checkExiledCardView("Swamp", true); + }); + playLand(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Swamp"); + setStopAt(3, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + assertExileCount(playerB, 0); + assertPermanentCount(playerA, "Swamp", 1); + } + + private void checkExiledCardView(String expectedCardName, boolean shouldOppSee) { + Card card = currentGame.getExile().getAllCards(currentGame, playerB.getId()).get(0); + GameView gameView = getGameView(playerA); + CardView controllerView = gameView.getExile() + .stream() + .flatMap(e -> e.values().stream()) + .findFirst() + .orElse(null); + gameView = getGameView(playerB); + CardView opponentView = gameView.getExile() + .stream() + .flatMap(e -> e.values().stream()) + .findFirst() + .orElse(null); + String controllerName = "Face Down: " + expectedCardName; + String opponentName = "Face Down" + (shouldOppSee ? ": " + expectedCardName : ""); + Assert.assertTrue("Card should be face down", card.isFaceDown(currentGame)); + Assert.assertTrue("Controller - can't play", card.getAbilities(currentGame).stream().anyMatch(ability -> !CardUtil.isInformationAbility(ability))); + Assert.assertEquals("Controller - wrong name", controllerName, controllerView.getName()); + Assert.assertEquals("Opponent - wrong name", opponentName, opponentView.getName()); + } + + @Test + public void ColfenorsPlans() { + addCard(Zone.HAND, playerA, "Colfenor's Plans"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.LIBRARY, playerA, "Forest", 1); + addCard(Zone.LIBRARY, playerA, "Llanowar Elves", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Colfenor's Plans"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + runCode("face down exile", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + checkExiledCardView("Llanowar Elves", false); + }); + playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Forest"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Llanowar Elves"); + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/brc/KaylasMusicBoxTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/brc/KaylasMusicBoxTest.java index 8c7c93dc7031..43f04e46d52f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/brc/KaylasMusicBoxTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/brc/KaylasMusicBoxTest.java @@ -48,4 +48,29 @@ public void testEffect() { assertPermanentCount(playerA, "Plains", 2); } + @Test + public void testOwnership() { + addCard(Zone.LIBRARY, playerA, LION); + addCard(Zone.BATTLEFIELD, playerA, BOX); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 4); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerB, "Axegrinder Giant", 1); + addCard(Zone.HAND, playerB, "Giant's Grasp", 1); + + // Player A activates the box, exiling lion + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}, {T}"); + + // Player B casts Giant's Grasp on the box + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Giant's Grasp", "Axegrinder Giant"); + addTarget(playerB, BOX); + + setStrictChooseMode(true); + setStopAt(4, PhaseStep.PRECOMBAT_MAIN); + execute(); + + checkPlayableAbility("Should not be able to cast lion", + 4, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast " + LION, false); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/GontiNightMinisterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/GontiNightMinisterTest.java new file mode 100644 index 000000000000..467a245f25ff --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/GontiNightMinisterTest.java @@ -0,0 +1,94 @@ +package org.mage.test.cards.single.dft; + +import mage.cards.Card; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.ExileZone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * + * @author Jmlundeen + */ +public class GontiNightMinisterTest extends CardTestCommander4Players { + + /* + Gonti, Night Minister + {2}{B}{B} + Legendary Creature - Aetherborn Rogue + Whenever a player casts a spell they don't own, that player creates a Treasure token. + Whenever a creature deals combat damage to one of your opponents, its controller looks at the top card of that opponent's library and exiles it face down. They may play that card for as long as it remains exiled. Mana of any type can be spent to cast a spell this way. + 3/4 + */ + private static final String gontiNightMinister = "Gonti, Night Minister"; + + /* + Bear Cub + {1}{G} + Creature - Bear + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + /* + Fugitive Wizard + {U} + Creature - Human Wizard + 1/1 + */ + private static final String fugitiveWizard = "Fugitive Wizard"; + + + @Test + public void testGontiNightMinister() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, gontiNightMinister); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerD, bearCub); + addCard(Zone.BATTLEFIELD, playerD, "Mountain"); + addCard(Zone.LIBRARY, playerC, fugitiveWizard); + addCard(Zone.LIBRARY, playerB, fugitiveWizard); + + attack(1, playerA, gontiNightMinister, playerC); + runCode("only playerA can see fugitive wizard", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { + for (ExileZone zone : game.getExile().getExileZones()) { + for (Card card : zone.getCards(game)) { + if (card.isFaceDown(game)) { + assertTrue("player A can see the card", zone.isPlayerAllowedToSeeCard(playerA.getId(), card)); + assertFalse("player B can not see the card", zone.isPlayerAllowedToSeeCard(playerB.getId(), card)); + assertFalse("player C can not see the card", zone.isPlayerAllowedToSeeCard(playerC.getId(), card)); + assertFalse("player D can not see the card", zone.isPlayerAllowedToSeeCard(playerD.getId(), card)); + } + } + } + }); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, fugitiveWizard); + + attack(2, playerD, bearCub, playerB); + runCode("only playerD can see fugitive wizard", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { + for (ExileZone zone : game.getExile().getExileZones()) { + for (Card card : zone.getCards(game)) { + if (card.isFaceDown(game)) { + assertTrue("player D can see the card", zone.isPlayerAllowedToSeeCard(playerD.getId(), card)); + assertFalse("player A can not see the card", zone.isPlayerAllowedToSeeCard(playerA.getId(), card)); + assertFalse("player B can not see the card", zone.isPlayerAllowedToSeeCard(playerB.getId(), card)); + assertFalse("player C can not see the card", zone.isPlayerAllowedToSeeCard(playerC.getId(), card)); + } + } + } + }); + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerD, fugitiveWizard); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Treasure Token", 1); + assertPermanentCount(playerD, "Treasure Token", 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltc/LobeliaDefenderOfBagEndTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltc/LobeliaDefenderOfBagEndTest.java new file mode 100644 index 000000000000..aabf1f410425 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltc/LobeliaDefenderOfBagEndTest.java @@ -0,0 +1,59 @@ +package org.mage.test.cards.single.ltc; + +import mage.cards.Card; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.util.CardUtil; +import mage.view.CardView; +import mage.view.GameView; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class LobeliaDefenderOfBagEndTest extends CardTestPlayerBase { + + private static final String lobelia = "Lobelia, Defender of Bag End"; + @Test + public void testOppGainVisibility() { + addCard(Zone.HAND, playerA, lobelia); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerB, "Act of Treason"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); + addCard(Zone.BATTLEFIELD, playerB, "Ornithopter"); + addCard(Zone.LIBRARY, playerB, "Bear Cub"); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lobelia); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Act of Treason", lobelia); + waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN); + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}, Sacrifice an artifact"); + setModeChoice(playerB, "1"); + setChoice(playerB, "Ornithopter"); + waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN); + + runCode("face down exile", 2, PhaseStep.PRECOMBAT_MAIN, playerB, ((info, player, game) -> { + Card card = currentGame.getExile().getAllCards(currentGame, playerB.getId()).get(0); + GameView gameView = getGameView(playerA); + CardView ownerView = gameView.getExile() + .stream() + .flatMap(e -> e.values().stream()) + .findFirst() + .orElse(null); + gameView = getGameView(playerB); + CardView opponentView = gameView.getExile() + .stream() + .flatMap(e -> e.values().stream()) + .findFirst() + .orElse(null); + String expectedName = "Face Down: Bear Cub"; + Assert.assertTrue("Card should be face down", card.isFaceDown(currentGame)); + Assert.assertTrue("Owner - can't play", card.getAbilities(currentGame).stream().anyMatch(ability -> !CardUtil.isInformationAbility(ability))); + Assert.assertEquals("Owner - wrong name", expectedName, ownerView.getName()); + Assert.assertEquals("Opponent - wrong name", expectedName, opponentView.getName()); + })); + setStrictChooseMode(true); + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index a60322ba64ab..a14337d03de7 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -975,6 +975,11 @@ public synchronized void apply(Game game) { effect.apply(Layer.CopyEffects_1, SubLayer.CopyEffects_1a, ability, game); } } + // copy effects can cause new face-down effects e.g. Clone a morph creature and turned face down + if (!layer.isEmpty()) { + activeLayerEffects = getLayeredEffects(game, "layer_1"); + layer = filterLayeredEffects(activeLayerEffects, Layer.CopyEffects_1); + } for (ContinuousEffect effect : layer) { Set abilities = layeredEffects.getAbility(effect.getId()); for (Ability ability : abilities) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect.java index 176717e55eb8..445f6f4c55be 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect.java @@ -2,18 +2,16 @@ import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.asthought.MayLookAtTargetCardEffect; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; import mage.constants.CastManaAdjustment; import mage.constants.Duration; import mage.constants.Outcome; +import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.Objects; @@ -94,9 +92,10 @@ public boolean apply(Game game, Ability source) { throw new IllegalArgumentException("Wrong code usage, manaAdjustment is not yet supported: " + manaAdjustment); } // For as long as that card remains exiled, you may look at it - ContinuousEffect effect = new MayLookAtTargetCardEffect(controller.getId()); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - game.addEffect(effect, source); + ExileZone exileZone = game.getExile().getExileZone(exileZoneId); + if (exileZone != null) { + exileZone.letPlayerSeeCards(controller.getId(), card); + } } } return true; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureAllEffect.java index 73a24776138a..6c53a3830a6a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureAllEffect.java @@ -1,41 +1,39 @@ package mage.abilities.effects.common.continuous; import mage.MageObjectReference; -import mage.ObjectColor; import mage.abilities.Ability; -import mage.abilities.common.TurnFaceUpAbility; import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.keyword.MorphAbility; -import mage.cards.Card; -import mage.constants.*; +import mage.cards.ModalDoubleFacedCardHalf; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; -import java.util.*; +import java.util.ArrayList; +import java.util.List; /** - * TODO: must be reworked to use same face down logic as BecomesFaceDownCreatureEffect + * * * @author LevelX2 */ public class BecomesFaceDownCreatureAllEffect extends ContinuousEffectImpl { - protected Map turnFaceUpAbilityMap = new HashMap<>(); protected FilterPermanent filter; public BecomesFaceDownCreatureAllEffect(FilterPermanent filter) { - super(Duration.EndOfGame, Outcome.BecomeCreature); + super(Duration.EndOfGame, Layer.CopyEffects_1, SubLayer.FaceDownEffects_1b, Outcome.Neutral); this.filter = filter; staticText = "turn all " + filter.getMessage() + " face down. (They're 2/2 creatures.)"; } protected BecomesFaceDownCreatureAllEffect(final BecomesFaceDownCreatureAllEffect effect) { super(effect); - for (Map.Entry entry : effect.turnFaceUpAbilityMap.entrySet()) { - this.turnFaceUpAbilityMap.put(entry.getKey(), entry.getValue()); - } this.filter = effect.filter.copy(); } @@ -50,18 +48,9 @@ public void init(Ability source, Game game) { // save permanents to become face down (one time usage on resolve) for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { - if (!perm.isFaceDown(game) && !perm.isTransformable()) { + if (!perm.isFaceDown(game) && !perm.isTransformable() && !(((PermanentCard) perm).getCard() instanceof ModalDoubleFacedCardHalf)) { affectedObjectList.add(new MageObjectReference(perm, game)); perm.setFaceDown(true, game); - // check for Morph - Card card = game.getCard(perm.getId()); - if (card != null) { - for (Ability ability : card.getAbilities(game)) { - if (ability instanceof MorphAbility) { - this.turnFaceUpAbilityMap.put(card.getId(), new TurnFaceUpAbility(((MorphAbility) ability).getFaceUpCosts())); - } - } - } } } } @@ -69,67 +58,25 @@ public void init(Ability source, Game game) { @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { boolean targetExists = false; + List objectsToRemove = new ArrayList<>(); for (MageObjectReference mor : affectedObjectList) { - // TODO: wtf, why it not use a BecomesFaceDownCreatureEffect.makeFaceDownObject and applied by layers?! Looks buggy Permanent permanent = mor.getPermanent(game); - if (permanent != null && permanent.isFaceDown(game)) { - targetExists = true; - switch (layer) { - case TypeChangingEffects_4: - permanent.setName(""); - permanent.removeAllSuperTypes(game); - permanent.removeAllCardTypes(game); - permanent.addCardType(game, CardType.CREATURE); - permanent.removeAllSubTypes(game); - permanent.getManaCost().clear(); - break; - case ColorChangingEffects_5: - permanent.getColor(game).setColor(new ObjectColor()); - break; - case AbilityAddingRemovingEffects_6: - Card card = game.getCard(permanent.getId()); // - List abilitiesToRemove = new ArrayList<>(); - for (Ability ability : permanent.getAbilities()) { - - // keep gained abilities from other sources, removes only own (card text) - if (card != null && !card.getAbilities().contains(ability)) { - continue; - } - - // 701.33c - // If a card with morph is manifested, its controller may turn that card face up using - // either the procedure described in rule 702.36e to turn a face-down permanent with morph face up - // or the procedure described above to turn a manifested permanent face up. - // - // so keep all tune face up abilities and other face down compatible - if (ability.getWorksFaceDown()) { - ability.setRuleVisible(false); - continue; - } - - if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) { - if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureAllEffect) { - continue; - } - } - abilitiesToRemove.add(ability); - } - permanent.removeAbilities(abilitiesToRemove, source.getSourceId(), game); - if (turnFaceUpAbilityMap.containsKey(permanent.getId())) { - permanent.addAbility(turnFaceUpAbilityMap.get(permanent.getId()), source.getSourceId(), game); - } - break; - case PTChangingEffects_7: - if (sublayer == SubLayer.SetPT_7b) { - permanent.getPower().setModifiedBaseValue(2); - permanent.getToughness().setModifiedBaseValue(2); - } - } + if (permanent == null || !permanent.isFaceDown(game)) { + objectsToRemove.add(mor); + continue; } + targetExists = true; + BecomesFaceDownCreatureEffect.FaceDownType type = BecomesFaceDownCreatureEffect.findFaceDownType(game, permanent); + BecomesFaceDownCreatureEffect.makeFaceDownObject(game, + source.getSourceId(), + permanent, + type, + null); } if (!targetExists) { discard(); } + affectedObjectList.removeAll(objectsToRemove); return true; } @@ -137,10 +84,4 @@ public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) public boolean apply(Game game, Ability source) { return false; } - - @Override - public boolean hasLayer(Layer layer) { - return layer == Layer.PTChangingEffects_7 || layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.ColorChangingEffects_5 || layer == Layer.TypeChangingEffects_4; - } - } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java index 099abc9d1ac4..1e5370a98944 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java @@ -264,7 +264,7 @@ public static void makeFaceDownObject(Game game, UUID sourceId, MageObject objec for (Ability ability : object.getAbilities()) { // keep gained abilities from other sources, removes only own (card text) - if (card != null && !card.getAbilities().contains(ability)) { + if (card != null && !card.getAbilities().contains(ability) && !object.isCopy()) { continue; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtCardsExiledWithThisEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtCardsExiledWithThisEffect.java new file mode 100644 index 000000000000..07ea77b6658c --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtCardsExiledWithThisEffect.java @@ -0,0 +1,44 @@ +package mage.abilities.effects.common.continuous; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +public class LookAtCardsExiledWithThisEffect extends ContinuousEffectImpl { + + public LookAtCardsExiledWithThisEffect() { + super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); + staticText = "You may look at cards exiled with {this}"; + } + + private LookAtCardsExiledWithThisEffect(final LookAtCardsExiledWithThisEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null || !source.isControlledBy(controller.getId())) { + return false; + } + int zcc = CardUtil.getActualSourceObjectZoneChangeCounter(game, source); + ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source, zcc)); + if (exile == null || exile.isEmpty()) { + return false; + } + exile.letPlayerSeeCards(controller.getId(), exile.getCards(game)); + return true; + } + + @Override + public LookAtCardsExiledWithThisEffect copy() { + return new LookAtCardsExiledWithThisEffect(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/MayPlayCardsExiledWithThisEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/MayPlayCardsExiledWithThisEffect.java new file mode 100644 index 000000000000..21095d85df4e --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/MayPlayCardsExiledWithThisEffect.java @@ -0,0 +1,44 @@ +package mage.abilities.effects.common.continuous; + +import mage.abilities.Ability; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.constants.AsThoughEffectType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.ExileZone; +import mage.game.Game; +import mage.util.CardUtil; + +import java.util.UUID; + +public class MayPlayCardsExiledWithThisEffect extends AsThoughEffectImpl { + + public MayPlayCardsExiledWithThisEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "You may play cards exiled with {this}"; + } + + private MayPlayCardsExiledWithThisEffect(final MayPlayCardsExiledWithThisEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public MayPlayCardsExiledWithThisEffect copy() { + return new MayPlayCardsExiledWithThisEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (affectedControllerId.equals(source.getControllerId()) && game.getState().getZone(objectId) == Zone.EXILED) { + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source)); + return exileZone != null && exileZone.contains(objectId); + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/CascadeAbility.java b/Mage/src/main/java/mage/abilities/keyword/CascadeAbility.java index 45a4ee863416..0bf8644f8cef 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CascadeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CascadeAbility.java @@ -111,14 +111,14 @@ public boolean apply(Game game, Ability source) { if (controller == null) { return false; } - Card sourceCard = game.getCard(source.getSourceId()); - if (sourceCard == null) { + Spell sourceSpell = game.getSpell(source.getSourceId()); + if (sourceSpell == null) { return false; } // exile cards from the top of your library until you exile a nonland card whose converted mana cost is less than this spell's converted mana cost Cards cardsToExile = new CardsImpl(); - int sourceCost = sourceCard.getManaValue(); + int sourceCost = sourceSpell.getManaValue(); Card cardToCast = null; for (Card card : controller.getLibrary().getCards(game)) { cardsToExile.add(card); diff --git a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java index 421a25858d3b..4b281deea053 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java @@ -1,16 +1,13 @@ package mage.abilities.keyword; -import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.SpecialAction; import mage.abilities.SpellAbility; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; @@ -52,10 +49,6 @@ public ForetellAbility(Card card, String foretellCost, String foretellSplitCost) this.addCost(new GenericManaCost(2)); // exile the card and it can't be cast the turn it was foretold this.addEffect(new ForetellExileEffect(card, foretellCost, foretellSplitCost)); - // look at face-down card anytime - Ability ability = new SimpleStaticAbility(Zone.ALL, new ForetellLookAtCardEffect()); - ability.setControllerId(controllerId); // if not set, anyone can look at the card in exile - addSubAbility(ability); this.setRuleVisible(true); this.addWatcher(new ForetoldWatcher()); } @@ -224,58 +217,20 @@ public boolean apply(Game game, Ability source) { game.getState().setValue(mainCardId.toString() + "Foretell Cost", foretellCost); game.getState().setValue(mainCardId.toString() + "Foretell Split Cost", foretellSplitCost); - // exile the card face-down - effect.setWithName(false); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - effect.apply(game, source); - card.setFaceDown(true, game); - game.addEffect(new ForetellAddCostEffect(new MageObjectReference(card, game)), source); + // exile the card face-down + effect.setWithName(false); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + effect.apply(game, source); + card.setFaceDown(true, game); + game.getExile().getExileZone(exileId) + .letPlayerSeeCards(controller.getId(), card); + game.addEffect(new ForetellAddCostEffect(new MageObjectReference(card, game)), source); game.fireEvent(new GameEvent(GameEvent.EventType.CARD_FORETOLD, card.getId(), source, source.getControllerId(), 0, true)); - return true; - } - return false; - } -} - -class ForetellLookAtCardEffect extends AsThoughEffectImpl { - - ForetellLookAtCardEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.AIDontUseIt); - } - - private ForetellLookAtCardEffect(final ForetellLookAtCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public ForetellLookAtCardEffect copy() { - return new ForetellLookAtCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (affectedControllerId.equals(source.getControllerId())) { - Card card = game.getCard(objectId); - if (card != null) { - MageObject sourceObject = game.getObject(source); - if (sourceObject == null) { - return false; - } - UUID mainCardId = card.getMainCard().getId(); - UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); - ExileZone exile = game.getExile().getExileZone(exileId); - return exile != null - && exile.contains(mainCardId); + return true; } + return false; } - return false; } -} class ForetellAddCostEffect extends ContinuousEffectImpl { diff --git a/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java b/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java index 4142d96a8eb0..92bcdc93087a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java @@ -3,13 +3,14 @@ import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; import mage.constants.*; import mage.filter.FilterCard; +import mage.game.ExileZone; import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; @@ -100,13 +101,12 @@ public boolean apply(Game game, Ability source) { controller.choose(Outcome.Detriment, cards, target, source, game); Card card = cards.get(target.getFirstTarget(), game); if (card != null) { - controller.moveCardsToExile( - card, source, game, false, - CardUtil.getExileZoneId(game, source), - "Hideaway (" + CardUtil.getSourceName(game, source) + ')' - ); + CardUtil.moveCardsToExileFaceDown( + game, source, controller, card, + CardUtil.getCardExileZoneId(game, source), + "Hideaway (" + CardUtil.getSourceName(game, source) + ")", + false); game.addEffect(new HideawayLookAtFaceDownCardEffect().setTargetPointer(new FixedTarget(card, game)), source); - card.setFaceDown(true, game); } cards.retainZone(Zone.LIBRARY, game); controller.putCardsOnBottomOfLibrary(cards, game, source, false); @@ -114,30 +114,32 @@ public boolean apply(Game game, Ability source) { } } -class HideawayLookAtFaceDownCardEffect extends AsThoughEffectImpl { +class HideawayLookAtFaceDownCardEffect extends ContinuousEffectImpl { HideawayLookAtFaceDownCardEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + super(Duration.EndOfGame, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); } private HideawayLookAtFaceDownCardEffect(final HideawayLookAtFaceDownCardEffect effect) { super(effect); } - @Override - public boolean apply(Game game, Ability source) { - return true; - } - @Override public HideawayLookAtFaceDownCardEffect copy() { return new HideawayLookAtFaceDownCardEffect(this); } @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - return Objects.equals(objectId, getTargetPointer().getFirst(game, source)) - && HideawayWatcher.check(affectedControllerId, source, game); + public boolean apply(Game game, Ability source) { + UUID controllerId = game.getPlayer(source.getControllerId()).getId(); + UUID cardId = getTargetPointer().getFirst(game, source); + ExileZone exile = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source)); + if (cardId == null || exile == null) { + this.discard(); + return false; + } + exile.letPlayerSeeCards(controllerId, game.getCard(cardId)); + return HideawayWatcher.check(controllerId, source, game); } } diff --git a/Mage/src/main/java/mage/game/Exile.java b/Mage/src/main/java/mage/game/Exile.java index 7d8b7bd5660b..f9a822122897 100644 --- a/Mage/src/main/java/mage/game/Exile.java +++ b/Mage/src/main/java/mage/game/Exile.java @@ -103,6 +103,15 @@ public boolean removeCard(Card card) { return false; } + public void removeCardAndCopyVisibility(Card card, ExileZone targetZone) { + for (ExileZone exile : exileZones.values()) { + if (exile.contains(card.getId())) { + exile.copyCardVisibility(card, targetZone); + exile.remove(card.getId()); + } + } + } + /** * Move card from one exile zone to another. Use case example: create special zone for exiled and castable card. */ @@ -113,7 +122,7 @@ public void moveToAnotherZone(Card card, Game game, ExileZone exileZone) { if (exileZone == null) { throw new IllegalArgumentException("Exile zone must exists: " + card.getIdName()); } - removeCard(card); + removeCardAndCopyVisibility(card, exileZone); exileZone.add(card); } @@ -147,6 +156,7 @@ public void cleanupEndOfTurnZones(Game game) { for (ExileZone zone : exileZones.values()) { if (zone.isCleanupOnEndTurn()) { for (Card card : zone.getCards(game)) { + zone.copyCardVisibility(card, mainZone); mainZone.add(card); zone.remove(card); } diff --git a/Mage/src/main/java/mage/game/ExileZone.java b/Mage/src/main/java/mage/game/ExileZone.java index ee34a2ac52fa..3be20b3bb73e 100644 --- a/Mage/src/main/java/mage/game/ExileZone.java +++ b/Mage/src/main/java/mage/game/ExileZone.java @@ -1,7 +1,12 @@ package mage.game; +import mage.cards.Card; +import mage.cards.Cards; import mage.cards.CardsImpl; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import java.util.UUID; /** @@ -12,6 +17,7 @@ public class ExileZone extends CardsImpl { private final UUID id; private final String name; private boolean cleanupOnEndTurn = false; // moved cards from that zone to default on end of turn (to cleanup exile windows) + private final Map playerCardMap = new HashMap<>(); public ExileZone(UUID id, String name) { super(); @@ -24,6 +30,7 @@ protected ExileZone(final ExileZone zone) { this.id = zone.id; this.name = zone.name; this.cleanupOnEndTurn = zone.cleanupOnEndTurn; + this.playerCardMap.putAll(zone.playerCardMap); } public UUID getId() { @@ -42,6 +49,57 @@ public void setCleanupOnEndTurn(boolean cleanupOnEndTurn) { this.cleanupOnEndTurn = cleanupOnEndTurn; } + public void letPlayerSeeCards(UUID playerId, Card card) { + letPlayerSeeCards(playerId, new CardsImpl(card)); + } + + public void letPlayerSeeCards(UUID playerId, Cards cards) { + if (playerId == null || cards == null) { + return; + } + playerCardMap.computeIfAbsent(playerId, k -> new CardsImpl()).addAll(cards); + } + + public void letPlayerSeeCards(UUID playerId, Set cards) { + if (playerId == null || cards == null) { + return; + } + for (Card card : cards) {; + playerCardMap.computeIfAbsent(playerId, k -> new CardsImpl()).add(card); + } + } + + public boolean isPlayerAllowedToSeeCard(UUID playerId, Card card) { + if (playerId == null || card == null) { + return false; + } + return playerCardMap.getOrDefault(playerId, new CardsImpl()).contains(card.getId()); + } + + public Map getPlayerCardMap() { + return playerCardMap; + } + + public void copyCardVisibility(Card card, ExileZone targetZone) { + for (Map.Entry entry : playerCardMap.entrySet()) { + Cards cards = entry.getValue(); + if (cards.contains(card.getId())) { + targetZone.letPlayerSeeCards(entry.getKey(), card); + } + } + } + + @Override + public boolean remove(Card card) { + boolean result = super.remove(card); + if (result) { + for (Cards cards : playerCardMap.values()) { + cards.remove(card.getId()); + } + } + return result; + } + @Override public ExileZone copy() { return new ExileZone(this); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 6a74a11ad232..772c3fb3b757 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -1900,9 +1900,9 @@ public void clearBandedCards() { public String getLogName() { if (name.isEmpty()) { if (faceDown) { - return GameLog.getNeutralColoredText("face down creature"); + return GameLog.getNeutralObjectIdName("face down creature", getId()); } else { - return GameLog.getNeutralColoredText("a creature without name"); + return GameLog.getNeutralObjectIdName("a creature without name", getId()); } } return GameLog.getColoredObjectIdName(this); diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 2b1ed1937c62..1808191204f7 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -558,7 +558,7 @@ public String getIdName() { @Override public String getLogName() { if (faceDown) { - return "face down spell"; + return GameLog.getNeutralObjectIdName("face down spell", getId()); } return GameLog.getColoredObjectIdName(card); } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 65a79575a4be..8ede445dbb68 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -33,6 +33,7 @@ import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.CardState; +import mage.game.ExileZone; import mage.game.Game; import mage.game.GameState; import mage.game.command.Commander; @@ -2336,6 +2337,56 @@ public static boolean moveCardWithCounter(Game game, Ability source, Player cont return true; } + /** + * Move a card to exile face down and let the controller look at it + * @param game + * @param source + * @param controller + * @param card + * @return true if card was moved to exile + */ + + public static boolean moveCardsToExileFaceDown(Game game, Ability source, Player controller, Card card, boolean canLookAtCard) { + UUID exileId = getExileZoneId(game, source); + String exileName = getSourceName(game, source); + return moveCardsToExileFaceDown(game, source, controller, new HashSet<>(Collections.singleton(card)), exileId, exileName, canLookAtCard); + } + + public static boolean moveCardsToExileFaceDown(Game game, Ability source, Player controller, Card card, UUID exileId, String exileName, boolean canLookAtCard) { + return moveCardsToExileFaceDown(game, source, controller, new HashSet<>(Collections.singleton(card)), exileId, exileName, canLookAtCard); + } + + public static boolean moveCardsToExileFaceDown(Game game, Ability source, Player controller, Set cards, boolean canLookAtCard) { + UUID zoneId = getExileZoneId(game, source); + String zoneName = getSourceName(game, source); + return moveCardsToExileFaceDown(game, source, controller, cards, zoneId, zoneName, canLookAtCard); + } + /** + * Move multiple cards to exile face down and optionally let the controller look at it + * + * @param game + * @param source ability that exiles the card + * @param controller player moving the card + * @param cards cards to exile + * @param zoneId zone to exile the card to + * @param zoneName name of the zone to exile the card to + * @param canLookAtCard if the controller can look at the card + * @return true if card was moved to exile + */ + public static boolean moveCardsToExileFaceDown(Game game, Ability source, Player controller, Set cards, UUID zoneId, String zoneName, boolean canLookAtCard) { + if (!controller.moveCardsToExile(cards, source, game, false, zoneId, zoneName)) { + return false; + } + ExileZone exile = game.getExile().getExileZone(zoneId); + cards.forEach(card -> { + card.setFaceDown(true, game); + if (canLookAtCard) { + exile.letPlayerSeeCards(controller.getId(), card); + } + }); + return true; + } + public static int setOrIncrementValue(T u, Integer i) { return i == null ? 1 : Integer.sum(i, 1); }