diff --git a/Mage.Sets/src/mage/cards/a/AmpedRaptor.java b/Mage.Sets/src/mage/cards/a/AmpedRaptor.java new file mode 100644 index 00000000000..79a27ac8fed --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AmpedRaptor.java @@ -0,0 +1,128 @@ +package mage.cards.a; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.CastFromHandSourcePermanentCondition; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; +import mage.watchers.common.CastFromHandWatcher; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Susucr + */ +public final class AmpedRaptor extends CardImpl { + + public AmpedRaptor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.DINOSAUR); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // When Amped Raptor enters the battlefield, you get {E}{E}. Then if you cast it from your hand, exile cards from the top of your library until you exile a nonland card. You may cast that card by paying an amount of {E} equal to its mana value rather than paying its mana cost. + Ability ability = new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(2)); + ability.addEffect(new ConditionalOneShotEffect( + new AmpedRaptorEffect(), + CastFromHandSourcePermanentCondition.instance + )); + this.addAbility(ability, new CastFromHandWatcher()); + } + + private AmpedRaptor(final AmpedRaptor card) { + super(card); + } + + @Override + public AmpedRaptor copy() { + return new AmpedRaptor(this); + } +} + +class AmpedRaptorEffect extends OneShotEffect { + + AmpedRaptorEffect() { + super(Outcome.PlayForFree); + staticText = "exile cards from the top of your library until you exile a nonland card. " + + "You may cast that card by paying an amount of {E} equal to its mana value rather than paying its mana cost"; + } + + private AmpedRaptorEffect(final AmpedRaptorEffect effect) { + super(effect); + } + + @Override + public AmpedRaptorEffect copy() { + return new AmpedRaptorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null || !controller.getLibrary().hasCards()) { + return false; + } + for (Card card : controller.getLibrary().getCards(game)) { + controller.moveCards(card, Zone.EXILED, source, game); + if (!card.isLand(game)) { + List castableComponents = CardUtil.getCastableComponents(card, null, source, controller, game, null, false); + if (castableComponents.isEmpty()) { + break; + } + String partsInfo = castableComponents + .stream() + .map(MageObject::getLogName) + .collect(Collectors.joining(" or ")); + if (!controller.chooseUse(Outcome.PlayForFree, "Cast spell by paying energy instead of mana (" + partsInfo + ")?", source, game)) { + break; + } + castableComponents.forEach(partCard -> game.getState().setValue("PlayFromNotOwnHandZone" + partCard.getId(), Boolean.TRUE)); + SpellAbility chosenAbility = controller.chooseAbilityForCast(card, game, true); + if (chosenAbility != null) { + Card faceCard = game.getCard(chosenAbility.getSourceId()); + if (faceCard != null) { + // pay energy instead of mana cost + PayEnergyCost energyCost = new PayEnergyCost(faceCard.getManaValue()); + Costs newCosts = new CostsImpl<>(); + newCosts.add(energyCost); + newCosts.addAll(chosenAbility.getCosts()); + controller.setCastSourceIdWithAlternateMana(faceCard.getId(), null, newCosts); + controller.cast( + chosenAbility, game, true, + new ApprovingObject(source, game) + ); + } + } + castableComponents.forEach(partCard -> game.getState().setValue("PlayFromNotOwnHandZone" + partCard.getId(), null)); + break; + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/a/AnrakyrTheTraveller.java b/Mage.Sets/src/mage/cards/a/AnrakyrTheTraveller.java index 8cfa165f755..b3dd353bc2c 100644 --- a/Mage.Sets/src/mage/cards/a/AnrakyrTheTraveller.java +++ b/Mage.Sets/src/mage/cards/a/AnrakyrTheTraveller.java @@ -30,7 +30,7 @@ import java.util.stream.Collectors; public final class AnrakyrTheTraveller extends CardImpl { public AnrakyrTheTraveller(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[] { CardType.ARTIFACT, CardType.CREATURE }, "{4}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}{B}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.NECRON); @@ -72,17 +72,17 @@ class AnrakyrTheTravellerEffect extends OneShotEffect { if (player == null) { return false; } - + Set cards = player.getHand().getCards(filter, source.getControllerId(), source, game); cards.addAll(player.getGraveyard().getCards(filter, source.getControllerId(), source, game)); - + Map> cardMap = new HashMap<>(); for (Card card : cards) { List castableComponents = CardUtil.getCastableComponents(card, filter, source, player, game, null, false); if (!castableComponents.isEmpty()) { cardMap.put(card.getId(), castableComponents); } - } + } Card cardToCast; if (cardMap.isEmpty()) { return false; @@ -109,10 +109,10 @@ class AnrakyrTheTravellerEffect extends OneShotEffect { return true; } partsToCast.forEach(card -> game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE)); - + // pay life // copied from BolassCitadelPlayTheTopCardEffect.applies - PayLifeCost lifeCost = new PayLifeCost(cardToCast.getSpellAbility().getManaCosts().manaValue()); + PayLifeCost lifeCost = new PayLifeCost(cardToCast.getSpellAbility().getManaCosts().manaValue()); // TODO: Cost is most likely wrong for multi part cards. See Amped Raptor way for a rework. Costs newCosts = new CostsImpl<>(); newCosts.add(lifeCost); newCosts.addAll(cardToCast.getSpellAbility().getCosts()); diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java index c6d91109fb3..2d9255bf953 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -27,6 +27,7 @@ public final class ModernHorizons3 extends ExpansionSet { cards.add(new SetCardInfo("Ajani Fells the Godsire", 19, Rarity.UNCOMMON, mage.cards.a.AjaniFellsTheGodsire.class)); cards.add(new SetCardInfo("Ajani, Nacatl Avenger", 237, Rarity.MYTHIC, mage.cards.a.AjaniNacatlAvenger.class)); cards.add(new SetCardInfo("Ajani, Nacatl Pariah", 237, Rarity.MYTHIC, mage.cards.a.AjaniNacatlPariah.class)); + cards.add(new SetCardInfo("Amped Raptor", 114, Rarity.UNCOMMON, mage.cards.a.AmpedRaptor.class)); cards.add(new SetCardInfo("Amphibian Downpour", 51, Rarity.RARE, mage.cards.a.AmphibianDownpour.class)); cards.add(new SetCardInfo("Angel of the Ruins", 262, Rarity.UNCOMMON, mage.cards.a.AngelOfTheRuins.class)); cards.add(new SetCardInfo("Annoyed Altisaur", 284, Rarity.UNCOMMON, mage.cards.a.AnnoyedAltisaur.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/AmpedRaptorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/AmpedRaptorTest.java new file mode 100644 index 00000000000..6746c2a92cd --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/AmpedRaptorTest.java @@ -0,0 +1,104 @@ +package org.mage.test.cards.single.mh3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.players.Player; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class AmpedRaptorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.a.AmpedRaptor Amped Raptor} {1}{R} + * Creature — Dinosaur + * First strike + * When Amped Raptor enters the battlefield, you get {E}{E} (two energy counters). Then if you cast it from your hand, exile cards from the top of your library until you exile a nonland card. You may cast that card by paying an amount of {E} equal to its mana value rather than paying its mana cost. + * 2/1 + */ + private static final String raptor = "Amped Raptor"; + + private static void checkEnergyCount(String message, Player player, int expected) { + Assert.assertEquals(message, expected, player.getCountersCount(CounterType.ENERGY)); + } + + @Test + public void test_Cast_Bolt() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.HAND, playerA, raptor); + addCard(Zone.LIBRARY, playerA, "Lightning Bolt"); + addCard(Zone.LIBRARY, playerA, "Plains"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, raptor); + setChoice(playerA, true); // yes to "you may cast" + addTarget(playerA, playerB); // Target for Bolt + + runCode("1 energy spent", 1, PhaseStep.BEGIN_COMBAT, playerA, + (info, player, game) -> checkEnergyCount(info, player, 2 - 1)); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 3); + assertExileCount(playerA, 1); + assertExileCount(playerA, "Plains", 1); + } + + @Test + public void test_CastStomp() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.HAND, playerA, raptor); + addCard(Zone.LIBRARY, playerA, "Bonecrusher Giant"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, raptor); + setChoice(playerA, true); // yes to "you may cast" + setChoice(playerA, "Cast Stomp"); // Choose Stomp + addTarget(playerA, playerB); + + runCode("After Stomp Cast, no energy left (2-2)", 1, PhaseStep.BEGIN_COMBAT, playerA, + (info, player, game) -> checkEnergyCount(info, player, 0)); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 2); + assertExileCount(playerA, 1); // Giant on an adventure + assertExileCount(playerA, "Bonecrusher Giant", 1); + } + + @Test + public void test_CantCast_BonecrusherGiant() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.HAND, playerA, raptor); + addCard(Zone.LIBRARY, playerA, "Bonecrusher Giant"); + addCard(Zone.LIBRARY, playerA, "Plains"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, raptor); + setChoice(playerA, true); // yes to "you may cast" + setChoice(playerA, "Cast Bonecrusher Giant"); // Choose Bonecrusher Giant, it can't be cast. + + runCode("No energy spent", 1, PhaseStep.BEGIN_COMBAT, playerA, + (info, player, game) -> checkEnergyCount(info, player, 2)); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20); + assertExileCount(playerA, 2); // Giant (not on an adventure) + Plains in exile + assertExileCount(playerA, "Bonecrusher Giant", 1); + assertExileCount(playerA, "Plains", 1); + } +}