forked from External/mage
implement [MH3] Amped Raptor
This commit is contained in:
parent
cc4c6be78b
commit
83ad5d26dc
4 changed files with 239 additions and 6 deletions
128
Mage.Sets/src/mage/cards/a/AmpedRaptor.java
Normal file
128
Mage.Sets/src/mage/cards/a/AmpedRaptor.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue