diff --git a/Mage.Sets/src/mage/cards/t/TishanasTidebinder.java b/Mage.Sets/src/mage/cards/t/TishanasTidebinder.java new file mode 100644 index 00000000000..2703e71ecbf --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TishanasTidebinder.java @@ -0,0 +1,90 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.LoseAllAbilitiesTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.StackObject; +import mage.target.common.TargetActivatedOrTriggeredAbility; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TishanasTidebinder extends CardImpl { + + public TishanasTidebinder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.MERFOLK); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When Tishana's Tidebinder enters the battlefield, counter up to one target activated or triggered ability. If an ability of an artifact, creature, or planeswalker is countered this way, that permanent loses all abilities for as long as Tishana's Tidebinder remains on the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new TishanasTidebinderEffect()); + ability.addTarget(new TargetActivatedOrTriggeredAbility(0, 1)); + this.addAbility(ability); + } + + private TishanasTidebinder(final TishanasTidebinder card) { + super(card); + } + + @Override + public TishanasTidebinder copy() { + return new TishanasTidebinder(this); + } +} + +class TishanasTidebinderEffect extends OneShotEffect { + + TishanasTidebinderEffect() { + super(Outcome.Benefit); + staticText = "counter up to one target activated or triggered ability. If an ability of an " + + "artifact, creature, or planeswalker is countered this way, that permanent loses " + + "all abilities for as long as {this} remains on the battlefield"; + } + + private TishanasTidebinderEffect(final TishanasTidebinderEffect effect) { + super(effect); + } + + @Override + public TishanasTidebinderEffect copy() { + return new TishanasTidebinderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + StackObject stackObject = game.getStack().getStackObject(getTargetPointer().getFirst(game, source)); + if (stackObject == null) { + return false; + } + Permanent permanent = game.getPermanent(stackObject.getSourceId()); + game.getStack().counter(stackObject.getId(), source, game); + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null || sourcePermanent == null + || !permanent.isArtifact(game) && !permanent.isCreature(game) && !permanent.isPlaneswalker(game)) { + return true; + } + game.addEffect(new LoseAllAbilitiesTargetEffect(Duration.UntilSourceLeavesBattlefield) + .setTargetPointer(new FixedTarget(permanent, game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java index 7e0e8dd9db6..6dd20658aa0 100644 --- a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java +++ b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java @@ -323,6 +323,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("Threefold Thunderhulk", 265, Rarity.RARE, mage.cards.t.ThreefoldThunderhulk.class)); cards.add(new SetCardInfo("Throne of the Grim Captain", 266, Rarity.RARE, mage.cards.t.ThroneOfTheGrimCaptain.class)); cards.add(new SetCardInfo("Tinker's Tote", 40, Rarity.COMMON, mage.cards.t.TinkersTote.class)); + cards.add(new SetCardInfo("Tishana's Tidebinder", 81, Rarity.RARE, mage.cards.t.TishanasTidebinder.class)); cards.add(new SetCardInfo("Tithing Blade", 128, Rarity.COMMON, mage.cards.t.TithingBlade.class)); cards.add(new SetCardInfo("Treasure Cove", 267, Rarity.RARE, mage.cards.t.TreasureCove.class)); cards.add(new SetCardInfo("Treasure Map", 267, Rarity.RARE, mage.cards.t.TreasureMap.class)); diff --git a/Mage/src/main/java/mage/filter/common/FilterActivatedOrTriggeredAbility.java b/Mage/src/main/java/mage/filter/common/FilterActivatedOrTriggeredAbility.java new file mode 100644 index 00000000000..af4071cbb68 --- /dev/null +++ b/Mage/src/main/java/mage/filter/common/FilterActivatedOrTriggeredAbility.java @@ -0,0 +1,47 @@ +package mage.filter.common; + +import mage.abilities.Ability; +import mage.constants.AbilityType; +import mage.filter.FilterStackObject; +import mage.game.Game; +import mage.game.stack.StackObject; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class FilterActivatedOrTriggeredAbility extends FilterStackObject { + + public FilterActivatedOrTriggeredAbility() { + this("activated or triggered ability"); + } + + public FilterActivatedOrTriggeredAbility(String name) { + super(name); + } + + protected FilterActivatedOrTriggeredAbility(final FilterActivatedOrTriggeredAbility filter) { + super(filter); + } + + @Override + public boolean match(StackObject stackObject, UUID playerId, Ability source, Game game) { + if (!super.match(stackObject, playerId, source, game) || !(stackObject instanceof Ability)) { + return false; + } + Ability ability = (Ability) stackObject; + return ability.getAbilityType() == AbilityType.TRIGGERED + || ability.getAbilityType() == AbilityType.ACTIVATED; + } + + @Override + public boolean match(StackObject stackObject, Game game) { + if (!super.match(stackObject, game) || !(stackObject instanceof Ability)) { + return false; + } + Ability ability = (Ability) stackObject; + return ability.getAbilityType() == AbilityType.TRIGGERED + || ability.getAbilityType() == AbilityType.ACTIVATED; + } +} diff --git a/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbility.java b/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbility.java index 1e993756401..e2da074615d 100644 --- a/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbility.java +++ b/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbility.java @@ -1,101 +1,30 @@ - package mage.target.common; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -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.StackObject; -import mage.target.TargetObject; +import mage.filter.common.FilterActivatedOrTriggeredAbility; +import mage.target.TargetStackObject; -public class TargetActivatedOrTriggeredAbility extends TargetObject { +public class TargetActivatedOrTriggeredAbility extends TargetStackObject { - protected final FilterStackObject filter; + private static final FilterStackObject defaultFilter = new FilterActivatedOrTriggeredAbility(); public TargetActivatedOrTriggeredAbility() { - this(new FilterStackObject("activated or triggered ability")); + this(1, 1); } public TargetActivatedOrTriggeredAbility(FilterStackObject filter) { - this.minNumberOfTargets = 1; - this.maxNumberOfTargets = 1; - this.zone = Zone.STACK; - this.targetName = filter.getMessage(); - this.filter = filter; + this(1, 1, filter); + } + + public TargetActivatedOrTriggeredAbility(int minNumTargets, int maxNumTargets) { + this(minNumTargets, maxNumTargets, defaultFilter); + } + + public TargetActivatedOrTriggeredAbility(int minNumTargets, int maxNumTargets, FilterStackObject filter) { + super(minNumTargets, maxNumTargets, filter); } protected TargetActivatedOrTriggeredAbility(final TargetActivatedOrTriggeredAbility 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 isActivatedOrTriggeredAbility(stackObject) && source != null && filter.match(stackObject, source.getControllerId(), source, game); - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - for (StackObject stackObject : game.getStack()) { - if (isActivatedOrTriggeredAbility(stackObject) - && filter.match(stackObject, sourceControllerId, source, game)) { - return true; - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return game.getStack() - .stream() - .anyMatch(TargetActivatedOrTriggeredAbility::isActivatedOrTriggeredAbility); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return possibleTargets(sourceControllerId, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return game.getStack().stream() - .filter(TargetActivatedOrTriggeredAbility::isActivatedOrTriggeredAbility) - .map(stackObject -> stackObject.getStackAbility().getId()) - .collect(Collectors.toSet()); - } - - @Override - public TargetActivatedOrTriggeredAbility copy() { - return new TargetActivatedOrTriggeredAbility(this); - } - - @Override - public Filter getFilter() { - return filter; - } - - static boolean isActivatedOrTriggeredAbility(StackObject stackObject) { - if (stackObject == null) { - return false; - } - if (stackObject instanceof Ability) { - Ability ability = (Ability) stackObject; - return ability.getAbilityType() == AbilityType.TRIGGERED - || ability.getAbilityType() == AbilityType.ACTIVATED; - } - return false; } }