From d829094f90b8c6e679beb5b9331d8809ea655efe Mon Sep 17 00:00:00 2001 From: Daniel Bomar Date: Wed, 8 Sep 2021 13:02:04 -0500 Subject: [PATCH] [MID] Implemented Bloodthirsty Adversary --- .../mage/cards/b/BloodthirstyAdversary.java | 155 ++++++++++++++++++ .../src/mage/cards/s/SpectralAdversary.java | 35 ++-- .../src/mage/cards/t/TaintedAdversary.java | 31 ++-- .../src/mage/sets/InnistradMidnightHunt.java | 1 + .../effects/common/DoIfAnyNumberCostPaid.java | 69 ++++++++ 5 files changed, 248 insertions(+), 43 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/b/BloodthirstyAdversary.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/DoIfAnyNumberCostPaid.java diff --git a/Mage.Sets/src/mage/cards/b/BloodthirstyAdversary.java b/Mage.Sets/src/mage/cards/b/BloodthirstyAdversary.java new file mode 100644 index 00000000000..116baea8721 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BloodthirstyAdversary.java @@ -0,0 +1,155 @@ +package mage.cards.b; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfAnyNumberCostPaid; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.Card; +import mage.constants.*; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.filter.common.FilterInstantOrSorceryCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author weirddan455 + */ +public final class BloodthirstyAdversary extends CardImpl { + + public BloodthirstyAdversary(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.VAMPIRE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When Bloodthirsty Adversary enters the battlefield, you may pay {2}{R} any number of times. + // When you pay this cost one or more times, put that many +1/+1 counters on Bloodthirsty Adversary, + // then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. + // You may cast any number of the copies without paying their mana costs. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoIfAnyNumberCostPaid( + new BloodthirstyAdversaryEffect(), new ManaCostsImpl<>("{2}{R}") + ))); + } + + private BloodthirstyAdversary(final BloodthirstyAdversary card) { + super(card); + } + + @Override + public BloodthirstyAdversary copy() { + return new BloodthirstyAdversary(this); + } +} + +class BloodthirstyAdversaryEffect extends OneShotEffect { + + private static final FilterInstantOrSorceryCard filter = + new FilterInstantOrSorceryCard("instant and/or sorcery cards with mana value 3 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); + } + + public BloodthirstyAdversaryEffect() { + super(Outcome.Benefit); + staticText = "put that many +1/+1 counters on {this}, " + + "then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. " + + "You may cast any number of the copies without paying their mana costs"; + } + + private BloodthirstyAdversaryEffect(final BloodthirstyAdversaryEffect effect) { + super(effect); + } + + @Override + public BloodthirstyAdversaryEffect copy() { + return new BloodthirstyAdversaryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Integer timesPaid = (Integer) getValue("timesPaid"); + if (timesPaid == null || timesPaid <= 0) { + return false; + } + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(timesPaid)), + false, staticText + ); + ability.addEffect(new BloodthirstyAdversaryCopyEffect()); + ability.addTarget(new TargetCardInYourGraveyard(0, timesPaid, filter)); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} + +class BloodthirstyAdversaryCopyEffect extends OneShotEffect { + + public BloodthirstyAdversaryCopyEffect() { + super(Outcome.PlayForFree); + } + + private BloodthirstyAdversaryCopyEffect(final BloodthirstyAdversaryCopyEffect effect) { + super(effect); + } + + @Override + public BloodthirstyAdversaryCopyEffect copy() { + return new BloodthirstyAdversaryCopyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Set cardsToExile = new LinkedHashSet<>(); + for (UUID cardId : targetPointer.getTargets(game, source)) { + Card card = game.getCard(cardId); + if (card != null) { + cardsToExile.add(card); + } + } + if (cardsToExile.isEmpty()) { + return false; + } + controller.moveCards(cardsToExile, Zone.EXILED, source, game); + ApprovingObject approvingObject = new ApprovingObject(source, game); + for (Card card : cardsToExile) { + Card copiedCard = game.copyCard(card, source, controller.getId()); + if (copiedCard != null && controller.chooseUse(outcome, "Cast " + copiedCard.getName() + " for free?", source, game)) { + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), Boolean.TRUE); + controller.cast( + controller.chooseAbilityForCast(copiedCard, game, true), + game, true, approvingObject + ); + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), null); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpectralAdversary.java b/Mage.Sets/src/mage/cards/s/SpectralAdversary.java index b1037b118db..b0b20ece2aa 100644 --- a/Mage.Sets/src/mage/cards/s/SpectralAdversary.java +++ b/Mage.Sets/src/mage/cards/s/SpectralAdversary.java @@ -7,6 +7,7 @@ import mage.abilities.common.delayed.ReflexiveTriggeredAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfAnyNumberCostPaid; import mage.abilities.effects.common.PhaseOutTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.FlashAbility; @@ -19,6 +20,7 @@ import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.players.Player; import mage.target.TargetPermanent; @@ -44,7 +46,9 @@ public final class SpectralAdversary extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Spectral Adversary enters the battlefield, you may pay {1}{U} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Spectral Adversary, then up to that many other target artifacts, creatures, and/or enchantments phase out. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SpectralAdversaryEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoIfAnyNumberCostPaid( + new SpectralAdversaryEffect(), new ManaCostsImpl<>("{1}{U}") + ))); } private SpectralAdversary(final SpectralAdversary card) { @@ -60,7 +64,7 @@ public final class SpectralAdversary extends CardImpl { class SpectralAdversaryEffect extends OneShotEffect { private static final FilterPermanent filter - = new FilterPermanent("artifacts, creatures, and/or enchantments"); + = new FilterPermanent("other artifacts, creatures, and/or enchantments"); static { filter.add(Predicates.or( @@ -68,12 +72,12 @@ class SpectralAdversaryEffect extends OneShotEffect { CardType.CREATURE.getPredicate(), CardType.ENCHANTMENT.getPredicate() )); + filter.add(AnotherPredicate.instance); } SpectralAdversaryEffect() { super(Outcome.Benefit); - staticText = "you may pay {1}{U} any number of times. When you pay this cost one or more times, " + - "put that many +1/+1 counters on {this}, then up to that many other target " + + staticText = "put that many +1/+1 counters on {this}, then up to that many other target " + "artifacts, creatures, and/or enchantments phase out"; } @@ -92,29 +96,16 @@ class SpectralAdversaryEffect extends OneShotEffect { if (player == null) { return false; } - Cost cost = new ManaCostsImpl<>("{1}{U}"); - int amount = 0; - while (player.canRespond()) { - cost.clearPaid(); - if (cost.canPay(source, source, source.getControllerId(), game) - && player.chooseUse( - outcome, "Pay {1}{U}? You have paid this cost " + - amount + " time" + (amount != 1 ? "s" : ""), source, game - ) && cost.pay(source, game, source, source.getControllerId(), false)) { - amount++; - } - break; - } - if (amount == 0) { + Integer timesPaid = (Integer) getValue("timesPaid"); + if (timesPaid == null || timesPaid <= 0) { return false; } ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new AddCountersSourceEffect(CounterType.P1P1.createInstance(amount)), - false, "put that many +1/+1 counters on {this}, then " + - "up to that many other target artifacts, creatures, and/or enchantments phase out" + new AddCountersSourceEffect(CounterType.P1P1.createInstance(timesPaid)), + false, staticText ); ability.addEffect(new PhaseOutTargetEffect()); - ability.addTarget(new TargetPermanent(0, amount, filter)); + ability.addTarget(new TargetPermanent(0, timesPaid, filter)); game.fireReflexiveTriggeredAbility(ability, source); return true; } diff --git a/Mage.Sets/src/mage/cards/t/TaintedAdversary.java b/Mage.Sets/src/mage/cards/t/TaintedAdversary.java index 081a5f9c842..82f8bf91003 100644 --- a/Mage.Sets/src/mage/cards/t/TaintedAdversary.java +++ b/Mage.Sets/src/mage/cards/t/TaintedAdversary.java @@ -8,6 +8,7 @@ import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfAnyNumberCostPaid; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.DeathtouchAbility; import mage.cards.CardImpl; @@ -38,7 +39,9 @@ public final class TaintedAdversary extends CardImpl { this.addAbility(DeathtouchAbility.getInstance()); // When Tainted Adversary enters the battlefield, you may pay {2}{B} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Tainted Adversary, then create twice that many black 2/2 Zombie creature tokens with decayed. - this.addAbility(new EntersBattlefieldTriggeredAbility(new TaintedAdversaryEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoIfAnyNumberCostPaid( + new TaintedAdversaryEffect(), new ManaCostsImpl<>("{2}{B}") + ))); } private TaintedAdversary(final TaintedAdversary card) { @@ -55,8 +58,7 @@ class TaintedAdversaryEffect extends OneShotEffect { TaintedAdversaryEffect() { super(Outcome.Benefit); - staticText = "you may pay {2}{B} any number of times. When you pay this cost " + - "one or more times, put that many +1/+1 counters on {this}, " + + staticText = "put that many +1/+1 counters on {this}, " + "then create twice that many black 2/2 Zombie creature tokens with decayed"; } @@ -75,28 +77,15 @@ class TaintedAdversaryEffect extends OneShotEffect { if (player == null) { return false; } - Cost cost = new ManaCostsImpl<>("{2}{B}"); - int amount = 0; - while (player.canRespond()) { - cost.clearPaid(); - if (cost.canPay(source, source, source.getControllerId(), game) - && player.chooseUse( - outcome, "Pay {2}{B}? You have paid this cost " + - amount + " time" + (amount != 1 ? "s" : ""), source, game - ) && cost.pay(source, game, source, source.getControllerId(), false)) { - amount++; - } - break; - } - if (amount == 0) { + Integer timesPaid = (Integer) getValue("timesPaid"); + if (timesPaid == null || timesPaid <= 0) { return false; } ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new AddCountersSourceEffect(CounterType.P1P1.createInstance(amount)), - false, "put that many +1/+1 counters on {this}, then create " + - "twice that many black 2/2 Zombie creature tokens with decayed" + new AddCountersSourceEffect(CounterType.P1P1.createInstance(timesPaid)), + false, staticText ); - ability.addEffect(new CreateTokenEffect(new ZombieDecayedToken(), 2 * amount)); + ability.addEffect(new CreateTokenEffect(new ZombieDecayedToken(), 2 * timesPaid)); game.fireReflexiveTriggeredAbility(ability, source); return true; } diff --git a/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java b/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java index 85ae34f9268..8cc48a570f6 100644 --- a/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java +++ b/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java @@ -35,6 +35,7 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Arrogant Outlaw", 84, Rarity.COMMON, mage.cards.a.ArrogantOutlaw.class)); cards.add(new SetCardInfo("Augur of Autumn", 168, Rarity.RARE, mage.cards.a.AugurOfAutumn.class)); cards.add(new SetCardInfo("Bladestitched Skaab", 212, Rarity.UNCOMMON, mage.cards.b.BladestitchedSkaab.class)); + cards.add(new SetCardInfo("Bloodthirsty Adversary", 129, Rarity.MYTHIC, mage.cards.b.BloodthirstyAdversary.class)); cards.add(new SetCardInfo("Briarbridge Tracker", 172, Rarity.RARE, mage.cards.b.BriarbridgeTracker.class)); cards.add(new SetCardInfo("Brimstone Vandal", 130, Rarity.COMMON, mage.cards.b.BrimstoneVandal.class)); cards.add(new SetCardInfo("Burly Breaker", 174, Rarity.UNCOMMON, mage.cards.b.BurlyBreaker.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoIfAnyNumberCostPaid.java b/Mage/src/main/java/mage/abilities/effects/common/DoIfAnyNumberCostPaid.java new file mode 100644 index 00000000000..611fa4c97fc --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/DoIfAnyNumberCostPaid.java @@ -0,0 +1,69 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.Cost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +public class DoIfAnyNumberCostPaid extends OneShotEffect { + + private final Effect executingEffect; + private final Cost cost; + + public DoIfAnyNumberCostPaid(OneShotEffect effect, Cost cost) { + super(Outcome.Benefit); + this.executingEffect = effect; + this.cost = cost; + } + + private DoIfAnyNumberCostPaid(final DoIfAnyNumberCostPaid effect) { + super(effect); + this.executingEffect = effect.executingEffect.copy(); + this.cost = effect.cost.copy(); + } + + @Override + public DoIfAnyNumberCostPaid copy() { + return new DoIfAnyNumberCostPaid(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + String costText = cost.getText(); + int timesPaid = 0; + while (controller.canRespond()) { + cost.clearPaid(); + if (cost.canPay(source, source, source.getControllerId(), game) + && controller.chooseUse( + outcome, "Pay " + costText + "? You have paid this cost " + + timesPaid + " time" + (timesPaid != 1 ? "s" : ""), source, game + ) && cost.pay(source, game, source, source.getControllerId(), false)) { + timesPaid++; + continue; + } + break; + } + if (timesPaid > 0) { + executingEffect.setValue("timesPaid", timesPaid); + return executingEffect.apply(game, source); + } + return false; + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return "you may pay " + cost.getText() + " any number of times. When you pay this cost one or more times, " + + executingEffect.getText(mode); + } +}