diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java new file mode 100644 index 00000000000..cee56e63951 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.modal; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class OneOrBothTest extends CardTestPlayerBase { + + @Test + public void testSubtleStrikeFirstMode() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Choose one or both — + // • Target creature gets -1/-1 until end of turn. + // • Put a +1/+1 counter on target creature. + addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Silvercoat Lion"); + setModeChoice(playerA, "1"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 2, 2); + assertPowerToughness(playerB, "Pillarfield Ox", 1, 3); + } + + @Test + public void testSubtleStrikeSecondMode() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Choose one or both — + // • Target creature gets -1/-1 until end of turn. + // • Put a +1/+1 counter on target creature. + addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); + setModeChoice(playerA, "2"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 2, 2); + assertPowerToughness(playerB, "Pillarfield Ox", 3, 5); + } + + @Test + public void testSubtleStrikeBothModes() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Choose one or both — + // • Target creature gets -1/-1 until end of turn. + // • Put a +1/+1 counter on target creature. + addCard(Zone.HAND, playerA, "Subtle Strike"); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); + addTarget(playerA, "Silvercoat Lion"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "2"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 3, 3); + assertPowerToughness(playerB, "Pillarfield Ox", 1, 3); + } +} diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index 08a317f95cf..c8fa0d3cae8 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -49,7 +49,7 @@ import mage.util.CardUtil; */ public class Modes extends LinkedHashMap { - private Mode mode; // the current mode of the selected modes + private Mode currentMode; // the current mode of the selected modes private final ArrayList selectedModes = new ArrayList<>(); private int minModes; private int maxModes; @@ -58,11 +58,11 @@ public class Modes extends LinkedHashMap { private boolean eachModeOnlyOnce; // state if each mode can be chosen only once as long as the source object exists public Modes() { - this.mode = new Mode(); - this.put(mode.getId(), mode); + this.currentMode = new Mode(); + this.put(currentMode.getId(), currentMode); this.minModes = 1; this.maxModes = 1; - this.selectedModes.add(mode); + this.selectedModes.add(currentMode); this.modeChooser = TargetController.YOU; this.eachModeOnlyOnce = false; this.eachModeMoreThanOnce = false; @@ -76,19 +76,17 @@ public class Modes extends LinkedHashMap { this.maxModes = modes.maxModes; if (modes.size() == 1) { - this.mode = values().iterator().next(); - this.selectedModes.add(mode); + this.currentMode = values().iterator().next(); + this.selectedModes.add(currentMode); } else { // probably there is still a problem with copying modes with the same mode selected multiple times. for (Mode selectedMode : modes.getSelectedModes()) { Mode copiedMode = selectedMode.copy(); this.selectedModes.add(copiedMode); if (modes.getSelectedModes().size() == 1) { - this.mode = copiedMode; - } else { - if (selectedMode.equals(modes.getMode())) { - this.mode = copiedMode; - } + this.currentMode = copiedMode; + } else if (selectedMode.equals(modes.getMode())) { + this.currentMode = copiedMode; } } } @@ -102,15 +100,15 @@ public class Modes extends LinkedHashMap { } public Mode getMode() { - return mode; + return currentMode; } public UUID getModeId(int index) { int idx = 0; - for (Mode currentMode : this.values()) { + for (Mode mode : this.values()) { idx++; if (idx == index) { - return currentMode.getId(); + return mode.getId(); } } return null; @@ -146,7 +144,7 @@ public class Modes extends LinkedHashMap { public void setActiveMode(Mode mode) { if (selectedModes.contains(mode)) { - this.mode = mode; + this.currentMode = mode; } } @@ -203,7 +201,7 @@ public class Modes extends LinkedHashMap { Player player = game.getPlayer(playerId); // player chooses modes manually - this.mode = null; + this.currentMode = null; while (this.selectedModes.size() < this.getMaxModes()) { Mode choice = player.chooseMode(this, source, game); if (choice == null) { @@ -213,8 +211,8 @@ public class Modes extends LinkedHashMap { return this.selectedModes.size() >= this.getMinModes(); } this.selectedModes.add(choice.copy()); - if (mode == null) { - mode = choice; + if (currentMode == null) { + currentMode = choice; } } if (isEachModeOnlyOnce()) { @@ -222,7 +220,7 @@ public class Modes extends LinkedHashMap { } return true; } - if (mode == null) { + if (currentMode == null) { this.selectedModes.clear(); Mode copiedMode = this.values().iterator().next().copy(); this.selectedModes.add(copiedMode); @@ -234,8 +232,16 @@ public class Modes extends LinkedHashMap { return true; } + /** + * Saves the already selected modes to the state value + * + * @param selectedModes + * @param source + * @param game + */ private void setAlreadySelectedModes(ArrayList selectedModes, Ability source, Game game) { String key = getKey(source, game); + @SuppressWarnings("unchecked") Set onceSelectedModes = (Set) game.getState().getValue(key); if (onceSelectedModes == null) { onceSelectedModes = new HashSet<>(); @@ -247,14 +253,25 @@ public class Modes extends LinkedHashMap { game.getState().setValue(key, onceSelectedModes); } + // The already once selected modes for a modal card are stored as a state value + // That's important for modal abilities with modes that can only selected once while the object stays in its zone + @SuppressWarnings("unchecked") private Set getAlreadySelectedModes(Ability source, Game game) { return (Set) game.getState().getValue(getKey(source, game)); } + // creates the key the selected modes are saved with to the state values private String getKey(Ability source, Game game) { return CardUtil.getObjectZoneString("selectedModes", source.getSourceId(), game, game.getState().getZoneChangeCounter(source.getSourceId()), false); } + /** + * Returns all (still) available modes of the ability + * + * @param source + * @param game + * @return + */ public List getAvailableModes(Ability source, Game game) { List availableModes = new ArrayList<>(); Set nonAvailableModes;