* Fixed spell's target still legal handling check as it begins to resolve (fixes problem of Sublime Epiphany #6646).

This commit is contained in:
LevelX2 2020-07-01 12:57:29 +02:00
parent ae165e5197
commit 41abefa8e4
2 changed files with 112 additions and 24 deletions

View file

@ -0,0 +1,61 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.mage.test.cards.modal;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentToken;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class OneOrMoreTest extends CardTestPlayerBase {
/**
* Sublime Epiphany can bounce and
* copy the same creature. This is because legality of targets is checked
* only as the spell begins to resolve, not in between modes, and because
* the games can use last known info of the legal target.
*/
@Test
public void testSubtleStrikeFirstMode() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
// Choose one or more
// 1 Counter target spell
// 2 Counter target activated or triggered ability.
// 3 Return target nonland permanent to its owner's hand.
// 4 Create a token that's a copy of target creature you control.
// 5 Target player draws a card.
addCard(Zone.HAND, playerA, "Sublime Epiphany"); // Instant {4}{U}{U}
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sublime Epiphany", "Silvercoat Lion");
setModeChoice(playerA, "3");
setModeChoice(playerA, "4");
addTarget(playerA, "Silvercoat Lion");
setModeChoice(playerA, "5");
addTarget(playerA, playerB);
setModeChoice(playerA, null);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertHandCount(playerB, 1);
assertHandCount(playerA, "Silvercoat Lion", 1);
assertPowerToughness(playerA, "Silvercoat Lion", 2, 2);
Permanent perm = getPermanent("Silvercoat Lion");
Assert.assertTrue("Silvercoat Lion has to be a Token", perm instanceof PermanentToken);
}
}

View file

@ -33,6 +33,7 @@ import mage.util.GameLog;
import mage.util.SubTypeList; import mage.util.SubTypeList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -219,15 +220,14 @@ public class Spell extends StackObjImpl implements Card {
// resolve if legal parts // resolve if legal parts
if (notTargeted || legalParts) { if (notTargeted || legalParts) {
for (SpellAbility spellAbility : this.spellAbilities) { for (SpellAbility spellAbility : this.spellAbilities) {
if (spellAbilityHasLegalParts(spellAbility, game)) { // legality of targets is checked only as the spell begins to resolve, not in between modes (spliced spells handeled correctly?)
if (spellAbilityCheckTargetsAndDeactivateModes(spellAbility, game)) {
for (UUID modeId : spellAbility.getModes().getSelectedModes()) { for (UUID modeId : spellAbility.getModes().getSelectedModes()) {
spellAbility.getModes().setActiveMode(modeId); spellAbility.getModes().setActiveMode(modeId);
if (spellAbility.getTargets().stillLegal(spellAbility, game)) { if (spellAbility.getSpellAbilityType() != SpellAbilityType.SPLICE) {
if (spellAbility.getSpellAbilityType() != SpellAbilityType.SPLICE) { updateOptionalCosts(index);
updateOptionalCosts(index);
}
result |= spellAbility.resolve(game);
} }
result |= spellAbility.resolve(game);
} }
index++; index++;
} }
@ -318,6 +318,31 @@ public class Spell extends StackObjImpl implements Card {
} }
} }
/**
* Legality of the targets of all modes are only checked as the spell begins to resolve
* A mode without any legal target (if it has targets at all) won't resolve.
* So modes with targets without legal targets are unselected.
*
* @param spellAbility
* @param game
* @return
*/
private boolean spellAbilityCheckTargetsAndDeactivateModes(SpellAbility spellAbility, Game game) {
boolean legalModes = false;
for (Iterator<UUID> iterator = spellAbility.getModes().getSelectedModes().iterator(); iterator.hasNext();) {
UUID nextSelectedModeId = iterator.next();
Mode mode = spellAbility.getModes().get(nextSelectedModeId);
if (!mode.getTargets().isEmpty()) {
if (!mode.getTargets().stillLegal(spellAbility, game)) {
iterator.remove();
continue;
}
}
legalModes = true;
}
return legalModes;
}
private boolean spellAbilityHasLegalParts(SpellAbility spellAbility, Game game) { private boolean spellAbilityHasLegalParts(SpellAbility spellAbility, Game game) {
if (spellAbility.getModes().getSelectedModes().size() > 1) { if (spellAbility.getModes().getSelectedModes().size() > 1) {
boolean targetedMode = false; boolean targetedMode = false;
@ -606,7 +631,9 @@ public class Spell extends StackObjImpl implements Card {
spellAbilities.add(spellAbility); spellAbilities.add(spellAbility);
} }
@Override
public void addAbility(Ability ability) { public void addAbility(Ability ability) {
throw new UnsupportedOperationException("Not supported.");
} }
@Override @Override
@ -709,7 +736,7 @@ public class Spell extends StackObjImpl implements Card {
} }
public Spell copySpell(UUID newController) { public Spell copySpell(UUID newController) {
Spell copy = new Spell(this.card, this.ability.copySpell(), this.controllerId, this.fromZone); Spell spellCopy = new Spell(this.card, this.ability.copySpell(), this.controllerId, this.fromZone);
boolean firstDone = false; boolean firstDone = false;
for (SpellAbility spellAbility : this.getSpellAbilities()) { for (SpellAbility spellAbility : this.getSpellAbilities()) {
if (!firstDone) { if (!firstDone) {
@ -718,11 +745,11 @@ public class Spell extends StackObjImpl implements Card {
} }
SpellAbility newAbility = spellAbility.copy(); // e.g. spliced spell SpellAbility newAbility = spellAbility.copy(); // e.g. spliced spell
newAbility.newId(); newAbility.newId();
copy.addSpellAbility(newAbility); spellCopy.addSpellAbility(newAbility);
} }
copy.setCopy(true, this); spellCopy.setCopy(true, this);
copy.setControllerId(newController); spellCopy.setControllerId(newController);
return copy; return spellCopy;
} }
@Override @Override
@ -947,12 +974,12 @@ public class Spell extends StackObjImpl implements Card {
} }
if (isTransformable()) { if (isTransformable()) {
Card secondCard = getSecondCardFace(); Card secondCard = getSecondCardFace();
ObjectColor color = secondCard.getColor(null); ObjectColor objectColor = secondCard.getColor(null);
mana.setBlack(mana.isBlack() || color.isBlack()); mana.setBlack(mana.isBlack() || objectColor.isBlack());
mana.setGreen(mana.isGreen() || color.isGreen()); mana.setGreen(mana.isGreen() || objectColor.isGreen());
mana.setRed(mana.isRed() || color.isRed()); mana.setRed(mana.isRed() || objectColor.isRed());
mana.setBlue(mana.isBlue() || color.isBlue()); mana.setBlue(mana.isBlue() || objectColor.isBlue());
mana.setWhite(mana.isWhite() || color.isWhite()); mana.setWhite(mana.isWhite() || objectColor.isWhite());
for (String rule : secondCard.getRules()) { for (String rule : secondCard.getRules()) {
rule = rule.replaceAll("(?i)<i.*?</i>", ""); // Ignoring reminder text in italic rule = rule.replaceAll("(?i)<i.*?</i>", ""); // Ignoring reminder text in italic
if (!mana.isBlack() && rule.matches(regexBlack)) { if (!mana.isBlack() && rule.matches(regexBlack)) {
@ -1006,21 +1033,21 @@ public class Spell extends StackObjImpl implements Card {
@Override @Override
public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) { public StackObject createCopyOnStack(Game game, Ability source, UUID newControllerId, boolean chooseNewTargets, int amount) {
Spell copy = null; Spell spellCopy = null;
GameEvent gameEvent = GameEvent.getEvent(EventType.COPY_STACKOBJECT, this.getId(), source.getSourceId(), newControllerId, amount); GameEvent gameEvent = GameEvent.getEvent(EventType.COPY_STACKOBJECT, this.getId(), source.getSourceId(), newControllerId, amount);
if (game.replaceEvent(gameEvent)) { if (game.replaceEvent(gameEvent)) {
return null; return null;
} }
for (int i = 0; i < gameEvent.getAmount(); i++) { for (int i = 0; i < gameEvent.getAmount(); i++) {
copy = this.copySpell(newControllerId); spellCopy = this.copySpell(newControllerId);
game.getState().setZone(copy.getId(), Zone.STACK); // required for targeting ex: Nivmagus Elemental game.getState().setZone(spellCopy.getId(), Zone.STACK); // required for targeting ex: Nivmagus Elemental
game.getStack().push(copy); game.getStack().push(spellCopy);
if (chooseNewTargets) { if (chooseNewTargets) {
copy.chooseNewTargets(game, newControllerId); spellCopy.chooseNewTargets(game, newControllerId);
} }
game.fireEvent(new GameEvent(EventType.COPIED_STACKOBJECT, copy.getId(), this.getId(), newControllerId)); game.fireEvent(new GameEvent(EventType.COPIED_STACKOBJECT, spellCopy.getId(), this.getId(), newControllerId));
} }
return copy; return spellCopy;
} }
@Override @Override