diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 83f3862b05b..f84b1c75270 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -289,19 +289,12 @@ public final class GamePanel extends javax.swing.JPanel { private void hidePickDialogs() { // temporary hide opened dialog on redraw/update - - //try { - // pick target - for (ShowCardsDialog dialog : this.pickTarget) { - dialog.setVisible(false); - } - // pick pile - for (PickPileDialog dialog : this.pickPile) { - dialog.setVisible(false); - } - //} catch (PropertyVetoException e) { - // logger.error("Couldn't close pick dialog", e); - //} + for (ShowCardsDialog dialog : this.pickTarget) { + dialog.setVisible(false); + } + for (PickPileDialog dialog : this.pickPile) { + dialog.setVisible(false); + } } private void clearPickDialogs() { diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index c8da4b693b5..59d2553e7d0 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -3002,7 +3002,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { @Override public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { - Map useable = PlayerImpl.getSpellAbilities(this.getId(), card, game.getState().getZone(card.getId()), game); + Map useable = PlayerImpl.getCastableSpellAbilities(game, this.getId(), card, game.getState().getZone(card.getId()), noMana); return (SpellAbility) useable.values().stream().findFirst().orElse(null); } diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 6312f27ab0a..352147acc40 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -2174,7 +2174,7 @@ public class HumanPlayer extends PlayerImpl { } @Override - public SpellAbility chooseAbilityForCast(Card card, Game game, boolean nonMana) { + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { if (gameInCheckPlayableState(game)) { return null; } @@ -2186,8 +2186,8 @@ public class HumanPlayer extends PlayerImpl { MageObject object = game.getObject(card.getId()); // must be object to find real abilities (example: commander) if (object != null) { - String message = "Choose ability to cast" + (nonMana ? " for FREE" : "") + "
" + object.getLogName(); - LinkedHashMap useableAbilities = getSpellAbilities(playerId, object, game.getState().getZone(object.getId()), game); + String message = "Choose ability to cast" + (noMana ? " for FREE" : "") + "
" + object.getLogName(); + LinkedHashMap useableAbilities = PlayerImpl.getCastableSpellAbilities(game, playerId, object, game.getState().getZone(object.getId()), noMana); if (useableAbilities != null && useableAbilities.size() == 1) { return (SpellAbility) useableAbilities.values().iterator().next(); diff --git a/Mage.Sets/src/mage/cards/i/IsochronScepter.java b/Mage.Sets/src/mage/cards/i/IsochronScepter.java index d2ab47d3751..b52f68e325d 100644 --- a/Mage.Sets/src/mage/cards/i/IsochronScepter.java +++ b/Mage.Sets/src/mage/cards/i/IsochronScepter.java @@ -33,15 +33,13 @@ public final class IsochronScepter extends CardImpl { public IsochronScepter(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); - // Imprint - When Isochron Scepter enters the battlefield, you may exile an - // instant card with converted mana cost 2 or less from your hand. + // Imprint - When Isochron Scepter enters the battlefield, you may exile an instant card with converted mana cost 2 or less from your hand. this.addAbility(new EntersBattlefieldTriggeredAbility( new IsochronScepterImprintEffect(),true) .withFlavorWord("Imprint") ); - // {2}, {tap}: You may copy the exiled card. If you do, you may cast the - // copy without paying its mana cost. + // {2}, {T}: You may copy the exiled card. If you do, you may cast the copy without paying its mana cost. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new IsochronScepterCopyEffect(), new GenericManaCost(2)); ability.addCost(new TapSourceCost()); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OverloadTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OverloadTest.java index 7e74f4420ee..101fe368ca1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OverloadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/OverloadTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; @@ -7,44 +6,147 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class OverloadTest extends CardTestPlayerBase { /** * My opponent cast an overloaded Vandalblast, and Xmage would not let me * cast Mental Misstep on it. - * + *

* The CMC of a card never changes, and Vandalblast's CMC is always 1. - * + *

* 4/15/2013 Casting a spell with overload doesn't change that spell's mana * cost. You just pay the overload cost instead. */ @Test - public void testCastByOverloadDoesNotChangeCMC() { - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + public void test_CastByOverloadDoesNotChangeCMC() { // Destroy target artifact you don't control. // Overload {4}{R} (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.") addCard(Zone.HAND, playerA, "Vandalblast"); - - addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // // Counter target spell with converted mana cost 1. addCard(Zone.HAND, playerB, "Mental Misstep", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 1); addCard(Zone.BATTLEFIELD, playerB, "War Horn", 2); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vandalblast with overload"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Mental Misstep", "Vandalblast"); + setChoice(playerB, false); // pay mana instead life + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Vandalblast", 1); assertGraveyardCount(playerB, "Mental Misstep", 1); - assertPermanentCount(playerB, "War Horn", 2); - } + @Test + public void test_CyclonicRift_NormalPlay() { + // Return target nonland permanent you don't control to its owner's hand. + // Overload {6}{U} (You may cast this spell for its overload cost. If you do, change its text + // by replacing all instances of "target" with "each.") + addCard(Zone.HAND, playerA, "Cyclonic Rift"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + // + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); + + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Cyclonic Rift", true); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Cyclonic Rift with overload", true); + + // cast and remove 1 target + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cyclonic Rift"); + addTarget(playerA, "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Cyclonic Rift", 1); + assertPermanentCount(playerB, "Swamp", 1); + assertHandCount(playerB, "Balduvian Bears", 1); + assertPermanentCount(playerB, "Spectral Bears", 1); + } + + @Test + public void test_CyclonicRift_OverloadPlay() { + // Return target nonland permanent you don't control to its owner's hand. + // Overload {6}{U} (You may cast this spell for its overload cost. If you do, change its text + // by replacing all instances of "target" with "each.") + addCard(Zone.HAND, playerA, "Cyclonic Rift"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + // + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); + + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Cyclonic Rift", true); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Cyclonic Rift with overload", true); + + // cast and remove all possible targets (all bears) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cyclonic Rift with overload"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Cyclonic Rift", 1); + assertPermanentCount(playerB, "Swamp", 1); + assertHandCount(playerB, "Balduvian Bears", 1); + assertHandCount(playerB, "Spectral Bears", 1); + } + + @Test + public void test_CyclonicRift_CantUseAlternativeSpellOnFreeCast() { + // bug: https://github.com/magefree/mage/issues/6925 + // Casting Overloaded Cyclonic Rift with Isochron Scepter - This should not be possible, + // You can't pay two alternate costs for the same thing. + + // Return target nonland permanent you don't control to its owner's hand. + // Overload {6}{U} (You may cast this spell for its overload cost. If you do, change its text + // by replacing all instances of "target" with "each.") + addCard(Zone.HAND, playerA, "Cyclonic Rift"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + // + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); + // + // Imprint - When Isochron Scepter enters the battlefield, you may exile an instant card with converted mana cost 2 or less from your hand. + // {2}, {T}: You may copy the exiled card. If you do, you may cast the copy without paying its mana cost. + addCard(Zone.HAND, playerA, "Isochron Scepter"); // {2} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // prepare scepter for imprint + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Isochron Scepter"); + setChoice(playerA, true); // use imprint + setChoice(playerA, "Cyclonic Rift"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // cast rift for free + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}:"); + setChoice(playerA, true); // create copy + setChoice(playerA, true); // use free cast + //setChoice(playerA, "Cast Cyclonic Rift with overload"); // must be NO choices, cause only normal cast allows here + addTarget(playerA, "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertExileCount(playerA, "Cyclonic Rift", 1); + assertGraveyardCount(playerA, "Cyclonic Rift", 0); // imprinted copy discarded + assertPermanentCount(playerB, "Swamp", 1); + assertHandCount(playerB, "Balduvian Bears", 1); + assertPermanentCount(playerB, "Spectral Bears", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index ed258018eb6..d7ff1c66a5d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -4351,7 +4351,7 @@ public class TestPlayer implements Player { assertAliasSupportInChoices(false); MageObject object = game.getObject(card.getId()); // must be object to find real abilities (example: commander) - Map useable = PlayerImpl.getSpellAbilities(this.getId(), object, game.getState().getZone(object.getId()), game); + Map useable = PlayerImpl.getCastableSpellAbilities(game, this.getId(), object, game.getState().getZone(object.getId()), noMana); String allInfo = useable.values().stream().map(Object::toString).collect(Collectors.joining("\n")); if (useable.size() == 1) { return (SpellAbility) useable.values().iterator().next(); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 89140dc946f..d66ad138d08 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1544,13 +1544,14 @@ public abstract class PlayerImpl implements Player, Serializable { * for choosing from the card (example: effect allow to cast card and player * must choose the spell ability) * + * @param game * @param playerId * @param object * @param zone - * @param game + * @param noMana * @return */ - public static LinkedHashMap getSpellAbilities(UUID playerId, MageObject object, Zone zone, Game game) { + public static LinkedHashMap getCastableSpellAbilities(Game game, UUID playerId, MageObject object, Zone zone, boolean noMana) { // it uses simple check from spellCanBeActivatedRegularlyNow // reason: no approved info here (e.g. forced to choose spell ability from cast card) LinkedHashMap useable = new LinkedHashMap<>(); @@ -1563,16 +1564,29 @@ public abstract class PlayerImpl implements Player, Serializable { for (Ability ability : allAbilities) { if (ability instanceof SpellAbility) { - switch (((SpellAbility) ability).getSpellAbilityType()) { + SpellAbility spellAbility = (SpellAbility) ability; + + switch (spellAbility.getSpellAbilityType()) { case BASE_ALTERNATE: - if (((SpellAbility) ability).spellCanBeActivatedRegularlyNow(playerId, game)) { - useable.put(ability.getId(), (SpellAbility) ability); // example: Chandra, Torch of Defiance +1 loyal ability + // rules: + // If you cast a spell “without paying its mana cost,” you can’t choose to cast it for + // any alternative costs. You can, however, pay additional costs, such as kicker costs. + // If the card has any mandatory additional costs, those must be paid to cast the spell. + // (2021-02-05) + if (!noMana) { + if (spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)) { + useable.put(spellAbility.getId(), spellAbility); // example: Chandra, Torch of Defiance +1 loyal ability + } + return useable; } - return useable; + break; case SPLIT_FUSED: + // rules: + // If you cast a split card with fuse from your hand without paying its mana cost, + // you can choose to use its fuse ability and cast both halves without paying their mana costs. if (zone == Zone.HAND) { - if (ability.canChooseTarget(game, playerId)) { - useable.put(ability.getId(), (SpellAbility) ability); + if (spellAbility.canChooseTarget(game, playerId)) { + useable.put(spellAbility.getId(), spellAbility); } } case SPLIT: @@ -1599,8 +1613,8 @@ public abstract class PlayerImpl implements Player, Serializable { } return useable; default: - if (((SpellAbility) ability).spellCanBeActivatedRegularlyNow(playerId, game)) { - useable.put(ability.getId(), (SpellAbility) ability); + if (spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)) { + useable.put(spellAbility.getId(), spellAbility); } } }