1
0
Fork 0
mirror of https://github.com/correl/mage.git synced 2025-04-10 17:00:08 -09:00

* Fixed a bug where wrongly a card object was moved for a copied spell.

This commit is contained in:
LevelX2 2015-11-26 00:48:28 +01:00
parent 69d3bc0575
commit e51c4d5f67
9 changed files with 142 additions and 39 deletions
Mage.Sets/src/mage/sets
commander2013
magic2013
returntoravnica
saviorsofkamigawa
visions
Mage.Tests/src/test/java/org/mage/test
Mage/src/mage
abilities/effects/common
players

View file

@ -30,6 +30,7 @@ package mage.sets.commander2013;
import java.util.UUID; import java.util.UUID;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CopyTargetSpellEffect; import mage.abilities.effects.common.CopyTargetSpellEffect;
import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.DoIfCostPaid;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -44,6 +45,7 @@ import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType; import mage.game.events.GameEvent.EventType;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.target.TargetSpell; import mage.target.TargetSpell;
import mage.target.targetpointer.FixedTarget;
/** /**
* *
@ -71,7 +73,6 @@ public class Mirari extends CardImpl {
} }
} }
class MirariTriggeredAbility extends TriggeredAbilityImpl { class MirariTriggeredAbility extends TriggeredAbilityImpl {
private static final FilterSpell filter = new FilterSpell(); private static final FilterSpell filter = new FilterSpell();
@ -106,8 +107,9 @@ class MirariTriggeredAbility extends TriggeredAbilityImpl {
if (event.getPlayerId().equals(this.getControllerId())) { if (event.getPlayerId().equals(this.getControllerId())) {
Spell spell = game.getStack().getSpell(event.getTargetId()); Spell spell = game.getStack().getSpell(event.getTargetId());
if (isControlledInstantOrSorcery(spell)) { if (isControlledInstantOrSorcery(spell)) {
this.getTargets().get(0).clearChosen(); for (Effect effect : getEffects()) {
this.getTargets().get(0).add(spell.getId(), game); effect.setTargetPointer(new FixedTarget(spell.getId()));
}
return true; return true;
} }
} }
@ -115,9 +117,9 @@ class MirariTriggeredAbility extends TriggeredAbilityImpl {
} }
private boolean isControlledInstantOrSorcery(Spell spell) { private boolean isControlledInstantOrSorcery(Spell spell) {
return spell != null && return spell != null
(spell.getControllerId().equals(this.getControllerId())) && && (spell.getControllerId().equals(this.getControllerId()))
(spell.getCardType().contains(CardType.INSTANT) || spell.getCardType().contains(CardType.SORCERY)); && (spell.getCardType().contains(CardType.INSTANT) || spell.getCardType().contains(CardType.SORCERY));
} }
@Override @Override

View file

@ -28,14 +28,14 @@
package mage.sets.magic2013; package mage.sets.magic2013;
import java.util.UUID; import java.util.UUID;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileSpellEffect; import mage.abilities.effects.common.ExileSpellEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.predicate.Predicates; 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}"); super(ownerId, 68, "Spelltwine", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{5}{U}");
this.expansionSetCode = "M13"; 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. // 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().addEffect(new SpelltwineEffect());
this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter)); this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter));
@ -105,10 +104,10 @@ class SpelltwineEffect extends OneShotEffect {
Card cardTwo = game.getCard(source.getTargets().get(1).getFirstTarget()); Card cardTwo = game.getCard(source.getTargets().get(1).getFirstTarget());
if (controller != null) { if (controller != null) {
if (cardOne != null) { if (cardOne != null) {
controller.moveCardToExileWithInfo(cardOne, null, "", source.getSourceId(), game, Zone.GRAVEYARD, true); controller.moveCards(cardOne, Zone.EXILED, source, game);
} }
if (cardTwo != null) { if (cardTwo != null) {
controller.moveCardToExileWithInfo(cardTwo, null, "", source.getSourceId(), game, Zone.GRAVEYARD, true); controller.moveCards(cardTwo, Zone.EXILED, source, game);
} }
boolean castCardOne = true; boolean castCardOne = true;
if (cardOne != null && controller.chooseUse(Outcome.Neutral, "Cast the copy of " + cardOne.getName() + " first?", source, game)) { if (cardOne != null && controller.chooseUse(Outcome.Neutral, "Cast the copy of " + cardOne.getName() + " first?", source, game)) {

View file

@ -27,7 +27,6 @@
*/ */
package mage.sets.returntoravnica; package mage.sets.returntoravnica;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import mage.constants.CardType; import mage.constants.CardType;
@ -61,7 +60,6 @@ public class IzzetStaticaster extends CardImpl {
this.subtype.add("Human"); this.subtype.add("Human");
this.subtype.add("Wizard"); this.subtype.add("Wizard");
this.power = new MageInt(0); this.power = new MageInt(0);
this.toughness = new MageInt(3); this.toughness = new MageInt(3);

View file

@ -56,7 +56,7 @@ public class PithingNeedle extends CardImpl {
// As Pithing Needle enters the battlefield, name a card. // As Pithing Needle enters the battlefield, name a card.
this.addAbility(new AsEntersBattlefieldAbility(new NameACardEffect(NameACardEffect.TypeOfName.ALL))); 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. // 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())); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PithingNeedleEffect()));
} }
@ -92,16 +92,19 @@ class PithingNeedleEffect extends ContinuousRuleModifyingEffectImpl {
return true; return true;
} }
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ACTIVATE_ABILITY;
}
@Override @Override
public boolean applies(GameEvent event, Ability source, Game game) { public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getType() == GameEvent.EventType.ACTIVATE_ABILITY) { MageObject object = game.getObject(event.getSourceId());
MageObject object = game.getObject(event.getSourceId()); Ability ability = game.getAbility(event.getTargetId(), event.getSourceId());
Ability ability = game.getAbility(event.getTargetId(), event.getSourceId()); if (ability != null && object != null) {
if (ability != null && object != null) { if (!ability.getAbilityType().equals(AbilityType.MANA)
if (ability.getAbilityType() != AbilityType.MANA && && object.getName().equals(game.getState().getValue(source.getSourceId().toString() + NameACardEffect.INFO_KEY))) {
object.getName().equals(game.getState().getValue(source.getSourceId().toString() + NameACardEffect.INFO_KEY))) { return true;
return true;
}
} }
} }
return false; return false;

View file

@ -46,7 +46,6 @@ public class Impulse extends CardImpl {
super(ownerId, 34, "Impulse", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{U}"); super(ownerId, 34, "Impulse", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{U}");
this.expansionSetCode = "VIS"; 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. // 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)); this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect(new StaticValue(4), false, new StaticValue(1), new FilterCard(), Zone.LIBRARY, false, false));

View file

@ -25,7 +25,6 @@
* authors and should not be interpreted as representing official policies, either expressed * authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com. * or implied, of BetaSteward_at_googlemail.com.
*/ */
package org.mage.test.cards.copy; package org.mage.test.cards.copy;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -39,15 +38,13 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
*/ */
public class SpelltwineTest extends CardTestPlayerBase { public class SpelltwineTest extends CardTestPlayerBase {
/** /**
* Spelltwine * Spelltwine Sorcery, 5U (6) Exile target instant or sorcery card from your
* Sorcery, 5U (6) * graveyard and target instant or sorcery card from an opponent's
* Exile target instant or sorcery card from your graveyard and target instant * graveyard. Copy those cards. Cast the copies if able without paying their
* or sorcery card from an opponent's graveyard. Copy those cards. Cast the * mana costs. Exile Spelltwine.
* copies if able without paying their mana costs. Exile Spelltwine. *
* */
*/
@Test @Test
public void testCopyCards() { public void testCopyCards() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 6); addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
@ -66,7 +63,55 @@ public class SpelltwineTest extends CardTestPlayerBase {
assertExileCount("Lightning Bolt", 1); assertExileCount("Lightning Bolt", 1);
assertExileCount("Shock", 1); assertExileCount("Shock", 1);
assertLife(playerB, 15); 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);
} }
} }

View file

@ -97,6 +97,8 @@ import mage.target.TargetSource;
import mage.target.TargetSpell; import mage.target.TargetSpell;
import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInHand;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetCardInOpponentsGraveyard;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.common.TargetCreaturePermanentAmount; import mage.target.common.TargetCreaturePermanentAmount;
import mage.target.common.TargetPermanentOrPlayer; import mage.target.common.TargetPermanentOrPlayer;
import mage.util.MessageToClient; 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) { if (target instanceof TargetSpell) {
for (String targetDefinition : targets) { for (String targetDefinition : targets) {

View file

@ -34,6 +34,7 @@ import mage.cards.Card;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player; import mage.players.Player;
/** /**
@ -62,9 +63,12 @@ public class ExileSpellEffect extends OneShotEffect implements MageSingleton {
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null) { if (controller != null) {
Card spellCard = game.getStack().getSpell(source.getSourceId()).getCard(); Spell spell = game.getStack().getSpell(source.getId());
if (spellCard != null) { if (spell != null && !spell.isCopiedSpell()) {
controller.moveCards(spellCard, Zone.EXILED, source, game); Card spellCard = spell.getCard();
if (spellCard != null) {
controller.moveCards(spellCard, Zone.EXILED, source, game);
}
} }
return true; return true;
} }

View file

@ -3299,7 +3299,7 @@ public abstract class PlayerImpl implements Player, Serializable {
card = game.getCard(card.getId()); card = game.getCard(card.getId());
} }
StringBuilder sb = new StringBuilder(this.getLogName()) 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) + " " : ""); .append(fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) + " " : "");
if (card.getOwnerId().equals(getId())) { if (card.getOwnerId().equals(getId())) {
sb.append("into his or her graveyard"); sb.append("into his or her graveyard");