mirror of
https://github.com/correl/mage.git
synced 2024-11-28 19:19:55 +00:00
Implement Read Ahead mechanic (#9407)
* implement Read Ahead mechanic * [DMU] Implemented The World Spell * [DMU] Implemented The Elder Dragon War * added read ahead test * fix verify failure * small change to test * fix read ahead text
This commit is contained in:
parent
e557b7a04b
commit
90bd0dbf63
5 changed files with 300 additions and 7 deletions
61
Mage.Sets/src/mage/cards/t/TheElderDragonWar.java
Normal file
61
Mage.Sets/src/mage/cards/t/TheElderDragonWar.java
Normal file
|
@ -0,0 +1,61 @@
|
|||
package mage.cards.t;
|
||||
|
||||
import mage.abilities.common.SagaAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.DamageAllEffect;
|
||||
import mage.abilities.effects.common.DamagePlayersEffect;
|
||||
import mage.abilities.effects.common.discard.DiscardAndDrawThatManyEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SagaChapter;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.permanent.token.DragonToken;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class TheElderDragonWar extends CardImpl {
|
||||
|
||||
public TheElderDragonWar(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}{R}");
|
||||
|
||||
this.subtype.add(SubType.SAGA);
|
||||
|
||||
// Read ahead
|
||||
SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_III, true);
|
||||
|
||||
// I -- The Elder Dragon War deals 2 damage to each creature and each opponent.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_I,
|
||||
new DamageAllEffect(2, StaticFilters.FILTER_PERMANENT_CREATURE),
|
||||
new DamagePlayersEffect(2, TargetController.OPPONENT).setText("and each opponent")
|
||||
);
|
||||
|
||||
// II -- Discard any number of cards, then draw that many cards.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_II,
|
||||
new DiscardAndDrawThatManyEffect(Integer.MAX_VALUE)
|
||||
);
|
||||
|
||||
// III -- Create a 4/4 red Dragon creature token with flying.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_III,
|
||||
new CreateTokenEffect(new DragonToken())
|
||||
);
|
||||
this.addAbility(sagaAbility);
|
||||
}
|
||||
|
||||
private TheElderDragonWar(final TheElderDragonWar card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TheElderDragonWar copy() {
|
||||
return new TheElderDragonWar(this);
|
||||
}
|
||||
}
|
97
Mage.Sets/src/mage/cards/t/TheWorldSpell.java
Normal file
97
Mage.Sets/src/mage/cards/t/TheWorldSpell.java
Normal file
|
@ -0,0 +1,97 @@
|
|||
package mage.cards.t;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SagaAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
|
||||
import mage.abilities.effects.common.LookLibraryControllerEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.common.FilterPermanentCard;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetCardInHand;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class TheWorldSpell extends CardImpl {
|
||||
|
||||
private static final FilterCard filter = new FilterPermanentCard("non-Saga permanent card");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.not(SubType.SAGA.getPredicate()));
|
||||
}
|
||||
|
||||
public TheWorldSpell(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{5}{G}{G}");
|
||||
|
||||
this.subtype.add(SubType.SAGA);
|
||||
|
||||
// Read ahead
|
||||
SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_III, true);
|
||||
|
||||
// I, II -- Look at the top seven cards of your library. You may reveal a non-Saga permanent card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_II,
|
||||
new LookLibraryAndPickControllerEffect(
|
||||
7, 1, filter,
|
||||
LookLibraryControllerEffect.PutCards.HAND,
|
||||
LookLibraryControllerEffect.PutCards.BOTTOM_RANDOM
|
||||
)
|
||||
);
|
||||
|
||||
// III -- Put up to two non-Saga permanent cards from your hand onto the battlefield.
|
||||
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new TheWorldSpellEffect());
|
||||
this.addAbility(sagaAbility);
|
||||
}
|
||||
|
||||
private TheWorldSpell(final TheWorldSpell card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TheWorldSpell copy() {
|
||||
return new TheWorldSpell(this);
|
||||
}
|
||||
}
|
||||
|
||||
class TheWorldSpellEffect extends OneShotEffect {
|
||||
|
||||
private static final FilterCard filter = new FilterPermanentCard("non-Saga permanent cards");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.not(SubType.SAGA.getPredicate()));
|
||||
}
|
||||
|
||||
TheWorldSpellEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "put up to two non-Saga permanent cards from your hand onto the battlefield";
|
||||
}
|
||||
|
||||
private TheWorldSpellEffect(final TheWorldSpellEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TheWorldSpellEffect copy() {
|
||||
return new TheWorldSpellEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
TargetCardInHand target = new TargetCardInHand(0, 2, filter);
|
||||
player.choose(outcome, player.getHand(), target, game);
|
||||
return player.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game);
|
||||
}
|
||||
}
|
|
@ -189,7 +189,9 @@ public final class DominariaUnited extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Tear Asunder", 183, Rarity.UNCOMMON, mage.cards.t.TearAsunder.class));
|
||||
cards.add(new SetCardInfo("Temporal Firestorm", 147, Rarity.RARE, mage.cards.t.TemporalFirestorm.class));
|
||||
cards.add(new SetCardInfo("Territorial Maro", 184, Rarity.UNCOMMON, mage.cards.t.TerritorialMaro.class));
|
||||
cards.add(new SetCardInfo("The Elder Dragon War", 121, Rarity.RARE, mage.cards.t.TheElderDragonWar.class));
|
||||
cards.add(new SetCardInfo("The Raven Man", 103, Rarity.RARE, mage.cards.t.TheRavenMan.class));
|
||||
cards.add(new SetCardInfo("The World Spell", 189, Rarity.MYTHIC, mage.cards.t.TheWorldSpell.class));
|
||||
cards.add(new SetCardInfo("Threats Undetected", 185, Rarity.RARE, mage.cards.t.ThreatsUndetected.class));
|
||||
cards.add(new SetCardInfo("Thrill of Possibility", 148, Rarity.COMMON, mage.cards.t.ThrillOfPossibility.class));
|
||||
cards.add(new SetCardInfo("Tidepool Turtle", 69, Rarity.COMMON, mage.cards.t.TidepoolTurtle.class));
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package org.mage.test.cards.enchantments;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class ReadAheadTest extends CardTestPlayerBase {
|
||||
|
||||
private static final String war = "The Elder Dragon War";
|
||||
private static final String recall = "Ancestral Recall";
|
||||
|
||||
@Test
|
||||
public void testElderDragonWarChapter1() {
|
||||
addCard(Zone.HAND, playerA, war);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, war);
|
||||
setChoice(playerA, "X=1");
|
||||
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20 - 2);
|
||||
assertPermanentCount(playerA, war, 1);
|
||||
assertGraveyardCount(playerA, war, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testElderDragonWarChapter2() {
|
||||
addCard(Zone.HAND, playerA, war);
|
||||
addCard(Zone.HAND, playerA, recall);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, war);
|
||||
setChoice(playerA, "X=2");
|
||||
setChoice(playerA, recall);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20);
|
||||
assertGraveyardCount(playerA, recall, 1);
|
||||
assertHandCount(playerA, recall, 0);
|
||||
assertPermanentCount(playerA, war, 1);
|
||||
assertGraveyardCount(playerA, war, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testElderDragonWarChapter3() {
|
||||
addCard(Zone.HAND, playerA, war);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, war);
|
||||
setChoice(playerA, "X=3");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20);
|
||||
assertPermanentCount(playerA, "Dragon Token", 1);
|
||||
assertPermanentCount(playerA, war, 0);
|
||||
assertGraveyardCount(playerA, war, 1);
|
||||
}
|
||||
}
|
|
@ -6,8 +6,9 @@ import mage.abilities.TriggeredAbility;
|
|||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.Effects;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SagaChapter;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
|
@ -16,6 +17,7 @@ import mage.game.events.GameEvent;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.StackAbility;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.Targets;
|
||||
import mage.util.CardUtil;
|
||||
|
@ -29,18 +31,24 @@ 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);
|
||||
}
|
||||
|
||||
public SagaAbility(Card card, SagaChapter maxChapter) {
|
||||
super(Zone.ALL, new AddCountersSourceEffect(CounterType.LORE.createInstance()));
|
||||
this(card, maxChapter, false);
|
||||
}
|
||||
|
||||
public SagaAbility(Card card, SagaChapter maxChapter, boolean readAhead) {
|
||||
super(Zone.ALL, null);
|
||||
this.maxChapter = maxChapter;
|
||||
this.showSacText = card.getSecondCardFace() == null;
|
||||
this.readAhead = readAhead;
|
||||
this.setRuleVisible(true);
|
||||
this.setRuleAtTheTop(true);
|
||||
Ability ability = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.LORE.createInstance()));
|
||||
Ability ability = new EntersBattlefieldAbility(new SagaLoreCountersEffect(readAhead, maxChapter));
|
||||
ability.setRuleVisible(false);
|
||||
card.addAbility(ability);
|
||||
}
|
||||
|
@ -49,6 +57,7 @@ public class SagaAbility extends SimpleStaticAbility {
|
|||
super(ability);
|
||||
this.maxChapter = ability.maxChapter;
|
||||
this.showSacText = ability.showSacText;
|
||||
this.readAhead = ability.readAhead;
|
||||
}
|
||||
|
||||
public void addChapterEffect(Card card, SagaChapter chapter, Effect... effects) {
|
||||
|
@ -81,7 +90,7 @@ public class SagaAbility extends SimpleStaticAbility {
|
|||
|
||||
public void addChapterEffect(Card card, SagaChapter fromChapter, SagaChapter toChapter, Effects effects, Targets targets, boolean optional, Mode... modes) {
|
||||
for (int i = fromChapter.getNumber(); i <= toChapter.getNumber(); i++) {
|
||||
ChapterTriggeredAbility ability = new ChapterTriggeredAbility(null, SagaChapter.getChapter(i), toChapter, optional);
|
||||
ChapterTriggeredAbility ability = new ChapterTriggeredAbility(null, SagaChapter.getChapter(i), toChapter, optional, readAhead);
|
||||
for (Effect effect : effects) {
|
||||
if (effect != null) {
|
||||
ability.addEffect(effect.copy());
|
||||
|
@ -108,7 +117,10 @@ public class SagaAbility extends SimpleStaticAbility {
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "<i>(As this Saga enters and after your draw step, add a lore counter."
|
||||
return (readAhead
|
||||
? "Read ahead <i>(Choose a chapter and start with that many lore counters. " +
|
||||
"Add one after your draw step. Skipped chapters don't trigger."
|
||||
: "<i>(As this Saga enters and after your draw step, add a lore counter.")
|
||||
+ (showSacText ? " Sacrifice after " + maxChapter.toString() + '.' : "") + ")</i> ";
|
||||
}
|
||||
|
||||
|
@ -129,25 +141,71 @@ public class SagaAbility extends SimpleStaticAbility {
|
|||
}
|
||||
}
|
||||
|
||||
class SagaLoreCountersEffect extends OneShotEffect {
|
||||
|
||||
private final boolean readAhead;
|
||||
private final SagaChapter maxChapter;
|
||||
|
||||
SagaLoreCountersEffect(boolean readAhead, 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SagaLoreCountersEffect copy() {
|
||||
return new SagaLoreCountersEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanentEntering(source.getSourceId());
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
if (!readAhead) {
|
||||
return permanent.addCounters(CounterType.LORE.createInstance(), source, game);
|
||||
}
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
int counters = player.getAmount(
|
||||
1, maxChapter.getNumber(),
|
||||
"Choose the number of lore counters to enter with", game
|
||||
);
|
||||
return permanent.addCounters(CounterType.LORE.createInstance(counters), source, game);
|
||||
}
|
||||
}
|
||||
|
||||
class ChapterTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
private final SagaChapter chapterFrom, chapterTo;
|
||||
private final boolean readAhead;
|
||||
|
||||
public ChapterTriggeredAbility(Effect effect, SagaChapter chapterFrom, SagaChapter chapterTo, boolean optional) {
|
||||
public ChapterTriggeredAbility(Effect effect, SagaChapter chapterFrom, SagaChapter chapterTo, boolean optional, boolean readAhead) {
|
||||
super(Zone.ALL, effect, optional);
|
||||
this.chapterFrom = chapterFrom;
|
||||
this.chapterTo = chapterTo;
|
||||
this.readAhead = readAhead;
|
||||
}
|
||||
|
||||
public ChapterTriggeredAbility(final ChapterTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.chapterFrom = ability.chapterFrom;
|
||||
this.chapterTo = ability.chapterTo;
|
||||
this.readAhead = ability.readAhead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.COUNTER_ADDED;
|
||||
return event.getType() == GameEvent.EventType.COUNTERS_ADDED;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -161,6 +219,9 @@ class ChapterTriggeredAbility extends TriggeredAbilityImpl {
|
|||
return false;
|
||||
}
|
||||
int loreCounters = permanent.getCounters(game).getCount(CounterType.LORE);
|
||||
if (readAhead && permanent.getTurnsOnBattlefield() == 0) {
|
||||
return chapterFrom.getNumber() == loreCounters;
|
||||
}
|
||||
return loreCounters - event.getAmount() < chapterFrom.getNumber()
|
||||
&& chapterFrom.getNumber() <= loreCounters;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue