* Added a auto select color for color choices of mana abilities that ask the human player to select a colo (implements #690).

This commit is contained in:
LevelX2 2015-07-21 17:23:10 +02:00
parent a8e519d671
commit eca37467a3
5 changed files with 228 additions and 142 deletions

View file

@ -111,6 +111,7 @@ public class HumanPlayer extends PlayerImpl {
} }
protected HashSet<String> autoSelectReplacementEffects = new HashSet<>(); protected HashSet<String> autoSelectReplacementEffects = new HashSet<>();
protected ManaCost currentlyUnpaidMana;
public HumanPlayer(String name, RangeOfInfluence range, int skill) { public HumanPlayer(String name, RangeOfInfluence range, int skill) {
super(name, range); super(name, range);
@ -119,6 +120,8 @@ public class HumanPlayer extends PlayerImpl {
public HumanPlayer(final HumanPlayer player) { public HumanPlayer(final HumanPlayer player) {
super(player); super(player);
this.autoSelectReplacementEffects.addAll(autoSelectReplacementEffects);
this.currentlyUnpaidMana = player.currentlyUnpaidMana;
} }
protected void waitForResponse(Game game) { protected void waitForResponse(Game game) {
@ -248,6 +251,12 @@ public class HumanPlayer extends PlayerImpl {
@Override @Override
public boolean choose(Outcome outcome, Choice choice, Game game) { public boolean choose(Outcome outcome, Choice choice, Game game) {
if (outcome.equals(Outcome.PutManaInPool)) {
if (currentlyUnpaidMana != null
&& ManaUtil.tryToAutoSelectAManaColor(choice, currentlyUnpaidMana)) {
return true;
}
}
updateGameStatePriority("choose(3)", game); updateGameStatePriority("choose(3)", game);
while (!abort) { while (!abort) {
game.fireChooseChoiceEvent(playerId, choice); game.fireChooseChoiceEvent(playerId, choice);
@ -765,8 +774,10 @@ public class HumanPlayer extends PlayerImpl {
if (zone != null) { if (zone != null) {
LinkedHashMap<UUID, ManaAbility> useableAbilities = getUseableManaAbilities(object, zone, game); LinkedHashMap<UUID, ManaAbility> useableAbilities = getUseableManaAbilities(object, zone, game);
if (useableAbilities != null && useableAbilities.size() > 0) { if (useableAbilities != null && useableAbilities.size() > 0) {
useableAbilities = ManaUtil.tryToAutoPay(unpaid, useableAbilities); useableAbilities = ManaUtil.tryToAutoPay(unpaid, useableAbilities); // eliminates other abilities if one fits perfectly
currentlyUnpaidMana = unpaid;
activateAbility(useableAbilities, object, game); activateAbility(useableAbilities, object, game);
currentlyUnpaidMana = null;
} }
} }
} }

View file

@ -39,28 +39,28 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
* *
* @author levelX2 * @author levelX2
*/ */
public class MorphTest extends CardTestPlayerBase { public class MorphTest extends CardTestPlayerBase {
/** /**
* Tests if a creature with Morph is cast normal, it behaves as normal creature * Tests if a creature with Morph is cast normal, it behaves as normal
* creature
* *
*/ */
@Test @Test
public void testCastMoprhCreatureWithoutMorph() { public void testCastMoprhCreatureWithoutMorph() {
/* /*
Pine Walker Pine Walker
Creature - Elemental Creature - Elemental
5/5 5/5
Morph {4}{G} (You may cast this card face down as a 2/2 creature for . Turn it face up any time for its morph cost.) Morph {4}{G} (You may cast this card face down as a 2/2 creature for . Turn it face up any time for its morph cost.)
Whenever Pine Walker or another creature you control is turned face up, untap that creature. Whenever Pine Walker or another creature you control is turned face up, untap that creature.
*/ */
addCard(Zone.HAND, playerA, "Pine Walker"); addCard(Zone.HAND, playerA, "Pine Walker");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); addCard(Zone.BATTLEFIELD, playerA, "Forest", 5);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker");
setChoice(playerA, "No"); // cast it normal as 5/5 setChoice(playerA, "No"); // cast it normal as 5/5
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute(); execute();
@ -69,7 +69,6 @@ public class MorphTest extends CardTestPlayerBase {
} }
/** /**
* Cast the creature face down as a 2/2 * Cast the creature face down as a 2/2
*/ */
@ -77,10 +76,10 @@ public class MorphTest extends CardTestPlayerBase {
public void testCastFaceDown() { public void testCastFaceDown() {
addCard(Zone.HAND, playerA, "Pine Walker"); addCard(Zone.HAND, playerA, "Pine Walker");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute(); execute();
@ -88,6 +87,7 @@ public class MorphTest extends CardTestPlayerBase {
assertPowerToughness(playerA, "", 2, 2); assertPowerToughness(playerA, "", 2, 2);
} }
/** /**
* Test triggered turn face up ability of Pine Walker * Test triggered turn face up ability of Pine Walker
*/ */
@ -95,29 +95,29 @@ public class MorphTest extends CardTestPlayerBase {
public void testTurnFaceUpTrigger() { public void testTurnFaceUpTrigger() {
addCard(Zone.HAND, playerA, "Pine Walker"); addCard(Zone.HAND, playerA, "Pine Walker");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); addCard(Zone.BATTLEFIELD, playerA, "Forest", 5);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
attack(3, playerA, ""); attack(3, playerA, "");
activateAbility(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "{4}{G}: Turn this face-down permanent face up."); activateAbility(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "{4}{G}: Turn this face-down permanent face up.");
setStopAt(3, PhaseStep.END_TURN); setStopAt(3, PhaseStep.END_TURN);
execute(); execute();
assertLife(playerB, 18); assertLife(playerB, 18);
assertPermanentCount(playerA, "", 0); assertPermanentCount(playerA, "", 0);
assertPermanentCount(playerA, "Pine Walker", 1); assertPermanentCount(playerA, "Pine Walker", 1);
assertPowerToughness(playerA, "Pine Walker", 5, 5); assertPowerToughness(playerA, "Pine Walker", 5, 5);
assertTapped("Pine Walker", false); assertTapped("Pine Walker", false);
} }
/** /**
* Test that the triggered "turned face up" ability of Pine Walker does not trigger * Test that the triggered "turned face up" ability of Pine Walker does not
* as long as Pine Walker is not turned face up. * trigger as long as Pine Walker is not turned face up.
* *
*/ */
@Test @Test
public void testDoesNotTriggerFaceDown() { public void testDoesNotTriggerFaceDown() {
@ -127,34 +127,35 @@ public class MorphTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Icefeather Aven"); addCard(Zone.HAND, playerA, "Icefeather Aven");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
addCard(Zone.BATTLEFIELD, playerA, "Island", 3); addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Icefeather Aven"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Icefeather Aven", NO_TARGET, "Pine Walker", StackClause.WHILE_NOT_ON_STACK);
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
attack(3, playerA, ""); attack(3, playerA, "");
attack(3, playerA, ""); attack(3, playerA, "");
activateAbility(3, PhaseStep.DECLARE_BLOCKERS, playerA, "{1}{G}{U}: Turn this face-down permanent face up."); activateAbility(3, PhaseStep.DECLARE_BLOCKERS, playerA, "{1}{G}{U}: Turn this face-down permanent face up.");
setChoice(playerA, "No"); // Don't use return permanent to hand effect setChoice(playerA, "No"); // Don't use return permanent to hand effect
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertLife(playerA, 20); assertLife(playerA, 20);
assertLife(playerB, 16); assertLife(playerB, 16);
assertHandCount(playerA, "Pine Walker", 0); assertHandCount(playerA, "Pine Walker", 0);
assertHandCount(playerA, "Icefeather Aven", 0); assertHandCount(playerA, "Icefeather Aven", 0);
assertPermanentCount(playerA, "", 1); assertPermanentCount(playerA, "", 1);
assertPermanentCount(playerA, "Icefeather Aven", 1); assertPermanentCount(playerA, "Icefeather Aven", 1);
assertTapped("Icefeather Aven", true); assertTapped("Icefeather Aven", true);
} }
/** /**
* Test that Morph creature do not trigger abilities with their face up attributes * Test that Morph creature do not trigger abilities with their face up
* * attributes
*
*/ */
@Test @Test
public void testMorphedRemovesAttributesCreature() { public void testMorphedRemovesAttributesCreature() {
@ -162,104 +163,106 @@ public class MorphTest extends CardTestPlayerBase {
// Creature - Goblin Warrior // Creature - Goblin Warrior
// 2/2 // 2/2
// When Ponyback Brigade enters the battlefield or is turned face up, put three 1/1 red Goblin creature tokens onto the battlefield. // When Ponyback Brigade enters the battlefield or is turned face up, put three 1/1 red Goblin creature tokens onto the battlefield.
// Morph {2}{R}{W}{B}(You may cast this card face down as a 2/2 creature for . Turn it face up any time for its morph cost.) // Morph {2}{R}{W}{B}(You may cast this card face down as a 2/2 creature for . Turn it face up any time for its morph cost.)
addCard(Zone.HAND, playerA, "Ponyback Brigade"); addCard(Zone.HAND, playerA, "Ponyback Brigade");
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerB, "Soldier of the Pantheon", 1); addCard(Zone.BATTLEFIELD, playerB, "Soldier of the Pantheon", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ponyback Brigade"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ponyback Brigade");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute(); execute();
assertLife(playerB, 20); // and not 21 assertLife(playerB, 20); // and not 21
assertPermanentCount(playerA, "", 1); assertPermanentCount(playerA, "", 1);
assertPermanentCount(playerB, "Soldier of the Pantheon", 1); assertPermanentCount(playerB, "Soldier of the Pantheon", 1);
} }
/** /**
* Test to copy a morphed 2/2 creature * Test to copy a morphed 2/2 creature
* *
*/ */
@Test @Test
public void testCopyAMorphedCreature() { public void testCopyAMorphedCreature() {
addCard(Zone.HAND, playerA, "Pine Walker"); addCard(Zone.HAND, playerA, "Pine Walker");
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// Clever Impersonator {2}{U}{U} // Clever Impersonator {2}{U}{U}
// Creature - Shapeshifter // Creature - Shapeshifter
// 0/0 // 0/0
// You may have Clever Impersonator enter the battlefield as a copy of any nonland permanent on the battlefield. // You may have Clever Impersonator enter the battlefield as a copy of any nonland permanent on the battlefield.
addCard(Zone.HAND, playerB, "Clever Impersonator", 1); addCard(Zone.HAND, playerB, "Clever Impersonator", 1);
addCard(Zone.BATTLEFIELD, playerB, "Island", 4); addCard(Zone.BATTLEFIELD, playerB, "Island", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Clever Impersonator"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Clever Impersonator");
setChoice(playerB, "Yes"); // use to copy a nonland permanent setChoice(playerB, "Yes"); // use to copy a nonland permanent
addTarget(playerB, ""); // Morphed creature addTarget(playerB, ""); // Morphed creature
setStopAt(2, PhaseStep.BEGIN_COMBAT); setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute(); execute();
assertLife(playerB, 20); assertLife(playerB, 20);
assertPermanentCount(playerA, "", 1);
assertPowerToughness(playerA, "", 2,2);
assertPermanentCount(playerB, "", 1);
assertPowerToughness(playerB, "", 2,2);
} assertPermanentCount(playerA, "", 1);
assertPowerToughness(playerA, "", 2, 2);
assertPermanentCount(playerB, "", 1);
assertPowerToughness(playerB, "", 2, 2);
}
/** /**
* *
* *
*/ */
@Test @Test
public void testPineWalkerWithUnboostEffect() { public void testPineWalkerWithUnboostEffect() {
addCard(Zone.HAND, playerA, "Pine Walker"); addCard(Zone.HAND, playerA, "Pine Walker");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 8); addCard(Zone.BATTLEFIELD, playerA, "Forest", 8);
// Doomwake Giant {4}{B} // Doomwake Giant {4}{B}
// Creature - Giant // Creature - Giant
// 4/6 // 4/6
// Constellation - When Doomwake Giant or another enchantment enters the battlefield under your control, creatures your opponents control get -1/-1 until end of turn. // Constellation - When Doomwake Giant or another enchantment enters the battlefield under your control, creatures your opponents control get -1/-1 until end of turn.
addCard(Zone.HAND, playerB, "Doomwake Giant", 1); addCard(Zone.HAND, playerB, "Doomwake Giant", 1);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 5); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 5);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Doomwake Giant"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Doomwake Giant");
// activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2}{R}{W}{B}: Turn this face-down permanent face up."); // activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2}{R}{W}{B}: Turn this face-down permanent face up.");
activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "{4}{G}: Turn this face-down permanent face up."); activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "{4}{G}: Turn this face-down permanent face up.");
setStopAt(2, PhaseStep.END_TURN); setStopAt(2, PhaseStep.END_TURN);
execute(); execute();
assertLife(playerB, 20); assertLife(playerB, 20);
assertHandCount(playerA, "Pine Walker", 0); assertHandCount(playerA, "Pine Walker", 0);
assertHandCount(playerB, "Doomwake Giant", 0); assertHandCount(playerB, "Doomwake Giant", 0);
assertPermanentCount(playerA, "", 0); assertPermanentCount(playerA, "", 0);
assertPermanentCount(playerB, "Doomwake Giant", 1); assertPermanentCount(playerB, "Doomwake Giant", 1);
assertPermanentCount(playerA, "Pine Walker", 1); assertPermanentCount(playerA, "Pine Walker", 1);
assertPowerToughness(playerA, "Pine Walker", 4,4); assertPowerToughness(playerA, "Pine Walker", 4, 4);
}
}
/** /**
* If a morph is on the table and an enemy Doomwake Giant comes down, the morph goes * If a morph is on the table and an enemy Doomwake Giant comes down, the
* down to 1/1 correctly. If you unmorph the 2/2 and is also a 2/2 after umorphing, * morph goes down to 1/1 correctly. If you unmorph the 2/2 and is also a
* the morph will be erroneously reduced to 0/0 and die. * 2/2 after umorphing, the morph will be erroneously reduced to 0/0 and
* * die.
*
*/ */
@Test @Test
public void testDoomwakeGiantEffect() { public void testDoomwakeGiantEffect() {
@ -267,38 +270,40 @@ public class MorphTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); addCard(Zone.BATTLEFIELD, playerA, "Plains", 6);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
// Doomwake Giant {4}{B} // Doomwake Giant {4}{B}
// Creature - Giant // Creature - Giant
// 4/6 // 4/6
// Constellation - When Doomwake Giant or another enchantment enters the battlefield under your control, creatures your opponents control get -1/-1 until end of turn. // Constellation - When Doomwake Giant or another enchantment enters the battlefield under your control, creatures your opponents control get -1/-1 until end of turn.
addCard(Zone.HAND, playerB, "Doomwake Giant", 1); addCard(Zone.HAND, playerB, "Doomwake Giant", 1);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 5); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 5);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ponyback Brigade"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ponyback Brigade");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Doomwake Giant"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Doomwake Giant");
activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2}{R}{W}{B}: Turn this face-down permanent face up."); activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "{2}{R}{W}{B}: Turn this face-down permanent face up.");
setStopAt(2, PhaseStep.END_TURN); setStopAt(2, PhaseStep.END_TURN);
execute(); execute();
assertLife(playerB, 20); assertLife(playerB, 20);
assertHandCount(playerA, "Ponyback Brigade", 0); assertHandCount(playerA, "Ponyback Brigade", 0);
assertHandCount(playerB, "Doomwake Giant", 0); assertHandCount(playerB, "Doomwake Giant", 0);
assertPermanentCount(playerA, "", 0); assertPermanentCount(playerA, "", 0);
assertPermanentCount(playerA, "Goblin", 3); assertPermanentCount(playerA, "Goblin", 3);
assertPowerToughness(playerA, "Goblin", 1,1,Filter.ComparisonScope.Any); assertPowerToughness(playerA, "Goblin", 1, 1, Filter.ComparisonScope.Any);
assertPermanentCount(playerB, "Doomwake Giant", 1); assertPermanentCount(playerB, "Doomwake Giant", 1);
assertPermanentCount(playerA, "Ponyback Brigade", 1); assertPermanentCount(playerA, "Ponyback Brigade", 1);
assertPowerToughness(playerA, "Ponyback Brigade", 1,1); assertPowerToughness(playerA, "Ponyback Brigade", 1, 1);
} }
/** /**
* Clone a Morph creature that was cast face down and meanwhile was turned face up * Clone a Morph creature that was cast face down and meanwhile was turned
* face up
* *
*/ */
@Test @Test
@ -326,12 +331,13 @@ public class MorphTest extends CardTestPlayerBase {
assertHandCount(playerA, "Clone", 0); assertHandCount(playerA, "Clone", 0);
assertPermanentCount(playerA, "Sagu Mauler", 2); assertPermanentCount(playerA, "Sagu Mauler", 2);
assertPowerToughness(playerA, "Sagu Mauler", 6,6,Filter.ComparisonScope.Any); assertPowerToughness(playerA, "Sagu Mauler", 6, 6, Filter.ComparisonScope.Any);
} }
/** /**
* Check that you can't counter a creature cast for it morph costs * Check that you can't counter a creature cast for it morph costs with
* with Disdainful Stroke if it's normal cmc > 3 * Disdainful Stroke if it's normal cmc > 3
* *
*/ */
@Test @Test
@ -366,16 +372,17 @@ public class MorphTest extends CardTestPlayerBase {
} }
/** /**
* Check that an effect like "Target creature and all other creatures with the same name" does * Check that an effect like "Target creature and all other creatures with
* only effect one face down creature, also if multiple on the battlefield. Because they have no * the same name" does only effect one face down creature, also if multiple
* name, they don't have the same name. * on the battlefield. Because they have no name, they don't have the same
* name.
* *
*/ */
@Test @Test
public void testEchoingDecaySameNameEffect() { public void testEchoingDecaySameNameEffect() {
// Sagu Mauler 6/6 - Creature - Beast // Sagu Mauler 6/6 - Creature - Beast
// Trample, hexproof // Trample, hexproof
// Morph {3}{G}{B} (You may cast this card face down as a 2/2 creature for . Turn it face up any time for its morph cost.) // Morph {3}{G}{B} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.)
addCard(Zone.HAND, playerA, "Sagu Mauler", 2); addCard(Zone.HAND, playerA, "Sagu Mauler", 2);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 6); addCard(Zone.BATTLEFIELD, playerA, "Forest", 6);
@ -387,7 +394,7 @@ public class MorphTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sagu Mauler"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sagu Mauler");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sagu Mauler"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sagu Mauler", NO_TARGET, "Sagu Mauler", StackClause.WHILE_NOT_ON_STACK);
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Echoing Decay", ""); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Echoing Decay", "");
@ -405,11 +412,11 @@ public class MorphTest extends CardTestPlayerBase {
} }
/** /**
* I played a Akroma, Angel of Fury face down, and my opponent tried to counter it. * I played a Akroma, Angel of Fury face down, and my opponent tried to
* The counter failed and Akroma face successfully play face down, when it should have * counter it. The counter failed and Akroma face successfully play face
* been countered. (The card text on akroma should not prevent her from being countered). * down, when it should have been countered. (The card text on akroma should
* not prevent her from being countered).
*/ */
@Test @Test
public void testRuleModifyingEffectsFromManifestedCardWontBeAppliedAbilities() { public void testRuleModifyingEffectsFromManifestedCardWontBeAppliedAbilities() {
addCard(Zone.HAND, playerA, "Akroma, Angel of Fury", 1); addCard(Zone.HAND, playerA, "Akroma, Angel of Fury", 1);
@ -418,7 +425,6 @@ public class MorphTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Counterspell", 1); addCard(Zone.HAND, playerB, "Counterspell", 1);
addCard(Zone.BATTLEFIELD, playerB, "Island", 2); addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Counterspell", "Akroma, Angel of Fury"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Counterspell", "Akroma, Angel of Fury");
@ -432,11 +438,11 @@ public class MorphTest extends CardTestPlayerBase {
assertGraveyardCount(playerA, "Akroma, Angel of Fury", 1); assertGraveyardCount(playerA, "Akroma, Angel of Fury", 1);
} }
/**
* Check if a face down Morph creature gets exiled, it will
* be face up in exile zone.
*/
/**
* Check if a face down Morph creature gets exiled, it will be face up in
* exile zone.
*/
@Test @Test
public void testExileFaceDownCreature() { public void testExileFaceDownCreature() {
addCard(Zone.HAND, playerA, "Birchlore Rangers", 1); addCard(Zone.HAND, playerA, "Birchlore Rangers", 1);
@ -445,7 +451,6 @@ public class MorphTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Swords to Plowshares", 1); addCard(Zone.HAND, playerB, "Swords to Plowshares", 1);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 1); addCard(Zone.BATTLEFIELD, playerB, "Plains", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Birchlore Rangers"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Birchlore Rangers");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
@ -460,8 +465,7 @@ public class MorphTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Swords to Plowshares", 1); assertGraveyardCount(playerB, "Swords to Plowshares", 1);
assertExileCount("Birchlore Rangers", 1); assertExileCount("Birchlore Rangers", 1);
for (Card card : currentGame.getExile().getAllCards(currentGame)) {
for (Card card: currentGame.getExile().getAllCards(currentGame)) {
if (card.getName().equals("Birchlore Rangers")) { if (card.getName().equals("Birchlore Rangers")) {
Assert.assertEquals("Birchlore Rangers has to be face up in exile", false, card.isFaceDown(currentGame)); Assert.assertEquals("Birchlore Rangers has to be face up in exile", false, card.isFaceDown(currentGame));
break; break;
@ -469,11 +473,11 @@ public class MorphTest extends CardTestPlayerBase {
} }
} }
/**
* Check that a DiesTriggeredAbility of a creature does not trigger
* if the creature dies face down
*/
/**
* Check that a DiesTriggeredAbility of a creature does not trigger if the
* creature dies face down
*/
@Test @Test
public void testDiesTriggeredDoesNotTriggerIfFaceDown() { public void testDiesTriggeredDoesNotTriggerIfFaceDown() {
// Flying // Flying
@ -486,7 +490,6 @@ public class MorphTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Lightning Bolt", 1); addCard(Zone.HAND, playerB, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ashcloud Phoenix"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ashcloud Phoenix");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
@ -501,20 +504,19 @@ public class MorphTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Lightning Bolt", 1); assertGraveyardCount(playerB, "Lightning Bolt", 1);
assertGraveyardCount(playerA, "Ashcloud Phoenix", 1); assertGraveyardCount(playerA, "Ashcloud Phoenix", 1);
for (Card card : playerA.getGraveyard().getCards(currentGame)) {
for (Card card: playerA.getGraveyard().getCards(currentGame)) {
if (card.getName().equals("Ashcloud Phoenix")) { if (card.getName().equals("Ashcloud Phoenix")) {
Assert.assertEquals("Ashcloud Phoenix has to be face up in graveyard", false, card.isFaceDown(currentGame)); Assert.assertEquals("Ashcloud Phoenix has to be face up in graveyard", false, card.isFaceDown(currentGame));
break; break;
} }
} }
} }
/**
* Check that a DiesTriggeredAbility of a creature does not trigger
* if the creature dies face down in combat
*/
/**
* Check that a DiesTriggeredAbility of a creature does not trigger if the
* creature dies face down in combat
*/
@Test @Test
public void testDiesTriggeredDoesNotTriggerInCombatIfFaceDown() { public void testDiesTriggeredDoesNotTriggerInCombatIfFaceDown() {
// Flying // Flying
@ -524,24 +526,22 @@ public class MorphTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Ashcloud Phoenix", 1); addCard(Zone.HAND, playerA, "Ashcloud Phoenix", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// First strike, forestwalk, vigilance // First strike, forestwalk, vigilance
// (This creature deals combat damage before creatures without first strike, it can't be blocked as long as defending player controls a Forest, and attacking doesn't cause this creature to tap.) // (This creature deals combat damage before creatures without first strike, it can't be blocked as long as defending player controls a Forest, and attacking doesn't cause this creature to tap.)
addCard(Zone.BATTLEFIELD, playerB, "Mirri, Cat Warrior"); addCard(Zone.BATTLEFIELD, playerB, "Mirri, Cat Warrior");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ashcloud Phoenix"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ashcloud Phoenix");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
attack(2, playerB, "Mirri, Cat Warrior"); attack(2, playerB, "Mirri, Cat Warrior");
block(2, playerA, "", "Mirri, Cat Warrior"); block(2, playerA, "", "Mirri, Cat Warrior");
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertGraveyardCount(playerA, "Ashcloud Phoenix", 1); assertGraveyardCount(playerA, "Ashcloud Phoenix", 1);
for (Card card : playerA.getGraveyard().getCards(currentGame)) {
for (Card card: playerA.getGraveyard().getCards(currentGame)) {
if (card.getName().equals("Ashcloud Phoenix")) { if (card.getName().equals("Ashcloud Phoenix")) {
Assert.assertEquals("Ashcloud Phoenix has to be face up in graveyard", false, card.isFaceDown(currentGame)); Assert.assertEquals("Ashcloud Phoenix has to be face up in graveyard", false, card.isFaceDown(currentGame));
break; break;
@ -551,13 +551,14 @@ public class MorphTest extends CardTestPlayerBase {
assertLife(playerA, 20); assertLife(playerA, 20);
assertLife(playerB, 20); assertLife(playerB, 20);
} }
/**
* Supplant Form does not work correctly with morph creatures. If you bounce and copy
* a face-down morph, the created token should be a colorless 2/2, but the token created
* is instead the face-up of what the morph creature was.
*/
/**
* Supplant Form does not work correctly with morph creatures. If you bounce
* and copy a face-down morph, the created token should be a colorless 2/2,
* but the token created is instead the face-up of what the morph creature
* was.
*/
@Test @Test
public void testSupplantFormWithMorphedCreature() { public void testSupplantFormWithMorphedCreature() {
addCard(Zone.HAND, playerA, "Akroma, Angel of Fury", 1); addCard(Zone.HAND, playerA, "Akroma, Angel of Fury", 1);
@ -567,7 +568,6 @@ public class MorphTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Supplant Form", 1); addCard(Zone.HAND, playerB, "Supplant Form", 1);
addCard(Zone.BATTLEFIELD, playerB, "Island", 6); addCard(Zone.BATTLEFIELD, playerB, "Island", 6);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Supplant Form", ""); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Supplant Form", "");
@ -579,18 +579,17 @@ public class MorphTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Supplant Form", 1); assertGraveyardCount(playerB, "Supplant Form", 1);
assertHandCount(playerA, "Akroma, Angel of Fury", 1); assertHandCount(playerA, "Akroma, Angel of Fury", 1);
assertPermanentCount(playerB, "Akroma, Angel of Fury", 0); assertPermanentCount(playerB, "Akroma, Angel of Fury", 0);
assertPermanentCount(playerB, "", 1); assertPermanentCount(playerB, "", 1);
assertPowerToughness(playerB, "", 2, 2); assertPowerToughness(playerB, "", 2, 2);
} }
/**
* Dragonlord Kolaghan passive of 10 damage works when you play a morph creature
* and it isn't suposed to. Because it is nameless.
*/
/**
* Dragonlord Kolaghan passive of 10 damage works when you play a morph
* creature and it isn't suposed to. Because it is nameless.
*/
@Test @Test
public void testDragonlordKolaghan() { public void testDragonlordKolaghan() {
addCard(Zone.GRAVEYARD, playerA, "Akroma, Angel of Fury", 1); addCard(Zone.GRAVEYARD, playerA, "Akroma, Angel of Fury", 1);
@ -602,7 +601,6 @@ public class MorphTest extends CardTestPlayerBase {
// Whenever an opponent casts a creature or planeswalker spell with the same name as a card in his or her graveyard, that player loses 10 life. // Whenever an opponent casts a creature or planeswalker spell with the same name as a card in his or her graveyard, that player loses 10 life.
addCard(Zone.BATTLEFIELD, playerB, "Dragonlord Kolaghan", 1); addCard(Zone.BATTLEFIELD, playerB, "Dragonlord Kolaghan", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
@ -610,8 +608,8 @@ public class MorphTest extends CardTestPlayerBase {
execute(); execute();
assertLife(playerA, 20); assertLife(playerA, 20);
assertPermanentCount(playerA, "", 1); assertPermanentCount(playerA, "", 1);
} }
} }

View file

@ -58,6 +58,9 @@ public class ManaUtilTest extends CardTestPlayerBase {
testManaToPayVsLand("{G}{W/R}{W}", "Sacred Foundry", 2, WhiteManaAbility.class); // auto choose for hybrid mana: choose {W} testManaToPayVsLand("{G}{W/R}{W}", "Sacred Foundry", 2, WhiteManaAbility.class); // auto choose for hybrid mana: choose {W}
testManaToPayVsLand("{W/B}{W/B}", "Swamp", 1, BlackManaAbility.class); testManaToPayVsLand("{W/B}{W/B}", "Swamp", 1, BlackManaAbility.class);
testManaToPayVsLand("{R}", "Glimmervoid", 1, 1);
testManaToPayVsLand("{R}{1}", "Glimmervoid", 1, 1);
// we can't auto choose here: // we can't auto choose here:
// let say we auto choose {R}, then we have to use it to pay for {R} not {W/R} (as {W/R} is more generic cost) // let say we auto choose {R}, then we have to use it to pay for {R} not {W/R} (as {W/R} is more generic cost)
// but in such case what is left to pay is {W/R}{W} and it is possible that we won't have 2 white sources // but in such case what is left to pay is {W/R}{W} and it is possible that we won't have 2 white sources

View file

@ -626,4 +626,24 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
} }
return moreMana; return moreMana;
} }
public int getDifferentColors() {
int count = 0;
if (blue > 0) {
count++;
}
if (black > 0) {
count++;
}
if (green > 0) {
count++;
}
if (white > 0) {
count++;
}
if (red > 0) {
count++;
}
return count;
}
} }

View file

@ -1,7 +1,10 @@
package mage.util; package mage.util;
import java.util.*; import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.Mana; import mage.Mana;
import mage.ManaSymbol; import mage.ManaSymbol;
@ -17,6 +20,8 @@ import mage.abilities.mana.ManaAbility;
import mage.abilities.mana.RedManaAbility; import mage.abilities.mana.RedManaAbility;
import mage.abilities.mana.WhiteManaAbility; import mage.abilities.mana.WhiteManaAbility;
import mage.cards.Card; import mage.cards.Card;
import mage.choices.Choice;
import mage.constants.ColoredManaSymbol;
import mage.game.Game; import mage.game.Game;
/** /**
@ -75,6 +80,56 @@ public class ManaUtil {
return useableAbilities; return useableAbilities;
} }
/**
* For Human players this is called before a player is asked to select a
* mana color to pay a specific cost. If the choice obvious, the color is
* auto picked by this method without bothering the human player
*
* @param choice the color choice to do
* @param unpaid the mana still to pay
* @return
*/
public static boolean tryToAutoSelectAManaColor(Choice choice, ManaCost unpaid) {
String colorToAutoPay = null;
if (unpaid.containsColor(ColoredManaSymbol.W) && choice.getChoices().contains("White")) {
colorToAutoPay = "White";
}
if (unpaid.containsColor(ColoredManaSymbol.R) && choice.getChoices().contains("Red")) {
if (colorToAutoPay != null) {
return false;
}
colorToAutoPay = "Red";
}
if (unpaid.containsColor(ColoredManaSymbol.G) && choice.getChoices().contains("Green")) {
if (colorToAutoPay != null) {
return false;
}
colorToAutoPay = "Green";
}
if (unpaid.containsColor(ColoredManaSymbol.U) && choice.getChoices().contains("Blue")) {
if (colorToAutoPay != null) {
return false;
}
colorToAutoPay = "Blue";
}
if (unpaid.containsColor(ColoredManaSymbol.B) && choice.getChoices().contains("Black")) {
if (colorToAutoPay != null) {
return false;
}
colorToAutoPay = "Black";
}
// only colorless to pay so take first choice
if (unpaid.getMana().getDifferentColors() == 0) {
colorToAutoPay = choice.getChoices().iterator().next();
}
// one possible useful option found
if (colorToAutoPay != null) {
choice.setChoice(colorToAutoPay);
return true;
}
return false;
}
private static LinkedHashMap<UUID, ManaAbility> getManaAbilitiesUsingManaSymbols(LinkedHashMap<UUID, ManaAbility> useableAbilities, ManaSymbols symbols, Mana unpaidMana) { private static LinkedHashMap<UUID, ManaAbility> getManaAbilitiesUsingManaSymbols(LinkedHashMap<UUID, ManaAbility> useableAbilities, ManaSymbols symbols, Mana unpaidMana) {
Set<ManaSymbol> countColored = new HashSet<>(); Set<ManaSymbol> countColored = new HashSet<>();
@ -378,13 +433,12 @@ public class ManaUtil {
} }
} }
/** Converts a collection of mana symbols into a single condensed string /**
* e.g. * Converts a collection of mana symbols into a single condensed string e.g.
* {1}{1}{1}{1}{1}{W} = {5}{W} * {1}{1}{1}{1}{1}{W} = {5}{W} {2}{B}{2}{B}{2}{B} = {6}{B}{B}{B}
* {2}{B}{2}{B}{2}{B} = {6}{B}{B}{B} * {1}{2}{R}{U}{1}{1} = {5}{R}{U} {B}{G}{R} = {B}{G}{R}
* {1}{2}{R}{U}{1}{1} = {5}{R}{U} *
* {B}{G}{R} = {B}{G}{R} */
* */
public static String condenseManaCostString(String rawCost) { public static String condenseManaCostString(String rawCost) {
int total = 0; int total = 0;
int index = 0; int index = 0;
@ -394,7 +448,7 @@ public class ManaUtil {
Arrays.sort(splitCost); Arrays.sort(splitCost);
for (String c : splitCost) { for (String c : splitCost) {
// If the string is a representation of a number // If the string is a representation of a number
if(c.matches("\\d+")) { if (c.matches("\\d+")) {
total += Integer.parseInt(c); total += Integer.parseInt(c);
} else { } else {
// First non-number we see we can finish as they are sorted // First non-number we see we can finish as they are sorted
@ -405,15 +459,15 @@ public class ManaUtil {
int splitCostLength = splitCost.length; int splitCostLength = splitCost.length;
// No need to add {total} to the mana cost if total == 0 // No need to add {total} to the mana cost if total == 0
int shift = (total > 0) ? 1 : 0; int shift = (total > 0) ? 1 : 0;
String [] finalCost = new String[shift + splitCostLength - index]; String[] finalCost = new String[shift + splitCostLength - index];
// Account for no colourless mana symbols seen // Account for no colourless mana symbols seen
if(total > 0) { if (total > 0) {
finalCost[0] = String.valueOf(total); finalCost[0] = String.valueOf(total);
} }
System.arraycopy(splitCost, index, finalCost, shift, splitCostLength - index); System.arraycopy(splitCost, index, finalCost, shift, splitCostLength - index);
// Combine the cost back as a mana string // Combine the cost back as a mana string
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for(String s: finalCost) { for (String s : finalCost) {
sb.append("{" + s + "}"); sb.append("{" + s + "}");
} }
// Return the condensed string // Return the condensed string