diff --git a/Mage.Sets/src/mage/cards/a/AcrobaticManeuver.java b/Mage.Sets/src/mage/cards/a/AcrobaticManeuver.java index 6ee33d1681b..ce1fae1572e 100644 --- a/Mage.Sets/src/mage/cards/a/AcrobaticManeuver.java +++ b/Mage.Sets/src/mage/cards/a/AcrobaticManeuver.java @@ -21,8 +21,7 @@ public final class AcrobaticManeuver extends CardImpl { // Exile target creature you control, then return that card to the battlefield under its owner's control. this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); - Effect effect = new ExileTargetForSourceEffect(); - this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addEffect(new ExileTargetForSourceEffect()); this.getSpellAbility().addEffect(new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false)); // Draw a card. diff --git a/Mage.Sets/src/mage/cards/a/AshioksErasure.java b/Mage.Sets/src/mage/cards/a/AshioksErasure.java index 1c65ff517fb..c72b2b1c22c 100644 --- a/Mage.Sets/src/mage/cards/a/AshioksErasure.java +++ b/Mage.Sets/src/mage/cards/a/AshioksErasure.java @@ -123,16 +123,11 @@ class AshioksErasureReplacementEffect extends ContinuousRuleModifyingEffectImpl } UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - if (exileZone == null) { - return false; - } ExileZone exile = game.getExile().getExileZone(exileZone); if (exile == null) { // try without ZoneChangeCounter - that is useful for tokens exileZone = CardUtil.getCardExileZoneId(game, source); - if (exileZone != null) { - exile = game.getExile().getExileZone(exileZone); - } + exile = game.getExile().getExileZone(exileZone); } if (exile == null) { diff --git a/Mage.Sets/src/mage/cards/c/Cloudshift.java b/Mage.Sets/src/mage/cards/c/Cloudshift.java index 58df0848950..534ebef85dd 100644 --- a/Mage.Sets/src/mage/cards/c/Cloudshift.java +++ b/Mage.Sets/src/mage/cards/c/Cloudshift.java @@ -20,8 +20,7 @@ public final class Cloudshift extends CardImpl { // Exile target creature you control, then return that card to the battlefield under your control. this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); - Effect effect = new ExileTargetForSourceEffect(); - this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addEffect(new ExileTargetForSourceEffect()); this.getSpellAbility().addEffect(new ReturnToBattlefieldUnderYourControlTargetEffect(false)); } diff --git a/Mage.Sets/src/mage/cards/d/Displace.java b/Mage.Sets/src/mage/cards/d/Displace.java index 87ab48e600c..8a7a6ac2c8f 100644 --- a/Mage.Sets/src/mage/cards/d/Displace.java +++ b/Mage.Sets/src/mage/cards/d/Displace.java @@ -21,11 +21,9 @@ public final class Displace extends CardImpl { // Exile up to two target creatures you control, then return those cards to the battlefield under their owner's control. this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(0, 2, new FilterControlledCreaturePermanent("creatures you control"), false)); - Effect effect = new ExileTargetForSourceEffect(); - this.getSpellAbility().addEffect(effect); - effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false) - .withReturnNames("those cards", "their owner's").concatBy(", then"); - this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addEffect(new ExileTargetForSourceEffect()); + this.getSpellAbility().addEffect(new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false) + .withReturnNames("those cards", "their owner's").concatBy(", then")); } public Displace(final Displace card) { diff --git a/Mage.Sets/src/mage/cards/e/EldraziDisplacer.java b/Mage.Sets/src/mage/cards/e/EldraziDisplacer.java index 48310f862c4..2ba5fffba62 100644 --- a/Mage.Sets/src/mage/cards/e/EldraziDisplacer.java +++ b/Mage.Sets/src/mage/cards/e/EldraziDisplacer.java @@ -40,11 +40,9 @@ public final class EldraziDisplacer extends CardImpl { this.addAbility(new DevoidAbility(this.color)); // {2}{C}: Exile another target creature, then return it to the battlefield tapped under its owner's control. - Effect effect = new ExileTargetForSourceEffect(); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl<>("{2}{C}")); - effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(true, false) - .withReturnNames("it", "its owner's").concatBy(", then"); - ability.addEffect(effect); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ExileTargetForSourceEffect(), new ManaCostsImpl<>("{2}{C}")); + ability.addEffect(new ReturnToBattlefieldUnderOwnerControlTargetEffect(true, false) + .withReturnNames("it", "its owner's").concatBy(", then")); ability.addTarget(new TargetCreaturePermanent(FILTER)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/EssenceFlux.java b/Mage.Sets/src/mage/cards/e/EssenceFlux.java index 907217250cb..4bea56f01d7 100644 --- a/Mage.Sets/src/mage/cards/e/EssenceFlux.java +++ b/Mage.Sets/src/mage/cards/e/EssenceFlux.java @@ -29,8 +29,7 @@ public final class EssenceFlux extends CardImpl { // Exile target creature you control, then return that card to the battlefield under its owner's control. If it's a Spirit, put a +1/+1 counter on it. this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); - Effect effect = new ExileTargetForSourceEffect(); - this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addEffect(new ExileTargetForSourceEffect()); this.getSpellAbility().addEffect(new EssenceFluxEffect()); } diff --git a/Mage.Sets/src/mage/cards/f/FacelessButcher.java b/Mage.Sets/src/mage/cards/f/FacelessButcher.java index feb68515f4e..4feee4a6af7 100644 --- a/Mage.Sets/src/mage/cards/f/FacelessButcher.java +++ b/Mage.Sets/src/mage/cards/f/FacelessButcher.java @@ -39,15 +39,12 @@ public final class FacelessButcher extends CardImpl { this.toughness = new MageInt(3); // When Faceless Butcher enters the battlefield, exile target creature other than Faceless Butcher. - Effect effect = new ExileTargetForSourceEffect(); - Ability ability1 = new EntersBattlefieldTriggeredAbility(effect, false); - Target target = new TargetPermanent(filter); - ability1.addTarget(target); - this.addAbility(ability1); + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetForSourceEffect(), false); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); // When Faceless Butcher leaves the battlefield, return the exiled card to the battlefield under its owner's control. - Ability ability2 = new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD), false); - this.addAbility(ability2); + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD), false)); } diff --git a/Mage.Sets/src/mage/cards/f/FacelessDevourer.java b/Mage.Sets/src/mage/cards/f/FacelessDevourer.java index 143d5ee6df0..7d6b0aa030f 100644 --- a/Mage.Sets/src/mage/cards/f/FacelessDevourer.java +++ b/Mage.Sets/src/mage/cards/f/FacelessDevourer.java @@ -44,10 +44,8 @@ public final class FacelessDevourer extends CardImpl { this.addAbility(ShadowAbility.getInstance()); // When Faceless Devourer enters the battlefield, exile another target creature with shadow. - Effect effect = new ExileTargetForSourceEffect(); - Ability ability = new EntersBattlefieldTriggeredAbility(effect, false); - Target target = new TargetPermanent(filter); - ability.addTarget(target); + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetForSourceEffect(), false); + ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); // When Faceless Devourer leaves the battlefield, return the exiled card to the battlefield under its owner's control. diff --git a/Mage.Sets/src/mage/cards/f/FelidarGuardian.java b/Mage.Sets/src/mage/cards/f/FelidarGuardian.java index 1312c76e692..9da56d48efa 100644 --- a/Mage.Sets/src/mage/cards/f/FelidarGuardian.java +++ b/Mage.Sets/src/mage/cards/f/FelidarGuardian.java @@ -36,12 +36,10 @@ public final class FelidarGuardian extends CardImpl { this.toughness = new MageInt(4); // When Felidar Guardian enters the battlefield, you may exile another target permanent you control, then return that card to the battlefield under its owner's control. - Effect effect = new ExileTargetForSourceEffect(); - Ability ability = new EntersBattlefieldTriggeredAbility(effect, true); + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetForSourceEffect(), true); ability.addEffect(new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false)); ability.addTarget(new TargetControlledPermanent(filter)); this.addAbility(ability); - } public FelidarGuardian(final FelidarGuardian card) { diff --git a/Mage.Sets/src/mage/cards/g/GhastlyDemise.java b/Mage.Sets/src/mage/cards/g/GhastlyDemise.java index 5c422c3c1fe..609c910cba5 100644 --- a/Mage.Sets/src/mage/cards/g/GhastlyDemise.java +++ b/Mage.Sets/src/mage/cards/g/GhastlyDemise.java @@ -71,7 +71,7 @@ class GhastlyDemiseEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - if (source.getTargets().size() > 1 && targetPointer instanceof FirstTargetPointer) { // for Rain of Thorns + if (source.getTargets().size() > 1 && this.targetPointer instanceof FirstTargetPointer) { // for Rain of Thorns for (Target target : source.getTargets()) { for (UUID permanentId : target.getTargets()) { Permanent permanent = game.getPermanent(permanentId); @@ -81,8 +81,8 @@ class GhastlyDemiseEffect extends OneShotEffect { } } } - } else if (!targetPointer.getTargets(game, source).isEmpty()) { - for (UUID permanentId : targetPointer.getTargets(game, source)) { + } else if (this.targetPointer != null && !this.targetPointer.getTargets(game, source).isEmpty()) { + for (UUID permanentId : this.targetPointer.getTargets(game, source)) { Permanent permanent = game.getPermanent(permanentId); if (permanent != null && permanent.getToughness().getValue() <= game.getPlayer(source.getControllerId()).getGraveyard().size()) { permanent.destroy(source, game, noRegen); diff --git a/Mage.Sets/src/mage/cards/o/OblivionRing.java b/Mage.Sets/src/mage/cards/o/OblivionRing.java index 8c4700b069f..f4e0ed1374d 100644 --- a/Mage.Sets/src/mage/cards/o/OblivionRing.java +++ b/Mage.Sets/src/mage/cards/o/OblivionRing.java @@ -34,14 +34,12 @@ public final class OblivionRing extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}"); // When Oblivion Ring enters the battlefield, exile another target nonland permanent. - Ability ability1 = new EntersBattlefieldTriggeredAbility(new ExileTargetForSourceEffect(), false); - Target target = new TargetPermanent(anotherNonlandPermanent); - ability1.addTarget(target); - this.addAbility(ability1); + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetForSourceEffect(), false); + ability.addTarget(new TargetPermanent(anotherNonlandPermanent)); + this.addAbility(ability); // When Oblivion Ring leaves the battlefield, return the exiled card to the battlefield under its owner's control. - Ability ability2 = new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD), false); - this.addAbility(ability2); + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD), false)); } public OblivionRing(final OblivionRing card) { diff --git a/Mage.Sets/src/mage/cards/p/PromiseOfTomorrow.java b/Mage.Sets/src/mage/cards/p/PromiseOfTomorrow.java index 7c1dcc08879..2ca27ffcd57 100644 --- a/Mage.Sets/src/mage/cards/p/PromiseOfTomorrow.java +++ b/Mage.Sets/src/mage/cards/p/PromiseOfTomorrow.java @@ -39,13 +39,12 @@ public final class PromiseOfTomorrow extends CardImpl { )); // At the beginning of each end step, if you control no creatures, sacrifice Promise of Tomorrow and return all cards exiled with it to the battlefield under your control. + BeginningOfEndStepTriggeredAbility returnAbility = new BeginningOfEndStepTriggeredAbility(new SacrificeSourceEffect(), TargetController.ANY, false); + returnAbility.addEffect(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD)); Ability ability = new ConditionalInterveningIfTriggeredAbility( - new BeginningOfEndStepTriggeredAbility( - new SacrificeSourceEffect(), TargetController.ANY, false - ), condition, "At the beginning of each end step, if you control no creatures, " + + returnAbility, condition, "At the beginning of each end step, if you control no creatures, " + "sacrifice {this} and return all cards exiled with it to the battlefield under your control." ); - ability.addEffect(new ReturnFromExileForSourceEffect(Zone.EXILED)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/w/WerewolfRansacker.java b/Mage.Sets/src/mage/cards/w/WerewolfRansacker.java index b73383907fd..53844805493 100644 --- a/Mage.Sets/src/mage/cards/w/WerewolfRansacker.java +++ b/Mage.Sets/src/mage/cards/w/WerewolfRansacker.java @@ -113,8 +113,8 @@ class WerewolfRansackerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int affectedTargets = 0; - if (!targetPointer.getTargets(game, source).isEmpty()) { - for (UUID permanentId : targetPointer.getTargets(game, source)) { + if (this.targetPointer != null && !this.targetPointer.getTargets(game, source).isEmpty()) { + for (UUID permanentId : this.targetPointer.getTargets(game, source)) { Permanent permanent = game.getPermanent(permanentId); if (permanent != null) { if (permanent.destroy(source, game, false)) { diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/ExileTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/ExileTargetTest.java index 67b03d91fd7..1ac6cd24d83 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/ExileTargetTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/ExileTargetTest.java @@ -25,11 +25,9 @@ public class ExileTargetTest extends CardTestCommander4Players { addCard(Zone.BATTLEFIELD, playerC, "Balduvian Bears", 1); // 2/2 // must select opponent's Balduvian Bears - // showAvailableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Oblivion Ring"); //addTarget(playerA, "Balduvian Bears"); // disable to activate AI target choose - // showAvailableAbilities("after", 1, PhaseStep.BEGIN_COMBAT, playerA); //setStrictChooseMode(true); // disable strict mode to activate AI for choosing setStopAt(1, PhaseStep.END_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/PromiseOfTomorrowTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/PromiseOfTomorrowTest.java new file mode 100644 index 00000000000..ca068800fa3 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/PromiseOfTomorrowTest.java @@ -0,0 +1,121 @@ +package org.mage.test.cards.single.cmr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +/** + * @author JayDi85 + */ + +public class PromiseOfTomorrowTest extends CardTestCommander4Players { + + @Test + public void test_NormalCard() { + // bug: https://github.com/magefree/mage/issues/7250 + + // Whenever a creature you control dies, exile it. + // At the beginning of each end step, if you control no creatures, sacrifice Promise of Tomorrow and return all + // cards exiled with it to the battlefield under your control. + addCard(Zone.BATTLEFIELD, playerA, "Promise of Tomorrow", 1); + // + addCard(Zone.HAND, playerA, "Lightning Bolt", 2); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears@bear", 2); + + // destroy two creatures + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "@bear.1"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "@bear.2"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkExileCount("after die", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 2); + + // must return + checkPermanentCount("after return", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 2); + checkGraveyardCount("after return", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Promise of Tomorrow", 1); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Commander_LeaveInZone() { + // bug: https://github.com/magefree/mage/issues/7250 + + // Whenever a creature you control dies, exile it. + // At the beginning of each end step, if you control no creatures, sacrifice Promise of Tomorrow and return all + // cards exiled with it to the battlefield under your control. + addCard(Zone.BATTLEFIELD, playerA, "Promise of Tomorrow", 1); + // + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.COMMAND, playerA, "Balduvian Bears", 1); // {1}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + // prepare commander + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 2); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + + // destroy creature + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkExileCount("after die", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + + setChoice(playerA, "No"); // move commander from graveyard to command zone + setChoice(playerA, "No"); // move commander from exile to command zone + + // must return + checkPermanentCount("after return", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + checkGraveyardCount("after return", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Promise of Tomorrow", 1); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Commander_MoveToCommandZoneFirst() { + // bug: https://github.com/magefree/mage/issues/7250 + + // Whenever a creature you control dies, exile it. + // At the beginning of each end step, if you control no creatures, sacrifice Promise of Tomorrow and return all + // cards exiled with it to the battlefield under your control. + addCard(Zone.BATTLEFIELD, playerA, "Promise of Tomorrow", 1); + // + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.COMMAND, playerA, "Balduvian Bears", 1); // {1}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + // prepare commander + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 2); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + + // destroy creature + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkExileCount("after die", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + + // possible bug: Promise of Tomorrow tries to move commander card to exile from command zone with error + setChoice(playerA, "Yes"); // move commander from graveyard to command zone + setChoice(playerA, "No"); // move commander from exile to command zone + + // must return + checkPermanentCount("after return", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + checkGraveyardCount("after return", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Promise of Tomorrow", 1); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java index 72662dbc510..b6ea3543b17 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetForSourceEffect.java @@ -3,6 +3,7 @@ package mage.abilities.effects.common; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.Mode; +import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.constants.Outcome; @@ -10,15 +11,19 @@ import mage.game.Game; import mage.players.Player; import mage.target.Target; import mage.target.targetpointer.FirstTargetPointer; -import mage.util.CardUtil; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.UUID; -import mage.abilities.effects.Effect; import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.TargetPointer; +import mage.util.CardUtil; + +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; /** + * Use it for combo with ReturnFromExileForSourceEffect (exile and return exiled later) + * * @author BetaSteward_at_googlemail.com */ public class ExileTargetForSourceEffect extends OneShotEffect { @@ -45,46 +50,50 @@ public class ExileTargetForSourceEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); + if (controller == null || sourceObject == null) { + return false; + } - if (controller != null - && sourceObject != null) { - Set cards = new LinkedHashSet<>(); - if (source.getTargets().size() > 1 - && targetPointer instanceof FirstTargetPointer) { - for (Target target : source.getTargets()) { - for (UUID targetId : target.getTargets()) { - MageObject mageObject = game.getObject(targetId); - if (mageObject instanceof Card) { - cards.add((Card) mageObject); - } + Set objectsToMove = new LinkedHashSet<>(); + if (this.targetPointer instanceof FirstTargetPointer + && source.getTargets().size() > 1) { + for (Target target : source.getTargets()) { + objectsToMove.addAll(target.getTargets()); + } + } else { + if (this.targetPointer != null && !this.targetPointer.getTargets(game, source).isEmpty()) { + objectsToMove.addAll(this.targetPointer.getTargets(game, source)); + } else { + // issue with Madness keyword #6889 + UUID fixedTargetId = null; + for (Effect effect : source.getEffects()) { + TargetPointer targetPointerId = effect.getTargetPointer(); + if (targetPointerId instanceof FixedTarget) { + fixedTargetId = (((FixedTarget) targetPointerId).getTarget()); } } - } else { - if (!targetPointer.getTargets(game, source).isEmpty()) { - for (UUID targetId : targetPointer.getTargets(game, source)) { - MageObject mageObject = game.getObject(targetId); - if (mageObject != null) { - cards.add((Card) mageObject); - } - } - } else { - // issue with Madness keyword #6889 - UUID fixedTargetId = null; - for (Effect effect : source.getEffects()) { - TargetPointer targetPointerId = effect.getTargetPointer(); - if (targetPointerId instanceof FixedTarget) { - fixedTargetId = (((FixedTarget) targetPointerId).getTarget()); - } - } - if (fixedTargetId != null) { - cards.add((Card) game.getObject(fixedTargetId)); - } + if (fixedTargetId != null) { + objectsToMove.add(fixedTargetId); } } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - return controller.moveCardsToExile(cards, source, game, true, exileId, sourceObject.getIdName()); } - return false; + + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + + // it can target permanents on battlefield, so use objects first + Set cardsToMove = objectsToMove.stream() + .map(game::getObject) + .filter(Objects::nonNull) + .map(object -> { + if (object instanceof Card) { + return (Card) object; + } else { + return game.getCard(object.getId()); + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + return controller.moveCardsToExile(cardsToMove, source, game, true, exileId, sourceObject.getIdName()); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java index 7bd9d4350ea..1ee026e6d2b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromExileForSourceEffect.java @@ -1,6 +1,5 @@ package mage.abilities.effects.common; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; @@ -10,16 +9,19 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.CardUtil; -import org.apache.log4j.Logger; + +import java.util.UUID; /** + * Use it for combo with ExileTargetForSourceEffect (exile and return exiled later) + * * @author BetaSteward_at_googlemail.com */ public class ReturnFromExileForSourceEffect extends OneShotEffect { - private Zone returnToZone; - private boolean tapped; - private boolean previousZone; + private final Zone returnToZone; + private final boolean tapped; + private final boolean previousZone; private String returnName = "cards"; private String returnControlName; @@ -78,24 +80,32 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = source.getSourceObject(game); - if (sourceObject != null && controller != null) { - Permanent permanentLeftBattlefield = (Permanent) getValue("permanentLeftBattlefield"); - if (permanentLeftBattlefield == null) { - Logger.getLogger(ReturnFromExileForSourceEffect.class).error("Permanent not found: " + sourceObject.getName()); - return false; - } - ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), permanentLeftBattlefield.getZoneChangeCounter(game))); - if (exile != null) { // null is valid if source left battlefield before enters the battlefield effect resolved - if (returnToZone == Zone.BATTLEFIELD) { - controller.moveCards(exile.getCards(game), returnToZone, source, game, false, false, true, null); - } else { - controller.moveCards(exile, returnToZone, source, game); - } - } - return true; + if (controller == null) { + return false; } - return false; + + // effect uses in two use cases: + // * on battlefield + // * after leaves the battlefield + // so ZCC must be different in different use cases + + UUID exileId; + Permanent permanentLeftBattlefield = (Permanent) getValue("permanentLeftBattlefield"); + if (permanentLeftBattlefield != null) { + exileId = CardUtil.getExileZoneId(game, source.getSourceId(), permanentLeftBattlefield.getZoneChangeCounter(game)); + } else { + exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + } + + ExileZone exile = game.getExile().getExileZone(exileId); + if (exile != null) { // null is valid if source left battlefield before enters the battlefield effect resolved + if (returnToZone == Zone.BATTLEFIELD) { + controller.moveCards(exile.getCards(game), returnToZone, source, game, false, false, true, null); + } else { + controller.moveCards(exile, returnToZone, source, game); + } + } + return true; } private void updateText() { diff --git a/Mage/src/main/java/mage/abilities/effects/common/SkipNextPlayerUntapStepEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SkipNextPlayerUntapStepEffect.java index 66c97e162ff..c4d7682bf37 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SkipNextPlayerUntapStepEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SkipNextPlayerUntapStepEffect.java @@ -61,7 +61,7 @@ public class SkipNextPlayerUntapStepEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = null; if (targetPointer != null) { - if (!targetPointer.getTargets(game, source).isEmpty()) { + if (!this.targetPointer.getTargets(game, source).isEmpty()) { player = game.getPlayer(targetPointer.getFirst(game, source)); } else { player = game.getPlayer(source.getControllerId()); diff --git a/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java b/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java index e3f3e7e831c..620bd70251d 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java @@ -154,7 +154,7 @@ class ChampionExileCost extends CostImpl { MageObject sourceObject = ability.getSourceObject(game); if (controller != null && sourceObject != null) { if (targets.choose(Outcome.Exile, controllerId, source.getSourceId(), game)) { - UUID exileId = CardUtil.getExileZoneId(game, ability.getSourceId(), ability.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, ability.getSourceId(), ability.getSourceObjectZoneChangeCounter()); // exileId important for return effect for (UUID targetId : targets.get(0).getTargets()) { Permanent permanent = game.getPermanent(targetId); if (permanent == null) {