diff --git a/Mage.Sets/src/mage/cards/d/DuneChanter.java b/Mage.Sets/src/mage/cards/d/DuneChanter.java new file mode 100644 index 00000000000..23a1c23af93 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DuneChanter.java @@ -0,0 +1,174 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterOwnedCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.List; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class DuneChanter extends CardImpl { + + public DuneChanter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Lands you control and land cards you own that aren't on the battlefield are Deserts in addition to their other types. + this.addAbility(new SimpleStaticAbility(new DuneChanterContinuousEffect())); + + // Lands you control have "{T}: Add one mana of any color." + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + new AnyColorManaAbility(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_LANDS, false + ))); + + // {T}: Mill two cards. You gain 1 life for each land card milled this way. + this.addAbility(new SimpleActivatedAbility(new DuneChanterEffect(), new TapSourceCost())); + } + + private DuneChanter(final DuneChanter card) { + super(card); + } + + @Override + public DuneChanter copy() { + return new DuneChanter(this); + } +} + +class DuneChanterContinuousEffect extends ContinuousEffectImpl { + private static final FilterPermanent filterPermanent = StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS; + private static final FilterOwnedCard filterCard = new FilterOwnedCard("land cards you own that aren't on the battlefield"); + private static final SubType subType = SubType.DESERT; + + static { + filterCard.add(CardType.LAND.getPredicate()); + } + + public DuneChanterContinuousEffect() { + super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); + staticText = "Lands you control and land cards you own that aren't on the battlefield are Deserts in addition to their other types"; + } + + private DuneChanterContinuousEffect(final DuneChanterContinuousEffect effect) { + super(effect); + } + + @Override + public DuneChanterContinuousEffect copy() { + return new DuneChanterContinuousEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + UUID controllerId = source.getControllerId(); + Player controller = game.getPlayer(controllerId); + if (controller == null) { + return false; + } + + // lands cards you own that aren't on the battlefield + // in graveyard + for (UUID cardId : controller.getGraveyard()) { + Card card = game.getCard(cardId); + if (filterCard.match(card, controllerId, source, game) && !card.hasSubtype(subType, game)) { + game.getState().getCreateMageObjectAttribute(card, game).getSubtype().add(subType); + } + } + // on hand + for (UUID cardId : controller.getHand()) { + Card card = game.getCard(cardId); + if (filterCard.match(card, controllerId, source, game) && !card.hasSubtype(subType, game)) { + game.getState().getCreateMageObjectAttribute(card, game).getSubtype().add(subType); + } + } + // in exile + for (Card card : game.getState().getExile().getAllCards(game, controllerId)) { + if (filterCard.match(card, controllerId, source, game) && !card.hasSubtype(subType, game)) { + game.getState().getCreateMageObjectAttribute(card, game).getSubtype().add(subType); + } + } + // in library + for (Card card : controller.getLibrary().getCards(game)) { + if (filterCard.match(card, controllerId, source, game) && !card.hasSubtype(subType, game)) { + game.getState().getCreateMageObjectAttribute(card, game).getSubtype().add(subType); + } + } + + // lands you control + List lands = game.getBattlefield().getAllActivePermanents( + filterPermanent, controllerId, game); + for (Permanent land : lands) { + if (land != null) { + land.addSubType(game, subType); + } + } + return true; + } +} + +class DuneChanterEffect extends OneShotEffect { + + DuneChanterEffect() { + super(Outcome.Benefit); + staticText = "mill two cards. You gain 1 life for each land card milled this way."; + } + + private DuneChanterEffect(final DuneChanterEffect effect) { + super(effect); + } + + @Override + public DuneChanterEffect copy() { + return new DuneChanterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int lifeToGain = player + .millCards(2, source, game) + .getCards(game) + .stream() + .filter(c -> c.isLand(game)) + .mapToInt(c -> game.getState().getZone(c.getId()) == Zone.GRAVEYARD ? 1 : 0) + .sum(); + if (lifeToGain > 0) { + new GainLifeEffect(lifeToGain).apply(game, source); + } + return true; + } +} + + diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java index 920adff46d0..26c600b7b28 100644 --- a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java @@ -91,6 +91,7 @@ public final class OutlawsOfThunderJunctionCommander extends ExpansionSet { cards.add(new SetCardInfo("Dire Fleet Ravager", 132, Rarity.MYTHIC, mage.cards.d.DireFleetRavager.class)); cards.add(new SetCardInfo("Dragonskull Summit", 289, Rarity.RARE, mage.cards.d.DragonskullSummit.class)); cards.add(new SetCardInfo("Drowned Catacomb", 290, Rarity.RARE, mage.cards.d.DrownedCatacomb.class)); + cards.add(new SetCardInfo("Dune Chanter", 31, Rarity.RARE, mage.cards.d.DuneChanter.class)); cards.add(new SetCardInfo("Dunes of the Dead", 291, Rarity.UNCOMMON, mage.cards.d.DunesOfTheDead.class)); cards.add(new SetCardInfo("Eccentric Farmer", 190, Rarity.COMMON, mage.cards.e.EccentricFarmer.class)); cards.add(new SetCardInfo("Edric, Spymaster of Trest", 221, Rarity.RARE, mage.cards.e.EdricSpymasterOfTrest.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/DuneChanterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/DuneChanterTest.java new file mode 100644 index 00000000000..0dc4b879caa --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/DuneChanterTest.java @@ -0,0 +1,108 @@ +package org.mage.test.cards.single.otc; + +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class DuneChanterTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.d.DuneChanter Dune Chanter} {2}{G} + * Creature — Plant Druid + * Reach + * Lands you control and land cards you own that aren’t on the battlefield are Deserts in addition to their other types. + * Lands you control have “{T}: Add one mana of any color.” + * {T}: Mill two cards. You gain 1 life for each land card milled this way. + * 2/3 + */ + private static final String chanter = "Dune Chanter"; + + private static void checkBattlefield(String info, Player player, Game game, int count) { + int amount = game + .getBattlefield() + .getAllActivePermanents(player.getId()) + .stream() + .filter(p -> p.getSubtype(game).contains(SubType.DESERT)) + .mapToInt(k -> 1) + .sum(); + Assert.assertEquals(info, count, amount); + } + + private static void checkTopLibrary(String info, Player player, Game game, boolean check) { + boolean hasDesert = player.getLibrary().getFromTop(game).getSubtype(game).contains(SubType.DESERT); + Assert.assertEquals(info, check, hasDesert); + } + + private static void checkHand(String info, Player player, Game game, int count) { + int amount = player + .getHand() + .getCards(game) + .stream() + .filter(c -> c.getSubtype(game).contains(SubType.DESERT)) + .mapToInt(k -> 1) + .sum(); + Assert.assertEquals(info, count, amount); + } + + private static void checkGraveyard(String info, Player player, Game game, int count) { + int amount = player + .getGraveyard() + .getCards(game) + .stream() + .filter(c -> c.getSubtype(game).contains(SubType.DESERT)) + .mapToInt(k -> 1) + .sum(); + Assert.assertEquals(info, count, amount); + } + + private static void checkExile(String info, Player player, Game game, int count) { + int amount = game + .getExile() + .getAllCards(game, player.getId()) + .stream() + .filter(c -> c.getSubtype(game).contains(SubType.DESERT)) + .mapToInt(k -> 1) + .sum(); + Assert.assertEquals(info, count, amount); + } + + @Test + public void test_Deserts_All_Zones() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.HAND, playerA, chanter); + addCard(Zone.BATTLEFIELD, playerA, "Bayou", 3); + addCard(Zone.HAND, playerA, "Tropical Island"); + addCard(Zone.GRAVEYARD, playerA, "Volcanic Island"); + addCard(Zone.EXILED, playerA, "Scrubland"); + addCard(Zone.LIBRARY, playerA, "Badlands"); + + runCode("Check battlefield before", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkBattlefield(i, p, g, 0)); + runCode("Check hand before ", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkHand(i, p, g, 0)); + runCode("Check library before ", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkTopLibrary(i, p, g, false)); + runCode("Check graveyard before ", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkGraveyard(i, p, g, 0)); + runCode("Check exile before ", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkExile(i, p, g, 0)); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, chanter, true); + + runCode("Check battlefield after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkBattlefield(i, p, g, 3)); + runCode("Check hand after ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkHand(i, p, g, 1)); + runCode("Check library after ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkTopLibrary(i, p, g, true)); + runCode("Check graveyard after ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkGraveyard(i, p, g, 1)); + runCode("Check exile after ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkExile(i, p, g, 1)); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, chanter, 1); + } +}