refactor selecting cards from multiple zones (#11584)

* refactor selecting cards from multiple zones

fix MV <= controlled lands predicate

fix filter checks without source parameter

* fix test choice
This commit is contained in:
xenohedron 2023-12-31 14:25:25 -05:00 committed by GitHub
parent 8459babbcd
commit e6bd35eb77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 258 additions and 319 deletions

View file

@ -4,7 +4,6 @@ import mage.ApprovingObject;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.costs.Cost;
@ -74,8 +73,8 @@ class AnrakyrTheTravellerEffect extends OneShotEffect {
return false;
}
Set<Card> cards = player.getHand().getCards(filter, game);
cards.addAll(player.getGraveyard().getCards(filter, game));
Set<Card> cards = player.getHand().getCards(filter, source.getControllerId(), source, game);
cards.addAll(player.getGraveyard().getCards(filter, source.getControllerId(), source, game));
Map<UUID, List<Card>> cardMap = new HashMap<>();
for (Card card : cards) {
@ -103,11 +102,11 @@ class AnrakyrTheTravellerEffect extends OneShotEffect {
.stream()
.map(MageObject::getLogName)
.collect(Collectors.joining(" or "));
if (partsToCast.size() < 1
if (partsToCast.isEmpty()
|| !player.chooseUse(
Outcome.PlayForFree, "Cast spell by paying life equal to its mana value rather than paying its mana cost (" + partsInfo + ")?", source, game
)) {
return false;
return true;
}
partsToCast.forEach(card -> game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE));
@ -119,12 +118,12 @@ class AnrakyrTheTravellerEffect extends OneShotEffect {
newCosts.addAll(cardToCast.getSpellAbility().getCosts());
player.setCastSourceIdWithAlternateMana(cardToCast.getId(), null, newCosts);
ActivatedAbility chosenAbility;
SpellAbility chosenAbility;
chosenAbility = player.chooseAbilityForCast(cardToCast, game, true);
boolean result = false;
if (chosenAbility instanceof SpellAbility) {
if (chosenAbility != null) {
result = player.cast(
(SpellAbility) chosenAbility,
chosenAbility,
game, true, new ApprovingObject(source, game)
);
}
@ -139,4 +138,4 @@ class AnrakyrTheTravellerEffect extends OneShotEffect {
public AnrakyrTheTravellerEffect copy() {
return new AnrakyrTheTravellerEffect(this);
}
}
}

View file

@ -7,7 +7,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.predicate.card.CardManaCostLessThanControlledLandCountPredicate;
import mage.filter.predicate.card.ManaValueLessThanControlledLandCountPredicate;
import mage.target.common.TargetCardInLibrary;
/**
@ -18,7 +18,7 @@ public final class BeseechTheQueen extends CardImpl {
private static final FilterCard filter = new FilterCard("card with mana value less than or equal to the number of lands you control");
static {
filter.add(CardManaCostLessThanControlledLandCountPredicate.getInstance());
filter.add(ManaValueLessThanControlledLandCountPredicate.instance);
}
public BeseechTheQueen(UUID ownerId, CardSetInfo setInfo) {

View file

@ -5,7 +5,7 @@ import mage.abilities.LoyaltyAbility;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.dynamicvalue.common.LandsYouControlCount;
import mage.abilities.effects.common.ExileTargetEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.PutCardFromHandOrGraveyardOntoBattlefieldEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.effects.keyword.SurveilEffect;
import mage.abilities.hint.common.LandsYouControlHint;
@ -46,7 +46,7 @@ public final class DakkonShadowSlayer extends CardImpl {
this.addAbility(ability);
// 6: You may put an artifact card from your hand or graveyard onto the battlefield.
this.addAbility(new LoyaltyAbility(new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(StaticFilters.FILTER_CARD_ARTIFACT), -6));
this.addAbility(new LoyaltyAbility(new PutCardFromHandOrGraveyardOntoBattlefieldEffect(StaticFilters.FILTER_CARD_ARTIFACT, false), -6));
}
private DakkonShadowSlayer(final DakkonShadowSlayer card) {

View file

@ -59,9 +59,9 @@ public final class DanithaBenaliasHope extends CardImpl {
class DanithaBenaliasHopeEffect extends OneShotEffect {
public DanithaBenaliasHopeEffect() {
DanithaBenaliasHopeEffect() {
super(Outcome.PutCardInPlay);
this.staticText = "put an Aura or Equipment card from your hand or graveyard onto the battlefield attached to Danitha";
this.staticText = "you may put an Aura or Equipment card from your hand or graveyard onto the battlefield attached to {this}";
}
private DanithaBenaliasHopeEffect(final DanithaBenaliasHopeEffect effect) {
@ -88,9 +88,12 @@ class DanithaBenaliasHopeEffect extends OneShotEffect {
SubType.EQUIPMENT.getPredicate()
));
Cards cards = new CardsImpl();
cards.addAllCards(controller.getHand().getCards(filter, game));
cards.addAllCards(controller.getGraveyard().getCards(filter, game));
TargetCard target = new TargetCard(Zone.ALL, filter);
cards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game));
cards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game));
if (cards.isEmpty()) {
return true;
}
TargetCard target = new TargetCard(0, 1, Zone.ALL, filter);
target.withNotTarget(true);
target.withChooseHint("to attach to " + sourcePermanentName);
controller.choose(outcome, cards, target, source, game);

View file

@ -1,6 +1,5 @@
package mage.cards.f;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
@ -11,9 +10,7 @@ import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.filter.predicate.card.ManaValueLessThanControlledLandCountPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.common.CastSpellLastTurnWatcher;
@ -29,7 +26,7 @@ public final class FiresOfInvention extends CardImpl {
= new FilterCard("spells with mana value less than or equal to the number of lands you control");
static {
filter.add(FiresOfInventionPredicate.instance);
filter.add(ManaValueLessThanControlledLandCountPredicate.instance);
}
public FiresOfInvention(UUID ownerId, CardSetInfo setInfo) {
@ -52,16 +49,6 @@ public final class FiresOfInvention extends CardImpl {
}
}
enum FiresOfInventionPredicate implements ObjectSourcePlayerPredicate<MageObject> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) {
return input.getObject().getManaValue() <=
game.getBattlefield().countAll(StaticFilters.FILTER_LAND, game.getControllerId(input.getSourceId()), game);
}
}
class FiresOfInventionCastEffect extends ContinuousRuleModifyingEffectImpl {
FiresOfInventionCastEffect() {
@ -96,4 +83,4 @@ class FiresOfInventionCastEffect extends ContinuousRuleModifyingEffectImpl {
|| !game.getActivePlayerId().equals(source.getControllerId());
}
}
}

View file

@ -78,8 +78,11 @@ class LiberatedLivestockEffect extends OneShotEffect {
filter.add(SubType.AURA.getPredicate());
filter.add(new AuraCardCanAttachToPermanentId(tokenPermanent.getId()));
Cards auraCards = new CardsImpl();
auraCards.addAllCards(controller.getHand().getCards(filter, game));
auraCards.addAllCards(controller.getGraveyard().getCards(filter, game));
auraCards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game));
auraCards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game));
if (auraCards.isEmpty()) {
continue;
}
TargetCard target = new TargetCard(0, 1, Zone.ALL, filter);
target.withNotTarget(true);
controller.chooseTarget(outcome, auraCards, target, source, game);

View file

@ -8,7 +8,7 @@ import mage.abilities.common.CanBeYourCommanderAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.PutCardFromHandOrGraveyardOntoBattlefieldEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -32,7 +32,7 @@ import mage.target.common.TargetControlledPermanent;
*/
public final class NahiriTheLithomancer extends CardImpl {
private static final FilterCard filter = new FilterCard("an Equipment card");
private static final FilterCard filter = new FilterCard("Equipment card");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
@ -49,7 +49,7 @@ public final class NahiriTheLithomancer extends CardImpl {
this.addAbility(new LoyaltyAbility(new NahiriTheLithomancerFirstAbilityEffect(), 2));
// -2: You may put an Equipment card from your hand or graveyard onto the battlefield.
this.addAbility(new LoyaltyAbility(new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(filter), -2));
this.addAbility(new LoyaltyAbility(new PutCardFromHandOrGraveyardOntoBattlefieldEffect(filter, false), -2));
// -10: Create a colorless Equipment artifact token named Stoneforged Blade. It has indestructible, "Equipped creature gets +5/+5 and has double strike," and equip {0}.
Effect effect = new CreateTokenEffect(new NahiriTheLithomancerEquipmentToken());

View file

@ -1,23 +1,16 @@
package mage.cards.n;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.common.DiesAttachedTriggeredAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.ReturnToBattlefieldAttachedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.*;
import mage.constants.*;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.card.CastFromZonePredicate;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -25,9 +18,9 @@ import mage.players.Player;
import mage.target.TargetCard;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author Alex-Vasile
@ -62,7 +55,7 @@ public class NextOfKin extends CardImpl {
class NextOfKinDiesEffect extends OneShotEffect {
NextOfKinDiesEffect() {
super(Outcome.Benefit);
super(Outcome.PutCardInPlay);
this.staticText = "you may put a creature card you own with lesser mana value from your hand or from the command zone onto the battlefield. " +
"If you do, return {this} to the battlefield attached to that creature at the beginning of the next end step.";
}
@ -81,21 +74,28 @@ class NextOfKinDiesEffect extends OneShotEffect {
}
int manaValue = ((Permanent) object).getManaValue();
FilterCreatureCard filterCreatureCard = new FilterCreatureCard("a creature card you own with lesser mana value from your hand or from the command zone");
filterCreatureCard.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, manaValue));
// This effect is used only to get the info about which card was added.
Effect hackTargetEffect = new InfoEffect("");
Effect putCardEffect = new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(filterCreatureCard, false, hackTargetEffect, Zone.HAND, Zone.COMMAND);
boolean cardPut = putCardEffect.apply(game, source);
if (!cardPut) {
return false;
FilterCreatureCard filter = new FilterCreatureCard("a creature card you own with lesser mana value");
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, manaValue));
Cards cards = new CardsImpl();
cards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game));
for (Card possibleCard : game.getCommanderCardsFromCommandZone(controller, CommanderCardType.ANY)) {
if (filter.match(possibleCard, source.getControllerId(), source, game)) {
cards.add(possibleCard);
}
}
if (cards.isEmpty()) {
return true;
}
TargetCard target = new TargetCard(0, 1, Zone.ALL, filter);
target.withNotTarget(true);
controller.choose(outcome, cards, target, source, game);
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game);
Effect returnToBattlefieldAttachedEffect = new ReturnToBattlefieldAttachedEffect();
returnToBattlefieldAttachedEffect.setTargetPointer(new FixedTarget(card, game));
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(returnToBattlefieldAttachedEffect), source);
}
Effect returnToBattlefieldAttachedEffect = new ReturnToBattlefieldAttachedEffect();
returnToBattlefieldAttachedEffect.setTargetPointer(hackTargetEffect.getTargetPointer());
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(returnToBattlefieldAttachedEffect), source);
return true;
}

View file

@ -3,25 +3,23 @@ package mage.cards.n;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.common.LandfallAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.UntapTargetEffect;
import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.HasteAbility;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.*;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.FilterCard;
import mage.counters.Counters;
import mage.filter.StaticFilters;
import mage.filter.predicate.card.CardManaCostLessThanControlledLandCountPredicate;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.card.ManaValueLessThanControlledLandCountPredicate;
import mage.game.Game;
import mage.game.permanent.token.custom.CreatureToken;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.TargetPermanent;
import java.util.UUID;
@ -31,11 +29,6 @@ import java.util.UUID;
*/
public final class NissaOfShadowedBoughs extends CardImpl {
private static final FilterCard filter = new FilterCard("card with mana value less than or equal to the number of lands you control");
static {
filter.add(CardManaCostLessThanControlledLandCountPredicate.getInstance());
}
public NissaOfShadowedBoughs(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{B}{G}");
@ -53,12 +46,7 @@ public final class NissaOfShadowedBoughs extends CardImpl {
this.addAbility(ability);
// 5: You may put a creature card with mana value less than or equal to the number of lands you control onto the battlefield from your hand or graveyard with two +1/+1 counters on it.
Effect putCardEffect = new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(filter, false, new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)));
putCardEffect.setText("You may put a creature card with mana value less than or equal to " +
"the number of lands you control onto the battlefield from your hand or graveyard " +
"with two +1/+1 counters on it.");
this.addAbility(new LoyaltyAbility(putCardEffect,-5)
);
this.addAbility(new LoyaltyAbility(new NissaOfShadowedBoughsPutCardEffect(),-5));
}
private NissaOfShadowedBoughs(final NissaOfShadowedBoughs card) {
@ -104,3 +92,54 @@ class NissaOfShadowedBoughsLandEffect extends OneShotEffect {
return true;
}
}
class NissaOfShadowedBoughsPutCardEffect extends OneShotEffect {
private static final FilterCreatureCard filter = new FilterCreatureCard("creature card with mana value less than or equal to the number of lands you control");
static {
filter.add(ManaValueLessThanControlledLandCountPredicate.instance);
}
NissaOfShadowedBoughsPutCardEffect() {
super(Outcome.PutCardInPlay);
this.staticText = "you may put a creature card with mana value less than or equal to " +
"the number of lands you control onto the battlefield from your hand or graveyard " +
"with two +1/+1 counters on it";
}
private NissaOfShadowedBoughsPutCardEffect(final NissaOfShadowedBoughsPutCardEffect effect) {
super(effect);
}
@Override
public NissaOfShadowedBoughsPutCardEffect copy() {
return new NissaOfShadowedBoughsPutCardEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Cards cards = new CardsImpl();
cards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game));
cards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game));
if (cards.isEmpty()) {
return true;
}
TargetCard target = new TargetCard(0, 1, Zone.ALL, filter);
target.withNotTarget(true);
controller.choose(outcome, cards, target, source, game);
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
Counters counters = new Counters();
counters.addCounter(CounterType.P1P1.createInstance(2));
game.setEnterWithCounters(card.getId(), counters);
controller.moveCards(card, Zone.BATTLEFIELD, source, game);
}
return true;
}
}

View file

@ -1,10 +1,7 @@
package mage.cards.r;
import mage.abilities.Mode;
import mage.abilities.effects.common.DamageAllEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.LoseLifeSourceControllerEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -42,7 +39,7 @@ public class RiveteersConfluence extends CardImpl {
this.getSpellAbility().addMode(new Mode(new DamageAllEffect(1, damageFilter)));
// You may put a land card from your hand or graveyard onto the battlefield tapped.
this.getSpellAbility().addMode(new Mode(new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A, true)));
this.getSpellAbility().addMode(new Mode(new PutCardFromHandOrGraveyardOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A, true)));
}
private RiveteersConfluence(final RiveteersConfluence card) {
@ -53,4 +50,4 @@ public class RiveteersConfluence extends CardImpl {
public RiveteersConfluence copy() {
return new RiveteersConfluence(this);
}
}
}

View file

@ -1,6 +1,5 @@
package mage.cards.s;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
@ -9,24 +8,21 @@ import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbil
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.ReturnToHandTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.ComparisonType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.cards.*;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
*
* @author jeffwadsworth
@ -50,10 +46,7 @@ public final class SwiftWarkite extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// When Swift Warkite enters the battlefield, you may put a creature card with converted mana cost 3 or less from your hand or graveyard onto the battlefield. That creature gains haste. Return it to your hand at the beginning of the next end step.
this.addAbility(new EntersBattlefieldTriggeredAbility(
new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(filter, false, new SwiftWarkiteEffect()),
true)
);
this.addAbility(new EntersBattlefieldTriggeredAbility(new SwiftWarkiteEffect()));
}
private SwiftWarkite(final SwiftWarkite card) {
@ -68,9 +61,17 @@ public final class SwiftWarkite extends CardImpl {
class SwiftWarkiteEffect extends OneShotEffect {
private static final FilterCard filter = new FilterCard("creature card with mana value 3 or less");
static {
filter.add(CardType.CREATURE.getPredicate());
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4));
}
SwiftWarkiteEffect() {
super(Outcome.AddAbility);
this.staticText = "That creature gains haste. Return it to your hand at the beginning of the next end step";
super(Outcome.PutCardInPlay);
this.staticText = "you may put a creature card with mana value 3 or less from your hand or graveyard onto the battlefield. " +
"That creature gains haste. Return it to your hand at the beginning of the next end step";
}
private SwiftWarkiteEffect(final SwiftWarkiteEffect effect) {
@ -84,19 +85,33 @@ class SwiftWarkiteEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Permanent movedCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
if (movedCreature == null) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Cards cards = new CardsImpl();
cards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game));
cards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game));
if (cards.isEmpty()) {
return true;
}
TargetCard target = new TargetCard(0, 1, Zone.ALL, filter);
target.withNotTarget(true);
controller.choose(outcome, cards, target, source, game);
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game);
ContinuousEffect gainHasteEffect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom);
gainHasteEffect.setTargetPointer(new FixedTarget(movedCreature.getId(), movedCreature.getZoneChangeCounter(game)));
game.addEffect(gainHasteEffect, source);
ContinuousEffect gainHasteEffect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom);
gainHasteEffect.setTargetPointer(new FixedTarget(card, game));
game.addEffect(gainHasteEffect, source);
Effect returnToHandEffect = new ReturnToHandTargetEffect();
returnToHandEffect.setTargetPointer(new FixedTarget(movedCreature.getId(), movedCreature.getZoneChangeCounter(game)));
DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(returnToHandEffect);
game.addDelayedTriggeredAbility(delayedAbility, source);
Effect returnToHandEffect = new ReturnToHandTargetEffect();
returnToHandEffect.setTargetPointer(new FixedTarget(card, game));
DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(returnToHandEffect);
game.addDelayedTriggeredAbility(delayedAbility, source);
}
return true;
}
}

View file

@ -91,8 +91,11 @@ class Vault101BirthdayPartyEffect extends OneShotEffect {
return false;
}
Cards cards = new CardsImpl();
cards.addAllCards(player.getHand().getCards(filter, game));
cards.addAllCards(player.getGraveyard().getCards(filter, game));
cards.addAllCards(player.getHand().getCards(filter, source.getControllerId(), source, game));
cards.addAllCards(player.getGraveyard().getCards(filter, source.getControllerId(), source, game));
if (cards.isEmpty()) {
return false;
}
TargetCard targetCard = new TargetCard(0, 1, Zone.ALL, filter);
targetCard.withNotTarget(true);
player.choose(outcome, cards, targetCard, source, game);
@ -103,7 +106,7 @@ class Vault101BirthdayPartyEffect extends OneShotEffect {
player.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent equipment = game.getPermanent(card.getId());
if (equipment == null || !equipment.hasSubtype(SubType.EQUIPMENT, game)) {
return false;
return true;
}
TargetPermanent targetPermanent = new TargetControlledCreaturePermanent(0, 1);
targetCard.withNotTarget(true);

View file

@ -93,7 +93,6 @@ public class PutCardFromOneOfTwoZonesOntoBattlefieldEffectTest extends CardTestP
setStrictChooseMode(true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2");
setChoice(playerA, "Yes"); // For player this choice is "Hand" but tests require "Yes"
setChoice(playerA, vorpal);
setStopAt(1, PhaseStep.END_TURN);
@ -113,7 +112,7 @@ public class PutCardFromOneOfTwoZonesOntoBattlefieldEffectTest extends CardTestP
public void testNissaCanPlay() {
addCard(Zone.BATTLEFIELD, playerA, nissa);
addCard(Zone.HAND, playerA, swift); // {4}{B}{R}
addCard(Zone.HAND, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.HAND, playerA, "Mountain");
setStrictChooseMode(true);
@ -121,7 +120,6 @@ public class PutCardFromOneOfTwoZonesOntoBattlefieldEffectTest extends CardTestP
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-5");
setChoice(playerA, swift);
setChoice(playerA, "Yes"); // Say yes to Swift Warkite's ETB (no further choice needed since there are no possible options
setStopAt(1, PhaseStep.END_TURN);
@ -145,7 +143,6 @@ public class PutCardFromOneOfTwoZonesOntoBattlefieldEffectTest extends CardTestP
addCard(Zone.HAND, playerA, swift); // {4}{B}{R}
addCard(Zone.HAND, playerA, "Mountain");
setStrictChooseMode(true);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain");
@ -174,7 +171,6 @@ public class PutCardFromOneOfTwoZonesOntoBattlefieldEffectTest extends CardTestP
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, swift);
setChoice(playerA, "Yes"); // Yes to activating Swift Warkite's ETB
setChoice(playerA, sliver); // Pick the sliver for the ETB
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);

View file

@ -5,7 +5,6 @@ import mage.abilities.keyword.FlyingAbility;
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;
/**
@ -45,7 +44,7 @@ public class DanithaBenaliasHopeTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, danitha);
setChoice(playerA, true); // attempt to use ability
setChoice(playerA, TestPlayer.CHOICE_SKIP);
// choice skip not needed
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
@ -105,7 +104,7 @@ public class DanithaBenaliasHopeTest extends CardTestPlayerBase {
setChoice(playerA, true); // use ability
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, swords, danitha);
setChoice(playerA, TestPlayer.CHOICE_SKIP); // no longer can attach Aura
// no longer can attach Aura
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);

View file

@ -199,7 +199,7 @@ public class LiberatedLivestockTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, MURDER, LIBERATEDLIVESTOCK);
addTarget(playerA, KEENSENSE);
addTarget(playerA, ARACHNOFORM);
addTarget(playerA, TestPlayer.TARGET_SKIP);
// no possible targets thus no need to target skip
waitStackResolved(1, PhaseStep.END_TURN);
execute();

View file

@ -0,0 +1,69 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.util.CardUtil;
/**
* @author xenohedron
*/
public class PutCardFromHandOrGraveyardOntoBattlefieldEffect extends OneShotEffect {
private final FilterCard filter;
private final boolean tapped;
/**
* @param filter for selecting a card (don't include zone-related text in filter message)
* @param tapped ETB tapped if true
*/
public PutCardFromHandOrGraveyardOntoBattlefieldEffect(FilterCard filter, boolean tapped) {
super(Outcome.PutCardInPlay);
this.filter = filter;
this.tapped = tapped;
this.staticText = "you may put " + CardUtil.addArticle(filter.getMessage())
+ " from your hand or graveyard onto the battlefield" + (tapped ? " tapped" : "");
}
private PutCardFromHandOrGraveyardOntoBattlefieldEffect(final PutCardFromHandOrGraveyardOntoBattlefieldEffect effect) {
super(effect);
this.filter = effect.filter;
this.tapped = effect.tapped;
}
@Override
public PutCardFromHandOrGraveyardOntoBattlefieldEffect copy() {
return new PutCardFromHandOrGraveyardOntoBattlefieldEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Cards cards = new CardsImpl();
cards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game));
cards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game));
if (cards.isEmpty()) {
return true;
}
TargetCard target = new TargetCard(0, 1, Zone.ALL, filter);
target.withNotTarget(true);
controller.choose(outcome, cards, target, source, game);
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game, tapped, false, false, null);
}
return true;
}
}

View file

@ -1,164 +0,0 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CommanderCardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInCommandZone;
import mage.target.common.TargetCardInGraveyard;
import mage.target.common.TargetCardInHand;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/**
* "Put a {filter} card from {zone 1} or {zone 2} onto the battlefield.
*
* @author TheElk801, Alex-Vasile
*/
public class PutCardFromOneOfTwoZonesOntoBattlefieldEffect extends OneShotEffect {
private final FilterCard filterCard;
private final boolean tapped;
private final Effect effectToApplyOnPermanent;
private final Zone zone1;
private final Zone zone2;
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard) {
this(filterCard, false);
}
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, boolean tapped) {
this(filterCard, tapped, null);
}
/**
*
* @param filterCard Filter used to filter which cards are valid choices. (no default)
* @param tapped If the permanent should enter the battlefield tapped (default is False)
* @param effectToApplyOnPermanent An effect to apply to the permanent after it enters (default null)
* See "Swift Warkite" or "Nissa of Shadowed Boughs".
* @param zone1 The first zone to pick from (default of HAND)
* @param zone2 The second zone to pick from (defualt of GRAVEYARD)
*/
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, boolean tapped, Effect effectToApplyOnPermanent, Zone zone1, Zone zone2) {
super(filterCard instanceof FilterCreatureCard ? Outcome.PutCreatureInPlay : Outcome.PutCardInPlay);
this.filterCard = filterCard;
this.tapped = tapped;
this.effectToApplyOnPermanent = effectToApplyOnPermanent;
this.zone1 = zone1;
this.zone2 = zone2;
}
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, boolean tapped, Effect effectToApplyOnPermanent) {
this(filterCard, tapped, effectToApplyOnPermanent, Zone.HAND, Zone.GRAVEYARD);
}
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, Zone zone1, Zone zone2) {
this(filterCard, false, null, zone1, zone2);
}
private PutCardFromOneOfTwoZonesOntoBattlefieldEffect(final PutCardFromOneOfTwoZonesOntoBattlefieldEffect effect) {
super(effect);
this.filterCard = effect.filterCard;
this.tapped = effect.tapped;
this.zone1 = effect.zone1;
this.zone2 = effect.zone2;
if (effect.effectToApplyOnPermanent != null) {
this.effectToApplyOnPermanent = effect.effectToApplyOnPermanent.copy();
} else {
this.effectToApplyOnPermanent = null;
}
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Cards cardsInZone1 = getCardsFromZone(game, controller, zone1);
Cards cardsInZone2 = getCardsFromZone(game, controller, zone2);
boolean cardsAvailableInZone1 = cardsInZone1.count(filterCard, game) > 0;
boolean cardsAvailableInZone2 = cardsInZone2.count(filterCard, game) > 0;
if (!cardsAvailableInZone1 && !cardsAvailableInZone2) {
return false;
}
boolean choose1stZone;
if (cardsAvailableInZone1 && cardsAvailableInZone2) {
choose1stZone = controller.chooseUse(outcome, "Where do you want to chose the card from?",
null, zone1.name(), zone2.name(), source, game);
} else {
choose1stZone = cardsAvailableInZone1;
}
Zone zone = choose1stZone ? zone1 : zone2;
Cards cards = choose1stZone ? cardsInZone1 : cardsInZone2;
TargetCard targetCard;
switch (zone) {
case HAND:
targetCard = new TargetCardInHand(filterCard);
break;
case GRAVEYARD:
targetCard = new TargetCardInGraveyard(filterCard);
break;
case COMMAND:
targetCard = new TargetCardInCommandZone(filterCard);
break;
default:
return false;
}
controller.choose(outcome, cards, targetCard, source, game);
Card card = game.getCard(targetCard.getFirstTarget());
if (card == null || !controller.moveCards(card, Zone.BATTLEFIELD, source, game, tapped, false, false, null)) {
return false;
}
if (effectToApplyOnPermanent != null) {
effectToApplyOnPermanent.setTargetPointer(new FixedTarget(card.getId()));
effectToApplyOnPermanent.apply(game, source);
}
return true;
}
private static Cards getCardsFromZone(Game game, Player player, Zone zone) {
switch (zone) {
case HAND:
return player.getHand();
case COMMAND:
return new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY));
case GRAVEYARD:
return player.getGraveyard();
default:
return new CardsImpl();
}
}
@Override
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect copy() {
return new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(this);
}
@Override
public String getText(Mode mode) {
return "you may put " + CardUtil.addArticle(this.filterCard.getMessage()) +
" from your hand or graveyard onto the battlefield" +
(this.tapped ? " tapped" : "") +
(effectToApplyOnPermanent == null ? "" : ". " + effectToApplyOnPermanent.getText(mode));
}
}

View file

@ -1,31 +0,0 @@
package mage.filter.predicate.card;
import mage.cards.Card;
import mage.filter.StaticFilters;
import mage.filter.predicate.Predicate;
import mage.game.Game;
/**
* @author Plopman, Alex-Vasile
*/
public class CardManaCostLessThanControlledLandCountPredicate implements Predicate<Card> {
private static final String string = "card with mana value less than or equal to the number of lands you control";
private static final CardManaCostLessThanControlledLandCountPredicate instance = new CardManaCostLessThanControlledLandCountPredicate();
private CardManaCostLessThanControlledLandCountPredicate() { }
public static CardManaCostLessThanControlledLandCountPredicate getInstance() {
return instance;
}
@Override
public boolean apply(Card input, Game game) {
return input.getManaValue() <= game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, input.getOwnerId(), game).size();
}
@Override
public String toString() {
return string;
}
}

View file

@ -0,0 +1,24 @@
package mage.filter.predicate.card;
import mage.MageObject;
import mage.filter.StaticFilters;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
/**
* @author xenohedron
*/
public enum ManaValueLessThanControlledLandCountPredicate implements ObjectSourcePlayerPredicate<MageObject> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) {
return input.getObject().getManaValue() <= game.getBattlefield().countAll(StaticFilters.FILTER_LAND, game.getControllerId(input.getSourceId()), game);
}
@Override
public String toString() {
return "mana value less than or equal to the number of lands you control";
}
}