Fixed that fused cards allows to cast from graveyard (see prev commit 63dbf5f40b);

This commit is contained in:
Oleg Agafonov 2020-05-24 09:21:49 +04:00
parent 63dbf5f40b
commit abda99e203
10 changed files with 143 additions and 40 deletions

View file

@ -1,7 +1,5 @@
package mage.cards.m; package mage.cards.m;
import java.util.UUID;
import mage.Mana; import mage.Mana;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbility;
@ -27,8 +25,9 @@ import mage.game.events.GameEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import java.util.UUID;
/** /**
*
* @author L_J * @author L_J
*/ */
public final class ManaCache extends CardImpl { 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 (player != null && playerId.equals(game.getActivePlayerId()) && game.getStep().getType().isBefore(PhaseStep.END_TURN)) {
if (costs.canPay(this, sourceId, playerId, game)) { if (costs.canPay(this, sourceId, playerId, game)) {
this.setControllerId(playerId); this.setControllerId(playerId);
return ActivationStatus.getTrue(); return ActivationStatus.getTrue(this, game);
} }
} }
return ActivationStatus.getFalse(); return ActivationStatus.getFalse();

View file

@ -1,7 +1,5 @@
package mage.cards.w; package mage.cards.w;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.ActivatedAbilityImpl; import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.condition.common.IsStepCondition; import mage.abilities.condition.common.IsStepCondition;
@ -10,16 +8,13 @@ import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.EffectType;
import mage.constants.Outcome;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import java.util.UUID;
/** /**
*
* @author Styxo * @author Styxo
*/ */
public final class WellOfKnowledge extends CardImpl { public final class WellOfKnowledge extends CardImpl {
@ -68,7 +63,7 @@ class WellOfKnowledgeConditionalActivatedAbility extends ActivatedAbilityImpl {
&& costs.canPay(this, sourceId, playerId, game) && costs.canPay(this, sourceId, playerId, game)
&& game.isActivePlayer(playerId)) { && game.isActivePlayer(playerId)) {
this.activatorId = playerId; this.activatorId = playerId;
return ActivationStatus.getTrue(); return ActivationStatus.getTrue(this, game);
} }
return ActivationStatus.getFalse(); return ActivationStatus.getFalse();

View file

@ -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();
}
}

View file

@ -1,18 +1,19 @@
package mage.abilities; package mage.abilities;
import java.util.UUID; import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.abilities.mana.ManaOptions; import mage.abilities.mana.ManaOptions;
import mage.constants.TargetController; import mage.constants.TargetController;
import mage.game.Game; import mage.game.Game;
import java.util.UUID;
/** /**
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
public interface ActivatedAbility extends Ability { public interface ActivatedAbility extends Ability {
final public class ActivationStatus { final class ActivationStatus {
private final boolean canActivate; private final boolean canActivate;
private final MageObjectReference permittingObject; private final MageObjectReference permittingObject;
@ -34,8 +35,13 @@ public interface ActivatedAbility extends Ability {
return new ActivationStatus(false, null); 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);
} }
} }

View file

@ -1,7 +1,5 @@
package mage.abilities; package mage.abilities;
import java.util.Optional;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.abilities.costs.Cost; import mage.abilities.costs.Cost;
@ -17,6 +15,9 @@ import mage.game.events.GameEvent;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.players.Player; import mage.players.Player;
import java.util.Optional;
import java.util.UUID;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
@ -108,9 +109,13 @@ public class SpellAbility extends ActivatedAbilityImpl {
if (getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) { if (getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
SplitCard splitCard = (SplitCard) game.getCard(getSourceId()); SplitCard splitCard = (SplitCard) game.getCard(getSourceId());
if (splitCard != null) { if (splitCard != 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) return new ActivationStatus(splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game)
&& splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game), null); && splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game), null);
} }
}
return ActivationStatus.getFalse(); return ActivationStatus.getFalse();
} else { } else {

View file

@ -1,14 +1,13 @@
package mage.abilities.common; package mage.abilities.common;
import java.util.UUID;
import mage.abilities.ActivatedAbilityImpl; import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.effects.common.PassEffect; import mage.abilities.effects.common.PassEffect;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import java.util.UUID;
/** /**
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
public class PassAbility extends ActivatedAbilityImpl { public class PassAbility extends ActivatedAbilityImpl {
@ -29,7 +28,7 @@ public class PassAbility extends ActivatedAbilityImpl {
@Override @Override
public ActivationStatus canActivate(UUID playerId, Game game) { public ActivationStatus canActivate(UUID playerId, Game game) {
return ActivationStatus.getTrue(); return ActivationStatus.getTrue(this, game);
} }
@Override @Override

View file

@ -57,7 +57,7 @@ public class EmergeAbility extends SpellAbility {
new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) { new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) {
ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getConvertedManaCost()); ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getConvertedManaCost());
if (costToPay.canPay(this, this.getSourceId(), this.getControllerId(), game)) { if (costToPay.canPay(this, this.getSourceId(), this.getControllerId(), game)) {
return ActivationStatus.getTrue(); return ActivationStatus.getTrue(this, game);
} }
} }
} }

View file

@ -1,6 +1,5 @@
package mage.abilities.keyword; package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.costs.Cost; import mage.abilities.costs.Cost;
@ -9,21 +8,18 @@ import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.SplitCard; import mage.cards.SplitCard;
import mage.constants.Duration; import mage.constants.*;
import mage.constants.Outcome;
import mage.constants.SpellAbilityCastMode;
import mage.constants.SpellAbilityType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent; import mage.game.events.ZoneChangeEvent;
import mage.players.Player; import mage.players.Player;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/** /**
* 702.32. Flashback * 702.32. Flashback
* * <p>
* 702.32a. Flashback appears on some instants and sorceries. It represents two * 702.32a. Flashback appears on some instants and sorceries. It represents two
* static abilities: one that functions while the card is in a players * static abilities: one that functions while the card is in a players
* graveyard and the other that functions while the card is on the stack. * 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(); return ActivationStatus.getFalse();
} }
// Flashback can never cast a split card by Fuse, because Fuse only works from hand // 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 (card.isSplitCard()) {
if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) {
return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game);
@ -218,9 +215,7 @@ class FlashbackReplacementEffect extends ReplacementEffectImpl {
&& ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) {
int zcc = game.getState().getZoneChangeCounter(source.getSourceId()); int zcc = game.getState().getZoneChangeCounter(source.getSourceId());
if (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc) { return ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc;
return true;
}
} }
return false; return false;

View file

@ -48,7 +48,7 @@ public class SpectacleAbility extends SpellAbility {
public ActivationStatus canActivate(UUID playerId, Game game) { public ActivationStatus canActivate(UUID playerId, Game game) {
if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0 if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0
&& super.canActivate(playerId, game).canActivate()) { && super.canActivate(playerId, game).canActivate()) {
return ActivationStatus.getTrue(); return ActivationStatus.getTrue(this, game);
} }
return ActivationStatus.getFalse(); return ActivationStatus.getFalse();
} }

View file

@ -54,7 +54,7 @@ public class SurgeAbility extends SpellAbility {
if (!player.hasOpponent(playerToCheckId, game)) { if (!player.hasOpponent(playerToCheckId, game)) {
if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0 if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0
&& super.canActivate(playerId, game).canActivate()) { && super.canActivate(playerId, game).canActivate()) {
return ActivationStatus.getTrue(); return ActivationStatus.getTrue(this, game);
} }
} }
} }