diff --git a/Mage.Sets/src/mage/cards/b/BodyOfResearch.java b/Mage.Sets/src/mage/cards/b/BodyOfResearch.java index 25498ae4280..2a178768c7d 100644 --- a/Mage.Sets/src/mage/cards/b/BodyOfResearch.java +++ b/Mage.Sets/src/mage/cards/b/BodyOfResearch.java @@ -1,14 +1,10 @@ package mage.cards.b; -import mage.abilities.Ability; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.CardsInControllerLibraryCount; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.game.Game; import mage.game.permanent.token.FractalToken; -import mage.players.Player; import java.util.UUID; @@ -22,7 +18,7 @@ public final class BodyOfResearch extends CardImpl { // Create a 0/0 green and blue Fractal creature token. Put X +1/+1 counters on it, where X is the number of cards in your library. this.getSpellAbility().addEffect(FractalToken.getEffect( - BodyOfResearchValue.instance, "Put X +1/+1 counters on it, " + + CardsInControllerLibraryCount.instance, "Put X +1/+1 counters on it, " + "where X is the number of cards in your library" )); } @@ -35,24 +31,4 @@ public final class BodyOfResearch extends CardImpl { public BodyOfResearch copy() { return new BodyOfResearch(this); } -} - -enum BodyOfResearchValue implements DynamicValue { - instance; - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - Player player = game.getPlayer(sourceAbility.getControllerId()); - return player != null ? player.getLibrary().size() : 0; - } - - @Override - public BodyOfResearchValue copy() { - return instance; - } - - @Override - public String getMessage() { - return ""; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/e/EnterTheInfinite.java b/Mage.Sets/src/mage/cards/e/EnterTheInfinite.java index 40945577a18..bdce8067239 100644 --- a/Mage.Sets/src/mage/cards/e/EnterTheInfinite.java +++ b/Mage.Sets/src/mage/cards/e/EnterTheInfinite.java @@ -1,10 +1,8 @@ package mage.cards.e; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.CardsInControllerLibraryCount; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; @@ -21,17 +19,18 @@ import mage.game.turn.Step; import mage.players.Player; import mage.target.common.TargetCardInHand; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class EnterTheInfinite extends CardImpl { public EnterTheInfinite(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{8}{U}{U}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{8}{U}{U}{U}{U}"); // Draw cards equal to the number of cards in your library, - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(new CardsInControllerLibraryCount())); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(CardsInControllerLibraryCount.instance)); //then put a card from your hand on top of your library. this.getSpellAbility().addEffect(new PutCardOnLibraryEffect()); //You have no maximum hand size until your next turn. @@ -48,35 +47,6 @@ public final class EnterTheInfinite extends CardImpl { } } -class CardsInControllerLibraryCount implements DynamicValue { - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - if (sourceAbility != null) { - Player controller = game.getPlayer(sourceAbility.getControllerId()); - if (controller != null) { - return controller.getLibrary().size(); - } - } - return 0; - } - - @Override - public CardsInControllerLibraryCount copy() { - return new CardsInControllerLibraryCount(); - } - - @Override - public String getMessage() { - return "card in your library"; - } - - @Override - public String toString() { - return "1"; - } -} - class PutCardOnLibraryEffect extends OneShotEffect { PutCardOnLibraryEffect() { diff --git a/Mage.Sets/src/mage/cards/t/TamiyoInquisitiveStudent.java b/Mage.Sets/src/mage/cards/t/TamiyoInquisitiveStudent.java new file mode 100644 index 00000000000..4b6a02e0053 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TamiyoInquisitiveStudent.java @@ -0,0 +1,58 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Pronoun; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.DrawNthCardTriggeredAbility; +import mage.abilities.effects.common.ExileAndReturnSourceEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TransformAbility; +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 java.util.UUID; + +/** + * @author Susucr + */ +public final class TamiyoInquisitiveStudent extends CardImpl { + + public TamiyoInquisitiveStudent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.MOONFOLK); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + this.secondSideCardClazz = TamiyoSeasonedScholar.class; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Tamiyo, Inquisitive Student attacks, investigate. + this.addAbility(new AttacksTriggeredAbility(new InvestigateEffect())); + + // When you draw your third card in a turn, exile Tamiyo, then return her to the battlefield transformed under her owner's control. + this.addAbility(new TransformAbility()); + this.addAbility(new DrawNthCardTriggeredAbility( + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED, Pronoun.SHE), + false, 3 + )); + } + + private TamiyoInquisitiveStudent(final TamiyoInquisitiveStudent card) { + super(card); + } + + @Override + public TamiyoInquisitiveStudent copy() { + return new TamiyoInquisitiveStudent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TamiyoSeasonedScholar.java b/Mage.Sets/src/mage/cards/t/TamiyoSeasonedScholar.java new file mode 100644 index 00000000000..4374ea9cb8f --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TamiyoSeasonedScholar.java @@ -0,0 +1,111 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.AttacksAllTriggeredAbility; +import mage.abilities.common.delayed.UntilYourNextTurnDelayedTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerLibraryCount; +import mage.abilities.dynamicvalue.common.HalfValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GetEmblemEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.mana.AddManaOfAnyColorEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.command.emblems.TamiyoSeasonedScholarEmblem; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TamiyoSeasonedScholar extends CardImpl { + + private static final DynamicValue xValue = new HalfValue(CardsInControllerLibraryCount.instance, true); + + public TamiyoSeasonedScholar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TAMIYO); + this.setStartingLoyalty(2); + + this.color.setGreen(true); + this.color.setBlue(true); + this.nightCard = true; + + // +2: Until your next turn, whenever a creature attacks you or a planeswalker you control, it gets -1/-0 until end of turn. + this.addAbility(new LoyaltyAbility(new CreateDelayedTriggeredAbilityEffect( + new UntilYourNextTurnDelayedTriggeredAbility( + new AttacksAllTriggeredAbility( + new BoostTargetEffect(-1, 0, Duration.EndOfTurn) + .setText("it gets -1/-0 until end of turn"), + false, StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, + SetTargetPointer.PERMANENT, true + ) + ) + ), 2)); + + // -3: Return target instant or sorcery card from your graveyard to your hand. If it's a green card, add one mana of any color. + Ability ability = new LoyaltyAbility(new TamiyoSeasonedScholarMinus3Effect(), -3); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD)); + this.addAbility(ability); + + // -7: Draw cards equal to half the number of cards in your library, rounded up. You get an emblem with "You have no maximum hand size." + ability = new LoyaltyAbility(new DrawCardSourceControllerEffect(xValue), -7); + ability.addEffect(new GetEmblemEffect(new TamiyoSeasonedScholarEmblem())); + this.addAbility(ability); + } + + private TamiyoSeasonedScholar(final TamiyoSeasonedScholar card) { + super(card); + } + + @Override + public TamiyoSeasonedScholar copy() { + return new TamiyoSeasonedScholar(this); + } +} + +class TamiyoSeasonedScholarMinus3Effect extends OneShotEffect { + + TamiyoSeasonedScholarMinus3Effect() { + super(Outcome.DrawCard); + this.staticText = "Return target instant or sorcery card from your graveyard to your hand. " + + "If it's a green card, add one mana of any color"; + } + + private TamiyoSeasonedScholarMinus3Effect(final TamiyoSeasonedScholarMinus3Effect effect) { + super(effect); + } + + @Override + public TamiyoSeasonedScholarMinus3Effect copy() { + return new TamiyoSeasonedScholarMinus3Effect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(source.getFirstTarget()); + if (card == null) { + return false; + } + Effect effect = new ReturnToHandTargetEffect(); + effect.setTargetPointer(getTargetPointer().copy()); + effect.apply(game, source); + if (card.getColor(game).isGreen()) { + new AddManaOfAnyColorEffect().apply(game, source); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java index 2fcfc858cf8..2652b80196c 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -80,6 +80,8 @@ public final class ModernHorizons3 extends ExpansionSet { cards.add(new SetCardInfo("Spawn-Gang Commander", 140, Rarity.UNCOMMON, mage.cards.s.SpawnGangCommander.class)); cards.add(new SetCardInfo("Swamp", 306, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Sylvan Safekeeper", 287, Rarity.RARE, mage.cards.s.SylvanSafekeeper.class)); + cards.add(new SetCardInfo("Tamiyo, Inquisitive Student", 242, Rarity.MYTHIC, mage.cards.t.TamiyoInquisitiveStudent.class)); + cards.add(new SetCardInfo("Tamiyo, Seasoned Scholar", 242, Rarity.MYTHIC, mage.cards.t.TamiyoSeasonedScholar.class)); cards.add(new SetCardInfo("Trickster's Elk", 175, Rarity.UNCOMMON, mage.cards.t.TrickstersElk.class)); cards.add(new SetCardInfo("Ugin's Labyrinth", 233, Rarity.MYTHIC, mage.cards.u.UginsLabyrinth.class)); cards.add(new SetCardInfo("Urza's Cave", 234, Rarity.UNCOMMON, mage.cards.u.UrzasCave.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/TamiyoInquisitiveStudentTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/TamiyoInquisitiveStudentTest.java new file mode 100644 index 00000000000..8b11a73b8e6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/TamiyoInquisitiveStudentTest.java @@ -0,0 +1,148 @@ +package org.mage.test.cards.single.mh3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class TamiyoInquisitiveStudentTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.TamiyoInquisitiveStudent Tamiyo, Inquisitive Student} {U} + * Legendary Creature — Moonfolk Wizard + * Flying + * Whenever Tamiyo, Inquisitive Student attacks, investigate. + * When you draw your third card in a turn, exile Tamiyo, then return her to the battlefield transformed under her owner's control. + * 0/3 + * // {@link mage.cards.t.TamiyoSeasonedScholar Tamiyo, Seasoned Scholar} + * +2: Until your next turn, whenever a creature attacks you or a planeswalker you control, it gets -1/-0 until end of turn. + * −3: Return target instant or sorcery card from your graveyard to your hand. If it’s a green card, add one mana of any color. + * −7: Draw cards equal to half the number of cards in your library, rounded up. You get an emblem with “You have no maximum hand size.” + */ + private static final String tamiyo = "Tamiyo, Inquisitive Student"; + private static final String tamiyoPW = "Tamiyo, Seasoned Scholar"; + + @Test + public void test_Trigger_Attack() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tamiyo); + + attack(1, playerA, tamiyo, playerB); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, tamiyo, 1); + assertPermanentCount(playerA, "Clue Token", 1); + } + + @Test + public void test_Trigger_Transform() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tamiyo); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.HAND, playerA, "Divination", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Divination"); + checkPermanentCount("1: Tamiyo didn't transform after draw 2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, tamiyo, 1); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Divination"); + // Tamiyo triggered, as playerA did draw its third card for the turn + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, tamiyo, 0); + assertPermanentCount(playerA, tamiyoPW, 1); + } + + @Test + public void test_PlusTwo() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tamiyoPW); + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Elite Vanguard"); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Centaur Courser"); // 3/3 + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+2"); + + attack(1, playerA, "Memnite", playerB); + + attack(2, playerB, "Elite Vanguard", tamiyoPW); + attack(2, playerB, "Centaur Courser", playerA); + setChoice(playerA, "Until"); // Stack the triggers + + setStopAt(2, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 20 - 1); + assertLife(playerA, 20 - (3 - 1)); + assertCounterCount(playerA, tamiyoPW, CounterType.LOYALTY, 2 + 2 - (2 - 1)); + } + + @Test + public void test_Minus3_NonGreen() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tamiyoPW); + addCard(Zone.GRAVEYARD, playerA, "Lightning Bolt"); + + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, tamiyoPW, CounterType.LOYALTY, 2); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-3"); + addTarget(playerA, "Lightning Bolt"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertHandCount(playerA, "Lightning Bolt", 1); + assertCounterCount(playerA, tamiyoPW, CounterType.LOYALTY, 1); + } + + @Test + public void test_Minus3_Green() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tamiyoPW); + addCard(Zone.GRAVEYARD, playerA, "Regrowth"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, tamiyoPW, CounterType.LOYALTY, 2); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-3"); + addTarget(playerA, "Regrowth"); + setChoice(playerA, "Red"); // choose that color of mana + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerB, 20 - 3); + assertHandCount(playerA, "Regrowth", 1); + assertCounterCount(playerA, tamiyoPW, CounterType.LOYALTY, 1); + } + + @Test + public void test_Minus7() { + setStrictChooseMode(true); + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Taiga", 45); + + addCard(Zone.BATTLEFIELD, playerA, tamiyoPW); + + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, tamiyoPW, CounterType.LOYALTY, 6); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-7"); + + setStopAt(2, PhaseStep.UPKEEP); + execute(); + + assertHandCount(playerA, 23); // Drew half of library, and emblem mean no maximum end size + assertCounterCount(playerA, tamiyoPW, CounterType.LOYALTY, 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInControllerLibraryCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInControllerLibraryCount.java new file mode 100644 index 00000000000..d0d90e813ef --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInControllerLibraryCount.java @@ -0,0 +1,35 @@ +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.players.Player; + +/** + * @author Susucr + */ +public enum CardsInControllerLibraryCount implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Player controller = game.getPlayer(sourceAbility.getControllerId()); + return controller != null ? controller.getLibrary().size() : 0; + } + + @Override + public CardsInControllerLibraryCount copy() { + return this; + } + + @Override + public String getMessage() { + return "the number of cards in your library"; + } + + @Override + public String toString() { + return "1"; + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/game/command/emblems/TamiyoSeasonedScholarEmblem.java b/Mage/src/main/java/mage/game/command/emblems/TamiyoSeasonedScholarEmblem.java new file mode 100644 index 00000000000..62166ab0331 --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/TamiyoSeasonedScholarEmblem.java @@ -0,0 +1,34 @@ +package mage.game.command.emblems; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect.HandSizeModification; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.game.command.Emblem; + +/** + * @author Susucr + */ +public final class TamiyoSeasonedScholarEmblem extends Emblem { + + /** + * Emblem with "You have no maximum hand size" + */ + + public TamiyoSeasonedScholarEmblem() { + super("Emblem Tamiyo"); + this.getAbilities().add(new SimpleStaticAbility(Zone.COMMAND, new MaximumHandSizeControllerEffect( + Integer.MAX_VALUE, Duration.Custom, HandSizeModification.SET + ))); + } + + private TamiyoSeasonedScholarEmblem(final TamiyoSeasonedScholarEmblem card) { + super(card); + } + + @Override + public TamiyoSeasonedScholarEmblem copy() { + return new TamiyoSeasonedScholarEmblem(this); + } +} diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index 54ce870aa66..81d0ec82f42 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -131,6 +131,7 @@ |Generate|EMBLEM:RVR|Emblem Domri|||DomriRadeEmblem| |Generate|EMBLEM:SCD|Emblem Nixilis|||ObNixilisReignitedEmblem| |Generate|EMBLEM:SCD|Emblem Sarkhan|||SarkhanTheDragonspeakerEmblem| +|Generate|EMBLEM:MH3|Emblem Tamiyo|||TamiyoSeasonedScholarEmblem| # ALL PLANES # Usage hints: