* 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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@ -219,15 +220,14 @@ public class Spell extends StackObjImpl implements Card {
// resolve if legal parts
if (notTargeted || legalParts) {
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()) {
spellAbility.getModes().setActiveMode(modeId);
if (spellAbility.getTargets().stillLegal(spellAbility, game)) {
if (spellAbility.getSpellAbilityType() != SpellAbilityType.SPLICE) {
updateOptionalCosts(index);
}
result |= spellAbility.resolve(game);
if (spellAbility.getSpellAbilityType() != SpellAbilityType.SPLICE) {
updateOptionalCosts(index);
}
result |= spellAbility.resolve(game);
}
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) {
if (spellAbility.getModes().getSelectedModes().size() > 1) {
boolean targetedMode = false;
@ -606,7 +631,9 @@ public class Spell extends StackObjImpl implements Card {
spellAbilities.add(spellAbility);
}
@Override
public void addAbility(Ability ability) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
@ -709,7 +736,7 @@ public class Spell extends StackObjImpl implements Card {
}
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;
for (SpellAbility spellAbility : this.getSpellAbilities()) {
if (!firstDone) {
@ -718,11 +745,11 @@ public class Spell extends StackObjImpl implements Card {
}
SpellAbility newAbility = spellAbility.copy(); // e.g. spliced spell
newAbility.newId();
copy.addSpellAbility(newAbility);
spellCopy.addSpellAbility(newAbility);
}
copy.setCopy(true, this);
copy.setControllerId(newController);
return copy;
spellCopy.setCopy(true, this);
spellCopy.setControllerId(newController);
return spellCopy;
}
@Override
@ -947,12 +974,12 @@ public class Spell extends StackObjImpl implements Card {
}
if (isTransformable()) {
Card secondCard = getSecondCardFace();
ObjectColor color = secondCard.getColor(null);
mana.setBlack(mana.isBlack() || color.isBlack());
mana.setGreen(mana.isGreen() || color.isGreen());
mana.setRed(mana.isRed() || color.isRed());
mana.setBlue(mana.isBlue() || color.isBlue());
mana.setWhite(mana.isWhite() || color.isWhite());
ObjectColor objectColor = secondCard.getColor(null);
mana.setBlack(mana.isBlack() || objectColor.isBlack());
mana.setGreen(mana.isGreen() || objectColor.isGreen());
mana.setRed(mana.isRed() || objectColor.isRed());
mana.setBlue(mana.isBlue() || objectColor.isBlue());
mana.setWhite(mana.isWhite() || objectColor.isWhite());
for (String rule : secondCard.getRules()) {
rule = rule.replaceAll("(?i)<i.*?</i>", ""); // Ignoring reminder text in italic
if (!mana.isBlack() && rule.matches(regexBlack)) {
@ -1006,21 +1033,21 @@ public class Spell extends StackObjImpl implements Card {
@Override
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);
if (game.replaceEvent(gameEvent)) {
return null;
}
for (int i = 0; i < gameEvent.getAmount(); i++) {
copy = this.copySpell(newControllerId);
game.getState().setZone(copy.getId(), Zone.STACK); // required for targeting ex: Nivmagus Elemental
game.getStack().push(copy);
spellCopy = this.copySpell(newControllerId);
game.getState().setZone(spellCopy.getId(), Zone.STACK); // required for targeting ex: Nivmagus Elemental
game.getStack().push(spellCopy);
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