diff --git a/Mage.Sets/src/mage/cards/r/RainOfRiches.java b/Mage.Sets/src/mage/cards/r/RainOfRiches.java index b15dcdaee2f..c87c6ea05d3 100644 --- a/Mage.Sets/src/mage/cards/r/RainOfRiches.java +++ b/Mage.Sets/src/mage/cards/r/RainOfRiches.java @@ -1,22 +1,24 @@ package mage.cards.r; import mage.MageObjectReference; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledSpellsEffect; import mage.abilities.keyword.CascadeAbility; +import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.common.FilterNonlandCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; import mage.game.permanent.token.TreasureToken; import mage.game.stack.Spell; import mage.game.stack.StackObject; -import mage.players.Player; import mage.watchers.Watcher; import mage.watchers.common.ManaPaidSourceWatcher; @@ -25,10 +27,18 @@ import java.util.Map; import java.util.UUID; /** - * @author Alex-Vasile + * @author Alex-Vasile, Susucr */ public class RainOfRiches extends CardImpl { + + private static final FilterNonlandCard filter = + new FilterNonlandCard("The first spell you cast each turn that mana from a Treasure was spent to cast"); + + static { + filter.add(RainOfRichesPredicate.instance); + } + public RainOfRiches(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}{R}"); @@ -40,7 +50,7 @@ public class RainOfRiches extends CardImpl { // You may cast it without paying its mana cost. // Put the exiled cards on the bottom of your library in a random order.) this.addAbility( - new SimpleStaticAbility(new RainOfRichesGainsCascadeEffect()), + new SimpleStaticAbility(new GainAbilityControlledSpellsEffect(new CascadeAbility(false), filter)), new RainOfRichesWatcher() ); } @@ -55,65 +65,20 @@ public class RainOfRiches extends CardImpl { } } -class RainOfRichesGainsCascadeEffect extends ContinuousEffectImpl { - - private final Ability cascadeAbility = new CascadeAbility(); - - RainOfRichesGainsCascadeEffect() { - super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.staticText = - "The first spell you cast each turn that mana from a Treasure was spent to cast has cascade. " + - "(When you cast the spell, exile cards from the top of your library until you exile a nonland card that costs less. " + - "You may cast it without paying its mana cost. " + - "Put the exiled cards on the bottom of your library in a random order.)"; - } - - private RainOfRichesGainsCascadeEffect(final RainOfRichesGainsCascadeEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - RainOfRichesWatcher watcher = game.getState().getWatcher(RainOfRichesWatcher.class); - if (controller == null || watcher == null) { - return false; - } - - for (StackObject stackObject : game.getStack()) { - // Only spells cast, so no copies of spells - if ((stackObject instanceof Spell) - && !stackObject.isCopy() - && stackObject.isControlledBy(source.getControllerId())) { - Spell spell = (Spell) stackObject; - - if (FirstSpellCastWithTreasureCondition.instance.apply(game, source)) { - game.getState().addOtherAbility(spell.getCard(), cascadeAbility); - return true; // TODO: I think this should return here as soon as it finds the first one. - // If it should, change WildMageSorcerer to also return early. - } - } - } - return false; - } - - @Override - public RainOfRichesGainsCascadeEffect copy() { - return new RainOfRichesGainsCascadeEffect(this); - } -} - -enum FirstSpellCastWithTreasureCondition implements Condition { +enum RainOfRichesPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(Game game, Ability source) { - if (game.getStack().isEmpty()) { + public boolean apply(ObjectSourcePlayer input, Game game) { + Permanent sourcePermanent = input.getSource().getSourcePermanentOrLKI(game); + if (sourcePermanent == null || !sourcePermanent.getControllerId().equals(input.getPlayerId())) { return false; } RainOfRichesWatcher watcher = game.getState().getWatcher(RainOfRichesWatcher.class); - StackObject so = game.getStack().getFirst(); - return watcher != null && RainOfRichesWatcher.checkSpell(so, game); + Card card = input.getObject(); + return watcher != null + && card instanceof StackObject + && watcher.checkSpell((Spell) card, game); } } @@ -127,7 +92,7 @@ class RainOfRichesWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.CAST_SPELL) { + if (event.getType() != GameEvent.EventType.SPELL_CAST) { return; } Spell spell = game.getSpell(event.getSourceId()); @@ -148,13 +113,15 @@ class RainOfRichesWatcher extends Watcher { super.reset(); } - static boolean checkSpell(StackObject stackObject, Game game) { + boolean checkSpell(StackObject stackObject, Game game) { if (stackObject.isCopy() || !(stackObject instanceof Spell)) { return false; } - RainOfRichesWatcher watcher = game.getState().getWatcher(RainOfRichesWatcher.class); - return watcher.playerMap.containsKey(stackObject.getControllerId()) - && watcher.playerMap.get(stackObject.getControllerId()).refersTo(((Spell) stackObject).getMainCard(), game); + if (playerMap.containsKey(stackObject.getControllerId())) { + return playerMap.get(stackObject.getControllerId()).refersTo(((Spell) stackObject).getMainCard(), game); + } else { + return ManaPaidSourceWatcher.getTreasurePaid(stackObject.getId(), game) >= 1; + } } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/RainOfRichesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/RainOfRichesTest.java new file mode 100644 index 00000000000..2a65b1976db --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/RainOfRichesTest.java @@ -0,0 +1,118 @@ +package org.mage.test.cards.single.ncc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class RainOfRichesTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.r.RainOfRiches Rain of Riches} {3}{R}{R} + * Enchantment + * When Rain of Riches enters the battlefield, create two Treasure tokens. + * The first spell you cast each turn that mana from a Treasure was spent to cast has cascade. + */ + private static final String rain = "Rain of Riches"; + + @Test + public void test_Using_Treasures() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, rain, 1); + addCard(Zone.HAND, playerA, "Goblin Piker", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.LIBRARY, playerA, "Elite Vanguard", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rain, true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Goblin Piker"); + setChoice(playerA, "Red"); // choice for treasure mana + setChoice(playerA, "Red"); // choice for treasure mana + setChoice(playerA, true); // yes to Cascade + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Goblin Piker", 1); + assertPermanentCount(playerA, "Elite Vanguard", 1); + } + + @Test + public void test_Not_Using_Treasures() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, rain, 1); + addCard(Zone.HAND, playerA, "Goblin Piker", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.LIBRARY, playerA, "Elite Vanguard", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rain, true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Goblin Piker"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Goblin Piker", 1); + assertPermanentCount(playerA, "Elite Vanguard", 0); + } + + @Test + @Ignore("Does not work until actions are processed between the last mana being paid and the spell being cast.") + public void test_Cast_Two_Using_Treasures() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.HAND, playerA, rain, 1); + addCard(Zone.HAND, playerA, "Raging Goblin", 2); // {R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.LIBRARY, playerA, "Memnite", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rain, true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Raging Goblin"); + setChoice(playerA, "Red"); // choice for treasure mana + setChoice(playerA, true); // yes to Cascade + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Raging Goblin"); + setChoice(playerA, "Red"); // choice for treasure mana + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Raging Goblin", 2); + assertPermanentCount(playerA, "Memnite", 1); + } + + @Test + @Ignore("Does not work until actions are processed between the last mana being paid and the spell being cast.") + public void test_Cast_SomethingElse_Then_Cast_Using_Treasure() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, rain, 1); + addCard(Zone.HAND, playerA, "Raging Goblin", 2); // {R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + addCard(Zone.LIBRARY, playerA, "Memnite", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rain, true); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Raging Goblin", true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Raging Goblin"); + setChoice(playerA, "Red"); // choice for treasure mana + setChoice(playerA, true); // yes to Cascade + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Raging Goblin", 2); + assertPermanentCount(playerA, "Memnite", 1); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java index d2243674730..7cef0dc05df 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java @@ -45,22 +45,22 @@ public class GainAbilityControlledSpellsEffect extends ContinuousEffectImpl { } for (Card card : game.getExile().getAllCardsByRange(game, source.getControllerId())) { - if (filter.match(card, game)) { + if (filter.match(card, player.getId(), source, game)) { game.getState().addOtherAbility(card, ability); } } for (Card card : player.getLibrary().getCards(game)) { - if (filter.match(card, game)) { + if (filter.match(card, player.getId(), source, game)) { game.getState().addOtherAbility(card, ability); } } for (Card card : player.getHand().getCards(game)) { - if (filter.match(card, game)) { + if (filter.match(card, player.getId(), source, game)) { game.getState().addOtherAbility(card, ability); } } for (Card card : player.getGraveyard().getCards(game)) { - if (filter.match(card, game)) { + if (filter.match(card, player.getId(), source, game)) { game.getState().addOtherAbility(card, ability); } } @@ -68,7 +68,7 @@ public class GainAbilityControlledSpellsEffect extends ContinuousEffectImpl { // workaround to gain cost reduction abilities to commanders before cast (make it playable) game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY) .stream() - .filter(card -> filter.match(card, game)) + .filter(card -> filter.match(card, player.getId(), source, game)) .forEach(card -> game.getState().addOtherAbility(card, ability)); for (StackObject stackObject : game.getStack()) { @@ -77,7 +77,7 @@ public class GainAbilityControlledSpellsEffect extends ContinuousEffectImpl { } // TODO: Distinguish "you cast" to exclude copies Card card = game.getCard(stackObject.getSourceId()); - if (card != null && filter.match((Spell) stackObject, game)) { + if (card != null && filter.match((Spell) stackObject, player.getId(), source, game)) { game.getState().addOtherAbility(card, ability); } }