diff --git a/Mage.Sets/src/mage/cards/t/TalesEnd.java b/Mage.Sets/src/mage/cards/t/TalesEnd.java new file mode 100644 index 000000000000..1188d08c4b97 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TalesEnd.java @@ -0,0 +1,35 @@ +package mage.cards.t; + +import java.util.UUID; + +import mage.abilities.effects.common.CounterTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetActivatedOrTriggeredAbilityOrLegendarySpell; + +/** + * + * @author rscoates + */ +public final class TalesEnd extends CardImpl { + + public TalesEnd(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + + // Counter target activated ability, triggered ability, or legendary spell. + // Counter target activated or triggered ability. + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addTarget(new TargetActivatedOrTriggeredAbilityOrLegendarySpell()); + } + + private TalesEnd(final TalesEnd card) { + super(card); + } + + @Override + public TalesEnd copy() { + return new TalesEnd(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2020.java b/Mage.Sets/src/mage/sets/CoreSet2020.java index 49580ad51d30..39d500e61d5d 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2020.java +++ b/Mage.Sets/src/mage/sets/CoreSet2020.java @@ -297,6 +297,7 @@ private CoreSet2020() { cards.add(new SetCardInfo("Swamp", 271, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 272, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swiftwater Cliffs", 252, Rarity.COMMON, mage.cards.s.SwiftwaterCliffs.class)); + cards.add(new SetCardInfo("Tale's End", 77, Rarity.RARE, mage.cards.t.TalesEnd.class)); cards.add(new SetCardInfo("Tectonic Rift", 161, Rarity.COMMON, mage.cards.t.TectonicRift.class)); cards.add(new SetCardInfo("Temple of Epiphany", 253, Rarity.RARE, mage.cards.t.TempleOfEpiphany.class)); cards.add(new SetCardInfo("Temple of Malady", 254, Rarity.RARE, mage.cards.t.TempleOfMalady.class)); diff --git a/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbilityOrLegendarySpell.java b/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbilityOrLegendarySpell.java new file mode 100644 index 000000000000..f8e04dd2f223 --- /dev/null +++ b/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbilityOrLegendarySpell.java @@ -0,0 +1,106 @@ + +package mage.target.common; + +import mage.abilities.Ability; +import mage.constants.AbilityType; +import mage.constants.Zone; +import mage.filter.Filter; +import mage.filter.FilterStackObject; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.target.TargetObject; + +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +public class TargetActivatedOrTriggeredAbilityOrLegendarySpell extends TargetObject { + + protected final FilterStackObject filter; + + public TargetActivatedOrTriggeredAbilityOrLegendarySpell() { + this(new FilterStackObject("activated ability, triggered ability, or legendary spell")); + } + + public TargetActivatedOrTriggeredAbilityOrLegendarySpell(FilterStackObject filter) { + this.minNumberOfTargets = 1; + this.maxNumberOfTargets = 1; + this.zone = Zone.STACK; + this.targetName = filter.getMessage(); + this.filter = filter; + } + + public TargetActivatedOrTriggeredAbilityOrLegendarySpell(final TargetActivatedOrTriggeredAbilityOrLegendarySpell target) { + super(target); + this.filter = target.filter.copy(); + } + + @Override + public boolean canTarget(UUID id, Ability source, Game game) { + // rule 114.4. A spell or ability on the stack is an illegal target for itself. + if (source != null && source.getId().equals(id)) { + return false; + } + + StackObject stackObject = game.getStack().getStackObject(id); + return isActivatedOrTriggeredAbilityOrLegendarySpell(stackObject) && source != null && filter.match(stackObject, source.getSourceId(), source.getControllerId(), game); + } + + @Override + public boolean canChoose(UUID sourceId, UUID sourceControllerId, Game game) { + for (StackObject stackObject : game.getStack()) { + if (isActivatedOrTriggeredAbilityOrLegendarySpell(stackObject) + && filter.match(stackObject, sourceId, sourceControllerId, game)) { + return true; + } + } + return false; + } + + @Override + public boolean canChoose(UUID sourceControllerId, Game game) { + return game.getStack() + .stream() + .anyMatch(TargetActivatedOrTriggeredAbilityOrLegendarySpell::isActivatedOrTriggeredAbilityOrLegendarySpell); + } + + @Override + public Set possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) { + return possibleTargets(sourceControllerId, game); + } + + @Override + public Set possibleTargets(UUID sourceControllerId, Game game) { + return game.getStack().stream() + .filter(TargetActivatedOrTriggeredAbilityOrLegendarySpell::isActivatedOrTriggeredAbilityOrLegendarySpell) + .map(stackObject -> stackObject.getStackAbility().getId()) + .collect(Collectors.toSet()); + } + + @Override + public TargetActivatedOrTriggeredAbilityOrLegendarySpell copy() { + return new TargetActivatedOrTriggeredAbilityOrLegendarySpell(this); + } + + @Override + public Filter getFilter() { + return filter; + } + + static boolean isActivatedOrTriggeredAbilityOrLegendarySpell(StackObject stackObject) { + if (stackObject == null) { + return false; + } + if (stackObject instanceof Ability) { + Ability ability = (Ability) stackObject; + return ability.getAbilityType() == AbilityType.TRIGGERED + || ability.getAbilityType() == AbilityType.ACTIVATED; + } + if (stackObject instanceof Spell) { + Spell spell = (Spell) stackObject; + return spell.isLegendary(); + } + return false; + } +}