* Split cards - added cost modification effects support for fused spells (#227, #2242, #6603, #6549);

This commit is contained in:
Oleg Agafonov 2020-06-10 08:28:18 +04:00
parent 2096229af8
commit b38ac2f575
6 changed files with 149 additions and 45 deletions

View file

@ -11,7 +11,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class BestowTest extends CardTestPlayerBase {
@ -20,7 +19,6 @@ public class BestowTest extends CardTestPlayerBase {
* Tests that if from bestow permanent targeted creature gets protection
* from the color of the bestow permanent, the bestow permanent becomes a
* creature on the battlefield.
*
*/
/* Silent Artisan
@ -157,7 +155,7 @@ public class BestowTest extends CardTestPlayerBase {
* // Away casting both sides, will the creature that has bestow come in
* time for it to be sacrificed or does it fully resolve before the creature
* comes in?
*
* <p>
* Bestowed creature can be used to sacrifice a creature for the Away part.
* http://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/513828-bestow-far-away
*/
@ -188,12 +186,16 @@ public class BestowTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nyxborn Rollicker using bestow", "Cyclops of One-Eyed Pass");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "fused Far // Away", "Cyclops of One-Eyed Pass");
addTarget(playerB, playerA);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "fused Far // Away");
addTarget(playerB, "Cyclops of One-Eyed Pass"); // Far
addTarget(playerB, playerA); // Away
addTarget(playerA, "Nyxborn Rollicker");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertHandCount(playerA, "Cyclops of One-Eyed Pass", 1);
assertHandCount(playerB, 0);
@ -246,8 +248,6 @@ public class BestowTest extends CardTestPlayerBase {
}
/**
*
*
*
*/
@Test

View file

@ -55,8 +55,8 @@ public class TappedForManaFromMultipleEffects extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nyxbloom Ancient");
// TODO: TAPPED_FOR_MANA replace event called from checkTappedForManaReplacement and start to choose replace events (is that problem?)
// use case (that test): comment one 1-2 choices to fail in 1-2 calls
setChoice(playerA, "Nyxbloom Ancient: If you tap a permanent"); // getPlayable... checkTappedForManaReplacement... chooseReplacementEffect
setChoice(playerA, "Nyxbloom Ancient: If you tap a permanent"); // playManaAbility... resolve... checkToFirePossibleEvents... chooseReplacementEffect
setChoice(playerA, "Nyxbloom Ancient"); // getPlayable... checkTappedForManaReplacement... chooseReplacementEffect
setChoice(playerA, "Nyxbloom Ancient"); // playManaAbility... resolve... checkToFirePossibleEvents... chooseReplacementEffect
// cast chloro
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Chlorophant");

View file

@ -1,8 +1,11 @@
package org.mage.test.cards.split;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.cost.SpellsCostReductionAllEffect;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import mage.filter.FilterCard;
import mage.filter.predicate.mageobject.NamePredicate;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -11,6 +14,118 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
*/
public class CastSplitCardsWithCostModificationTest extends CardTestPlayerBase {
private void prepareReduceEffect(String cardNameToReduce, int reduceAmount) {
FilterCard filter = new FilterCard();
filter.add(new NamePredicate(cardNameToReduce));
addCustomCardWithAbility("reduce", playerA, new SimpleStaticAbility(
new SpellsCostReductionAllEffect(filter, reduceAmount))
);
}
@Test
public void test_Playable_Left() {
// cost reduce for easy test
prepareReduceEffect("Armed", 3);
// Armed {1}{R} Target creature gets +1/+1 and gains double strike until end of turn.
// Dangerous {3}{G} All creatures able to block target creature this turn do so.
addCard(Zone.HAND, playerA, "Armed // Dangerous", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
//addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Armed", true);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Dangerous", false);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast fused Armed // Dangerous", false);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Armed", "Balduvian Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Armed // Dangerous", 1);
}
@Test
public void test_Playable_Right() {
// cost reduce for easy test
prepareReduceEffect("Dangerous", 3);
// Armed {1}{R} Target creature gets +1/+1 and gains double strike until end of turn.
// Dangerous {3}{G} All creatures able to block target creature this turn do so.
addCard(Zone.HAND, playerA, "Armed // Dangerous", 1);
//addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Armed", false);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Dangerous", true);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast fused Armed // Dangerous", false);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dangerous", "Balduvian Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Armed // Dangerous", 1);
}
@Test
public void test_Playable_Fused_Left() {
// cost reduce for easy test
prepareReduceEffect("Armed", 4);
// Armed {1}{R} Target creature gets +1/+1 and gains double strike until end of turn.
// Dangerous {3}{G} All creatures able to block target creature this turn do so.
addCard(Zone.HAND, playerA, "Armed // Dangerous", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Armed", true);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Dangerous", false);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast fused Armed // Dangerous", true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Armed // Dangerous");
addTarget(playerA, "Balduvian Bears");
addTarget(playerA, "Balduvian Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Armed // Dangerous", 1);
}
@Test
public void test_Playable_Fused_Right() {
// cost reduce for easy test
prepareReduceEffect("Dangerous", 4);
// Armed {1}{R} Target creature gets +1/+1 and gains double strike until end of turn.
// Dangerous {3}{G} All creatures able to block target creature this turn do so.
addCard(Zone.HAND, playerA, "Armed // Dangerous", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Armed", true); // no reduced, but have basic lands ({G}{R})
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Dangerous", true);
checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast fused Armed // Dangerous", true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Armed // Dangerous");
addTarget(playerA, "Balduvian Bears");
addTarget(playerA, "Balduvian Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Armed // Dangerous", 1);
}
@Test
public void test_CostReduction_Simple() {
// {2}{W}{U}
@ -26,6 +141,8 @@ public class CastSplitCardsWithCostModificationTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
// cast Council
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}", 3);
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Council of the Absolute");
setChoice(playerA, "Blastfire Bolt");
@ -69,7 +186,6 @@ public class CastSplitCardsWithCostModificationTest extends CardTestPlayerBase {
checkPlayableAbility("after reduction", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Armed", true);
checkPlayableAbility("after reduction", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Dangerous", false);
checkPlayableAbility("after reduction", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast fused Armed // Dangerous", false);
showAvaileableAbilities("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Armed", "Balduvian Bears");
setStrictChooseMode(true);
@ -118,7 +234,6 @@ public class CastSplitCardsWithCostModificationTest extends CardTestPlayerBase {
}
@Test
@Ignore // TODO: cost modification don't work for fused spells, only for one of the part, see https://github.com/magefree/mage/issues/6603
public void test_CostReduction_SplitFused_ReduceRight() {
// {2}{W}{U}
// As Council of the Absolute enters the battlefield, choose a noncreature, nonland card name.
@ -159,7 +274,6 @@ public class CastSplitCardsWithCostModificationTest extends CardTestPlayerBase {
}
@Test
@Ignore // TODO: cost modification don't work for fused spells, only for one of the part, see https://github.com/magefree/mage/issues/6603
public void test_CostReduction_SplitFused_ReduceLeft() {
// {2}{W}{U}
// As Council of the Absolute enters the battlefield, choose a noncreature, nonland card name.

View file

@ -314,17 +314,7 @@ public class TestPlayer implements Player {
if (group.startsWith("spell") || group.startsWith("!spell") || group.startsWith("target=null") || group.startsWith("manaInPool=")) {
break;
}
if (ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
if (group.contains("FuseLeft-")) {
result = handleTargetString(group.substring(group.indexOf("FuseLeft-") + 9), ability, game);
} else if (group.startsWith("FuseRight-")) {
result = handleTargetString(group.substring(group.indexOf("FuseRight-") + 10), ability, game);
} else {
result = false;
}
} else {
result = handleTargetString(group, ability, game);
}
result = handleTargetString(group, ability, game);
}
return result;
}

View file

@ -124,24 +124,28 @@ public class Spell extends StackObjImpl implements Card {
}
public boolean activate(Game game, boolean noMana) {
setDoneActivatingManaAbilities(false); // Used for e.g. improvise
if (!spellAbilities.get(0).activate(game, noMana)) {
setDoneActivatingManaAbilities(false); // used for e.g. improvise
if (!ability.activate(game, noMana)) {
return false;
}
if (spellAbilities.size() > 1) {
// if there are more abilities (fused split spell) or first ability added new abilities (splice), activate the additional abilities
boolean ignoreAbility = true;
// spell can contains multiple abilities to activate (fused split, splice)
for (SpellAbility spellAbility : spellAbilities) {
if (ability.equals(spellAbility)) {
// activated first
continue;
}
boolean payNoMana = noMana;
for (SpellAbility spellAbility : spellAbilities) {
if (ignoreAbility) {
ignoreAbility = false;
} else {
// costs for spliced abilities were added to main spellAbility, so pay no mana for spliced abilities
payNoMana |= spellAbility.getSpellAbilityType() == SpellAbilityType.SPLICE;
if (!spellAbility.activate(game, payNoMana)) {
return false;
}
}
// costs for spliced abilities were added to main spellAbility, so pay no mana for spliced abilities
payNoMana |= spellAbility.getSpellAbilityType() == SpellAbilityType.SPLICE;
// costs for fused ability pay on first spell activate, so all parts must be without mana
// see https://github.com/magefree/mage/issues/6603
payNoMana |= ability.getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED;
if (!spellAbility.activate(game, payNoMana)) {
return false;
}
}
setDoneActivatingManaAbilities(true); // can be activated again maybe during the resolution of the spell (e.g. Metallic Rebuke)

View file

@ -2699,11 +2699,7 @@ public abstract class PlayerImpl implements Player, Serializable {
(event.isWinnable() ? "(You called " + event.getChosenName() + ")" : null),
"Heads", "Tails", source, game
));
} else if (canChooseHeads) {
event.setResult(true);
} else {
event.setResult(false);
}
} else event.setResult(canChooseHeads);
game.informPlayers(getLogName() + " chose to keep " + CardUtil.booleanToFlipName(event.getResult()));
}
if (event.isWinnable()) {
@ -2935,7 +2931,7 @@ public abstract class PlayerImpl implements Player, Serializable {
*/
protected boolean canPlay(ActivatedAbility ability, ManaOptions available, MageObject sourceObject, Game game) {
if (!(ability instanceof ActivatedManaAbilityImpl)) {
ActivatedAbility copy = ability.copy(); // Copy is needed because cost reduction effects modify e.g. the mana to activate/cast the ability
ActivatedAbility copy = ability.copy(); // copy is needed because cost reduction effects modify e.g. the mana to activate/cast the ability
if (!copy.canActivate(playerId, game).canActivate()) {
return false;
}