mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
[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", 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'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("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("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", 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));
|
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