mirror of
https://github.com/correl/mage.git
synced 2025-01-11 19:13:02 +00:00
* Morph ability - fixed that card with morph ability marked as playable all the time (#6680);
This commit is contained in:
parent
27123f41e8
commit
6e1da09023
3 changed files with 119 additions and 71 deletions
|
@ -636,15 +636,14 @@ public class MorphTest extends CardTestPlayerBase {
|
|||
* restriction."
|
||||
*/
|
||||
@Test
|
||||
public void testReflectorMageBouncesFaceupCreatureReplayAsMorph() {
|
||||
|
||||
public void test_ReflectorMageCantStopMorphToCast_TryNormalCast() {
|
||||
// {1}{W}{U} When Reflector Mage enters the battlefield, return target creature an opponent controls to its owner's hand.
|
||||
// That creature's owner can't cast spells with the same name as that creature until your next turn.
|
||||
addCard(Zone.HAND, playerA, "Reflector Mage"); // 2/3
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
|
||||
//Tap: Add {G}, {U}, or {R}.
|
||||
// Tap: Add {G}, {U}, or {R}.
|
||||
// Morph 2 (You may cast this card face down as a 2/2 creature for 3. Turn it face up any time for its morph cost.)
|
||||
// When Rattleclaw Mystic is turned face up, add {G}{U}{R}.
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Rattleclaw Mystic"); // 2/1
|
||||
|
@ -652,20 +651,58 @@ public class MorphTest extends CardTestPlayerBase {
|
|||
addCard(Zone.BATTLEFIELD, playerB, "Island");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Mountain");
|
||||
|
||||
// return to hand
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reflector Mage");
|
||||
addTarget(playerA, "Rattleclaw Mystic");
|
||||
|
||||
// try cast as normal -- must not work
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Rattleclaw Mystic");
|
||||
setChoice(playerB, "Yes"); // cast it face down as 2/2 creature
|
||||
setChoice(playerB, "No"); // try cast as normal
|
||||
|
||||
//setStrictChooseMode(true); // no strict mode - cause can't cast as normal
|
||||
setStopAt(2, PhaseStep.BEGIN_COMBAT);
|
||||
|
||||
execute();
|
||||
//assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Reflector Mage", 1);
|
||||
assertPermanentCount(playerB, "Rattleclaw Mystic", 0);
|
||||
assertHandCount(playerB, "Rattleclaw Mystic", 0); // should have been replayed
|
||||
assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); // Rattleclaw played as a morph
|
||||
assertHandCount(playerB, "Rattleclaw Mystic", 1); // can't play
|
||||
assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); // don't try as morph
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_ReflectorMageCantStopMorphToCast_TryMorph() {
|
||||
// {1}{W}{U} When Reflector Mage enters the battlefield, return target creature an opponent controls to its owner's hand.
|
||||
// That creature's owner can't cast spells with the same name as that creature until your next turn.
|
||||
addCard(Zone.HAND, playerA, "Reflector Mage"); // 2/3
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
|
||||
// Tap: Add {G}, {U}, or {R}.
|
||||
// Morph 2 (You may cast this card face down as a 2/2 creature for 3. Turn it face up any time for its morph cost.)
|
||||
// When Rattleclaw Mystic is turned face up, add {G}{U}{R}.
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Rattleclaw Mystic"); // 2/1
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Forest");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Mountain");
|
||||
|
||||
// return to hand
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reflector Mage");
|
||||
addTarget(playerA, "Rattleclaw Mystic");
|
||||
|
||||
// try cast as morph - must work
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Rattleclaw Mystic");
|
||||
setChoice(playerB, "Yes"); // try cast as morph
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(2, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Reflector Mage", 1);
|
||||
assertPermanentCount(playerB, "Rattleclaw Mystic", 0);
|
||||
assertHandCount(playerB, "Rattleclaw Mystic", 0); // able cast as morph
|
||||
assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1007,4 +1044,25 @@ public class MorphTest extends CardTestPlayerBase {
|
|||
assertPermanentCount(playerA, "Zoetic Cavern", 0);
|
||||
assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_CantActivateOnOpponentTurn() {
|
||||
// https://github.com/magefree/mage/issues/6698
|
||||
|
||||
// Morph {1}{U} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.)
|
||||
// When Willbender is turned face up, change the target of target spell or ability with a single target.
|
||||
addCard(Zone.HAND, playerA, "Willbender");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
|
||||
|
||||
// can play on own turn
|
||||
checkPlayableAbility("can", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Willbender", true);
|
||||
|
||||
// can't play on opponent turn
|
||||
checkPlayableAbility("can't", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Willbender", false);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(2, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -425,36 +425,28 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
}
|
||||
|
||||
boolean alternativeCostisUsed = false;
|
||||
boolean alternativeCostUsed = false;
|
||||
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
||||
Abilities<Ability> abilities = null;
|
||||
if (sourceObject instanceof Card) {
|
||||
abilities = ((Card) sourceObject).getAbilities(game);
|
||||
} else {
|
||||
sourceObject.getAbilities();
|
||||
}
|
||||
|
||||
if (abilities != null) {
|
||||
for (Ability ability : abilities) {
|
||||
// if cast for noMana no Alternative costs are allowed
|
||||
if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) {
|
||||
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
|
||||
if (alternativeSpellCosts.isAvailable(this, game)) {
|
||||
if (alternativeSpellCosts.askToActivateAlternativeCosts(this, game)) {
|
||||
// only one alternative costs may be activated
|
||||
alternativeCostisUsed = true;
|
||||
break;
|
||||
}
|
||||
Abilities<Ability> abilities = CardUtil.getAbilities(sourceObject, game);
|
||||
for (Ability ability : abilities) {
|
||||
// if cast for noMana no Alternative costs are allowed
|
||||
if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) {
|
||||
AlternativeSourceCosts alternativeSpellCosts = (AlternativeSourceCosts) ability;
|
||||
if (alternativeSpellCosts.isAvailable(this, game)) {
|
||||
if (alternativeSpellCosts.askToActivateAlternativeCosts(this, game)) {
|
||||
// only one alternative costs may be activated
|
||||
alternativeCostUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) {
|
||||
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
|
||||
}
|
||||
}
|
||||
if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) {
|
||||
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
|
||||
}
|
||||
}
|
||||
|
||||
// controller specific alternate spell costs
|
||||
if (canUseAlternativeCost && !noMana && !alternativeCostisUsed) {
|
||||
if (canUseAlternativeCost && !noMana && !alternativeCostUsed) {
|
||||
if (this.getAbilityType() == AbilityType.SPELL
|
||||
// 117.9a Only one alternative cost can be applied to any one spell as it's being cast.
|
||||
// So an alternate spell ability can't be paid with Omniscience
|
||||
|
@ -463,7 +455,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
if (alternativeSourceCosts.isAvailable(this, game)) {
|
||||
if (alternativeSourceCosts.askToActivateAlternativeCosts(this, game)) {
|
||||
// only one alternative costs may be activated
|
||||
alternativeCostisUsed = true;
|
||||
alternativeCostUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -472,7 +464,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
}
|
||||
|
||||
return alternativeCostisUsed;
|
||||
return alternativeCostUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3140,54 +3140,52 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
protected ActivatedAbility findActivatedAbilityFromAlternativeSourceCost(MageObject object, ManaOptions manaAvailable, Ability ability, Game game) {
|
||||
// alternative cost must be replaced by real play ability
|
||||
if (ability instanceof AlternativeSourceCosts) {
|
||||
AlternativeSourceCosts altAbility = (AlternativeSourceCosts) ability;
|
||||
// return play ability that can activate AlternativeSourceCosts
|
||||
if (ability instanceof AlternativeSourceCosts && !(object instanceof Permanent)) {
|
||||
ActivatedAbility playAbility = null;
|
||||
if (object.isLand()) {
|
||||
// land
|
||||
// morph ability is static, so it must be replaced with play land ability (playLand search and try to use face down first)
|
||||
if (canLandPlayAlternateSourceCostsAbility(object, manaAvailable, ability, game)) { // e.g. Land with Morph
|
||||
Ability landAbility = CardUtil.getAbilities(object, game).stream().filter(a -> a instanceof PlayLandAbility).findFirst().orElse(null);
|
||||
if (landAbility != null) {
|
||||
return (PlayLandAbility) landAbility;
|
||||
}
|
||||
}
|
||||
playAbility = (PlayLandAbility) CardUtil.getAbilities(object, game).stream().filter(a -> a instanceof PlayLandAbility).findFirst().orElse(null);
|
||||
} else if (object instanceof Card) {
|
||||
playAbility = ((Card) object).getSpellAbility();
|
||||
}
|
||||
if (playAbility == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 707.4.Objects that are cast face down are turned face down before they are put onto the stack
|
||||
// (e.g. no lands per turn limit, no cast restrictions, another cost, etc)
|
||||
// so morph must checks only mana payment here
|
||||
boolean canUse;
|
||||
if (ability instanceof MorphAbility) {
|
||||
canUse = game.canPlaySorcery(playerId) && canPayAlternateSourceCostsAbility(object, playAbility, manaAvailable, ability, game);
|
||||
} else {
|
||||
// creature and other
|
||||
if (object instanceof Card) {
|
||||
SpellAbility spellAbility = ((Card) object).getSpellAbility();
|
||||
if (altAbility.isAvailable(spellAbility, game)) {
|
||||
return spellAbility;
|
||||
}
|
||||
}
|
||||
canUse = canPlay(playAbility, manaAvailable, object, game); // canPlay already checks alternative source costs and all conditions
|
||||
}
|
||||
|
||||
if (canUse) {
|
||||
return playAbility;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected boolean canLandPlayAlternateSourceCostsAbility(MageObject sourceObject, ManaOptions available, Ability ability, Game game) {
|
||||
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
||||
Ability sourceAbility = sourceObject.getAbilities().stream()
|
||||
.filter(landAbility -> landAbility.getAbilityType() == AbilityType.PLAY_LAND)
|
||||
.findFirst().orElse(null);
|
||||
|
||||
if (sourceAbility != null && ((AlternativeSourceCosts) ability).isAvailable(sourceAbility, game)) {
|
||||
if (ability.getCosts().canPay(ability, sourceObject.getId(), this.getId(), game)) {
|
||||
ManaCostsImpl manaCosts = new ManaCostsImpl();
|
||||
for (Cost cost : ability.getCosts()) {
|
||||
if (cost instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) cost);
|
||||
}
|
||||
protected boolean canPayAlternateSourceCostsAbility(MageObject sourceObject, Ability sourceAbility, ManaOptions available, Ability alternativeAbility, Game game) {
|
||||
if (sourceAbility != null && ((AlternativeSourceCosts) alternativeAbility).isAvailable(sourceAbility, game)) {
|
||||
if (alternativeAbility.getCosts().canPay(alternativeAbility, sourceObject.getId(), this.getId(), game)) {
|
||||
ManaCostsImpl manaCosts = new ManaCostsImpl();
|
||||
for (Cost cost : alternativeAbility.getCosts()) {
|
||||
if (cost instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) cost);
|
||||
}
|
||||
}
|
||||
|
||||
if (manaCosts.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
for (Mana mana : manaCosts.getOptions()) {
|
||||
for (Mana avail : available) {
|
||||
if (mana.enough(avail)) {
|
||||
return true;
|
||||
}
|
||||
if (manaCosts.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
for (Mana mana : manaCosts.getOptions()) {
|
||||
for (Mana avail : available) {
|
||||
if (mana.enough(avail)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue