diff --git a/Mage.Sets/src/mage/cards/c/CurseOfInertia.java b/Mage.Sets/src/mage/cards/c/CurseOfInertia.java
index ee6c5120633..c31c6e391dc 100644
--- a/Mage.Sets/src/mage/cards/c/CurseOfInertia.java
+++ b/Mage.Sets/src/mage/cards/c/CurseOfInertia.java
@@ -13,11 +13,11 @@ import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
-import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.TargetPlayer;
+import mage.target.targetadjustment.DefineByTriggerTargetAdjuster;
import java.util.UUID;
@@ -56,6 +56,7 @@ class CurseOfInertiaTriggeredAbility extends TriggeredAbilityImpl {
public CurseOfInertiaTriggeredAbility() {
super(Zone.BATTLEFIELD, new CurseOfInertiaTapOrUntapTargetEffect(), false);
+ setTargetAdjuster(DefineByTriggerTargetAdjuster.instance);
}
private CurseOfInertiaTriggeredAbility(final CurseOfInertiaTriggeredAbility ability) {
@@ -75,7 +76,8 @@ class CurseOfInertiaTriggeredAbility extends TriggeredAbilityImpl {
&& game.getCombat().getPlayerDefenders(game, false).contains(enchantment.getAttachedTo())) {
TargetPermanent target = new TargetPermanent();
target.setTargetController(game.getCombat().getAttackingPlayerId());
- addTarget(target);
+ this.getTargets().clear();
+ this.addTarget(target);
return true;
}
return false;
diff --git a/Mage.Sets/src/mage/cards/d/DreamEater.java b/Mage.Sets/src/mage/cards/d/DreamEater.java
index 5c728566480..6ad226cde45 100644
--- a/Mage.Sets/src/mage/cards/d/DreamEater.java
+++ b/Mage.Sets/src/mage/cards/d/DreamEater.java
@@ -5,6 +5,7 @@ import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
+import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.ReturnToHandTargetEffect;
import mage.abilities.effects.keyword.SurveilEffect;
import mage.abilities.keyword.FlashAbility;
@@ -42,8 +43,9 @@ public final class DreamEater extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// When Dream Eater enters the battlefield, surveil 4. When you do, you may return target nonland permanent an opponent controls to its owner's hand.
- Ability ability = new EntersBattlefieldTriggeredAbility(new SurveilEffect(4));
+ Ability ability = new EntersBattlefieldTriggeredAbility(new SurveilEffect(4, false));
ability.addEffect(new DreamEaterEffect());
+ ability.addEffect(new InfoEffect("(To surveil 4, look at the top four cards of your library, then put any number of them into your graveyard and the rest on top of your library in any order.)"));
this.addAbility(ability);
}
diff --git a/Mage.Sets/src/mage/cards/f/FarrelsMantle.java b/Mage.Sets/src/mage/cards/f/FarrelsMantle.java
index 945e1488a63..596abc2e441 100644
--- a/Mage.Sets/src/mage/cards/f/FarrelsMantle.java
+++ b/Mage.Sets/src/mage/cards/f/FarrelsMantle.java
@@ -21,6 +21,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
+import mage.target.targetadjustment.DefineByTriggerTargetAdjuster;
import java.util.UUID;
@@ -58,6 +59,7 @@ class FarrelsMantleTriggeredAbility extends TriggeredAbilityImpl {
FarrelsMantleTriggeredAbility() {
super(Zone.BATTLEFIELD, null, false);
+ setTargetAdjuster(DefineByTriggerTargetAdjuster.instance);
}
private FarrelsMantleTriggeredAbility(final FarrelsMantleTriggeredAbility ability) {
diff --git a/Mage.Sets/src/mage/cards/i/Incendiary.java b/Mage.Sets/src/mage/cards/i/Incendiary.java
index b349674d1a1..1da5f208c95 100644
--- a/Mage.Sets/src/mage/cards/i/Incendiary.java
+++ b/Mage.Sets/src/mage/cards/i/Incendiary.java
@@ -1,24 +1,25 @@
package mage.cards.i;
-import java.util.UUID;
-import mage.constants.SubType;
-import mage.target.common.TargetCreaturePermanent;
import mage.abilities.Ability;
-import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.DiesAttachedTriggeredAbility;
import mage.abilities.dynamicvalue.common.CountersSourceCount;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
-import mage.constants.Outcome;
-import mage.target.TargetPermanent;
import mage.abilities.keyword.EnchantAbility;
+import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
+import mage.constants.Outcome;
+import mage.constants.SubType;
import mage.counters.CounterType;
+import mage.target.TargetPermanent;
import mage.target.common.TargetCreatureOrPlayer;
+import mage.target.common.TargetCreaturePermanent;
+
+import java.util.UUID;
/**
*
@@ -47,7 +48,7 @@ public final class Incendiary extends CardImpl {
// When enchanted creature dies, Incendiary deals X damage to any target, where X is the number of fuse counters on Incendiary.
Effect effect = new DamageTargetEffect(new CountersSourceCount(CounterType.FUSE)).setText(rule);
Ability ability2 = new DiesAttachedTriggeredAbility(effect, "enchanted creature");
- ability.addTarget(new TargetCreatureOrPlayer());
+ ability2.addTarget(new TargetCreatureOrPlayer());
this.addAbility(ability2);
}
diff --git a/Mage.Sets/src/mage/cards/j/JubilantMascot.java b/Mage.Sets/src/mage/cards/j/JubilantMascot.java
index 4f706c1c689..12dd6115469 100644
--- a/Mage.Sets/src/mage/cards/j/JubilantMascot.java
+++ b/Mage.Sets/src/mage/cards/j/JubilantMascot.java
@@ -5,13 +5,12 @@ import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DoIfCostPaid;
-import mage.abilities.effects.common.counter.AddCountersTargetEffect;
+import mage.abilities.effects.keyword.SupportEffect;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
-import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.target.TargetPermanent;
@@ -39,8 +38,7 @@ public final class JubilantMascot extends CardImpl {
// At the beginning of combat on your turn, you may pay {3}{W}. If you do, support 2. (Put a +1/+1 counter on each of up to two other target creatures.)
Ability ability = new BeginningOfCombatTriggeredAbility(
new DoIfCostPaid(
- new AddCountersTargetEffect(CounterType.P1P1.createInstance())
- .setText("support 2"),
+ new SupportEffect(this, 2, true),
new ManaCostsImpl<>("{3}{W}")
));
ability.addTarget(new TargetPermanent(0, 2, filter));
diff --git a/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java b/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java
index 7f41fa32cc8..d01b21e9f6f 100644
--- a/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java
+++ b/Mage.Sets/src/mage/cards/m/MantleOfTheAncients.java
@@ -15,22 +15,32 @@ import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterCard;
-import mage.filter.predicate.Predicate;
+import mage.filter.common.FilterPermanentCard;
+import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
-import mage.target.TargetCard;
import mage.target.TargetPermanent;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.Objects;
+import java.util.Set;
import java.util.UUID;
+import java.util.stream.Collectors;
/**
* @author TheElk801
*/
public final class MantleOfTheAncients extends CardImpl {
+ private static final FilterCard filter = new FilterPermanentCard();
+
+ static {
+ filter.add(Predicates.or(
+ SubType.AURA.getPredicate(),
+ SubType.EQUIPMENT.getPredicate()
+ ));
+ }
public MantleOfTheAncients(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{W}");
@@ -41,11 +51,12 @@ public final class MantleOfTheAncients extends CardImpl {
TargetPermanent auraTarget = new TargetControlledCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
- Ability ability = new EnchantAbility(auraTarget);
- this.addAbility(ability);
+ this.addAbility(new EnchantAbility(auraTarget));
// When Mantle of the Ancients enters the battlefield, return any number of target Aura and/or Equipment cards from your graveyard to the battlefield attached to enchanted creature.
- this.addAbility(new EntersBattlefieldTriggeredAbility(new MantleOfTheAncientsEffect()));
+ Ability ability = new EntersBattlefieldTriggeredAbility(new MantleOfTheAncientsEffect());
+ ability.addTarget(new TargetCardInYourGraveyard(0, Integer.MAX_VALUE, filter));
+ this.addAbility(ability);
// Enchanted creature gets +1/+1 for each Aura and Equipment attached to it.
this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(
@@ -68,8 +79,7 @@ class MantleOfTheAncientsEffect extends OneShotEffect {
MantleOfTheAncientsEffect() {
super(Outcome.Benefit);
staticText = "return any number of target Aura and/or Equipment cards " +
- "that could be attached to enchanted creature from your graveyard " +
- "to the battlefield attached to enchanted creature";
+ "from your graveyard to the battlefield attached to enchanted creature";
}
private MantleOfTheAncientsEffect(final MantleOfTheAncientsEffect effect) {
@@ -92,46 +102,21 @@ class MantleOfTheAncientsEffect extends OneShotEffect {
if (permanent == null) {
return false;
}
- FilterCard filter = new FilterCard("Aura or Equipment card that can be attached to " + permanent.getName());
- filter.add(new MantleOfTheAncientsPredicate(permanent));
- TargetCard target = new TargetCardInYourGraveyard(0, Integer.MAX_VALUE, filter, true);
- player.choose(outcome, target, source, game);
- Cards cards = new CardsImpl(target.getTargets());
+ Set cards = getTargetPointer().getTargets(game, source).stream().map(game::getCard).filter(Objects::nonNull)
+ .filter(card -> !permanent.cantBeAttachedBy(card, source, game, true)).collect(Collectors.toSet());
if (cards.isEmpty()) {
return false;
}
- cards.getCards(game)
- .stream()
- .forEach(card -> game.getState().setValue("attachTo:" + card.getId(), permanent));
+
+ cards.forEach(card -> game.getState().setValue("attachTo:" + card.getId(), permanent));
player.moveCards(cards, Zone.BATTLEFIELD, source, game);
- for (UUID cardId : cards) {
- permanent.addAttachment(cardId, source, game);
- }
+ Cards movedCards = new CardsImpl(cards);
+ movedCards.retainZone(Zone.BATTLEFIELD, game);
+ movedCards.forEach(card -> permanent.addAttachment(card, source, game));
return true;
}
}
-class MantleOfTheAncientsPredicate implements Predicate {
-
- private final Permanent permanent;
-
- MantleOfTheAncientsPredicate(Permanent permanent) {
- this.permanent = permanent;
- }
-
- @Override
- public boolean apply(Card input, Game game) {
- if (input.hasSubtype(SubType.AURA, game)) {
- return input
- .getSpellAbility()
- .getTargets()
- .stream()
- .anyMatch(target -> target.getFilter().match(permanent, game));
- }
- return input.hasSubtype(SubType.EQUIPMENT, game);
- }
-}
-
enum MantleOfTheAncientsValue implements DynamicValue {
instance;
diff --git a/Mage.Sets/src/mage/cards/m/MuseVessel.java b/Mage.Sets/src/mage/cards/m/MuseVessel.java
index 04a1518e5e6..fd4dcda2ee6 100644
--- a/Mage.Sets/src/mage/cards/m/MuseVessel.java
+++ b/Mage.Sets/src/mage/cards/m/MuseVessel.java
@@ -6,24 +6,26 @@ import mage.abilities.common.ActivateAsSorceryActivatedAbility;
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.cards.Card;
+import mage.abilities.effects.OneShotNonTargetEffect;
+import mage.abilities.effects.common.AddContinuousEffectToGame;
+import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
-import mage.constants.*;
-import mage.filter.FilterCard;
-import mage.game.ExileZone;
+import mage.constants.CardType;
+import mage.constants.Duration;
+import mage.constants.Outcome;
+import mage.constants.Zone;
+import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetPlayer;
import mage.target.common.TargetCardInExile;
import mage.target.common.TargetCardInHand;
+import mage.target.targetadjustment.TargetAdjuster;
import mage.util.CardUtil;
-import java.util.HashSet;
-import java.util.Set;
import java.util.UUID;
/**
@@ -40,10 +42,12 @@ public final class MuseVessel extends CardImpl {
tapAbility.addCost(new ManaCostsImpl<>("{3}"));
tapAbility.addTarget(new TargetPlayer());
this.addAbility(tapAbility);
-
// {1}: Choose a card exiled with Muse Vessel. You may play that card this turn.
- SimpleActivatedAbility playAbility = new SimpleActivatedAbility(new MuseVesselMayPlayExiledEffect(), new ManaCostsImpl<>("{1}"));
- playAbility.addTarget(new TargetCardInMuseVesselExile());
+ SimpleActivatedAbility playAbility = new SimpleActivatedAbility(new OneShotNonTargetEffect(
+ new AddContinuousEffectToGame(new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, Duration.EndOfTurn))
+ .setText("Choose a card exiled with Muse Vessel. You may play that card this turn."),
+ new TargetCardInExile(StaticFilters.FILTER_CARD), MuseVesselAdjuster.instance
+ ), new ManaCostsImpl<>("{1}"));
this.addAbility(playAbility);
}
@@ -80,8 +84,8 @@ class MuseVesselExileEffect extends OneShotEffect {
}
TargetCardInHand target = new TargetCardInHand();
if (target.canChoose(player.getId(), source, game)
- && target.chooseTarget(Outcome.Exile, player.getId(), source, game)) {
- UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter());
+ && target.choose(Outcome.Exile, player.getId(), source, game)) {
+ UUID exileId = CardUtil.getExileZoneId(game, source);
return player.moveCardsToExile(new CardsImpl(target.getTargets()).getCards(game), source, game, true, exileId, sourceObject.getIdName());
}
return false;
@@ -94,90 +98,14 @@ class MuseVesselExileEffect extends OneShotEffect {
}
-class MuseVesselMayPlayExiledEffect extends AsThoughEffectImpl {
-
- MuseVesselMayPlayExiledEffect() {
- super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit);
- this.staticText = "Choose a card exiled with {this}. You may play that card this turn";
- }
-
- private MuseVesselMayPlayExiledEffect(final MuseVesselMayPlayExiledEffect effect) {
- super(effect);
- }
+enum MuseVesselAdjuster implements TargetAdjuster {
+ instance;
@Override
- public MuseVesselMayPlayExiledEffect copy() {
- return new MuseVesselMayPlayExiledEffect(this);
- }
-
- @Override
- public boolean apply(Game game, Ability source) {
- return true;
- }
-
- @Override
- public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
- return affectedControllerId.equals(source.getControllerId())
- && getTargetPointer().getTargets(game, source).contains(objectId);
- }
-
-}
-
-// TODO: cleanup. there should be no need for custom Target there.
-class TargetCardInMuseVesselExile extends TargetCardInExile {
-
- public TargetCardInMuseVesselExile() {
- super(new FilterCard("card exiled with Muse Vessel"));
- }
-
- private TargetCardInMuseVesselExile(final TargetCardInMuseVesselExile target) {
- super(target);
- }
-
- @Override
- public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) {
- Set possibleTargets = new HashSet<>();
- Card sourceCard = game.getCard(source.getSourceId());
- if (sourceCard != null) {
- UUID exileId = CardUtil.getCardExileZoneId(game, source.getSourceId());
- ExileZone exile = game.getExile().getExileZone(exileId);
- if (exile != null && !exile.isEmpty()) {
- possibleTargets.addAll(exile);
- }
- }
- return possibleTargets;
- }
-
- @Override
- public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
- Card sourceCard = game.getCard(source.getSourceId());
- if (sourceCard != null) {
- UUID exileId = CardUtil.getCardExileZoneId(game, source.getSourceId());
- ExileZone exile = game.getExile().getExileZone(exileId);
- return exile != null && !exile.isEmpty();
- }
- return false;
- }
-
- @Override
- public boolean canTarget(UUID id, Ability source, Game game) {
- Card card = game.getCard(id);
- if (card != null && game.getState().getZone(card.getId()) == Zone.EXILED) {
- ExileZone exile = null;
- Card sourceCard = game.getCard(source.getSourceId());
- if (sourceCard != null) {
- UUID exileId = CardUtil.getCardExileZoneId(game, source);
- exile = game.getExile().getExileZone(exileId);
- }
- if (exile != null && exile.contains(id)) {
- return filter.match(card, source.getControllerId(), source, game);
- }
- }
- return false;
- }
-
- @Override
- public TargetCardInMuseVesselExile copy() {
- return new TargetCardInMuseVesselExile(this);
+ public void adjustTargets(Ability ability, Game game) {
+ ability.getTargets().clear();
+ ability.addTarget(
+ new TargetCardInExile(StaticFilters.FILTER_CARD, CardUtil.getCardExileZoneId(game, ability.getSourceId()))
+ .withNotTarget(true));
}
}
diff --git a/Mage.Sets/src/mage/cards/s/SoulSeizer.java b/Mage.Sets/src/mage/cards/s/SoulSeizer.java
index f7e7b786183..5dc4a1fe428 100644
--- a/Mage.Sets/src/mage/cards/s/SoulSeizer.java
+++ b/Mage.Sets/src/mage/cards/s/SoulSeizer.java
@@ -2,7 +2,8 @@ package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
-import mage.abilities.TriggeredAbilityImpl;
+import mage.abilities.TriggeredAbility;
+import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.TransformAbility;
@@ -11,16 +12,11 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
-import mage.constants.Zone;
-import mage.filter.common.FilterCreaturePermanent;
-import mage.filter.predicate.permanent.ControllerIdPredicate;
+import mage.filter.StaticFilters;
import mage.game.Game;
-import mage.game.events.DamagedPlayerEvent;
-import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
-import mage.players.Player;
import mage.target.TargetPermanent;
-import mage.target.common.TargetCreaturePermanent;
+import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster;
import java.util.UUID;
@@ -42,7 +38,11 @@ public final class SoulSeizer extends CardImpl {
// When Soul Seizer deals combat damage to a player, you may transform it. If you do, attach it to target creature that player controls.
this.addAbility(new TransformAbility());
- this.addAbility(new SoulSeizerTriggeredAbility());
+ TriggeredAbility ability = new DealsCombatDamageToAPlayerTriggeredAbility(new SoulSeizerEffect(), true, true);
+ ability.setTriggerPhrase("When {this} deals combat damage to a player, ");
+ ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE));
+ ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster());
+ this.addAbility(ability);
}
private SoulSeizer(final SoulSeizer card) {
@@ -55,53 +55,11 @@ public final class SoulSeizer extends CardImpl {
}
}
-class SoulSeizerTriggeredAbility extends TriggeredAbilityImpl {
-
- public SoulSeizerTriggeredAbility() {
- super(Zone.BATTLEFIELD, new SoulSeizerEffect(), true);
- }
-
- private SoulSeizerTriggeredAbility(final SoulSeizerTriggeredAbility ability) {
- super(ability);
- }
-
- @Override
- public SoulSeizerTriggeredAbility copy() {
- return new SoulSeizerTriggeredAbility(this);
- }
-
- @Override
- public boolean checkEventType(GameEvent event, Game game) {
- return event.getType() == GameEvent.EventType.DAMAGED_PLAYER;
- }
-
- @Override
- public boolean checkTrigger(GameEvent event, Game game) {
- DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event;
- if (damageEvent.isCombatDamage() && event.getSourceId().equals(this.getSourceId())) {
- Player opponent = game.getPlayer(event.getPlayerId());
- if (opponent != null) {
- FilterCreaturePermanent filter = new FilterCreaturePermanent("creature " + opponent.getLogName() + " controls");
- filter.add(new ControllerIdPredicate(opponent.getId()));
-
- this.getTargets().clear();
- this.addTarget(new TargetPermanent(filter));
- return true;
- }
- }
- return false;
- }
-
- @Override
- public String getRule() {
- return "When {this} deals combat damage to a player, you may transform it. If you do, attach it to target creature that player controls";
- }
-}
-
class SoulSeizerEffect extends OneShotEffect {
SoulSeizerEffect() {
super(Outcome.GainControl);
+ this.staticText = "you may transform it. If you do, attach it to target creature that player controls";
}
private SoulSeizerEffect(final SoulSeizerEffect effect) {
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java
index 686b7ae7597..0475b151603 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java
@@ -74,4 +74,70 @@ public class OneShotNonTargetTest extends CardTestPlayerBase {
assertPowerToughness(playerA, "Squire", 3, 4);
assertPowerToughness(playerA, "Soldier Token", 2, 2);
}
+
+ @Test
+ public void MuseVesselTest() {
+ String muse = "Muse Vessel";
+ addCard(Zone.HAND, playerA, muse);
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 30);
+ addCard(Zone.BATTLEFIELD, playerA, "Vizier of Tumbling Sands", 2);
+
+ addCard(Zone.HAND, playerA, "Squire");
+ addCard(Zone.HAND, playerA, "Alpha Myr");
+ addCard(Zone.HAND, playerA, "Void Snare");
+ addCard(Zone.HAND, playerB, "Island");
+ addCard(Zone.HAND, playerB, "Corridor Monitor");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, muse, true);
+
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Target player", playerB);
+ setChoice(playerB, "Island");
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Untap", muse);
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
+
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Target player", playerB);
+ setChoice(playerB, "Corridor Monitor");
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Untap", muse);
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
+
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Target player", playerA);
+ setChoice(playerA, "Squire");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
+
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}: Choose");
+ setChoice(playerA, "Island");
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}: Choose");
+ setChoice(playerA, "Squire");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Squire", true);
+ playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island");
+
+ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Void Snare", muse, true); // Bounce the vessel to hand, check exile ID correctly managed
+ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, muse, true);
+ activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{1}: Choose"); // Should activate but no possible choices
+
+ waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN);
+ activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{3}, {T}: Target player", playerA);
+ setChoice(playerA, "Alpha Myr");
+ waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN);
+ activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{1}: Choose");
+ setChoice(playerA, "Alpha Myr");
+ checkPlayableAbility("Can cast on current turn", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Alpha Myr", true);
+ checkPlayableAbility("Can't cast on future turn", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Alpha Myr", false);
+
+ activateAbility(3, PhaseStep.BEGIN_COMBAT, playerA, "{1}: Choose");
+ setChoice(playerA, "Alpha Myr");
+ castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Alpha Myr");
+
+ setStrictChooseMode(true);
+ setStopAt(3, PhaseStep.END_TURN);
+ execute();
+ assertGraveyardCount(playerA, 1); // Void Snare
+ assertGraveyardCount(playerB, 0);
+ assertExileCount(playerA, 0);
+ assertExileCount(playerB, 1); // Corridor Monitor remains in exile
+ assertPermanentCount(playerA, "Island", 1);
+ assertPermanentCount(playerA, "Squire", 1);
+ assertPermanentCount(playerA, "Alpha Myr", 1);
+ }
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java
new file mode 100644
index 00000000000..e425d65dc53
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java
@@ -0,0 +1,70 @@
+package org.mage.test.cards.single.afc;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Test;
+import org.mage.test.player.TestPlayer;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ * {@link mage.cards.m.MantleOfTheAncients Mantle of the Ancients}
+ * {3}{W}{W}
+ * Enchantment — Aura
+ * Enchant creature you control
+ * When this Aura enters, return any number of target Aura and/or Equipment cards from your graveyard to the battlefield attached to enchanted creature.
+ * Enchanted creature gets +1/+1 for each Aura and Equipment attached to it.
+ *
+ * @author notgreat
+ */
+public class MantleOfTheAncientsTest extends CardTestPlayerBase {
+
+ /**
+ * Ensure that cards that can't be attached are not returned, and that cards that can be are correctly attached
+ */
+ @Test
+ public void testCardReturnsCorrectAttachments() {
+ String creature = "Skylasher";// Protection from Blue, 2/2 Creature and +1/+1 from first Mantle
+
+ addCard(Zone.HAND, playerA, "Mantle of the Ancients", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 10);
+ addCard(Zone.BATTLEFIELD, playerA, creature);
+ addCard(Zone.BATTLEFIELD, playerA, "Grim Guardian"); // Counts number of enchantments entering
+
+ addCard(Zone.GRAVEYARD, playerA, "Konda's Banner"); // No attach, Not legendary, but is returned
+ addCard(Zone.GRAVEYARD, playerA, "O-Naginata"); // Yes attach, Pow >= 3
+
+ addCard(Zone.GRAVEYARD, playerA, "Aether Tunnel"); // No attach, Pro Blue, then Yes attach on 2nd try
+ addCard(Zone.GRAVEYARD, playerA, "Reprobation"); // Yes attach, Enchant Creature and removes Pro Blue ability
+ addCard(Zone.GRAVEYARD, playerA, "Indestructibility"); // Yes attach, Enchant Permanent
+ addCard(Zone.GRAVEYARD, playerA, "Abundant Growth"); // No attach, Enchant Land
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mantle of the Ancients", creature);
+ setChoice(playerA, "Constellation"); //Stack trigger, Mantle + Grim
+ addTarget(playerA, "Konda's Banner^O-Naginata^Aether Tunnel^Reprobation^Indestructibility^Abundant Growth");
+ setChoice(playerA, "Constellation"); //Stack trigger, Grim x2
+ checkPermanentCount("Gate Smasher not returned", 1, PhaseStep.BEGIN_COMBAT, playerA, "Gate Smasher",0);
+ checkPermanentCount("Aether Tunnel not returned", 1, PhaseStep.BEGIN_COMBAT, playerA, "Aether Tunnel",0);
+
+ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Mantle of the Ancients", creature);
+ setChoice(playerA, "Constellation"); //Stack trigger, Mantle -> Grim
+ addTarget(playerA, "Gate Smasher^Aether Tunnel");
+ addTarget(playerA, TestPlayer.TARGET_SKIP);
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertAttachedTo(playerA, "Konda's Banner", creature, false);
+ assertPermanentCount(playerA, "Konda's Banner", 1);
+ assertAttachedTo(playerA, "O-Naginata", creature, true);
+
+ assertAttachedTo(playerA, "Aether Tunnel", creature, true);
+ assertAttachedTo(playerA, "Reprobation", creature, true);
+ assertAttachedTo(playerA, "Indestructibility", creature, true);
+ assertPermanentCount(playerA, "Abundant Growth", 0);
+
+ assertPermanentCount(playerA, "Mantle of the Ancients", 2);
+ assertPowerToughness(playerA, creature, 16, 13); // base 0/1, 6 attachments so +12/+12, O-Naginata plus Aether Tunnel +4/+0
+ assertLife(playerB, 15); // Mantle, Reprobation, Indestructibility + Mantle, Aether Tunnel
+ }
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/SoulSeizerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/SoulSeizerTest.java
index 1b904c4f136..7219846c3c3 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/SoulSeizerTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/SoulSeizerTest.java
@@ -19,7 +19,11 @@ public class SoulSeizerTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Craw Wurm");
attack(1, playerA, "Soul Seizer");
+ addTarget(playerA, "Craw Wurm");
+ setChoice(playerA, true);
+
setStopAt(1, PhaseStep.END_COMBAT);
+ setStrictChooseMode(true);
execute();
assertLife(playerA, 20);
@@ -38,8 +42,13 @@ public class SoulSeizerTest extends CardTestPlayerBase {
attack(1, playerA, "Soul Seizer");
+ addTarget(playerA, "Craw Wurm");
+ setChoice(playerA, true);
+
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Clear", "Ghastly Haunting");
+
setStopAt(2, PhaseStep.BEGIN_COMBAT);
+ setStrictChooseMode(true);
execute();
assertLife(playerA, 20);
@@ -59,7 +68,11 @@ public class SoulSeizerTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Battlegrowth", "Soul Seizer");
attack(1, playerA, "Soul Seizer");
+ addTarget(playerA, "Craw Wurm");
+ setChoice(playerA, true);
+
setStopAt(1, PhaseStep.END_COMBAT);
+ setStrictChooseMode(true);
execute();
assertLife(playerA, 20);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/UnfinishedBusinessTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/UnfinishedBusinessTest.java
index 0a6684a6bd3..31a630235e8 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/UnfinishedBusinessTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/UnfinishedBusinessTest.java
@@ -92,6 +92,7 @@ public class UnfinishedBusinessTest extends CardTestPlayerBase {
// EEB should never have been attached and therefore the White knight should be untapped
assertTapped(APOSTLE,false);
assertAttachedTo(playerA, EEB, APOSTLE,false);
+ assertPermanentCount(playerA, EEB, 1);
// Check that Ghoulflesh never entered the battlefield
assertLife(playerA, 20);
diff --git a/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java b/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java
index fac6a4a26b6..fc11c56c0e7 100644
--- a/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java
+++ b/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java
@@ -4,35 +4,30 @@ import mage.abilities.Ability;
import mage.abilities.effects.common.InfoEffect;
import mage.constants.Zone;
import mage.game.Game;
-import mage.game.permanent.Permanent;
import mage.target.Target;
+import java.util.UUID;
+
/**
*
* @author LevelX2
*/
public class AttachableToRestrictedAbility extends SimpleStaticAbility {
-
+ private final Target attachable;
public AttachableToRestrictedAbility(Target target) {
super(Zone.BATTLEFIELD, new InfoEffect("{this} can be attached only to a " + target.getTargetName()));
- addTarget(target);
+ this.attachable = target.copy();
}
private AttachableToRestrictedAbility(AttachableToRestrictedAbility ability) {
super(ability);
+ this.attachable = ability.attachable; // Since we never modify the target, we don't need to re-copy it
}
- public boolean canEquip(Permanent toEquip, Ability source, Game game) {
- for (Target target : getTargets()) {
- if (source == null) {
- if (!target.canTarget(toEquip.getId(), game)) {
- return false;
- }
- } else if (!target.canTarget(toEquip.getId(), source, game)) {
- return false;
- }
- }
- return true;
+ public boolean canEquip(UUID toEquip, Ability source, Game game) {
+ if (source == null) {
+ return attachable.canTarget(toEquip, game);
+ } else return attachable.canTarget(toEquip, source, game);
}
@Override
diff --git a/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java b/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java
index 7c068c514b4..7108aa802e1 100644
--- a/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java
@@ -6,12 +6,12 @@ import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.effects.common.UntapTargetEffect;
import mage.constants.Duration;
-import mage.filter.common.FilterCreaturePermanent;
-import mage.filter.predicate.permanent.ControllerIdPredicate;
+import mage.constants.SetTargetPointer;
+import mage.filter.StaticFilters;
import mage.game.Game;
-import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
+import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster;
import java.util.UUID;
@@ -31,27 +31,16 @@ public class ProvokeAbility extends AttacksTriggeredAbility {
}
public ProvokeAbility(String text) {
- super(new UntapTargetEffect(), true, text);
+ super(new UntapTargetEffect(), true, text, SetTargetPointer.PLAYER);
this.addEffect(new ProvokeRequirementEffect());
+ this.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE));
+ this.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster());
}
protected ProvokeAbility(final ProvokeAbility ability) {
super(ability);
}
- @Override
- public boolean checkTrigger(GameEvent event, Game game) {
- if (super.checkTrigger(event, game)) {
- FilterCreaturePermanent filter = new FilterCreaturePermanent("creature defending player controls");
- UUID defendingPlayerId = game.getCombat().getDefendingPlayerId(sourceId, game);
- filter.add(new ControllerIdPredicate(defendingPlayerId));
- this.getTargets().clear();
- this.addTarget(new TargetPermanent(filter));
- return true;
- }
- return false;
- }
-
@Override
public ProvokeAbility copy() {
return new ProvokeAbility(this);
diff --git a/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java b/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java
index c1b08e6947e..6a4eb980ccd 100644
--- a/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java
@@ -2,19 +2,15 @@
package mage.abilities.keyword;
-import mage.constants.ComparisonType;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.common.ReturnToHandTargetEffect;
+import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.filter.FilterCard;
-import mage.filter.predicate.mageobject.ManaValuePredicate;
-import mage.game.Game;
-import mage.game.events.GameEvent;
import mage.target.common.TargetCardInYourGraveyard;
-
-import java.util.UUID;
+import mage.target.targetadjustment.ManaValueTargetAdjuster;
/**
* 702.45. Soulshift
@@ -38,6 +34,10 @@ public class SoulshiftAbility extends DiesSourceTriggeredAbility {
public SoulshiftAbility(DynamicValue amount) {
super(new ReturnToHandTargetEffect());
this.amount = amount;
+ FilterCard filter = new FilterCard("Spirit card from your graveyard");
+ filter.add(SubType.SPIRIT.getPredicate());
+ this.addTarget(new TargetCardInYourGraveyard(filter));
+ this.setTargetAdjuster(new ManaValueTargetAdjuster(amount, ComparisonType.OR_LESS));
}
protected SoulshiftAbility(final SoulshiftAbility ability) {
@@ -45,17 +45,6 @@ public class SoulshiftAbility extends DiesSourceTriggeredAbility {
this.amount = ability.amount;
}
- @Override
- public void trigger(Game game, UUID controllerId, GameEvent triggeringEvent) {
- this.getTargets().clear();
- int intValue = amount.calculate(game, this, null);
- FilterCard filter = new FilterCard("Spirit card with mana value " + intValue + " or less from your graveyard");
- filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, intValue + 1));
- filter.add(SubType.SPIRIT.getPredicate());
- this.addTarget(new TargetCardInYourGraveyard(filter));
- super.trigger(game, controllerId, triggeringEvent);
- }
-
@Override
public SoulshiftAbility copy() {
return new SoulshiftAbility(this);
diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java
index 2edbf43c918..90acef7ccce 100644
--- a/Mage/src/main/java/mage/cards/CardImpl.java
+++ b/Mage/src/main/java/mage/cards/CardImpl.java
@@ -918,27 +918,41 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
@Override
public boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode) {
+ boolean canAttach = true;
for (ProtectionAbility ability : this.getAbilities(game).getProtectionAbilities()) {
if ((!attachment.hasSubtype(SubType.AURA, game) || ability.removesAuras())
&& (!attachment.hasSubtype(SubType.EQUIPMENT, game) || ability.removesEquipment())
&& !attachment.getId().equals(ability.getAuraIdNotToBeRemoved())
&& !ability.canTarget(attachment, game)) {
- return !ability.getDoesntRemoveControlled() || Objects.equals(getControllerOrOwnerId(), game.getControllerId(attachment.getId()));
+ canAttach &= ability.getDoesntRemoveControlled() && Objects.equals(getControllerOrOwnerId(), game.getControllerId(attachment.getId()));
}
}
- boolean canAttach = true;
- Permanent attachmentPermanent = game.getPermanent(attachment.getId());
// If attachment is an aura, ensures this permanent can still be legally enchanted, according to the enchantment's Enchant ability
- if (attachment.hasSubtype(SubType.AURA, game)
- && attachmentPermanent != null
- && attachmentPermanent.getSpellAbility() != null
- && !attachmentPermanent.getSpellAbility().getTargets().isEmpty()) {
- // Line of code below functionally gets the target of the aura's Enchant ability, then compares to this permanent. Enchant improperly implemented in XMage, see #9583
- // Note: stillLegalTarget used exclusively to account for Dream Leash. Can be made canTarget in the event that that card is rewritten (and "stillLegalTarget" removed from TargetImpl).
- canAttach = attachmentPermanent.getSpellAbility().getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(attachmentPermanent.getControllerId(), this.getId(), source, game);
+ if (attachment.hasSubtype(SubType.AURA, game)) {
+ SpellAbility spellAbility = null;
+ UUID controller = null;
+ Permanent attachmentPermanent = game.getPermanent(attachment.getId());
+ if (attachmentPermanent != null) {
+ spellAbility = attachmentPermanent.getSpellAbility(); // Permanent's SpellAbility might be modified, so if possible use that one
+ controller = attachmentPermanent.getControllerId();
+ } else { // Used for checking if it can be attached from the graveyard, such as Unfinished Business
+ Card attachmentCard = game.getCard(attachment.getId());
+ if (attachmentCard != null) {
+ spellAbility = attachmentCard.getSpellAbility();
+ if (source != null) {
+ controller = source.getControllerId();
+ } else {
+ controller = attachmentCard.getControllerOrOwnerId();
+ }
+ }
+ }
+ if (controller != null && spellAbility != null && !spellAbility.getTargets().isEmpty()){
+ // Line of code below functionally gets the target of the aura's Enchant ability, then compares to this permanent. Enchant improperly implemented in XMage, see #9583
+ // Note: stillLegalTarget used exclusively to account for Dream Leash. Can be made canTarget in the event that that card is rewritten (and "stillLegalTarget" removed from TargetImpl).
+ canAttach &= spellAbility.getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(controller, this.getId(), source, game);
+ }
}
-
return !canAttach || game.getContinuousEffects().preventedByRuleModification(new StayAttachedEvent(this.getId(), attachment.getId(), source), null, game, silentMode);
}
diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java
index 547ebaed81a..dea911f2ace 100644
--- a/Mage/src/main/java/mage/game/GameImpl.java
+++ b/Mage/src/main/java/mage/game/GameImpl.java
@@ -2803,7 +2803,7 @@ public abstract class GameImpl implements Game {
if (attachedTo != null) {
for (Ability ability : perm.getAbilities(this)) {
if (ability instanceof AttachableToRestrictedAbility) {
- if (!((AttachableToRestrictedAbility) ability).canEquip(attachedTo, null, this)) {
+ if (!((AttachableToRestrictedAbility) ability).canEquip(attachedTo.getId(), null, this)) {
attachedTo = null;
break;
}