forked from External/mage
[SPM] Implement Lady Octopus, Inspired Inventor
This commit is contained in:
parent
5bb8ff2c7f
commit
e7636fb17d
5 changed files with 374 additions and 0 deletions
60
Mage.Sets/src/mage/cards/l/LadyOctopusInspiredInventor.java
Normal file
60
Mage.Sets/src/mage/cards/l/LadyOctopusInspiredInventor.java
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package mage.cards.l;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.DrawNthOrNthCardTriggeredAbility;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.effects.common.cost.CastFromHandForFreeEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.common.FilterArtifactCard;
|
||||
import mage.filter.predicate.mageobject.ManaValueCompareToCountersSourceCountPredicate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jmlundeen
|
||||
*/
|
||||
public final class LadyOctopusInspiredInventor extends CardImpl {
|
||||
|
||||
private static final FilterCard filter = new FilterArtifactCard("an artifact spell from your hand with mana value less than or " +
|
||||
"equal to the number of ingenuity counters on {this}");
|
||||
|
||||
static {
|
||||
filter.add(new ManaValueCompareToCountersSourceCountPredicate(CounterType.INGENUITY, ComparisonType.OR_LESS));
|
||||
}
|
||||
|
||||
public LadyOctopusInspiredInventor(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.SCIENTIST);
|
||||
this.subtype.add(SubType.VILLAIN);
|
||||
this.power = new MageInt(0);
|
||||
this.toughness = new MageInt(2);
|
||||
|
||||
// Whenever you draw your first or second card each turn, put an ingenuity counter on Lady Octopus.
|
||||
this.addAbility(new DrawNthOrNthCardTriggeredAbility(new AddCountersSourceEffect(CounterType.INGENUITY.createInstance())));
|
||||
|
||||
// {T}: You may cast an artifact spell from your hand with mana value less than or equal to the number of ingenuity counters on Lady Octopus without paying its mana cost.
|
||||
this.addAbility(new SimpleActivatedAbility(new CastFromHandForFreeEffect(filter), new TapSourceCost()));
|
||||
}
|
||||
|
||||
private LadyOctopusInspiredInventor(final LadyOctopusInspiredInventor card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LadyOctopusInspiredInventor copy() {
|
||||
return new LadyOctopusInspiredInventor(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -90,6 +90,8 @@ public final class MarvelsSpiderMan extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Kraven's Last Hunt", 105, Rarity.RARE, mage.cards.k.KravensLastHunt.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kraven's Last Hunt", 226, Rarity.RARE, mage.cards.k.KravensLastHunt.class, FULL_ART_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Kraven, Proud Predator", 132, Rarity.UNCOMMON, mage.cards.k.KravenProudPredator.class));
|
||||
cards.add(new SetCardInfo("Lady Octopus, Inspired Inventor", 252, Rarity.RARE, mage.cards.l.LadyOctopusInspiredInventor.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Lady Octopus, Inspired Inventor", 35, Rarity.RARE, mage.cards.l.LadyOctopusInspiredInventor.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Lurking Lizards", 107, Rarity.COMMON, mage.cards.l.LurkingLizards.class));
|
||||
cards.add(new SetCardInfo("Mary Jane Watson", 134, Rarity.RARE, mage.cards.m.MaryJaneWatson.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("Mary Jane Watson", 229, Rarity.RARE, mage.cards.m.MaryJaneWatson.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
package org.mage.test.cards.single.spm;
|
||||
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.UntapAllControllerEffect;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jmlundeen
|
||||
*/
|
||||
public class LadyOctopusInspiredInventorTest extends CardTestPlayerBase {
|
||||
|
||||
/*
|
||||
Lady Octopus, Inspired Inventor
|
||||
{U}
|
||||
Legendary Creature - Human Scientist Villain
|
||||
Whenever you draw your first or second card each turn, put an ingenuity counter on Lady Octopus.
|
||||
{T}: You may cast an artifact spell from your hand with mana value less than or equal to the number of ingenuity counters on Lady Octopus without paying its mana cost.
|
||||
*/
|
||||
private static final String ladyOctopusInspiredInventor = "Lady Octopus, Inspired Inventor";
|
||||
|
||||
/*
|
||||
Aether Vial
|
||||
{1}
|
||||
Artifact
|
||||
At the beginning of your upkeep, you may put a charge counter on Aether Vial.
|
||||
{T}: You may put a creature card with converted mana cost equal to the number of charge counters on ther Vial from your hand onto the battlefield.
|
||||
*/
|
||||
private static final String aetherVial = "Aether Vial";
|
||||
|
||||
/*
|
||||
Tormod's Crypt
|
||||
{0}
|
||||
Artifact
|
||||
{tap}, Sacrifice Tormod's Crypt: Exile all cards from target player's graveyard.
|
||||
*/
|
||||
private static final String tormodsCrypt = "Tormod's Crypt";
|
||||
|
||||
/*
|
||||
Howling Mine
|
||||
{2}
|
||||
Artifact
|
||||
At the beginning of each player's draw step, if Howling Mine is untapped, that player draws an additional card.
|
||||
*/
|
||||
private static final String howlingMine = "Howling Mine";
|
||||
|
||||
@Test
|
||||
public void testLadyOctopusInspiredInventor() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCustomCardWithAbility("untap all creatures", playerA, new SimpleActivatedAbility(
|
||||
new UntapAllControllerEffect(new FilterCreaturePermanent()),
|
||||
new ManaCostsImpl<>("")
|
||||
));
|
||||
addCustomCardWithAbility("draw a card", playerA, new SimpleActivatedAbility(
|
||||
new DrawCardSourceControllerEffect(1),
|
||||
new ManaCostsImpl<>("")
|
||||
));
|
||||
addCard(Zone.BATTLEFIELD, playerA, ladyOctopusInspiredInventor);
|
||||
addCard(Zone.HAND, playerA, aetherVial);
|
||||
addCard(Zone.HAND, playerA, tormodsCrypt);
|
||||
addCard(Zone.HAND, playerA, howlingMine);
|
||||
|
||||
activateDrawCardAndUntap(); // Tormod's crypt
|
||||
activateDrawCardAndUntap(); // Aether Vial
|
||||
activateDrawCardAndUntap(); // Howling Mine
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertCounterCount(playerA, ladyOctopusInspiredInventor, CounterType.INGENUITY, 2);
|
||||
assertHandCount(playerA, 3);
|
||||
}
|
||||
|
||||
private void activateDrawCardAndUntap() {
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: You may cast");
|
||||
setChoice(playerA, true);
|
||||
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "draw a");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLadyOctopusInspiredInventorChoose() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCustomCardWithAbility("draw a card", playerA, new SimpleActivatedAbility(
|
||||
new DrawCardSourceControllerEffect(3),
|
||||
new ManaCostsImpl<>("")
|
||||
));
|
||||
addCard(Zone.BATTLEFIELD, playerA, ladyOctopusInspiredInventor);
|
||||
addCard(Zone.HAND, playerA, aetherVial);
|
||||
addCard(Zone.HAND, playerA, tormodsCrypt);
|
||||
addCard(Zone.HAND, playerA, howlingMine);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "draw ");
|
||||
setChoice(playerA, "Whenever you draw your first"); // trigger stack
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: You may cast");
|
||||
setChoice(playerA, tormodsCrypt);
|
||||
setChoice(playerA, true);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertCounterCount(playerA, ladyOctopusInspiredInventor, CounterType.INGENUITY, 2);
|
||||
assertHandCount(playerA, 3 + 2);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.dynamicvalue.common.CardsDrawnThisTurnDynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.hint.ValueHint;
|
||||
import mage.constants.TargetController;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.util.CardUtil;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class DrawNthOrNthCardTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
private static final Hint hint = new ValueHint(
|
||||
"Cards drawn this turn", CardsDrawnThisTurnDynamicValue.instance
|
||||
);
|
||||
private final TargetController targetController;
|
||||
private final int firstCardNumber;
|
||||
private final int secondCardNumber;
|
||||
|
||||
public DrawNthOrNthCardTriggeredAbility(Effect effect) {
|
||||
this(effect, false);
|
||||
}
|
||||
|
||||
public DrawNthOrNthCardTriggeredAbility(Effect effect, boolean optional) {
|
||||
this(effect, optional, 1);
|
||||
}
|
||||
|
||||
public DrawNthOrNthCardTriggeredAbility(Effect effect, boolean optional, int firstCardNumber) {
|
||||
this(effect, optional, TargetController.YOU, firstCardNumber);
|
||||
}
|
||||
|
||||
public DrawNthOrNthCardTriggeredAbility(Effect effect, boolean optional, TargetController targetController, int firstCardNumber) {
|
||||
this(Zone.BATTLEFIELD, effect, optional, targetController, firstCardNumber, 2);
|
||||
}
|
||||
|
||||
public DrawNthOrNthCardTriggeredAbility(Zone zone, Effect effect, boolean optional, TargetController targetController, int firstCardNumber, int secondCardNumber) {
|
||||
super(zone, effect, optional);
|
||||
this.targetController = targetController;
|
||||
this.firstCardNumber = firstCardNumber;
|
||||
this.secondCardNumber = secondCardNumber;
|
||||
if (targetController == TargetController.YOU) {
|
||||
this.addHint(hint);
|
||||
}
|
||||
setTriggerPhrase(generateTriggerPhrase());
|
||||
this.addWatcher(new DrawNthOrNthCardWatcher());
|
||||
}
|
||||
|
||||
protected DrawNthOrNthCardTriggeredAbility(final DrawNthOrNthCardTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.targetController = ability.targetController;
|
||||
this.firstCardNumber = ability.firstCardNumber;
|
||||
this.secondCardNumber = ability.secondCardNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.DREW_CARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
switch (targetController) {
|
||||
case YOU:
|
||||
if (!isControlledBy(event.getPlayerId())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case ACTIVE:
|
||||
if (!game.isActivePlayer(event.getPlayerId())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPPONENT:
|
||||
if (!game.getOpponents(getControllerId()).contains(event.getPlayerId())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case ANY:
|
||||
// Doesn't matter who
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("TargetController " + targetController + " not supported");
|
||||
}
|
||||
int drawnCards = DrawNthOrNthCardWatcher.checkEvent(event.getPlayerId(), event.getId(), game) + 1;
|
||||
return drawnCards == firstCardNumber || drawnCards == secondCardNumber;
|
||||
}
|
||||
|
||||
public String generateTriggerPhrase() {
|
||||
String numberText = CardUtil.numberToOrdinalText(firstCardNumber) + " or " + CardUtil.numberToOrdinalText(secondCardNumber);
|
||||
switch (targetController) {
|
||||
case YOU:
|
||||
return "Whenever you draw your " + numberText + " card each turn, ";
|
||||
case ACTIVE:
|
||||
return "Whenever a player draws their " + numberText + " card during their turn, ";
|
||||
case OPPONENT:
|
||||
return "Whenever an opponent draws their " + numberText + " card each turn, ";
|
||||
case ANY:
|
||||
return "Whenever a player draws their " + numberText + " card each turn, ";
|
||||
default:
|
||||
throw new IllegalArgumentException("TargetController " + targetController + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DrawNthOrNthCardTriggeredAbility copy() {
|
||||
return new DrawNthOrNthCardTriggeredAbility(this);
|
||||
}
|
||||
}
|
||||
|
||||
class DrawNthOrNthCardWatcher extends Watcher {
|
||||
|
||||
private final Map<UUID, List<UUID>> playerDrawEventMap = new HashMap<>();
|
||||
|
||||
DrawNthOrNthCardWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.DREW_CARD) {
|
||||
playerDrawEventMap
|
||||
.computeIfAbsent(event.getPlayerId(), x -> new ArrayList<>())
|
||||
.add(event.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
playerDrawEventMap.clear();
|
||||
}
|
||||
|
||||
static int checkEvent(UUID playerId, UUID eventId, Game game) {
|
||||
return game
|
||||
.getState()
|
||||
.getWatcher(DrawNthOrNthCardWatcher.class)
|
||||
.playerDrawEventMap
|
||||
.getOrDefault(playerId, Collections.emptyList())
|
||||
.indexOf(eventId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package mage.filter.predicate.mageobject;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author jmlundeen
|
||||
*/
|
||||
public class ManaValueCompareToCountersSourceCountPredicate implements ObjectSourcePlayerPredicate<MageObject> {
|
||||
|
||||
private final CounterType counterType;
|
||||
private final ComparisonType comparisonType;
|
||||
|
||||
public ManaValueCompareToCountersSourceCountPredicate(CounterType counterType, ComparisonType comparisonType) {
|
||||
this.counterType = counterType;
|
||||
this.comparisonType = comparisonType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) {
|
||||
int counterCount = Optional
|
||||
.ofNullable(input.getSource().getSourcePermanentOrLKI(game))
|
||||
.map(permanent -> permanent.getCounters(game))
|
||||
.map(counters -> counters.getCount(counterType))
|
||||
.orElse(-1); // always false
|
||||
return ComparisonType.compare(input.getObject().getManaValue(), comparisonType, counterCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "mana value " + comparisonType.toString() + " to the number of " + counterType.getName() + " counters on {this}";
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue