forked from External/mage
[40K] Implement Anrakyr the Traveller (#11160)
--------- Co-authored-by: gravitybone <7361357+gravitybone@users.noreply.github.com> Co-authored-by: Aquid <Aquid117@users.noreply.github.com>
This commit is contained in:
parent
5e3c0764a5
commit
5b845ef7e4
4 changed files with 371 additions and 1 deletions
143
Mage.Sets/src/mage/cards/a/AnrakyrTheTraveller.java
Normal file
143
Mage.Sets/src/mage/cards/a/AnrakyrTheTraveller.java
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbility;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.common.AttacksTriggeredAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
import mage.abilities.costs.CostsImpl;
|
||||
import mage.abilities.costs.common.PayLifeCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.*;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.common.FilterArtifactCard;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author gravitybone
|
||||
* @author Aquid
|
||||
*/
|
||||
public final class AnrakyrTheTraveller extends CardImpl {
|
||||
|
||||
public AnrakyrTheTraveller(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[] { CardType.ARTIFACT, CardType.CREATURE }, "{4}{B}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.NECRON);
|
||||
this.power = new MageInt(4);
|
||||
this.toughness = new MageInt(4);
|
||||
|
||||
// Lord of the Pyrrhian Legions — Whenever Anrakyr the Traveller attacks, you may cast an artifact spell from your hand or graveyard by paying life equal to its mana value rather than paying its mana cost.
|
||||
Ability ability = new AttacksTriggeredAbility(new AnrakyrTheTravellerEffect(), true);
|
||||
|
||||
this.addAbility(ability.withFlavorWord("Lord of the Pyrrhian Legions"));
|
||||
}
|
||||
|
||||
private AnrakyrTheTraveller(final AnrakyrTheTraveller card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnrakyrTheTraveller copy() {
|
||||
return new AnrakyrTheTraveller(this);
|
||||
}
|
||||
}
|
||||
|
||||
class AnrakyrTheTravellerEffect extends OneShotEffect {
|
||||
|
||||
private static final FilterCard filter = new FilterArtifactCard("an artifact spell");
|
||||
|
||||
AnrakyrTheTravellerEffect() {
|
||||
super(Outcome.AIDontUseIt);
|
||||
this.staticText = "you may cast " + filter.getMessage() + " from your hand or graveyard by paying life equal to its mana value rather than paying its mana cost.";
|
||||
}
|
||||
|
||||
private AnrakyrTheTravellerEffect(final AnrakyrTheTravellerEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<Card> cards = player.getHand().getCards(filter, game);
|
||||
cards.addAll(player.getGraveyard().getCards(filter, 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;
|
||||
}
|
||||
Cards castableCards = new CardsImpl(cardMap.keySet());
|
||||
TargetCard target = new TargetCard(0, 1, Zone.ALL, filter);
|
||||
target.withNotTarget(true);
|
||||
player.choose(Outcome.Benefit, castableCards, target, source, game);
|
||||
cardToCast = castableCards.get(target.getFirstTarget(), game);
|
||||
|
||||
if (cardToCast == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Card> partsToCast = cardMap.get(cardToCast.getId());
|
||||
String partsInfo = partsToCast
|
||||
.stream()
|
||||
.map(MageObject::getIdName)
|
||||
.collect(Collectors.joining(" or "));
|
||||
if (cardToCast == null
|
||||
|| partsToCast.size() < 1
|
||||
|| !player.chooseUse(
|
||||
Outcome.PlayForFree, "Cast spell by paying life equal to its mana value rather than paying its mana cost (" + partsInfo + ")?", source, game
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
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());
|
||||
Costs<Cost> newCosts = new CostsImpl<>();
|
||||
newCosts.add(lifeCost);
|
||||
newCosts.addAll(cardToCast.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(cardToCast.getId(), null, newCosts);
|
||||
|
||||
ActivatedAbility chosenAbility;
|
||||
chosenAbility = player.chooseAbilityForCast(cardToCast, game, true);
|
||||
boolean result = false;
|
||||
if (chosenAbility instanceof SpellAbility) {
|
||||
result = player.cast(
|
||||
(SpellAbility) chosenAbility,
|
||||
game, true, new ApprovingObject(source, game)
|
||||
);
|
||||
}
|
||||
partsToCast.forEach(card -> game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null));
|
||||
if (player.isComputer() && !result) {
|
||||
cards.remove(cardToCast);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnrakyrTheTravellerEffect copy() {
|
||||
return new AnrakyrTheTravellerEffect(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ public final class Warhammer40000 extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Acolyte Hybrid", 70, Rarity.UNCOMMON, mage.cards.a.AcolyteHybrid.class));
|
||||
cards.add(new SetCardInfo("Aetherize", 191, Rarity.UNCOMMON, mage.cards.a.Aetherize.class));
|
||||
cards.add(new SetCardInfo("And They Shall Know No Fear", 9, Rarity.UNCOMMON, mage.cards.a.AndTheyShallKnowNoFear.class));
|
||||
cards.add(new SetCardInfo("Anrakyr the Traveller", 28, Rarity.RARE, mage.cards.a.AnrakyrTheTraveller.class));
|
||||
cards.add(new SetCardInfo("Arcane Sanctum", 264, Rarity.UNCOMMON, mage.cards.a.ArcaneSanctum.class));
|
||||
cards.add(new SetCardInfo("Arcane Signet", 227, Rarity.COMMON, mage.cards.a.ArcaneSignet.class));
|
||||
cards.add(new SetCardInfo("Arco-Flagellant", 29, Rarity.RARE, mage.cards.a.ArcoFlagellant.class));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,211 @@
|
|||
package org.mage.test.cards.single._40k;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
public class AnrakyrTheTravellerTest extends CardTestPlayerBase {
|
||||
|
||||
// Lord of the Pyrrhian Legions — Whenever Anrakyr the Traveller attacks, you may cast an artifact spell from your
|
||||
// hand or graveyard by paying life equal to its mana value rather than paying its mana cost.
|
||||
private static final String ANRAKYR = "Anrakyr the Traveller";
|
||||
|
||||
private static final String CRYPTEK = "Cryptek";
|
||||
|
||||
// Multikicker {2}
|
||||
// Everflowing Chalice enters the battlefield with a charge counter on it for each time it was kicked.
|
||||
private static final String EVERFLOWING_CHALICE = "Everflowing Chalice";
|
||||
|
||||
// As an additional cost to cast this spell, discard a card.
|
||||
private static final String LESSER_MASTICORE = "Lesser Masticore";
|
||||
|
||||
@Test
|
||||
public void testCastArtifactCardForLifeFromHand_ShouldCastForLife() {
|
||||
// prepare
|
||||
addCard(Zone.BATTLEFIELD, playerA, ANRAKYR);
|
||||
addCard(Zone.HAND, playerA, CRYPTEK);
|
||||
|
||||
// execute
|
||||
attack(1, playerA, ANRAKYR);
|
||||
setChoice(playerA, true); // confirm use of Anrakyr's ability
|
||||
setChoice(playerA, CRYPTEK); // select Cryptek to cast
|
||||
setChoice(playerA, true); // confirm casting spell by paying life instead of mana
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// assert
|
||||
assertLife(playerA, 20 - 4);
|
||||
assertPermanentCount(playerA, CRYPTEK, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEnoughLife_ShouldNotCast() {
|
||||
// prepare
|
||||
addCard(Zone.BATTLEFIELD, playerA, ANRAKYR);
|
||||
addCard(Zone.HAND, playerA, CRYPTEK);
|
||||
setLife(playerA, 3);
|
||||
|
||||
// execute
|
||||
attack(1, playerA, ANRAKYR);
|
||||
setChoice(playerA, true); // confirm use of Anrakyr's ability
|
||||
setChoice(playerA, CRYPTEK); // select Cryptek to cast
|
||||
setChoice(playerA, true); // confirm casting spell by paying life instead of mana
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// assert
|
||||
assertLife(playerA, 3);
|
||||
assertPermanentCount(playerA, CRYPTEK, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCastArtifactCardForLifeFromGraveyard_ShouldCastForLife() {
|
||||
// prepare
|
||||
addCard(Zone.BATTLEFIELD, playerA, ANRAKYR);
|
||||
addCard(Zone.GRAVEYARD, playerA, CRYPTEK);
|
||||
|
||||
// execute
|
||||
attack(1, playerA, ANRAKYR);
|
||||
setChoice(playerA, true); // confirm use of Anrakyr's ability
|
||||
setChoice(playerA, CRYPTEK); // select Cryptek to cast
|
||||
setChoice(playerA, true); // confirm casting spell by paying life instead of mana
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// assert
|
||||
assertLife(playerA, 20 - 4);
|
||||
assertPermanentCount(playerA, CRYPTEK, 1);
|
||||
assertGraveyardCount(playerA, CRYPTEK, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLesserMasticore_ShouldCast() {
|
||||
// prepare
|
||||
addCard(Zone.BATTLEFIELD, playerA, ANRAKYR);
|
||||
addCard(Zone.HAND, playerA, LESSER_MASTICORE);
|
||||
addCard(Zone.HAND, playerA, CRYPTEK);
|
||||
|
||||
// execute
|
||||
attack(1, playerA, ANRAKYR);
|
||||
setChoice(playerA, true); // confirm use of Anrakyr's ability
|
||||
setChoice(playerA, LESSER_MASTICORE); // select Lesser Masticore to cast
|
||||
setChoice(playerA, true); // confirm casting spell by paying life instead of mana
|
||||
setChoice(playerA, CRYPTEK); // select Cryptek to discard to pay for Lesser Masticore
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// assert
|
||||
assertLife(playerA, 20 - 2);
|
||||
assertPermanentCount(playerA, LESSER_MASTICORE, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLesserMasticoreNoCardInHand_ShouldNotCast() {
|
||||
// prepare
|
||||
addCard(Zone.BATTLEFIELD, playerA, ANRAKYR);
|
||||
addCard(Zone.HAND, playerA, LESSER_MASTICORE);
|
||||
|
||||
// execute
|
||||
attack(1, playerA, ANRAKYR);
|
||||
setChoice(playerA, true); // confirm use of Anrakyr's ability
|
||||
setChoice(playerA, LESSER_MASTICORE); // select Lesser Masticore to cast
|
||||
setChoice(playerA, true); // confirm casting spell by paying life instead of mana
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// assert
|
||||
assertLife(playerA, 20);
|
||||
assertPermanentCount(playerA, LESSER_MASTICORE, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEverflowingChaliceMultikicker1_ShouldCast() {
|
||||
// prepare
|
||||
final String swamp = "Swamp";
|
||||
addCard(Zone.BATTLEFIELD, playerA, ANRAKYR);
|
||||
addCard(Zone.BATTLEFIELD, playerA, swamp);
|
||||
addCard(Zone.BATTLEFIELD, playerA, swamp);
|
||||
addCard(Zone.HAND, playerA, EVERFLOWING_CHALICE);
|
||||
|
||||
// execute
|
||||
attack(1, playerA, ANRAKYR);
|
||||
setChoice(playerA, true); // confirm use of Anrakyr's ability
|
||||
setChoice(playerA, EVERFLOWING_CHALICE); // select Everflowing Chalice to cast
|
||||
setChoice(playerA, true); // confirm casting spell by paying life instead of mana
|
||||
|
||||
// multikicker 1
|
||||
setChoice(playerA, true);
|
||||
setChoice(playerA, false);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// assert
|
||||
assertLife(playerA, 20);
|
||||
assertPermanentCount(playerA, EVERFLOWING_CHALICE, 1);
|
||||
assertCounterCount(playerA, EVERFLOWING_CHALICE, CounterType.CHARGE, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEverflowingChaliceMultikicker0_ShouldCast() {
|
||||
// prepare
|
||||
addCard(Zone.BATTLEFIELD, playerA, ANRAKYR);
|
||||
addCard(Zone.HAND, playerA, EVERFLOWING_CHALICE);
|
||||
|
||||
// execute
|
||||
attack(1, playerA, ANRAKYR);
|
||||
setChoice(playerA, true); // confirm use of Anrakyr's ability
|
||||
setChoice(playerA, EVERFLOWING_CHALICE); // select Everflowing Chalice to cast
|
||||
setChoice(playerA, true); // confirm casting spell by paying life instead of mana
|
||||
|
||||
// multikicker 0
|
||||
setChoice(playerA, false);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// assert
|
||||
assertLife(playerA, 20);
|
||||
assertPermanentCount(playerA, EVERFLOWING_CHALICE, 1);
|
||||
assertCounterCount(playerA, EVERFLOWING_CHALICE, CounterType.CHARGE, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEverflowingChaliceMultikicker1NoMana_ShouldNotCast() {
|
||||
// prepare
|
||||
addCard(Zone.BATTLEFIELD, playerA, ANRAKYR);
|
||||
addCard(Zone.HAND, playerA, EVERFLOWING_CHALICE);
|
||||
|
||||
// execute
|
||||
attack(1, playerA, ANRAKYR);
|
||||
setChoice(playerA, true); // confirm use of Anrakyr's ability
|
||||
setChoice(playerA, EVERFLOWING_CHALICE); // select Everflowing Chalice to cast
|
||||
setChoice(playerA, true); // confirm casting spell by paying life instead of mana
|
||||
|
||||
// multikicker 1
|
||||
setChoice(playerA, true);
|
||||
setChoice(playerA, false);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
// assert
|
||||
assertLife(playerA, 20);
|
||||
assertPermanentCount(playerA, EVERFLOWING_CHALICE, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -1292,7 +1292,22 @@ public final class CardUtil {
|
|||
void addCard(Card card, Ability source, Game game);
|
||||
}
|
||||
|
||||
private static List<Card> getCastableComponents(Card cardToCast, FilterCard filter, Ability source, Player player, Game game, SpellCastTracker spellCastTracker, boolean playLand) {
|
||||
/**
|
||||
* Retrieves a list of all castable components from a given card based on certain conditions.
|
||||
*
|
||||
* Castable components are parts of a card that can be played or cast,
|
||||
* such as the adventure and main side of adventure spells or both sides of a fuse card.
|
||||
*
|
||||
* @param cardToCast
|
||||
* @param filter A filter to determine if a card is eligible for casting.
|
||||
* @param source The ability or source responsible for the casting.
|
||||
* @param player
|
||||
* @param game
|
||||
* @param spellCastTracker An optional tracker for spell casting.
|
||||
* @param playLand A boolean flag indicating whether playing lands is allowed.
|
||||
* @return A list of castable components from the input card, considering the provided conditions.
|
||||
*/
|
||||
public static List<Card> getCastableComponents(Card cardToCast, FilterCard filter, Ability source, Player player, Game game, SpellCastTracker spellCastTracker, boolean playLand) {
|
||||
UUID playerId = player.getId();
|
||||
List<Card> cards = new ArrayList<>();
|
||||
if (cardToCast instanceof CardWithHalves) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue