diff --git a/Mage.Sets/src/mage/sets/commander2013/Mirari.java b/Mage.Sets/src/mage/sets/commander2013/Mirari.java index a0ed6f66ee..a2129f2259 100644 --- a/Mage.Sets/src/mage/sets/commander2013/Mirari.java +++ b/Mage.Sets/src/mage/sets/commander2013/Mirari.java @@ -30,6 +30,7 @@ package mage.sets.commander2013; import java.util.UUID; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.CopyTargetSpellEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.cards.CardImpl; @@ -44,6 +45,7 @@ import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.stack.Spell; import mage.target.TargetSpell; +import mage.target.targetpointer.FixedTarget; /** * @@ -71,7 +73,6 @@ public class Mirari extends CardImpl { } } - class MirariTriggeredAbility extends TriggeredAbilityImpl { private static final FilterSpell filter = new FilterSpell(); @@ -106,8 +107,9 @@ class MirariTriggeredAbility extends TriggeredAbilityImpl { if (event.getPlayerId().equals(this.getControllerId())) { Spell spell = game.getStack().getSpell(event.getTargetId()); if (isControlledInstantOrSorcery(spell)) { - this.getTargets().get(0).clearChosen(); - this.getTargets().get(0).add(spell.getId(), game); + for (Effect effect : getEffects()) { + effect.setTargetPointer(new FixedTarget(spell.getId())); + } return true; } } @@ -115,9 +117,9 @@ class MirariTriggeredAbility extends TriggeredAbilityImpl { } private boolean isControlledInstantOrSorcery(Spell spell) { - return spell != null && - (spell.getControllerId().equals(this.getControllerId())) && - (spell.getCardType().contains(CardType.INSTANT) || spell.getCardType().contains(CardType.SORCERY)); + return spell != null + && (spell.getControllerId().equals(this.getControllerId())) + && (spell.getCardType().contains(CardType.INSTANT) || spell.getCardType().contains(CardType.SORCERY)); } @Override diff --git a/Mage.Sets/src/mage/sets/magic2013/Spelltwine.java b/Mage.Sets/src/mage/sets/magic2013/Spelltwine.java index b37080dda6..a0cb37881f 100644 --- a/Mage.Sets/src/mage/sets/magic2013/Spelltwine.java +++ b/Mage.Sets/src/mage/sets/magic2013/Spelltwine.java @@ -28,14 +28,14 @@ package mage.sets.magic2013; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ExileSpellEffect; import mage.cards.Card; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; @@ -68,7 +68,6 @@ public class Spelltwine extends CardImpl { super(ownerId, 68, "Spelltwine", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{5}{U}"); this.expansionSetCode = "M13"; - // Exile target instant or sorcery card from your graveyard and target instant or sorcery card from an opponent's graveyard. Copy those cards. Cast the copies if able without paying their mana costs. Exile Spelltwine. this.getSpellAbility().addEffect(new SpelltwineEffect()); this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter)); @@ -105,10 +104,10 @@ class SpelltwineEffect extends OneShotEffect { Card cardTwo = game.getCard(source.getTargets().get(1).getFirstTarget()); if (controller != null) { if (cardOne != null) { - controller.moveCardToExileWithInfo(cardOne, null, "", source.getSourceId(), game, Zone.GRAVEYARD, true); + controller.moveCards(cardOne, Zone.EXILED, source, game); } if (cardTwo != null) { - controller.moveCardToExileWithInfo(cardTwo, null, "", source.getSourceId(), game, Zone.GRAVEYARD, true); + controller.moveCards(cardTwo, Zone.EXILED, source, game); } boolean castCardOne = true; if (cardOne != null && controller.chooseUse(Outcome.Neutral, "Cast the copy of " + cardOne.getName() + " first?", source, game)) { diff --git a/Mage.Sets/src/mage/sets/returntoravnica/IzzetStaticaster.java b/Mage.Sets/src/mage/sets/returntoravnica/IzzetStaticaster.java index 9b151e7528..321e056a6c 100644 --- a/Mage.Sets/src/mage/sets/returntoravnica/IzzetStaticaster.java +++ b/Mage.Sets/src/mage/sets/returntoravnica/IzzetStaticaster.java @@ -27,7 +27,6 @@ */ package mage.sets.returntoravnica; -import java.util.List; import java.util.UUID; import mage.constants.CardType; @@ -61,7 +60,6 @@ public class IzzetStaticaster extends CardImpl { this.subtype.add("Human"); this.subtype.add("Wizard"); - this.power = new MageInt(0); this.toughness = new MageInt(3); diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/PithingNeedle.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/PithingNeedle.java index 8edb0491e2..c219622eba 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/PithingNeedle.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/PithingNeedle.java @@ -56,7 +56,7 @@ public class PithingNeedle extends CardImpl { // As Pithing Needle enters the battlefield, name a card. this.addAbility(new AsEntersBattlefieldAbility(new NameACardEffect(NameACardEffect.TypeOfName.ALL))); - + // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PithingNeedleEffect())); } @@ -92,16 +92,19 @@ class PithingNeedleEffect extends ContinuousRuleModifyingEffectImpl { return true; } + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ACTIVATE_ABILITY; + } + @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getType() == GameEvent.EventType.ACTIVATE_ABILITY) { - MageObject object = game.getObject(event.getSourceId()); - Ability ability = game.getAbility(event.getTargetId(), event.getSourceId()); - if (ability != null && object != null) { - if (ability.getAbilityType() != AbilityType.MANA && - object.getName().equals(game.getState().getValue(source.getSourceId().toString() + NameACardEffect.INFO_KEY))) { - return true; - } + MageObject object = game.getObject(event.getSourceId()); + Ability ability = game.getAbility(event.getTargetId(), event.getSourceId()); + if (ability != null && object != null) { + if (!ability.getAbilityType().equals(AbilityType.MANA) + && object.getName().equals(game.getState().getValue(source.getSourceId().toString() + NameACardEffect.INFO_KEY))) { + return true; } } return false; diff --git a/Mage.Sets/src/mage/sets/visions/Impulse.java b/Mage.Sets/src/mage/sets/visions/Impulse.java index a259fd3b8e..2a3a2bd0db 100644 --- a/Mage.Sets/src/mage/sets/visions/Impulse.java +++ b/Mage.Sets/src/mage/sets/visions/Impulse.java @@ -46,7 +46,6 @@ public class Impulse extends CardImpl { super(ownerId, 34, "Impulse", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{U}"); this.expansionSetCode = "VIS"; - // Look at the top four cards of your library. Put one of them into your hand and the rest on the bottom of your library in any order. this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect(new StaticValue(4), false, new StaticValue(1), new FilterCard(), Zone.LIBRARY, false, false)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SpelltwineTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SpelltwineTest.java index dc1ed92799..5c788c4d38 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SpelltwineTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SpelltwineTest.java @@ -25,7 +25,6 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package org.mage.test.cards.copy; import mage.constants.PhaseStep; @@ -39,15 +38,13 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class SpelltwineTest extends CardTestPlayerBase { - /** - * Spelltwine - * Sorcery, 5U (6) - * Exile target instant or sorcery card from your graveyard and target instant - * or sorcery card from an opponent's graveyard. Copy those cards. Cast the - * copies if able without paying their mana costs. Exile Spelltwine. - * - */ + * Spelltwine Sorcery, 5U (6) Exile target instant or sorcery card from your + * graveyard and target instant or sorcery card from an opponent's + * graveyard. Copy those cards. Cast the copies if able without paying their + * mana costs. Exile Spelltwine. + * + */ @Test public void testCopyCards() { addCard(Zone.BATTLEFIELD, playerA, "Island", 6); @@ -66,7 +63,55 @@ public class SpelltwineTest extends CardTestPlayerBase { assertExileCount("Lightning Bolt", 1); assertExileCount("Shock", 1); assertLife(playerB, 15); - + + } + + /** + * In a game of Commander, I cast Spelltwine, targeting Impulse and + * Blasphemous Act. This triggered my Mirari, which I paid the 3 and copied + * the Spelltwine. I chose new targets for the copy, naming Path to Exile + * and Shape Anew. Somehow, the original Spelltwine was completely lost + * after this, failing to be in the stack box or resolve all. + */ + @Test + public void testCopyCardsMirari() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 9); + // Exile target instant or sorcery card from your graveyard and target instant or sorcery card from an opponent's graveyard. + // Copy those cards. Cast the copies if able without paying their mana costs. Exile Spelltwine. + addCard(Zone.HAND, playerA, "Spelltwine"); // {5}{U} + // Look at the top four cards of your library. Put one of them into your hand and the rest on the bottom of your library in any order. + addCard(Zone.GRAVEYARD, playerA, "Impulse"); + // You draw two cards and you lose 2 life. + addCard(Zone.GRAVEYARD, playerA, "Night's Whisper"); + // Blasphemous Act costs {1} less to cast for each creature on the battlefield. + // Blasphemous Act deals 13 damage to each creature. + addCard(Zone.GRAVEYARD, playerB, "Blasphemous Act"); + // Draw two cards. + addCard(Zone.GRAVEYARD, playerB, "Divination"); + + // Whenever you cast an instant or sorcery spell, you may pay {3}. If you do, copy that spell. You may choose new targets for the copy. + addCard(Zone.BATTLEFIELD, playerA, "Mirari", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spelltwine"); + addTarget(playerA, "Impulse"); + addTarget(playerA, "Blasphemous Act"); + setChoice(playerA, "Yes"); // pay {3} and copy spell + setChoice(playerA, "Yes"); // Change targets + addTarget(playerA, "Night's Whisper"); + addTarget(playerA, "Divination"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount("Impulse", 1); + assertExileCount("Blasphemous Act", 1); + assertExileCount("Spelltwine", 1); + assertExileCount("Night's Whisper", 1); + assertExileCount("Divination", 1); + + assertHandCount(playerA, 5); + + assertLife(playerA, 18); + assertLife(playerB, 20); + } - } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 1f59b9387a..6950e3c8dd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -97,6 +97,8 @@ import mage.target.TargetSource; import mage.target.TargetSpell; import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCardInOpponentsGraveyard; +import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCreaturePermanentAmount; import mage.target.common.TargetPermanentOrPlayer; import mage.util.MessageToClient; @@ -785,6 +787,57 @@ public class TestPlayer implements Player { } } + } + if (target instanceof TargetCardInYourGraveyard) { + for (String targetDefinition : targets) { + String[] targetList = targetDefinition.split("\\^"); + boolean targetFound = false; + for (String targetName : targetList) { + for (Card card : computerPlayer.getGraveyard().getCards(((TargetCardInYourGraveyard) target).getFilter(), game)) { + if (card.getName().equals(targetName) || (card.getName() + "-" + card.getExpansionSetCode()).equals(targetName)) { + if (((TargetCardInYourGraveyard) target).canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { + target.add(card.getId(), game); + targetFound = true; + break; + } + } + } + } + if (targetFound) { + targets.remove(targetDefinition); + return true; + } + } + + } + if (target instanceof TargetCardInOpponentsGraveyard) { + for (String targetDefinition : targets) { + String[] targetList = targetDefinition.split("\\^"); + boolean targetFound = false; + + for (String targetName : targetList) { + IterateOpponentsGraveyards: + for (UUID opponentId : game.getState().getPlayersInRange(getId(), game)) { + if (computerPlayer.hasOpponent(opponentId, game)) { + Player opponent = game.getPlayer(opponentId); + for (Card card : opponent.getGraveyard().getCards(((TargetCardInOpponentsGraveyard) target).getFilter(), game)) { + if (card.getName().equals(targetName) || (card.getName() + "-" + card.getExpansionSetCode()).equals(targetName)) { + if (((TargetCardInOpponentsGraveyard) target).canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { + target.add(card.getId(), game); + targetFound = true; + break IterateOpponentsGraveyards; + } + } + } + } + } + } + if (targetFound) { + targets.remove(targetDefinition); + return true; + } + } + } if (target instanceof TargetSpell) { for (String targetDefinition : targets) { diff --git a/Mage/src/mage/abilities/effects/common/ExileSpellEffect.java b/Mage/src/mage/abilities/effects/common/ExileSpellEffect.java index f74c0d9fd3..dc53188563 100644 --- a/Mage/src/mage/abilities/effects/common/ExileSpellEffect.java +++ b/Mage/src/mage/abilities/effects/common/ExileSpellEffect.java @@ -34,6 +34,7 @@ import mage.cards.Card; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; +import mage.game.stack.Spell; import mage.players.Player; /** @@ -62,9 +63,12 @@ public class ExileSpellEffect extends OneShotEffect implements MageSingleton { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Card spellCard = game.getStack().getSpell(source.getSourceId()).getCard(); - if (spellCard != null) { - controller.moveCards(spellCard, Zone.EXILED, source, game); + Spell spell = game.getStack().getSpell(source.getId()); + if (spell != null && !spell.isCopiedSpell()) { + Card spellCard = spell.getCard(); + if (spellCard != null) { + controller.moveCards(spellCard, Zone.EXILED, source, game); + } } return true; } diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 5cc9ff2710..2ab0017ffe 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -3299,7 +3299,7 @@ public abstract class PlayerImpl implements Player, Serializable { card = game.getCard(card.getId()); } StringBuilder sb = new StringBuilder(this.getLogName()) - .append(" puts ").append(card.getLogName()).append(" ") + .append(" puts ").append(card.getLogName()).append(" ").append(card.isCopy() ? "(Copy) " : "") .append(fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) + " " : ""); if (card.getOwnerId().equals(getId())) { sb.append("into his or her graveyard");