diff --git a/Mage.Sets/src/mage/cards/n/NimbleObstructionist.java b/Mage.Sets/src/mage/cards/n/NimbleObstructionist.java new file mode 100644 index 00000000000..6057b41bde9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NimbleObstructionist.java @@ -0,0 +1,89 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.n; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CycleTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.keyword.CyclingAbility; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.FilterStackObject; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.target.common.TargetActivatedOrTriggeredAbility; + +/** + * + * @author caldover + */ +public class NimbleObstructionist extends CardImpl { + + private static final FilterStackObject filter = new FilterStackObject("activated or triggered ability you don't control"); + static { + filter.add(new ControllerPredicate(TargetController.NOT_YOU)); + } + + public NimbleObstructionist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add("Bird"); + this.subtype.add("Wizard"); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Cycling {2}{U} + this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}{U}"))); + + // When you cycle Nimble Obstructionist, counter target activated or triggered ability you don't control. + Ability ability = new CycleTriggeredAbility(new CounterTargetEffect()); + ability.addTarget(new TargetActivatedOrTriggeredAbility(filter)); + this.addAbility(ability); + } + + public NimbleObstructionist(final NimbleObstructionist card) { + super(card); + } + + @Override + public NimbleObstructionist copy() { + return new NimbleObstructionist(this); + } +} diff --git a/Mage.Sets/src/mage/sets/HourOfDevastation.java b/Mage.Sets/src/mage/sets/HourOfDevastation.java index f3769997699..d888e7ee32b 100644 --- a/Mage.Sets/src/mage/sets/HourOfDevastation.java +++ b/Mage.Sets/src/mage/sets/HourOfDevastation.java @@ -170,6 +170,7 @@ public class HourOfDevastation extends ExpansionSet { cards.add(new SetCardInfo("Neheb, the Eternal", 104, Rarity.MYTHIC, mage.cards.n.NehebTheEternal.class)); cards.add(new SetCardInfo("Nicol Bolas, God-Pharaoh", 140, Rarity.MYTHIC, mage.cards.n.NicolBolasGodPharaoh.class)); cards.add(new SetCardInfo("Nicol Bolas, the Deceiver", 205, Rarity.MYTHIC, mage.cards.n.NicolBolasTheDeceiver.class)); + cards.add(new SetCardInfo("Nimble Obstructionist", 40, Rarity.RARE, mage.cards.n.NimbleObstructionist.class)); cards.add(new SetCardInfo("Nissa's Defeat", 123, Rarity.UNCOMMON, mage.cards.n.NissasDefeat.class)); cards.add(new SetCardInfo("Nissa's Encouragement", 203, Rarity.RARE, mage.cards.n.NissasEncouragement.class)); cards.add(new SetCardInfo("Nissa, Genesis Mage", 200, Rarity.MYTHIC, mage.cards.n.NissaGenesisMage.class)); diff --git a/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbility.java b/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbility.java index 9b472017ed3..1a35f40c37f 100644 --- a/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbility.java +++ b/Mage/src/main/java/mage/target/common/TargetActivatedOrTriggeredAbility.java @@ -1,120 +1,124 @@ -/* -* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, are -* permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, this list of -* conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright notice, this list -* of conditions and the following disclaimer in the documentation and/or other materials -* provided with the distribution. -* -* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED -* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR -* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -* The views and conclusions contained in the software and documentation are those of the -* authors and should not be interpreted as representing official policies, either expressed -* or implied, of BetaSteward_at_googlemail.com. -*/ - -package mage.target.common; - -import mage.abilities.Ability; -import mage.constants.AbilityType; -import mage.constants.Zone; -import mage.filter.Filter; -import mage.filter.FilterAbility; -import mage.game.Game; -import mage.game.stack.StackObject; -import mage.target.TargetObject; - -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * @author LevelX2 - */ - - -public class TargetActivatedOrTriggeredAbility extends TargetObject { - - public TargetActivatedOrTriggeredAbility() { - this.minNumberOfTargets = 1; - this.maxNumberOfTargets = 1; - this.zone = Zone.STACK; - this.targetName = "target activated or triggered ability"; - } - - public TargetActivatedOrTriggeredAbility(final TargetActivatedOrTriggeredAbility target) { - super(target); - } - - - @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); - } - - @Override - public boolean canChoose(UUID sourceId, UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, game); - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return game.getStack() - .stream() - .anyMatch(TargetActivatedOrTriggeredAbility::isActivatedOrTriggeredAbility); - } - - @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(TargetActivatedOrTriggeredAbility::isActivatedOrTriggeredAbility) - .map(stackObject -> stackObject.getStackAbility().getId()) - .collect(Collectors.toSet()); - } - - @Override - public TargetActivatedOrTriggeredAbility copy() { - return new TargetActivatedOrTriggeredAbility(this); - } - - @Override - public Filter getFilter() { - return new FilterAbility(); - } - - 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; - } -} +/* +* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this list of +* conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this list +* of conditions and the following disclaimer in the documentation and/or other materials +* provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The views and conclusions contained in the software and documentation are those of the +* authors and should not be interpreted as representing official policies, either expressed +* or implied, of BetaSteward_at_googlemail.com. +*/ + +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; + + + +public class TargetActivatedOrTriggeredAbility extends TargetObject { + + protected final FilterStackObject filter; + + public TargetActivatedOrTriggeredAbility() { + this(new FilterStackObject()); + } + + public TargetActivatedOrTriggeredAbility(FilterStackObject filter) { + this.minNumberOfTargets = 1; + this.maxNumberOfTargets = 1; + this.zone = Zone.STACK; + this.targetName = filter.getMessage(); + this.filter = filter; + } + + public 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) && filter.match(stackObject, source.getSourceId(), source.getControllerId(), game); + } + + @Override + public boolean canChoose(UUID sourceId, UUID sourceControllerId, Game game) { + return canChoose(sourceControllerId, game); + } + + @Override + public boolean canChoose(UUID sourceControllerId, Game game) { + return game.getStack() + .stream() + .anyMatch(TargetActivatedOrTriggeredAbility::isActivatedOrTriggeredAbility); + } + + @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(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; + } +}