diff --git a/Mage.Sets/src/mage/abilities/dynamicvalue/common/PermanentEnteringBattlefieldManaValue.java b/Mage.Sets/src/mage/abilities/dynamicvalue/common/PermanentEnteringBattlefieldManaValue.java new file mode 100644 index 00000000000..d0352ee7a2b --- /dev/null +++ b/Mage.Sets/src/mage/abilities/dynamicvalue/common/PermanentEnteringBattlefieldManaValue.java @@ -0,0 +1,27 @@ +package mage.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.game.Game; +import mage.game.permanent.Permanent; + +public enum PermanentEnteringBattlefieldManaValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability source, Effect effect) { + Permanent permanent = (Permanent) effect.getValue("permanentEnteringBattlefield"); + return permanent == null ? 0 : permanent.getManaValue(); + } + + @Override + public DynamicValue copy() { + return instance; + } + + @Override + public String getMessage() { + return "that creature's mana value"; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RadagastTheBrown.java b/Mage.Sets/src/mage/cards/r/RadagastTheBrown.java new file mode 100644 index 00000000000..0742fb6f05a --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RadagastTheBrown.java @@ -0,0 +1,83 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; +import mage.abilities.dynamicvalue.common.PermanentEnteringBattlefieldManaValue; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.List; +import java.util.UUID; + +/** + * + * @author bobby-mccann + */ +public final class RadagastTheBrown extends CardImpl { + + static final FilterCreatureCard cardFilter = new FilterCreatureCard("creature card that doesn't share a creature type with a creature you control"); + + static { + cardFilter.add(RadagastTheBrownPredicate.instance); + } + + public RadagastTheBrown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Whenever Radagast the Brown or another nontoken creature enters the battlefield under your control, look at the top X cards of your library, where X is that creature's mana value. You may reveal a creature card from among them that doesn't share a creature type with a creature you control and put it into your hand. Put the rest on the bottom of your library in a random order. + this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility( + new LookLibraryAndPickControllerEffect( + PermanentEnteringBattlefieldManaValue.instance, 1, + cardFilter, + PutCards.HAND, PutCards.BOTTOM_RANDOM + ), + StaticFilters.FILTER_CREATURE_NON_TOKEN, + false, + true + )); + } + + private RadagastTheBrown(final RadagastTheBrown card) { + super(card); + } + + @Override + public RadagastTheBrown copy() { + return new RadagastTheBrown(this); + } +} + +enum RadagastTheBrownPredicate implements Predicate { + instance; + public boolean apply(Card card, Game game) { + UUID playerId = card.getOwnerId(); + List creaturesYouControl = game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + playerId, + game + ); + for (Permanent creature : creaturesYouControl) { + if (creature.shareCreatureTypes(game, card)) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java index 450c8054b58..8bd73ebcc69 100644 --- a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java +++ b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java @@ -203,6 +203,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Protector of Gondor", 25, Rarity.COMMON, mage.cards.p.ProtectorOfGondor.class)); cards.add(new SetCardInfo("Quarrel's End", 141, Rarity.COMMON, mage.cards.q.QuarrelsEnd.class)); cards.add(new SetCardInfo("Quickbeam, Upstart Ent", 183, Rarity.UNCOMMON, mage.cards.q.QuickbeamUpstartEnt.class)); + cards.add(new SetCardInfo("Radagast the Brown", 184, Rarity.MYTHIC, mage.cards.r.RadagastTheBrown.class)); cards.add(new SetCardInfo("Rally at the Hornburg", 142, Rarity.COMMON, mage.cards.r.RallyAtTheHornburg.class)); cards.add(new SetCardInfo("Ranger's Firebrand", 143, Rarity.UNCOMMON, mage.cards.r.RangersFirebrand.class)); cards.add(new SetCardInfo("Rangers of Ithilien", 66, Rarity.RARE, mage.cards.r.RangersOfIthilien.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/RadagastTheBrownTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/RadagastTheBrownTest.java new file mode 100644 index 00000000000..ff49c2bcc3b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/RadagastTheBrownTest.java @@ -0,0 +1,87 @@ +package org.mage.test.cards.single.ltr; + +import mage.cards.f.FeldonOfTheThirdPath; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class RadagastTheBrownTest extends CardTestPlayerBase { + private static final String radagast = "Radagast the Brown"; + @Test + public void libraryTest() { + setStrictChooseMode(true); + + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Goblin Guide"); // Goblin - 1 CMC + addCard(Zone.LIBRARY, playerA, "Amoeboid Changeling"); // Changeling - 2 CMC - should share creature types with anything + addCard(Zone.LIBRARY, playerA, "Heliod's Emissary"); // Enchantment Creature - Elk - 4 CMC + addCard(Zone.LIBRARY, playerA, "Overbeing of Myth"); // Spirit Avatar - 5 CMC + addCard(Zone.LIBRARY, playerA, "Boggart Shenanigans"); // Tribal Enchantment - Goblin + addCard(Zone.LIBRARY, playerA, "Stampeding Elk Herd"); // Elk - 5 CMC + + addCard(Zone.HAND, playerA, radagast); + + addCard(Zone.LIBRARY, playerB, "Swamp", 50); + + addCard(Zone.BATTLEFIELD, playerA, "Savannah", 10); + + addCard(Zone.BATTLEFIELD, playerB, "Amoeboid Changeling", 10); + + // 4 cards revealed - choose from Stampeding Elk Herd and Heliod's Emissary: + setChoice(playerA, "Yes"); + addTarget(playerA, "Stampeding Elk Herd"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, radagast); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHandCount(playerA, 1); + assertHandCount(playerA, "Stampeding Elk Herd", 1); + assertLibraryCount(playerA, 5); + + setChoice(playerA, "Yes"); + addTarget(playerA, "Goblin Guide"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Stampeding Elk Herd"); + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHandCount(playerA, 2); + assertHandCount(playerA, "Goblin Guide", 1); + } + + @Test + public void whenItsAToken() { + setStrictChooseMode(true); + + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Goblin Guide"); // Goblin - 1 CMC + addCard(Zone.LIBRARY, playerA, "Amoeboid Changeling"); // Changeling - 2 CMC - should share creature types with anything + addCard(Zone.LIBRARY, playerA, "Heliod's Emissary"); // Enchantment Creature - Elk - 4 CMC + addCard(Zone.LIBRARY, playerA, "Overbeing of Myth"); // Spirit Avatar - 5 CMC + addCard(Zone.LIBRARY, playerA, "Boggart Shenanigans"); // Tribal Enchantment - Goblin + addCard(Zone.LIBRARY, playerA, "Stampeding Elk Herd"); // Elk - 5 CMC + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); + + addCard(Zone.GRAVEYARD, playerA, radagast); + addCard(Zone.BATTLEFIELD, playerA, "Feldon of the Third Path"); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, + "{2}{R}, {T}: Create a token that's a copy of target creature card in your graveyard, except it's an artifact in addition to its other types. It gains haste. Sacrifice it at the beginning of the next end step.", + radagast); + + // 4 cards revealed - choose from Stampeding Elk Herd and Heliod's Emissary: + setChoice(playerA, "Yes"); + addTarget(playerA, "Heliod's Emissary"); + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, 12); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m20/RisenReefTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m20/RisenReefTest.java new file mode 100644 index 00000000000..45c2a6a9111 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m20/RisenReefTest.java @@ -0,0 +1,37 @@ +package org.mage.test.cards.single.m20; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class RisenReefTest extends CardTestPlayerBase { + private static final String risenReef = "Risen Reef"; + @Test + public void croakingCounterPartCountsForTrigger() { + setStrictChooseMode(true); + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Mountain", 10); + addCard(Zone.BATTLEFIELD, playerA, "Breeding Pool", 10); + + addCard(Zone.BATTLEFIELD, playerA, risenReef); + addCard(Zone.HAND, playerA, "Croaking Counterpart"); + addCard(Zone.HAND, playerA, "Air Elemental"); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Croaking Counterpart"); + addTarget(playerA, risenReef); + setChoice(playerA, "Yes"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Air Elemental"); + // We get two triggers, so we have to choose which one to put on the stack first (they're identical): + setChoice(playerA, "Whenever"); + // Put both lands onto the battlefield: + setChoice(playerA, "Yes", 2); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Mountain", 3); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/CaldaiaGuardianTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/CaldaiaGuardianTest.java new file mode 100644 index 00000000000..89b9b8764df --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/CaldaiaGuardianTest.java @@ -0,0 +1,41 @@ +package org.mage.test.cards.single.ncc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class CaldaiaGuardianTest extends CardTestPlayerBase { + private static final String guardian = "Caldaia Guardian"; + @Test + public void croakingCounterPartCountsForTrigger() { + setStrictChooseMode(true); + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Mountain", 10); + addCard(Zone.BATTLEFIELD, playerA, "Breeding Pool", 10); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); + + addCard(Zone.BATTLEFIELD, playerA, guardian); + + addCard(Zone.HAND, playerA, "Croaking Counterpart"); + addCard(Zone.HAND, playerA, "Murder", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Croaking Counterpart"); + addTarget(playerA, guardian); + + // This kills the 4/3 Guardian, which should cause two triggers: + addTarget(playerA, guardian+"[no copy]"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Murder"); + // Choose trigger order: + setChoice(playerA, "Whenever"); + + // Kill the 1/1, which should also trigger: + addTarget(playerA, guardian); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder"); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Citizen Token", 6); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldAllTriggeredAbility.java index 157333a3542..6115387a56d 100644 --- a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldAllTriggeredAbility.java @@ -21,7 +21,6 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl { protected String rule; protected boolean controlledText; protected SetTargetPointer setTargetPointer; - protected final boolean thisOrAnother; /** * zone = BATTLEFIELD optional = false @@ -54,16 +53,11 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl { } public EntersBattlefieldAllTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, String rule, boolean controlledText) { - this(zone, effect, filter, optional, setTargetPointer, rule, controlledText, false); - } - - protected EntersBattlefieldAllTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, String rule, boolean controlledText, boolean thisOrAnother) { super(zone, effect, optional); this.filter = filter; this.rule = rule; this.controlledText = controlledText; this.setTargetPointer = setTargetPointer; - this.thisOrAnother = thisOrAnother; setTriggerPhrase(generateTriggerPhrase()); } @@ -73,7 +67,6 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl { this.rule = ability.rule; this.controlledText = ability.controlledText; this.setTargetPointer = ability.setTargetPointer; - this.thisOrAnother = ability.thisOrAnother; } @Override @@ -93,12 +86,13 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl { switch (setTargetPointer) { case PLAYER: this.getEffects().setTargetPointer(new FixedTarget(permanent.getControllerId())); - return true; + break; case PERMANENT: this.getEffects().setTargetPointer(new FixedTarget(permanent, game)); + break; default: - return true; } + return true; } @Override @@ -111,9 +105,6 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl { protected String generateTriggerPhrase() { StringBuilder sb = new StringBuilder("Whenever "); - if (thisOrAnother) { - sb.append("{this} or another "); - } sb.append(filter.getMessage()); if (filter.getMessage().startsWith("one or more")) { sb.append(" enter the battlefield"); diff --git a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldCastTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldCastTriggeredAbility.java index 508e9532ee6..d51e0dff29d 100644 --- a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldCastTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldCastTriggeredAbility.java @@ -59,13 +59,7 @@ public class EntersBattlefieldCastTriggeredAbility extends EntersBattlefieldAllT public EntersBattlefieldCastTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean mustCast, boolean optional, SetTargetPointer setTargetPointer, String rule, boolean controlledText) { - this(zone, effect, filter, mustCast, optional, setTargetPointer, rule, controlledText, false); - } - - protected EntersBattlefieldCastTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean mustCast, - boolean optional, - SetTargetPointer setTargetPointer, String rule, boolean controlledText, boolean thisOrAnother) { - super(zone, effect, filter, optional, setTargetPointer, rule, controlledText, thisOrAnother); + super(zone, effect, filter, optional, setTargetPointer, rule, controlledText); this.mustCast = mustCast; this.addWatcher(new PermanentWasCastWatcher()); diff --git a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldThisOrAnotherTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldThisOrAnotherTriggeredAbility.java index 39b7be1d3ff..73687b70e14 100644 --- a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldThisOrAnotherTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldThisOrAnotherTriggeredAbility.java @@ -4,17 +4,13 @@ import mage.abilities.effects.Effect; import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.filter.FilterPermanent; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; +import mage.filter.FilterPermanentThisOrAnother; /** * @author TheElk801 */ public class EntersBattlefieldThisOrAnotherTriggeredAbility extends EntersBattlefieldAllTriggeredAbility { - private final boolean onlyControlled; - public EntersBattlefieldThisOrAnotherTriggeredAbility(Effect effect, FilterPermanent filter) { this(effect, filter, false, false); } @@ -28,35 +24,17 @@ public class EntersBattlefieldThisOrAnotherTriggeredAbility extends EntersBattle } public EntersBattlefieldThisOrAnotherTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyControlled) { - super(zone, effect, filter, optional, setTargetPointer, null, onlyControlled, true); - this.onlyControlled = onlyControlled; + super(zone, effect, + new FilterPermanentThisOrAnother(filter, onlyControlled), + optional, setTargetPointer, null, onlyControlled); } private EntersBattlefieldThisOrAnotherTriggeredAbility(final EntersBattlefieldThisOrAnotherTriggeredAbility ability) { super(ability); - this.onlyControlled = ability.onlyControlled; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!super.checkTrigger(event, game)) { - return false; - } - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent == null) { - return false; - } - if (permanent.getId().equals(getSourceId())) { - return true; - } - if (onlyControlled && !permanent.isControlledBy(this.getControllerId())) { - return false; - } - return filter.match(permanent, getControllerId(), this, game); } @Override public EntersBattlefieldThisOrAnotherTriggeredAbility copy() { return new EntersBattlefieldThisOrAnotherTriggeredAbility(this); } -} +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java b/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java new file mode 100644 index 00000000000..5a177d6a5c9 --- /dev/null +++ b/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java @@ -0,0 +1,59 @@ +package mage.filter; + +import mage.abilities.Ability; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +public class FilterPermanentThisOrAnother extends FilterPermanent { + + final FilterPermanent otherFilter; + final boolean onlyControlled; + + public FilterPermanentThisOrAnother(FilterPermanent otherFilter, boolean onlyControlled) { + this(otherFilter, onlyControlled, generateFilterMessage(otherFilter)); + } + public FilterPermanentThisOrAnother(FilterPermanent otherFilter, boolean onlyControlled, String name) { + super(name); + this.otherFilter = otherFilter; + this.onlyControlled = onlyControlled; + } + @Override + public boolean match(Permanent permanent, UUID playerId, Ability source, Game game) { + if (!super.match(permanent, playerId, source, game)) { + return false; + } + if (onlyControlled && !permanent.isControlledBy(source.getControllerId())) { + return false; + } + if (permanent.getId().equals(source.getSourceId())) { + return true; + } else { + return otherFilter.match(permanent, playerId, source, game); + } + } + + private FilterPermanentThisOrAnother(FilterPermanentThisOrAnother filter) { + super(filter); + this.otherFilter = filter.otherFilter.copy(); + this.onlyControlled = filter.onlyControlled; + } + + @Override + public FilterPermanentThisOrAnother copy() { + return new FilterPermanentThisOrAnother(this); + } + + protected static String generateFilterMessage(FilterPermanent otherFilter) { + // Remove the indefinite article from the beginning of the message: + String otherFilterMessage = otherFilter.getMessage(); + if (otherFilterMessage.startsWith("a ")) { + otherFilterMessage = otherFilterMessage.substring(2); + } else if (otherFilterMessage.startsWith("an ")) { + otherFilterMessage = otherFilterMessage.substring(3); + } + + return "{this} or another " + otherFilterMessage; + } +}