From 41abefa8e4fd2c3824ccb4f0760adeb7ff532958 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 1 Jul 2020 12:57:29 +0200 Subject: [PATCH] * Fixed spell's target still legal handling check as it begins to resolve (fixes problem of Sublime Epiphany #6646). --- .../mage/test/cards/modal/OneOrMoreTest.java | 61 +++++++++++++++ Mage/src/main/java/mage/game/stack/Spell.java | 75 +++++++++++++------ 2 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java new file mode 100644 index 0000000000..172950663a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java @@ -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); + + } +} diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 11a2562aa7..bfc7d4b24f 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -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 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)", ""); // 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