implement [MH3] Amped Raptor

This commit is contained in:
Susucre 2024-06-04 14:12:04 +02:00
parent cc4c6be78b
commit 83ad5d26dc
4 changed files with 239 additions and 6 deletions

View file

@ -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<Card> 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<Cost> 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;
}
}

View file

@ -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<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) {
List<Card> 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<Cost> newCosts = new CostsImpl<>();
newCosts.add(lifeCost);
newCosts.addAll(cardToCast.getSpellAbility().getCosts());

View file

@ -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));

View file

@ -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);
}
}