mirror of
https://github.com/correl/mage.git
synced 2025-01-11 11:05:23 +00:00
* Split cards - added cost modification effects support for fused spells (#227, #2242, #6603, #6549);
This commit is contained in:
parent
2096229af8
commit
b38ac2f575
6 changed files with 149 additions and 45 deletions
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue