mirror of
https://github.com/magefree/mage.git
synced 2025-12-22 11:32:00 -08:00
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 final class AnrakyrTheTraveller extends CardImpl {
|
||||||
|
|
||||||
public AnrakyrTheTraveller(UUID ownerId, CardSetInfo setInfo) {
|
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.supertype.add(SuperType.LEGENDARY);
|
||||||
this.subtype.add(SubType.NECRON);
|
this.subtype.add(SubType.NECRON);
|
||||||
|
|
@ -112,7 +112,7 @@ class AnrakyrTheTravellerEffect extends OneShotEffect {
|
||||||
|
|
||||||
// pay life
|
// pay life
|
||||||
// copied from BolassCitadelPlayTheTopCardEffect.applies
|
// 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<>();
|
Costs<Cost> newCosts = new CostsImpl<>();
|
||||||
newCosts.add(lifeCost);
|
newCosts.add(lifeCost);
|
||||||
newCosts.addAll(cardToCast.getSpellAbility().getCosts());
|
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 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 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("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("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("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));
|
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