diff --git a/Mage.Sets/src/mage/cards/d/DeepWater.java b/Mage.Sets/src/mage/cards/d/DeepWater.java index f169edd52fc..fc41ee608b8 100644 --- a/Mage.Sets/src/mage/cards/d/DeepWater.java +++ b/Mage.Sets/src/mage/cards/d/DeepWater.java @@ -20,7 +20,6 @@ import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.events.ManaEvent; import mage.game.permanent.Permanent; -import mage.players.Player; /** * diff --git a/Mage.Sets/src/mage/cards/d/Desolation.java b/Mage.Sets/src/mage/cards/d/Desolation.java index e0e13efc7aa..55447a30abe 100644 --- a/Mage.Sets/src/mage/cards/d/Desolation.java +++ b/Mage.Sets/src/mage/cards/d/Desolation.java @@ -53,7 +53,7 @@ class DesolationEffect extends OneShotEffect { public DesolationEffect() { super(Outcome.Damage); - this.staticText = "each player who tapped a land for mana this turn sacrifices a land. Desolation deals 2 damage to each player who sacrificed a Plains this way"; + this.staticText = "each player who tapped a land for mana this turn sacrifices a land. {this} deals 2 damage to each player who sacrificed a Plains this way"; } public DesolationEffect(DesolationEffect copy) { @@ -108,7 +108,8 @@ class DesolationWatcher extends Watcher { if (event.getType() == GameEvent.EventType.UNTAP_STEP_PRE) { reset(); } - if (event.getType() == GameEvent.EventType.TAPPED_FOR_MANA) { + if (event.getType() == GameEvent.EventType.TAPPED_FOR_MANA + && !game.inCheckPlayableState()) { // Ignored - see GameEvent.TAPPED_FOR_MANA UUID playerId = event.getPlayerId(); if (playerId != null) { Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/f/ForbiddenOrchard.java b/Mage.Sets/src/mage/cards/f/ForbiddenOrchard.java index ebf3b785375..65895bb145e 100644 --- a/Mage.Sets/src/mage/cards/f/ForbiddenOrchard.java +++ b/Mage.Sets/src/mage/cards/f/ForbiddenOrchard.java @@ -86,7 +86,7 @@ class ForbiddenOrchardTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - return event.getSourceId().equals(getSourceId()); + return event.getSourceId().equals(getSourceId()) && !game.inCheckPlayableState(); } @Override diff --git a/Mage.Sets/src/mage/cards/g/GauntletOfPower.java b/Mage.Sets/src/mage/cards/g/GauntletOfPower.java index e274f286526..f3e43486833 100644 --- a/Mage.Sets/src/mage/cards/g/GauntletOfPower.java +++ b/Mage.Sets/src/mage/cards/g/GauntletOfPower.java @@ -1,5 +1,7 @@ package mage.cards.g; +import java.util.ArrayList; +import java.util.List; import mage.Mana; import mage.ObjectColor; import mage.abilities.Ability; @@ -43,10 +45,10 @@ public final class GauntletOfPower extends CardImpl { // As Gauntlet of Power enters the battlefield, choose a color. this.addAbility(new EntersBattlefieldAbility(new ChooseColorEffect(Outcome.Neutral))); // Creatures of the chosen color get +1/+1. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GauntletOfPowerEffect1())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GauntletOfPowerBoostEffect())); // Whenever a basic land is tapped for mana of the chosen color, its controller adds one mana of that color. - this.addAbility(new TapForManaAllTriggeredAbility(new GauntletOfPowerEffectEffect2(), filter, SetTargetPointer.PERMANENT)); + this.addAbility(new GauntletOfPowerTapForManaAllTriggeredAbility(new GauntletOfPowerManaEffect2(), filter, SetTargetPointer.PERMANENT)); } public GauntletOfPower(final GauntletOfPower card) { @@ -59,22 +61,22 @@ public final class GauntletOfPower extends CardImpl { } } -class GauntletOfPowerEffect1 extends ContinuousEffectImpl { +class GauntletOfPowerBoostEffect extends ContinuousEffectImpl { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); - public GauntletOfPowerEffect1() { + public GauntletOfPowerBoostEffect() { super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.ModifyPT_7c, Outcome.BoostCreature); staticText = "Creatures of the chosen color get +1/+1"; } - public GauntletOfPowerEffect1(final GauntletOfPowerEffect1 effect) { + public GauntletOfPowerBoostEffect(final GauntletOfPowerBoostEffect effect) { super(effect); } @Override - public GauntletOfPowerEffect1 copy() { - return new GauntletOfPowerEffect1(this); + public GauntletOfPowerBoostEffect copy() { + return new GauntletOfPowerBoostEffect(this); } @Override @@ -93,18 +95,18 @@ class GauntletOfPowerEffect1 extends ContinuousEffectImpl { } -class TapForManaAllTriggeredAbility extends TriggeredManaAbility { +class GauntletOfPowerTapForManaAllTriggeredAbility extends TriggeredManaAbility { private final FilterPermanent filter; private final SetTargetPointer setTargetPointer; - public TapForManaAllTriggeredAbility(ManaEffect effect, FilterPermanent filter, SetTargetPointer setTargetPointer) { + public GauntletOfPowerTapForManaAllTriggeredAbility(ManaEffect effect, FilterPermanent filter, SetTargetPointer setTargetPointer) { super(Zone.BATTLEFIELD, effect, false); this.filter = filter; this.setTargetPointer = setTargetPointer; } - public TapForManaAllTriggeredAbility(TapForManaAllTriggeredAbility ability) { + public GauntletOfPowerTapForManaAllTriggeredAbility(GauntletOfPowerTapForManaAllTriggeredAbility ability) { super(ability); this.filter = ability.filter.copy(); this.setTargetPointer = ability.setTargetPointer; @@ -156,8 +158,8 @@ class TapForManaAllTriggeredAbility extends TriggeredManaAbility { } @Override - public TapForManaAllTriggeredAbility copy() { - return new TapForManaAllTriggeredAbility(this); + public GauntletOfPowerTapForManaAllTriggeredAbility copy() { + return new GauntletOfPowerTapForManaAllTriggeredAbility(this); } @Override @@ -167,14 +169,14 @@ class TapForManaAllTriggeredAbility extends TriggeredManaAbility { } } -class GauntletOfPowerEffectEffect2 extends ManaEffect { +class GauntletOfPowerManaEffect2 extends ManaEffect { - public GauntletOfPowerEffectEffect2() { + public GauntletOfPowerManaEffect2() { super(); staticText = "its controller adds one additional mana of that color"; } - public GauntletOfPowerEffectEffect2(final GauntletOfPowerEffectEffect2 effect) { + public GauntletOfPowerManaEffect2(final GauntletOfPowerManaEffect2 effect) { super(effect); } @@ -187,6 +189,18 @@ class GauntletOfPowerEffectEffect2 extends ManaEffect { return null; } + @Override + public List getNetMana(Game game, Ability source) { + List netMana = new ArrayList<>(); + if (game != null) { + Mana mana = (Mana) getValue("mana"); + if (mana != null) { + netMana.add(mana.copy()); + } + } + return netMana; + } + @Override public Mana produceMana(Game game, Ability source) { if (game != null) { @@ -202,7 +216,7 @@ class GauntletOfPowerEffectEffect2 extends ManaEffect { } @Override - public GauntletOfPowerEffectEffect2 copy() { - return new GauntletOfPowerEffectEffect2(this); + public GauntletOfPowerManaEffect2 copy() { + return new GauntletOfPowerManaEffect2(this); } } diff --git a/Mage.Sets/src/mage/cards/s/SasayaOrochiAscendant.java b/Mage.Sets/src/mage/cards/s/SasayaOrochiAscendant.java index 0cd6b7ace1a..e802e0f5017 100644 --- a/Mage.Sets/src/mage/cards/s/SasayaOrochiAscendant.java +++ b/Mage.Sets/src/mage/cards/s/SasayaOrochiAscendant.java @@ -1,5 +1,6 @@ package mage.cards.s; +import java.util.ArrayList; import mage.MageInt; import mage.Mana; import mage.abilities.Ability; @@ -11,8 +12,6 @@ import mage.abilities.effects.common.FlipSourceEffect; import mage.abilities.effects.common.ManaEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.choices.Choice; -import mage.choices.ChoiceColor; import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledLandPermanent; @@ -26,9 +25,10 @@ import mage.game.permanent.Permanent; import mage.game.permanent.token.TokenImpl; import mage.players.Player; -import java.util.ArrayList; import java.util.List; import java.util.UUID; +import mage.choices.Choice; +import mage.choices.ChoiceColor; /** * @author LevelX2 @@ -133,9 +133,55 @@ class SasayasEssenceManaEffect extends ManaEffect { @Override public List getNetMana(Game game, Ability source) { - return new ArrayList<>(); + List netMana = new ArrayList<>(); + Player controller = game.getPlayer(source.getControllerId()); + Mana producedMana = (Mana) this.getValue("mana"); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (controller != null && producedMana != null && permanent != null) { + FilterPermanent filter = new FilterLandPermanent(); + filter.add(Predicates.not(new PermanentIdPredicate(permanent.getId()))); + filter.add(new NamePredicate(permanent.getName())); + int count = game.getBattlefield().countAll(filter, controller.getId(), game); + if (count > 0) { + if (producedMana.getBlack() > 0) { + netMana.add(Mana.BlackMana(count)); + } + if (producedMana.getRed() > 0) { + netMana.add(Mana.RedMana(count)); + } + if (producedMana.getBlue() > 0) { + netMana.add(Mana.BlueMana(count)); + } + if (producedMana.getGreen() > 0) { + netMana.add(Mana.GreenMana(count)); + } + if (producedMana.getWhite() > 0) { + netMana.add(Mana.WhiteMana(count)); + } + if (producedMana.getColorless() > 0) { + netMana.add(Mana.ColorlessMana(count)); + } + } + } + return netMana; + } + /** + * RULINGS 6/1/2005 If Sasaya’s Essence’s controller has four Forests and + * taps one of them for Green, the Essence will add GreenGreenGreen to that + * player’s mana pool for a total of GreenGreenGreenGreen. + * + * 6/1/2005 If Sasaya’s Essence’s controller has four Mossfire Valley and + * taps one of them for RedGreen, the Essence will add three mana (one for + * each other Mossfire Valley) of any combination of Red and/or Green to + * that player’s mana pool. + * + * 6/1/2005 If Sasaya’s Essence’s controller has two Brushlands and taps one + * of them for White, Sasaya’s Essence adds another White to that player’s + * mana pool. It won’t produce Green or Colorless unless the land was tapped + * for Green or Colorless instead. + */ @Override public Mana produceMana(Game game, Ability source) { Mana newMana = new Mana(); diff --git a/Mage.Sets/src/mage/cards/s/SavageFirecat.java b/Mage.Sets/src/mage/cards/s/SavageFirecat.java index c5644725413..0187df8c7e6 100644 --- a/Mage.Sets/src/mage/cards/s/SavageFirecat.java +++ b/Mage.Sets/src/mage/cards/s/SavageFirecat.java @@ -74,6 +74,9 @@ class SavageFirecatTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + if (game.inCheckPlayableState()) { // Ignored - see GameEvent.TAPPED_FOR_MANA + return false; + } return game.getCard(event.getSourceId()).isLand() && event.getPlayerId().equals(this.controllerId); } diff --git a/Mage.Sets/src/mage/cards/v/VorinclexVoiceOfHunger.java b/Mage.Sets/src/mage/cards/v/VorinclexVoiceOfHunger.java index 4a633939796..0397b488a8e 100644 --- a/Mage.Sets/src/mage/cards/v/VorinclexVoiceOfHunger.java +++ b/Mage.Sets/src/mage/cards/v/VorinclexVoiceOfHunger.java @@ -18,7 +18,6 @@ import mage.constants.Zone; import mage.filter.common.FilterControlledLandPermanent; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; @@ -72,12 +71,15 @@ class VorinclexTriggeredAbility2 extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == EventType.TAPPED_FOR_MANA; + return event.getType() == GameEvent.EventType.TAPPED_FOR_MANA; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (game.getOpponents(controllerId).contains(event.getPlayerId())) { + if (game.inCheckPlayableState()) { // Ignored - see GameEvent.TAPPED_FOR_MANA + return false; + } + if (game.getOpponents(getControllerId()).contains(event.getPlayerId())) { Permanent permanent = game.getPermanent(event.getSourceId()); if (permanent != null && permanent.isLand()) { getEffects().get(0).setTargetPointer(new FixedTarget(permanent.getId())); diff --git a/Mage.Sets/src/mage/cards/w/WintersNight.java b/Mage.Sets/src/mage/cards/w/WintersNight.java index 632b1b3a6ea..550e0b7d81f 100644 --- a/Mage.Sets/src/mage/cards/w/WintersNight.java +++ b/Mage.Sets/src/mage/cards/w/WintersNight.java @@ -22,7 +22,8 @@ import mage.filter.common.FilterLandPermanent; public final class WintersNight extends CardImpl { private static final FilterLandPermanent filter = new FilterLandPermanent("a player taps a snow land"); - { + + static { filter.add(SuperType.SNOW.getPredicate()); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/flip/SasayaOrochiAscendantTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/flip/SasayaOrochiAscendantTest.java index 00f9d4a148b..755a5b8bf58 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/flip/SasayaOrochiAscendantTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/flip/SasayaOrochiAscendantTest.java @@ -29,13 +29,16 @@ public class SasayaOrochiAscendantTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Upwelling", 1); // Enchantment {3}{G} activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reveal your hand: If you have seven or more land cards in your hand, flip"); + activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {G}"); activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {G}"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Upwelling"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - + assertAllCommandsUsed(); + assertPermanentCount(playerA, "Sasaya's Essence", 1); assertPermanentCount(playerA, "Upwelling", 1); @@ -45,7 +48,89 @@ public class SasayaOrochiAscendantTest extends CardTestPlayerBase { assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - assertManaOptions("{G}", manaOptions); + assertManaOptions("{G}{G}{G}", manaOptions); } + + @Test + public void testSasayasEssence2() { + addCard(Zone.HAND, playerA, "Plains", 7); + addCard(Zone.BATTLEFIELD, playerA, "Brushland", 3); + + // Reveal your hand: If you have seven or more land cards in your hand, flip Sasaya, Orochi Ascendant. + // Sasaya's Essence: Legendary Enchantment + // Whenever a land you control is tapped for mana, for each other land you control with the same name, add one mana of any type that land produced. + addCard(Zone.BATTLEFIELD, playerA, "Sasaya, Orochi Ascendant", 1); + // Mana pools don't empty as steps and phases end. + addCard(Zone.HAND, playerA, "Upwelling", 1); // Enchantment {3}{G} + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reveal your hand: If you have seven or more land cards in your hand, flip"); + activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {G}"); + activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {G}"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Upwelling"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Sasaya's Essence", 1); + assertPermanentCount(playerA, "Upwelling", 1); + + assertManaPool(playerA, ManaType.GREEN, 2); + + assertLife(playerA, 18); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + + Assert.assertEquals("mana variations don't fit", 3, manaOptions.size()); + assertManaOptions("{C}{C}{C}", manaOptions); + assertManaOptions("{G}{G}{G}", manaOptions); + assertManaOptions("{W}{W}{W}", manaOptions); + + } + + + @Test + public void testSasayasEssence3() { + addCard(Zone.HAND, playerA, "Plains", 7); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // {1}, {T}: Add {R}{G}. + addCard(Zone.BATTLEFIELD, playerA, "Mossfire Valley", 2); + + // Reveal your hand: If you have seven or more land cards in your hand, flip Sasaya, Orochi Ascendant. + // Sasaya's Essence: Legendary Enchantment + // Whenever a land you control is tapped for mana, for each other land you control with the same name, add one mana of any type that land produced. + addCard(Zone.BATTLEFIELD, playerA, "Sasaya, Orochi Ascendant", 1); + // Mana pools don't empty as steps and phases end. + addCard(Zone.HAND, playerA, "Upwelling", 1); // Enchantment {3}{G} + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reveal your hand: If you have seven or more land cards in your hand, flip"); + activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {G}"); + activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {G}"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Upwelling"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Sasaya's Essence", 1); + assertPermanentCount(playerA, "Upwelling", 1); + + assertManaPool(playerA, ManaType.GREEN, 2); + + assertLife(playerA, 20); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + + Assert.assertEquals("mana variations don't fit", 4, manaOptions.size()); + assertManaOptions("{R}{R}{R}{R}{G}{G}{G}", manaOptions); + assertManaOptions("{R}{R}{R}{G}{G}{G}{G}", manaOptions); + assertManaOptions("{R}{R}{G}{G}{G}{G}{G}", manaOptions); + assertManaOptions("{R}{G}{G}{G}{G}{G}{G}", manaOptions); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ConditionalManaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ConditionalManaTest.java index 0a1fed3ee82..598eedc8c89 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ConditionalManaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ConditionalManaTest.java @@ -53,7 +53,6 @@ public class ConditionalManaTest extends CardTestPlayerBase { } @Test - @Ignore public void testWorkingWithReflectingPool() { addCard(Zone.BATTLEFIELD, playerA, "Cavern of Souls", 1); // can give {C] or {any} mana ({any} with restrictions) addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); // must give {C} or {any} mana from the Cavern, but without restrictions @@ -321,7 +320,6 @@ public class ConditionalManaTest extends CardTestPlayerBase { // and process all available net mana by special call like TriggeredManaAbility->getNetManaForEvent(ManaEvent xxx) @Test - @Ignore public void TriggeredManaAbilityMustGivesExtraManaOptions() { // TriggeredManaAbility must give extra mana options (2 red instead 1) // Whenever you tap a land for mana, add one mana of any type that land produced. @@ -340,7 +338,6 @@ public class ConditionalManaTest extends CardTestPlayerBase { } @Test - @Ignore public void DictateOfKarametra_AutoPay() { // Whenever you tap a land for mana, add one mana of any type that land produced. addCard(Zone.BATTLEFIELD, playerA, "Dictate of Karametra"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java index 5ba92659ea8..c1b6021bc36 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java @@ -5,7 +5,6 @@ import mage.constants.ManaType; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -133,7 +132,6 @@ public class ReflectingPoolTest extends CardTestPlayerBase { * producing mana */ @Test - @Ignore public void testWithDifferentLands() { addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); @@ -215,7 +213,6 @@ public class ReflectingPoolTest extends CardTestPlayerBase { } @Test - @Ignore public void testReflectingPoolAnyManaNeedWithoutCondition() { // any mana source without conditions (use any mana at any time) addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); @@ -233,7 +230,6 @@ public class ReflectingPoolTest extends CardTestPlayerBase { } @Test - @Ignore public void testReflectingPoolAnyManaNeedWithCondition() { // any mana source have condition to use (Reflecting Pool must ignore that condition) addCard(Zone.BATTLEFIELD, playerA, "Cavern of Souls", 1); // {C} or {any} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/TappedForManaRelatedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/TappedForManaRelatedTest.java new file mode 100644 index 00000000000..52e044ec548 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/TappedForManaRelatedTest.java @@ -0,0 +1,97 @@ +package org.mage.test.cards.mana; + +import mage.abilities.mana.ManaOptions; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.mage.test.utils.ManaOptionsTestUtils.assertManaOptions; + +/** + * + * @author LevelX2 + */ +public class TappedForManaRelatedTest extends CardTestPlayerBase { + + /** + * This is a new rule that slightly changes how we resolve abilities that + * trigger whenever a permanent is tapped for mana or for mana of a + * specified type. Now, you look at what was actually produced after the + * activated mana ability resolves. So, tapping a Gaea's Cradle while you no + * control no creatures won't cause a Wild Growth attached to it to trigger. + */ + @Test + public void TestCradleWithWildGrowthNoCreatures() { + // {T}: Add {G} for each creature you control. + addCard(Zone.BATTLEFIELD, playerA, "Gaea's Cradle", 1); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + // Enchant land + // Whenever enchanted land is tapped for mana, its controller adds {G}. + addCard(Zone.HAND, playerA, "Wild Growth", 1); // Enchantment {G} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wild Growth", "Gaea's Cradle"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Wild Growth", 1); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + assertManaOptions("{G}{G}", manaOptions); + + } + + // Mana producedc by triggered mana abilities is not calculated in manaOptions calculations yet. + @Test + // @Ignore + public void TestCradleWithWildGrowthTwoCreatures() { + // {T}: Add {G} for each creature you control. + addCard(Zone.BATTLEFIELD, playerA, "Gaea's Cradle", 1); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 2); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + // Enchant land + // Whenever enchanted land is tapped for mana, its controller adds {G}. + addCard(Zone.HAND, playerA, "Wild Growth", 1); // Enchantment {G} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wild Growth", "Gaea's Cradle"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Wild Growth", 1); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + assertManaOptions("{G}{G}{G}{G}", manaOptions); + + } + + // Mana producedc by triggered mana abilities is not calculated in manaOptions calculations yet. + @Test + // @Ignore + public void TestWildGrowth() { + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 2); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + // Enchant land + // Whenever enchanted land is tapped for mana, its controller adds {G}. + addCard(Zone.HAND, playerA, "Wild Growth", 1); // Enchantment {G} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wild Growth", "Forest"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Wild Growth", 1); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + assertManaOptions("{G}{G}", manaOptions); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index fc85af74c49..558a00f8665 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -63,6 +63,7 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import mage.Mana; import static org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.*; @@ -3274,7 +3275,17 @@ public class TestPlayer implements Player { public ManaOptions getManaAvailable(Game game) { return computerPlayer.getManaAvailable(game); } + + @Override + public void addAvailableTriggeredMana(List availableTriggeredMana) { + computerPlayer.addAvailableTriggeredMana(availableTriggeredMana); + } + @Override + public List> getAvailableTriggeredMana() { + return computerPlayer.getAvailableTriggeredMana(); + } + @Override public List getPlayable(Game game, boolean hidden) { return computerPlayer.getPlayable(game, hidden); diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 358d9ee6211..a2d170a1e61 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -41,6 +41,7 @@ import mage.target.common.TargetCardInLibrary; import java.io.Serializable; import java.util.*; +import mage.Mana; /** * @author Quercitron @@ -1037,6 +1038,21 @@ public class PlayerStub implements Player { return null; } + @Override + public void addAvailableTriggeredMana(List availableTriggeredMan) { + + } + + @Override + public List> getAvailableTriggeredMana() { + return null; + } + + @Override + public int announceXMana(int min, int max, String message, Game game, Ability ability) { + return 0; + } + @Override public List getPlayable(Game game, boolean hidden) { return null; diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java index e7d2c5e9461..16dd0fd727d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java @@ -209,11 +209,32 @@ public class ManaOptionsTest extends CardTestPlayerBase { assertManaOptions("{C}{G}{Any}", manaOptions); } + // Nykthos, Shrine to Nyx + @Test + public void testNykthos4a() { + addCard(Zone.BATTLEFIELD, playerA, "Sedge Scorpion", 4); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + // {T}: Add {C}. + // {2}, {T}: Choose a color. Add an amount of mana of that color equal to your devotion to that color. (Your devotion to a color is the number of mana symbols of that color in the mana costs of permanents you control.) + addCard(Zone.BATTLEFIELD, playerA, "Nykthos, Shrine to Nyx", 1); + + setStopAt(1, PhaseStep.UPKEEP); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + + Assert.assertEquals("mana variations don't fit", 2, manaOptions.size()); + assertManaOptions("{C}{G}{G}{G}", manaOptions); + assertManaOptions("{G}{G}{G}{G}{G}", manaOptions); + + } + // Nykthos, Shrine to Nyx // {T}: Add {C}. // {2}, {T}: Choose a color. Add an amount of mana of that color equal to your devotion to that color. (Your devotion to a color is the number of mana symbols of that color in the mana costs of permanents you control.) @Test - public void testNykthos4() { + public void testNykthos4b() { // If a land is tapped for two or more mana, it produces {C} instead of any other type and amount. // Each spell a player casts costs {1} more to cast for each other spell that player has cast this turn. addCard(Zone.BATTLEFIELD, playerA, "Damping Sphere", 1); @@ -399,7 +420,6 @@ public class ManaOptionsTest extends CardTestPlayerBase { } @Test - @Ignore // TriggeredManaAbilities not supported yet for getAvailableMana public void testCryptGhast() { //Extort (Whenever you cast a spell, you may pay {WB}. If you do, each opponent loses 1 life and you gain that much life.) // Whenever you tap a Swamp for mana, add {B} (in addition to the mana the land produces). diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index a0309647650..e58033ec892 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; +import mage.abilities.effects.common.ManaEffect; /** * @author BetaSteward_at_googlemail.com @@ -171,6 +172,9 @@ public abstract class AbilityImpl implements Ability { private boolean resolveMode(Game game) { boolean result = true; for (Effect effect : getEffects()) { + if (game.inCheckPlayableState() && !(effect instanceof ManaEffect)) { + continue; // Ignored non mana effects - see GameEvent.TAPPED_FOR_MANA + } if (effect instanceof OneShotEffect) { boolean effectResult = effect.apply(game, this); result &= effectResult; diff --git a/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredAbility.java index 6d9cf2e45fc..dd5caf60c2f 100644 --- a/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredAbility.java @@ -41,6 +41,9 @@ public class TapForManaAllTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + if (game.inCheckPlayableState()) { // Ignored - see GameEvent.TAPPED_FOR_MANA + return false; + } Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); if (permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game)) { ManaEvent mEvent = (ManaEvent) event; diff --git a/Mage/src/main/java/mage/abilities/common/TapLandForManaAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/TapLandForManaAllTriggeredAbility.java index f6cba6af6ba..da10a22b405 100644 --- a/Mage/src/main/java/mage/abilities/common/TapLandForManaAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/TapLandForManaAllTriggeredAbility.java @@ -34,6 +34,9 @@ public class TapLandForManaAllTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + if (game.inCheckPlayableState()) { // Ignored - see GameEvent.TAPPED_FOR_MANA + return false; + } Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); if (permanent != null && permanent.isLand()) { if (setTargetPointer) { diff --git a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java index b88a5e445e2..c32ba68b222 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java @@ -26,7 +26,7 @@ public class ReturnToHandChosenControlledPermanentCost extends CostImpl { target.setNotTarget(true); this.addTarget(target); if (target.getMaxNumberOfTargets() > 1 && target.getMaxNumberOfTargets() == target.getNumberOfTargets()) { - this.text = "return " + CardUtil.numberToText(target.getMaxNumberOfTargets()) + ' ' + this.text = "Return " + CardUtil.numberToText(target.getMaxNumberOfTargets()) + ' ' + target.getTargetName() + (target.getTargetName().endsWith(" you control") ? "" : " you control") + " to their owner's hand"; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ManaEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ManaEffect.java index 4acd0a564e1..12dddc1452a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ManaEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ManaEffect.java @@ -14,6 +14,7 @@ import mage.players.Player; import java.util.ArrayList; import java.util.List; +import mage.abilities.TriggeredAbility; /** * @author BetaSteward_at_googlemail.com @@ -38,6 +39,15 @@ public abstract class ManaEffect extends OneShotEffect { if (player == null) { return false; } + if (game.inCheckPlayableState()) { + // During calculation of the available mana for a player the "TappedForMana" event is fired to simulate triggered mana production. + // By checking the inCheckPlayableState these events are handled to give back only the available mana of instead really producing mana + // So it's important if ManaEffects overwrite the apply method to take care for this. + if (source instanceof TriggeredAbility) { + player.addAvailableTriggeredMana(getNetMana(game, source)); + } + return true; // No need to add mana to pool during checkPlayable + } Mana manaToAdd = produceMana(game, source); if (manaToAdd != null && manaToAdd.count() > 0) { checkToFirePossibleEvents(manaToAdd, game, source); @@ -72,11 +82,13 @@ public abstract class ManaEffect extends OneShotEffect { } /** - * Produced the mana the effect can produce (DO NOT add it to mana pool -- return all added as mana object to process by replace events) + * Produced the mana the effect can produce (DO NOT add it to mana pool -- + * return all added as mana object to process by replace events) *

- * WARNING, produceMana can be called multiple times for mana and spell available calculations - * if you don't want it then overide getNetMana to return max possible mana values - * (if you have choose dialogs or extra effects like new counters in produceMana) + * WARNING, produceMana can be called multiple times for mana and spell + * available calculations if you don't want it then overide getNetMana to + * return max possible mana values (if you have choose dialogs or extra + * effects like new counters in produceMana) * * @param game warning, can be NULL for AI score calcs (game == null) * @param source diff --git a/Mage/src/main/java/mage/abilities/effects/mana/AddManaOfAnyTypeProducedEffect.java b/Mage/src/main/java/mage/abilities/effects/mana/AddManaOfAnyTypeProducedEffect.java index e80e2f288b6..f849c21b69e 100644 --- a/Mage/src/main/java/mage/abilities/effects/mana/AddManaOfAnyTypeProducedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/mana/AddManaOfAnyTypeProducedEffect.java @@ -38,9 +38,26 @@ public class AddManaOfAnyTypeProducedEffect extends ManaEffect { @Override public List getNetMana(Game game, Ability source) { List netMana = new ArrayList<>(); - Mana types = (Mana) this.getValue("mana"); // TODO: will not work until TriggeredManaAbility fix (see TriggeredManaAbilityMustGivesExtraManaOptions test) + Mana types = (Mana) this.getValue("mana"); if (types != null) { - netMana.add(types.copy()); + if (types.getBlack() > 0) { + netMana.add(Mana.BlackMana(1)); + } + if (types.getRed() > 0) { + netMana.add(Mana.RedMana(1)); + } + if (types.getBlue() > 0) { + netMana.add(Mana.BlueMana(1)); + } + if (types.getGreen() > 0) { + netMana.add(Mana.GreenMana(1)); + } + if (types.getWhite() > 0) { + netMana.add(Mana.WhiteMana(1)); + } + if (types.getColorless() > 0) { + netMana.add(Mana.ColorlessMana(1)); + } } return netMana; } diff --git a/Mage/src/main/java/mage/abilities/effects/mana/BasicManaEffect.java b/Mage/src/main/java/mage/abilities/effects/mana/BasicManaEffect.java index c3c0e99633a..60314cecc11 100644 --- a/Mage/src/main/java/mage/abilities/effects/mana/BasicManaEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/mana/BasicManaEffect.java @@ -5,7 +5,6 @@ import mage.Mana; import mage.abilities.Ability; import mage.abilities.effects.common.ManaEffect; import mage.game.Game; -import mage.players.Player; public class BasicManaEffect extends ManaEffect { diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index f79ecf358a7..60f57856a29 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -11,6 +11,7 @@ import mage.abilities.costs.common.TapSourceCost; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ManaEvent; +import mage.players.Player; import org.apache.log4j.Logger; /** @@ -44,24 +45,11 @@ public class ManaOptions extends ArrayList { //if there is only one mana option available add it to all the existing options List netManas = abilities.get(0).getNetMana(game); if (netManas.size() == 1) { - if (!hasTapCost(abilities.get(0)) || checkTappedForManaReplacement(abilities.get(0), game, netManas.get(0))) { - addMana(netManas.get(0)); - } + checkTappedForManaReplacement(abilities.get(0), game, netManas.get(0)); + addMana(netManas.get(0)); + addTriggeredMana(game, abilities.get(0)); } else if (netManas.size() > 1) { - List copy = copy(); - this.clear(); -// boolean hasTapCost = hasTapCost(abilities.get(0)); // needed if checkTappedForManaReplacement is reactivated - for (Mana netMana : netManas) { - for (Mana mana : copy) { - // checkTappedForManaReplacement seems in some situations to produce endless iterations so deactivated for now: https://github.com/magefree/mage/issues/5023 - if (true/* !hasTapCost || checkTappedForManaReplacement(abilities.get(0), game, netMana) */) { - Mana newMana = new Mana(); - newMana.add(mana); - newMana.add(netMana); - this.add(newMana); - } - } - } + addManaVariation(netManas, abilities.get(0), game); } } else { // mana source has more than 1 ability @@ -69,14 +57,14 @@ public class ManaOptions extends ArrayList { List copy = copy(); this.clear(); for (ActivatedManaAbilityImpl ability : abilities) { - boolean hasTapCost = hasTapCost(ability); for (Mana netMana : ability.getNetMana(game)) { - if (!hasTapCost || checkTappedForManaReplacement(ability, game, netMana)) { + checkTappedForManaReplacement(ability, game, netMana); + for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { SkipAddMana: for (Mana mana : copy) { Mana newMana = new Mana(); newMana.add(mana); - newMana.add(netMana); + newMana.add(triggeredManaVariation); for (Mana existingMana : this) { if (existingMana.equalManaValue(newMana)) { continue SkipAddMana; @@ -91,18 +79,48 @@ public class ManaOptions extends ArrayList { this.add(newMana); } } + } } } } } - private boolean checkTappedForManaReplacement(Ability ability, Game game, Mana mana) { - ManaEvent event = new ManaEvent(GameEvent.EventType.TAPPED_FOR_MANA, ability.getSourceId(), ability.getSourceId(), ability.getControllerId(), mana); - if (!game.replaceEvent(event)) { - return true; + private void addManaVariation(List netManas, ActivatedManaAbilityImpl ability, Game game) { + List copy = copy(); + this.clear(); + for (Mana netMana : netManas) { + for (Mana mana : copy) { + if (!hasTapCost(ability) || checkTappedForManaReplacement(ability, game, netMana)) { + Mana newMana = new Mana(); + newMana.add(mana); + newMana.add(netMana); + this.add(newMana); + } + } } - return false; + } + + private List> getSimulatedTriggeredManaFromPlayer(Game game, Ability ability) { + Player player = game.getPlayer(ability.getControllerId()); + List> newList = new ArrayList<>(); + if (player != null) { + newList.addAll(player.getAvailableTriggeredMana()); + player.getAvailableTriggeredMana().clear(); + } + return newList; + } + + private boolean checkTappedForManaReplacement(Ability ability, Game game, Mana mana) { + if (hasTapCost(ability)) { + ManaEvent event = new ManaEvent(GameEvent.EventType.TAPPED_FOR_MANA, ability.getSourceId(), ability.getSourceId(), ability.getControllerId(), mana); + if (!game.replaceEvent(event)) { + game.fireEvent(event); + return true; + } + return false; + } + return true; } private boolean hasTapCost(Ability ability) { @@ -127,31 +145,41 @@ public class ManaOptions extends ArrayList { // no mana costs if (ability.getManaCosts().isEmpty()) { if (netManas.size() == 1) { + checkTappedForManaReplacement(ability, game, netManas.get(0)); addMana(netManas.get(0)); + addTriggeredMana(game, ability); } else { List copy = copy(); this.clear(); for (Mana netMana : netManas) { - for (Mana mana : copy) { - Mana newMana = new Mana(); - newMana.add(mana); - newMana.add(netMana); - this.add(newMana); + checkTappedForManaReplacement(ability, game, netMana); + for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { + for (Mana mana : copy) { + Mana newMana = new Mana(); + newMana.add(mana); + newMana.add(triggeredManaVariation); + this.add(newMana); + } } } } } else // the ability has mana costs if (netManas.size() == 1) { + checkTappedForManaReplacement(ability, game, netManas.get(0)); subtractCostAddMana(ability.getManaCosts().getMana(), netManas.get(0), ability.getCosts().isEmpty()); + addTriggeredMana(game, ability); } else { List copy = copy(); this.clear(); for (Mana netMana : netManas) { - for (Mana mana : copy) { - Mana newMana = new Mana(); - newMana.add(mana); - newMana.add(netMana); - subtractCostAddMana(ability.getManaCosts().getMana(), netMana, ability.getCosts().isEmpty()); + checkTappedForManaReplacement(ability, game, netMana); + for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { + for (Mana mana : copy) { + Mana newMana = new Mana(); + newMana.add(mana); + newMana.add(triggeredManaVariation); + subtractCostAddMana(ability.getManaCosts().getMana(), netMana, ability.getCosts().isEmpty()); + } } } } @@ -160,30 +188,30 @@ public class ManaOptions extends ArrayList { List copy = copy(); this.clear(); for (ActivatedManaAbilityImpl ability : abilities) { - boolean hasTapCost = hasTapCost(ability); List netManas = ability.getNetMana(game); - if (ability.getManaCosts().isEmpty()) { for (Mana netMana : netManas) { - if (!hasTapCost || checkTappedForManaReplacement(ability, game, netMana)) { + checkTappedForManaReplacement(ability, game, netMana); + for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { for (Mana mana : copy) { Mana newMana = new Mana(); newMana.add(mana); - newMana.add(netMana); + newMana.add(triggeredManaVariation); this.add(newMana); } } } } else { for (Mana netMana : netManas) { - if (!hasTapCost || checkTappedForManaReplacement(ability, game, netMana)) { + checkTappedForManaReplacement(ability, game, netMana); + for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { for (Mana previousMana : copy) { CombineWithExisting: for (Mana manaOption : ability.getManaCosts().getManaOptions()) { Mana newMana = new Mana(previousMana); if (previousMana.includesMana(manaOption)) { // costs can be paid newMana.subtractCost(manaOption); - newMana.add(netMana); + newMana.add(triggeredManaVariation); // if the new mana is in all colors more than another already existing than replace for (Mana existingMana : this) { Mana moreValuable = Mana.getMoreValuableMana(newMana, existingMana); @@ -211,6 +239,52 @@ public class ManaOptions extends ArrayList { } } + private List getTriggeredManaVariations(Game game, Ability ability, Mana baseMana) { + List baseManaPlusTriggeredMana = new ArrayList<>(); + baseManaPlusTriggeredMana.add(baseMana); + List> availableTriggeredManaList = getSimulatedTriggeredManaFromPlayer(game, ability); + for (List availableTriggeredMana : availableTriggeredManaList) { + if (availableTriggeredMana.size() == 1) { + for (Mana prevMana : baseManaPlusTriggeredMana) { + prevMana.add(availableTriggeredMana.get(0)); + } + } else if (availableTriggeredMana.size() > 1) { + List copy = new ArrayList<>(baseManaPlusTriggeredMana); + baseManaPlusTriggeredMana.clear(); + for (Mana triggeredMana : availableTriggeredMana) { + for (Mana prevMana : copy) { + Mana newMana = new Mana(); + newMana.add(prevMana); + newMana.add(triggeredMana); + baseManaPlusTriggeredMana.add(newMana); + } + } + } + } + return baseManaPlusTriggeredMana; + } + + private void addTriggeredMana(Game game, Ability ability) { + List> netManaList = getSimulatedTriggeredManaFromPlayer(game, ability); + for (List triggeredNetMana : netManaList) { + if (triggeredNetMana.size() == 1) { + addMana(triggeredNetMana.get(0)); + } else if (triggeredNetMana.size() > 1) { + // Add variations + List copy = copy(); + this.clear(); + for (Mana triggeredMana : triggeredNetMana) { + for (Mana mana : copy) { + Mana newMana = new Mana(); + newMana.add(mana); + newMana.add(triggeredMana); + this.add(newMana); + } + } + } + } + } + public void addMana(Mana addMana) { if (isEmpty()) { this.add(new Mana()); diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index c9070f9193c..24a483f82a6 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -133,7 +133,7 @@ public class GameEvent implements Serializable { targetId id of the spell that's cast playerId player that casts the spell or ability amount X multiplier to change X value, default 1 - */ + */ CAST_SPELL, /* SPELL_CAST x-Costs are already defined @@ -153,13 +153,13 @@ public class GameEvent implements Serializable { targetId id of the ability to activate / use sourceId sourceId of the object with that ability playerId player that tries to use this ability - */ + */ TAKE_SPECIAL_ACTION, TAKEN_SPECIAL_ACTION, // not used in implementation yet /* TAKE_SPECIAL_ACTION, TAKEN_SPECIAL_ACTION, targetId id of the ability to activate / use sourceId sourceId of the object with that ability playerId player that tries to use this ability - */ + */ TRIGGERED_ABILITY, RESOLVING_ABILITY, COPY_STACKOBJECT, COPIED_STACKOBJECT, @@ -254,7 +254,13 @@ public class GameEvent implements Serializable { ENTERS_THE_BATTLEFIELD_CONTROL, // 616.1b ENTERS_THE_BATTLEFIELD_COPY, // 616.1c ENTERS_THE_BATTLEFIELD, // 616.1d - TAP, TAPPED, TAPPED_FOR_MANA, + TAP, TAPPED, + TAPPED_FOR_MANA, + /* TAPPED_FOR_MANA + During calculation of the available mana for a player the "TappedForMana" event is fired to simulate triggered mana production. + By checking the inCheckPlayableState these events are handled to give back only the available mana of instead really producing mana. + IMPORTANT: Triggered non mana abilities have to ignore the event if game.inCheckPlayableState is true. + */ UNTAP, UNTAPPED, FLIP, FLIPPED, UNFLIP, UNFLIPPED, @@ -412,12 +418,12 @@ public class GameEvent implements Serializable { } private GameEvent(EventType type, UUID customEventType, - UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag) { + UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag) { this(type, customEventType, targetId, sourceId, playerId, amount, flag, null); } private GameEvent(EventType type, UUID customEventType, - UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag, MageObjectReference reference) { + UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag, MageObjectReference reference) { this.type = type; this.customEventType = customEventType; this.targetId = targetId; diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index e01ab4f6cef..958e08a2caa 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -40,6 +40,7 @@ import mage.util.Copyable; import java.io.Serializable; import java.util.*; +import mage.Mana; /** * @author BetaSteward_at_googlemail.com @@ -635,6 +636,10 @@ public interface Player extends MageItem, Copyable { void untap(Game game); ManaOptions getManaAvailable(Game game); + + void addAvailableTriggeredMana(List netManaAvailable); + + List> getAvailableTriggeredMana(); List getPlayable(Game game, boolean hidden); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 078bcb4bb2f..568646726e9 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -177,6 +177,9 @@ public abstract class PlayerImpl implements Player, Serializable { protected FilterMana phyrexianColors; + // Used during available mana calculation to give back possible available net mana from triggered mana abilities (No need to copy) + protected final List> availableTriggeredManaList = new ArrayList<>(); + /** * During some steps we can't play anything */ @@ -2848,8 +2851,18 @@ public abstract class PlayerImpl implements Player, Serializable { return game.getBattlefield().getAllActivePermanents(blockFilter, playerId, game); } + /** + * Returns the mana options the player currently has. That means which combinations of + * mana are available to cast spells or activate abilities etc. + * + * @param game + * @return + */ @Override public ManaOptions getManaAvailable(Game game) { + boolean oldState = game.inCheckPlayableState(); + game.setCheckPlayableState(true); + ManaOptions availableMana = new ManaOptions(); List> sourceWithoutManaCosts = new ArrayList<>(); @@ -2891,10 +2904,34 @@ public abstract class PlayerImpl implements Player, Serializable { // remove duplicated variants (see ManaOptionsTest for info - when that rises) availableMana.removeDuplicated(); - + + game.setCheckPlayableState(oldState); return availableMana; } + /** + * Used during calculation of available mana to gather the amount of producable triggered mana caused by using mana sources. + * So the set value is only used during the calculation of the mana produced by one source and cleared thereafter + * + * @param netManaAvailable the net mana produced by the triggered mana abaility + */ + @Override + public void addAvailableTriggeredMana(List netManaAvailable) { + this.availableTriggeredManaList.add(netManaAvailable); + } + + /** + * Used during calculation of available mana to get the amount of producable triggered mana caused by using mana sources. + * The list is cleared as soon the value is retrieved during available mana calculation. + * + * @return + */ + @Override + public List> getAvailableTriggeredMana() { + return availableTriggeredManaList; + } + + // returns only mana producers that don't require mana payment protected List getAvailableManaProducers(Game game) { List result = new ArrayList<>();