diff --git a/Mage.Sets/src/mage/cards/b/BarbaraWright.java b/Mage.Sets/src/mage/cards/b/BarbaraWright.java new file mode 100644 index 00000000000..be851f57e90 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BarbaraWright.java @@ -0,0 +1,43 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.SagaAbility; +import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BarbaraWright extends CardImpl { + + public BarbaraWright(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // History Teacher -- Sagas you control have read ahead. + this.addAbility(SagaAbility.makeGainReadAheadAbility().withFlavorWord("History Teacher")); + + // Doctor's companion + this.addAbility(DoctorsCompanionAbility.getInstance()); + } + + private BarbaraWright(final BarbaraWright card) { + super(card); + } + + @Override + public BarbaraWright copy() { + return new BarbaraWright(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DoctorWho.java b/Mage.Sets/src/mage/sets/DoctorWho.java index 774318dee67..3f66c667127 100644 --- a/Mage.Sets/src/mage/sets/DoctorWho.java +++ b/Mage.Sets/src/mage/sets/DoctorWho.java @@ -78,10 +78,10 @@ public final class DoctorWho extends ExpansionSet { //cards.add(new SetCardInfo("Bad Wolf Bay", 569, Rarity.COMMON, mage.cards.b.BadWolfBay.class)); cards.add(new SetCardInfo("Banish to Another Universe", 13, Rarity.UNCOMMON, mage.cards.b.BanishToAnotherUniverse.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Banish to Another Universe", 618, Rarity.UNCOMMON, mage.cards.b.BanishToAnotherUniverse.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Barbara Wright", 14, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Barbara Wright", 335, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Barbara Wright", 619, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Barbara Wright", 926, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barbara Wright", 14, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barbara Wright", 335, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barbara Wright", 619, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barbara Wright", 926, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Beast Within", 228, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Beast Within", 819, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Become the Pilot", 354, Rarity.RARE, mage.cards.b.BecomeThePilot.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/ReadAheadTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/ReadAheadTest.java index b87cf4f45f8..72b486a8f63 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/ReadAheadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/ReadAheadTest.java @@ -1,7 +1,9 @@ package org.mage.test.cards.enchantments; +import mage.abilities.keyword.ReadAheadAbility; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -69,4 +71,78 @@ public class ReadAheadTest extends CardTestPlayerBase { assertPermanentCount(playerA, war, 0); assertGraveyardCount(playerA, war, 1); } + + private static final String rite = "Rite of Belzenlok"; + private static final String babs = "Barbara Wright"; + + @Test + public void testBarbaraWrightChapter1() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, babs); + addCard(Zone.HAND, playerA, rite); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rite); + setChoice(playerA, "X=1"); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertCounterCount(rite, CounterType.LORE, 1); + assertPermanentCount(playerA, "Cleric Token", 2); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertCounterCount(rite, CounterType.LORE, 2); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); + + setStopAt(5, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, rite, 1); + assertPermanentCount(playerA, rite, 0); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); + assertPermanentCount(playerA, "Demon Token", 1); + } + + @Test + public void testBarbaraWrightChapter2() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, babs); + addCard(Zone.HAND, playerA, rite); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rite); + setChoice(playerA, "X=2"); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertCounterCount(rite, CounterType.LORE, 2); + assertAbility(playerA, rite, ReadAheadAbility.getInstance(), true); + assertPermanentCount(playerA, "Cleric Token", 2); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, rite, 1); + assertPermanentCount(playerA, rite, 0); + assertPermanentCount(playerA, "Cleric Token", 2); + assertPermanentCount(playerA, "Demon Token", 1); + } + + @Test + public void testBarbaraWrightChapter3() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, babs); + addCard(Zone.HAND, playerA, rite); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rite); + setChoice(playerA, "X=3"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, rite, 1); + assertPermanentCount(playerA, rite, 0); + assertPermanentCount(playerA, "Cleric Token", 0); + assertPermanentCount(playerA, "Demon Token", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SagaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SagaTest.java index dccb20ec471..25d12b4ea0e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SagaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SagaTest.java @@ -35,14 +35,14 @@ public class SagaTest extends CardTestPlayerBase { execute(); assertCounterCount(rite, CounterType.LORE, 2); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); setStopAt(5, PhaseStep.BEGIN_COMBAT); execute(); assertGraveyardCount(playerA, rite, 1); assertPermanentCount(playerA, rite, 0); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); assertPermanentCount(playerA, "Demon Token", 1); } @@ -64,7 +64,7 @@ public class SagaTest extends CardTestPlayerBase { execute(); assertCounterCount(rite, CounterType.LORE, 2); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, flicker, rite); setStopAt(3, PhaseStep.END_TURN); @@ -73,7 +73,7 @@ public class SagaTest extends CardTestPlayerBase { assertGraveyardCount(playerA, rite, 0); assertPermanentCount(playerA, rite, 1); assertCounterCount(playerA, rite, CounterType.LORE, 1); - assertPermanentCount(playerA, "Cleric Token", 6); + assertPermanentCount(playerA, "Cleric Token", 2 + 2 + 2); assertPermanentCount(playerA, "Demon Token", 0); } @@ -95,7 +95,7 @@ public class SagaTest extends CardTestPlayerBase { execute(); assertCounterCount(rite, CounterType.LORE, 2); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, boomerang, rite); setStopAt(5, PhaseStep.BEGIN_COMBAT); @@ -104,7 +104,7 @@ public class SagaTest extends CardTestPlayerBase { assertHandCount(playerA, rite, 1); assertPermanentCount(playerA, rite, 0); assertGraveyardCount(playerA, boomerang, 1); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); assertPermanentCount(playerA, "Demon Token", 1); } @@ -119,14 +119,14 @@ public class SagaTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); assertCounterCount(rite, CounterType.LORE, 2); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); setStopAt(3, PhaseStep.PRECOMBAT_MAIN); execute(); assertGraveyardCount(playerA, rite, 1); assertPermanentCount(playerA, rite, 0); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); assertPermanentCount(playerA, "Demon Token", 1); } @@ -220,14 +220,14 @@ public class SagaTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, triumph); addTarget(playerA, memnite); - checkPT("+1/+0", 1, PhaseStep.BEGIN_COMBAT, playerA, memnite, 2, 1); + checkPT("+1/+0", 1, PhaseStep.BEGIN_COMBAT, playerA, memnite, 1 + 1, 1); checkPT("next turn", 2, PhaseStep.BEGIN_COMBAT, playerA, memnite, 1, 1); addTarget(playerA, memnite); - checkPT("+2/+0", 3, PhaseStep.BEGIN_COMBAT, playerA, memnite, 3, 1); + checkPT("+2/+0", 3, PhaseStep.BEGIN_COMBAT, playerA, memnite, 1 + 1 + 1, 1); addTarget(playerA, memnite); - checkPT("+3/+0", 5, PhaseStep.BEGIN_COMBAT, playerA, memnite, 4, 1); + checkPT("+3/+0", 5, PhaseStep.BEGIN_COMBAT, playerA, memnite, 1 + 1 + 1 + 1, 1); addTarget(playerA, memnite); addTarget(playerA, kraken); diff --git a/Mage/src/main/java/mage/abilities/common/SagaAbility.java b/Mage/src/main/java/mage/abilities/common/SagaAbility.java index 5bc4a46236d..06c9333750b 100644 --- a/Mage/src/main/java/mage/abilities/common/SagaAbility.java +++ b/Mage/src/main/java/mage/abilities/common/SagaAbility.java @@ -6,11 +6,13 @@ import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.ReadAheadAbility; import mage.cards.Card; -import mage.constants.Outcome; -import mage.constants.SagaChapter; -import mage.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -30,7 +32,6 @@ public class SagaAbility extends SimpleStaticAbility { private final SagaChapter maxChapter; private final boolean showSacText; - private final boolean readAhead; public SagaAbility(Card card) { this(card, SagaChapter.CHAPTER_III, false); @@ -44,19 +45,20 @@ public class SagaAbility extends SimpleStaticAbility { super(Zone.ALL, null); this.maxChapter = maxChapter; this.showSacText = card.getSecondCardFace() == null && !card.isNightCard(); - this.readAhead = readAhead; this.setRuleVisible(true); this.setRuleAtTheTop(true); - Ability ability = new EntersBattlefieldAbility(new SagaLoreCountersEffect(readAhead, maxChapter)); + Ability ability = new EntersBattlefieldAbility(new SagaLoreCountersEffect(maxChapter)); ability.setRuleVisible(false); card.addAbility(ability); + if (readAhead) { + card.addAbility(ReadAheadAbility.getInstance()); + } } protected SagaAbility(final SagaAbility ability) { super(ability); this.maxChapter = ability.maxChapter; this.showSacText = ability.showSacText; - this.readAhead = ability.readAhead; } public void addChapterEffect(Card card, SagaChapter chapter, Effect... effects) { @@ -115,7 +117,7 @@ public class SagaAbility extends SimpleStaticAbility { public void addChapterEffect(Card card, SagaChapter fromChapter, SagaChapter toChapter, boolean optional, Consumer applier) { for (int i = fromChapter.getNumber(); i <= toChapter.getNumber(); i++) { ChapterTriggeredAbility ability = new ChapterTriggeredAbility( - SagaChapter.getChapter(i), toChapter, optional, readAhead + SagaChapter.getChapter(i), toChapter, optional ); applier.accept(ability); if (i > fromChapter.getNumber()) { @@ -131,10 +133,7 @@ public class SagaAbility extends SimpleStaticAbility { @Override public String getRule() { - return (readAhead - ? "Read ahead (Choose a chapter and start with that many lore counters. " + - "Add one after your draw step. Skipped chapters don't trigger." - : "(As this Saga enters and after your draw step, add a lore counter.") + return "(As this Saga enters and after your draw step, add a lore counter." + (showSacText ? " Sacrifice after " + maxChapter.toString() + '.' : "") + ") "; } @@ -158,22 +157,23 @@ public class SagaAbility extends SimpleStaticAbility { return ability instanceof ChapterTriggeredAbility && ((ChapterTriggeredAbility) ability).getChapterFrom().getNumber() == maxChapter; } + + public static Ability makeGainReadAheadAbility() { + return new GainReadAheadAbility(); + } } class SagaLoreCountersEffect extends OneShotEffect { - private final boolean readAhead; private final SagaChapter maxChapter; - SagaLoreCountersEffect(boolean readAhead, SagaChapter maxChapter) { + SagaLoreCountersEffect(SagaChapter maxChapter) { super(Outcome.Benefit); - this.readAhead = readAhead; this.maxChapter = maxChapter; } private SagaLoreCountersEffect(final SagaLoreCountersEffect effect) { super(effect); - this.readAhead = effect.readAhead; this.maxChapter = effect.maxChapter; } @@ -188,7 +188,8 @@ class SagaLoreCountersEffect extends OneShotEffect { if (permanent == null) { return false; } - if (!readAhead) { + if (!permanent.hasAbility(ReadAheadAbility.getInstance(), game) + && !GainReadAheadAbility.checkForAbility(game, source)) { return permanent.addCounters(CounterType.LORE.createInstance(), source, game); } Player player = game.getPlayer(source.getControllerId()); @@ -206,20 +207,17 @@ class SagaLoreCountersEffect extends OneShotEffect { class ChapterTriggeredAbility extends TriggeredAbilityImpl { private final SagaChapter chapterFrom, chapterTo; - private final boolean readAhead; - ChapterTriggeredAbility(SagaChapter chapterFrom, SagaChapter chapterTo, boolean optional, boolean readAhead) { + ChapterTriggeredAbility(SagaChapter chapterFrom, SagaChapter chapterTo, boolean optional) { super(Zone.ALL, null, optional); this.chapterFrom = chapterFrom; this.chapterTo = chapterTo; - this.readAhead = readAhead; } private ChapterTriggeredAbility(final ChapterTriggeredAbility ability) { super(ability); this.chapterFrom = ability.chapterFrom; this.chapterTo = ability.chapterTo; - this.readAhead = ability.readAhead; } @Override @@ -238,7 +236,9 @@ class ChapterTriggeredAbility extends TriggeredAbilityImpl { return false; } int loreCounters = permanent.getCounters(game).getCount(CounterType.LORE); - if (readAhead && permanent.getTurnsOnBattlefield() == 0) { + if (permanent.getTurnsOnBattlefield() == 0 + && (permanent.hasAbility(ReadAheadAbility.getInstance(), game) + || GainReadAheadAbility.checkForAbility(game, this))) { return chapterFrom.getNumber() == loreCounters; } return loreCounters - event.getAmount() < chapterFrom.getNumber() @@ -275,3 +275,35 @@ class ChapterTriggeredAbility extends TriggeredAbilityImpl { + " - " + CardUtil.getTextWithFirstCharUpperCase(super.getRule()); } } + +class GainReadAheadAbility extends SimpleStaticAbility { + + private static final FilterPermanent filter = new FilterPermanent(SubType.SAGA, "Sagas"); + + GainReadAheadAbility() { + super(new GainAbilityControlledEffect( + ReadAheadAbility.getInstance(), Duration.WhileOnBattlefield, filter + ).setText("Sagas you control have read ahead. (As a Saga enters, choose a chapter " + + "and start with that many lore counters. Skipped chapters don't trigger.)")); + } + + private GainReadAheadAbility(final GainReadAheadAbility ability) { + super(ability); + } + + @Override + public GainReadAheadAbility copy() { + return new GainReadAheadAbility(this); + } + + static boolean checkForAbility(Game game, Ability source) { + return game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT, + source.getControllerId(), source, game + ) + .stream() + .anyMatch(p -> p.getAbilities(game).containsClass(GainReadAheadAbility.class)); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/ReadAheadAbility.java b/Mage/src/main/java/mage/abilities/keyword/ReadAheadAbility.java new file mode 100644 index 00000000000..0fe24e3d801 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/ReadAheadAbility.java @@ -0,0 +1,43 @@ +package mage.abilities.keyword; + +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; +import mage.constants.Zone; + +import java.io.ObjectStreamException; + +/** + * @author TheElk801 + */ +public class ReadAheadAbility extends StaticAbility implements MageSingleton { + + private static final ReadAheadAbility instance; + + static { + instance = new ReadAheadAbility(); + } + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static ReadAheadAbility getInstance() { + return instance; + } + + private ReadAheadAbility() { + super(Zone.BATTLEFIELD, null); + this.setRuleAtTheTop(true); + } + + @Override + public String getRule() { + return "read ahead"; + } + + @Override + public ReadAheadAbility copy() { + return instance; + } + +}