From abda99e203fcabdae107200b6103b668b822cadd Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 24 May 2020 09:21:49 +0400 Subject: [PATCH] Fixed that fused cards allows to cast from graveyard (see prev commit 63dbf5f40b2b83daa53215beb0c0c0280c43416b); --- Mage.Sets/src/mage/cards/m/ManaCache.java | 7 +- .../src/mage/cards/w/WellOfKnowledge.java | 13 +-- .../cards/single/KessDissidentMageTest.java | 104 ++++++++++++++++++ .../java/mage/abilities/ActivatedAbility.java | 16 ++- .../java/mage/abilities/SpellAbility.java | 13 ++- .../mage/abilities/common/PassAbility.java | 7 +- .../mage/abilities/keyword/EmergeAbility.java | 2 +- .../abilities/keyword/FlashbackAbility.java | 17 +-- .../abilities/keyword/SpectacleAbility.java | 2 +- .../mage/abilities/keyword/SurgeAbility.java | 2 +- 10 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/KessDissidentMageTest.java diff --git a/Mage.Sets/src/mage/cards/m/ManaCache.java b/Mage.Sets/src/mage/cards/m/ManaCache.java index 54cb19a0ba..be93b51c67 100644 --- a/Mage.Sets/src/mage/cards/m/ManaCache.java +++ b/Mage.Sets/src/mage/cards/m/ManaCache.java @@ -1,7 +1,5 @@ - package mage.cards.m; -import java.util.UUID; import mage.Mana; import mage.abilities.Ability; import mage.abilities.TriggeredAbility; @@ -27,8 +25,9 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** - * * @author L_J */ public final class ManaCache extends CardImpl { @@ -105,7 +104,7 @@ class ManaCacheManaAbility extends ActivatedManaAbilityImpl { if (player != null && playerId.equals(game.getActivePlayerId()) && game.getStep().getType().isBefore(PhaseStep.END_TURN)) { if (costs.canPay(this, sourceId, playerId, game)) { this.setControllerId(playerId); - return ActivationStatus.getTrue(); + return ActivationStatus.getTrue(this, game); } } return ActivationStatus.getFalse(); diff --git a/Mage.Sets/src/mage/cards/w/WellOfKnowledge.java b/Mage.Sets/src/mage/cards/w/WellOfKnowledge.java index 2986d5b86d..a054e2ced6 100644 --- a/Mage.Sets/src/mage/cards/w/WellOfKnowledge.java +++ b/Mage.Sets/src/mage/cards/w/WellOfKnowledge.java @@ -1,7 +1,5 @@ - package mage.cards.w; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.condition.common.IsStepCondition; @@ -10,16 +8,13 @@ import mage.abilities.effects.Effects; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.EffectType; -import mage.constants.Outcome; -import mage.constants.PhaseStep; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** - * * @author Styxo */ public final class WellOfKnowledge extends CardImpl { @@ -68,7 +63,7 @@ class WellOfKnowledgeConditionalActivatedAbility extends ActivatedAbilityImpl { && costs.canPay(this, sourceId, playerId, game) && game.isActivePlayer(playerId)) { this.activatorId = playerId; - return ActivationStatus.getTrue(); + return ActivationStatus.getTrue(this, game); } return ActivationStatus.getFalse(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/KessDissidentMageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/KessDissidentMageTest.java new file mode 100644 index 0000000000..fce7afd9db --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/KessDissidentMageTest.java @@ -0,0 +1,104 @@ +package org.mage.test.cards.single; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ + +public class KessDissidentMageTest extends CardTestPlayerBase { + + // Kess, Dissident Mage + // During each of your turns, you may cast an instant or sorcery card from your graveyard. + // If a card cast this way would be put into your graveyard this turn, exile it instead. + + @Test + public void test_Simple() { + addCard(Zone.BATTLEFIELD, playerA, "Kess, Dissident Mage", 1); + // + addCard(Zone.GRAVEYARD, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + + checkPlayableAbility("must play simple", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertExileCount(playerA, "Lightning Bolt", 1); + assertLife(playerB, 20 - 3); + } + + @Test + public void test_Split_OnePart() { + addCard(Zone.BATTLEFIELD, playerA, "Kess, Dissident Mage", 1); + // + // Create a 3/3 green Centaur creature token. + // You gain 2 life for each creature you control. + addCard(Zone.GRAVEYARD, playerA, "Alive // Well", 1); // {3}{G} // {W} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + + checkPlayableAbility("must play part", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Alive", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alive"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Centaur", 1); + assertLife(playerA, 20); + assertExileCount(playerA, "Alive // Well", 1); + } + + @Test + public void test_Split_Check() { + // testing check command only for fused cards + + // Create a 3/3 green Centaur creature token. + // You gain 2 life for each creature you control. + addCard(Zone.HAND, playerA, "Alive // Well", 1); // {3}{G} // {W} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + // tap green first for Alive spell + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 4); + checkPlayableAbility("must play fused", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast fused Alive // Well", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Alive // Well"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Centaur", 1); + assertLife(playerA, 20 + 2); + assertGraveyardCount(playerA, "Alive // Well", 1); + } + + @Test + public void test_Split_CantPlay() { + addCard(Zone.BATTLEFIELD, playerA, "Kess, Dissident Mage", 1); + // + // Create a 3/3 green Centaur creature token. + // You gain 2 life for each creature you control. + addCard(Zone.GRAVEYARD, playerA, "Alive // Well", 1); // {3}{G} // {W} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + // tap green first for Alive spell + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 4); + checkPlayableAbility("can't play fused from graveyard", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast fused Alive // Well", false); + //castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Alive // Well"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage/src/main/java/mage/abilities/ActivatedAbility.java b/Mage/src/main/java/mage/abilities/ActivatedAbility.java index 4601964969..95e97d2bfd 100644 --- a/Mage/src/main/java/mage/abilities/ActivatedAbility.java +++ b/Mage/src/main/java/mage/abilities/ActivatedAbility.java @@ -1,18 +1,19 @@ package mage.abilities; -import java.util.UUID; +import mage.MageObject; import mage.MageObjectReference; import mage.abilities.mana.ManaOptions; import mage.constants.TargetController; import mage.game.Game; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public interface ActivatedAbility extends Ability { - final public class ActivationStatus { + final class ActivationStatus { private final boolean canActivate; private final MageObjectReference permittingObject; @@ -34,8 +35,13 @@ public interface ActivatedAbility extends Ability { return new ActivationStatus(false, null); } - public static ActivationStatus getTrue() { - return new ActivationStatus(true, null); + /** + * @param permittingObjectAbility card or permanent that allows to activate current ability + */ + public static ActivationStatus getTrue(Ability permittingObjectAbility, Game game) { + MageObject object = permittingObjectAbility == null ? null : permittingObjectAbility.getSourceObject(game); + MageObjectReference ref = object == null ? null : new MageObjectReference(object, game); + return new ActivationStatus(true, ref); } } diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 9bd506f34a..0df442d16c 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -1,7 +1,5 @@ package mage.abilities; -import java.util.Optional; -import java.util.UUID; import mage.MageObject; import mage.MageObjectReference; import mage.abilities.costs.Cost; @@ -17,6 +15,9 @@ import mage.game.events.GameEvent; import mage.game.stack.Spell; import mage.players.Player; +import java.util.Optional; +import java.util.UUID; + /** * @author BetaSteward_at_googlemail.com */ @@ -108,8 +109,12 @@ public class SpellAbility extends ActivatedAbilityImpl { if (getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) { SplitCard splitCard = (SplitCard) game.getCard(getSourceId()); if (splitCard != null) { - return new ActivationStatus(splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game) - && splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game), null); + // fused can be called from hand only, so not permitting object allows or other zones checks + // see https://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/251926-snapcaster-mage-and-fuse + if (game.getState().getZone(splitCard.getId()) == Zone.HAND) { + return new ActivationStatus(splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game) + && splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game), null); + } } return ActivationStatus.getFalse(); diff --git a/Mage/src/main/java/mage/abilities/common/PassAbility.java b/Mage/src/main/java/mage/abilities/common/PassAbility.java index 08ebb1608a..e49cc4ca4a 100644 --- a/Mage/src/main/java/mage/abilities/common/PassAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PassAbility.java @@ -1,14 +1,13 @@ - package mage.abilities.common; -import java.util.UUID; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.effects.common.PassEffect; import mage.constants.Zone; import mage.game.Game; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public class PassAbility extends ActivatedAbilityImpl { @@ -29,7 +28,7 @@ public class PassAbility extends ActivatedAbilityImpl { @Override public ActivationStatus canActivate(UUID playerId, Game game) { - return ActivationStatus.getTrue(); + return ActivationStatus.getTrue(this, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java index c11ec9da55..75ef4762ba 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java @@ -57,7 +57,7 @@ public class EmergeAbility extends SpellAbility { new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) { ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getConvertedManaCost()); if (costToPay.canPay(this, this.getSourceId(), this.getControllerId(), game)) { - return ActivationStatus.getTrue(); + return ActivationStatus.getTrue(this, game); } } } diff --git a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java index 145257ae4b..980716538d 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java @@ -1,6 +1,5 @@ package mage.abilities.keyword; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.costs.Cost; @@ -9,21 +8,18 @@ import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ReplacementEffectImpl; import mage.cards.Card; import mage.cards.SplitCard; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.SpellAbilityCastMode; -import mage.constants.SpellAbilityType; -import mage.constants.TimingRule; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.players.Player; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * 702.32. Flashback - * + *

* 702.32a. Flashback appears on some instants and sorceries. It represents two * static abilities: one that functions while the card is in a player‘s * graveyard and the other that functions while the card is on the stack. @@ -69,6 +65,7 @@ public class FlashbackAbility extends SpellAbility { return ActivationStatus.getFalse(); } // Flashback can never cast a split card by Fuse, because Fuse only works from hand + // https://tappedout.net/mtg-questions/snapcaster-mage-and-flashback-on-a-fuse-card-one-or-both-halves-legal-targets/ if (card.isSplitCard()) { if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); @@ -218,9 +215,7 @@ class FlashbackReplacementEffect extends ReplacementEffectImpl { && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { int zcc = game.getState().getZoneChangeCounter(source.getSourceId()); - if (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc) { - return true; - } + return ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc; } return false; diff --git a/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java b/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java index 0bf383e545..9cb4a763a7 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SpectacleAbility.java @@ -48,7 +48,7 @@ public class SpectacleAbility extends SpellAbility { public ActivationStatus canActivate(UUID playerId, Game game) { if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0 && super.canActivate(playerId, game).canActivate()) { - return ActivationStatus.getTrue(); + return ActivationStatus.getTrue(this, game); } return ActivationStatus.getFalse(); } diff --git a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java index 207bf737be..0833adf2df 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java @@ -54,7 +54,7 @@ public class SurgeAbility extends SpellAbility { if (!player.hasOpponent(playerToCheckId, game)) { if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0 && super.canActivate(playerId, game).canActivate()) { - return ActivationStatus.getTrue(); + return ActivationStatus.getTrue(this, game); } } }