[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:
Aquid 2023-10-25 01:03:44 +02:00 committed by GitHub
parent 5e3c0764a5
commit 5b845ef7e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 371 additions and 1 deletions

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

View file

@ -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("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("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("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 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("Arcane Signet", 227, Rarity.COMMON, mage.cards.a.ArcaneSignet.class));
cards.add(new SetCardInfo("Arco-Flagellant", 29, Rarity.RARE, mage.cards.a.ArcoFlagellant.class)); cards.add(new SetCardInfo("Arco-Flagellant", 29, Rarity.RARE, mage.cards.a.ArcoFlagellant.class));

View file

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

View file

@ -1292,7 +1292,22 @@ public final class CardUtil {
void addCard(Card card, Ability source, Game game); 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(); UUID playerId = player.getId();
List<Card> cards = new ArrayList<>(); List<Card> cards = new ArrayList<>();
if (cardToCast instanceof CardWithHalves) { if (cardToCast instanceof CardWithHalves) {