Merge remote-tracking branch 'origin/master'

# Conflicts:
#	Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java
This commit is contained in:
Grath 2023-04-12 10:55:30 -04:00
commit 5dbb68f72f
12 changed files with 625 additions and 39 deletions

View file

@ -0,0 +1,80 @@
package mage.cards.c;
import mage.abilities.Ability;
import mage.abilities.common.AttacksAttachedTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EquipAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
import java.util.Objects;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class ConjurersMantle extends CardImpl {
private static final FilterCard filter
= new FilterCard("a card that shares a creature type with that creature");
static {
filter.add(ConjurersMantlePredicate.instance);
}
public ConjurersMantle(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}");
this.subtype.add(SubType.EQUIPMENT);
// Equipped creature gets +1/+1 and has vigilance.
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 1));
ability.addEffect(new GainAbilityAttachedEffect(
VigilanceAbility.getInstance(), AttachmentType.EQUIPMENT
).setText("and has vigilance"));
this.addAbility(ability);
// Whenever equipped creature attacks, look at the top six cards of your library. You may reveal a card that shares a creature type with that creature from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
this.addAbility(new AttacksAttachedTriggeredAbility(new LookLibraryAndPickControllerEffect(
6, 1, filter, PutCards.HAND, PutCards.BOTTOM_RANDOM
), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT));
// Equip {1}
this.addAbility(new EquipAbility(1, false));
}
private ConjurersMantle(final ConjurersMantle card) {
super(card);
}
@Override
public ConjurersMantle copy() {
return new ConjurersMantle(this);
}
}
enum ConjurersMantlePredicate implements ObjectSourcePlayerPredicate<Card> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<Card> input, Game game) {
return input
.getSource()
.getEffects()
.stream()
.map(effect -> effect.getTargetPointer().getFirst(game, input.getSource()))
.map(game::getPermanent)
.filter(Objects::nonNull)
.anyMatch(permanent -> permanent.shareCreatureTypes(game, input.getObject()));
}
}

View file

@ -0,0 +1,90 @@
package mage.cards.d;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class DanceWithCalamity extends CardImpl {
public DanceWithCalamity(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{7}{R}");
// Shuffle your library. As many times as you choose, you may exile the top card of your library. If the total mana value of the cards exiled this way is 13 or less, you may cast any number of spells from among those cards without paying their mana costs.
this.getSpellAbility().addEffect(new DanceWithCalamityEffect());
}
private DanceWithCalamity(final DanceWithCalamity card) {
super(card);
}
@Override
public DanceWithCalamity copy() {
return new DanceWithCalamity(this);
}
}
class DanceWithCalamityEffect extends OneShotEffect {
DanceWithCalamityEffect() {
super(Outcome.Benefit);
staticText = "shuffle your library. As many times as you choose, you may exile the top card of your library. " +
"If the total mana value of the cards exiled this way is 13 or less, you may cast any number " +
"of spells from among those cards without paying their mana costs";
}
private DanceWithCalamityEffect(final DanceWithCalamityEffect effect) {
super(effect);
}
@Override
public DanceWithCalamityEffect copy() {
return new DanceWithCalamityEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
Cards cards = new CardsImpl();
while (player.getLibrary().hasCards()) {
int totalMV = cards
.getCards(game)
.stream()
.mapToInt(MageObject::getManaValue)
.sum();
if (!player.chooseUse(
outcome, "Exile the top card of your library?",
"Current total mana value is " + totalMV,
"Yes", "No", source, game
)) {
break;
}
Card card = player.getLibrary().getFromTop(game);
player.moveCards(card, Zone.EXILED, source, game);
cards.add(card);
}
if (cards
.getCards(game)
.stream()
.mapToInt(MageObject::getManaValue)
.sum() <= 13) {
CardUtil.castMultipleWithAttributeForFree(player, source, game, cards, StaticFilters.FILTER_CARD);
}
return true;
}
}

View file

@ -0,0 +1,62 @@
package mage.cards.d;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.OpponentsCount;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.keyword.IndestructibleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.permanent.token.PhyrexianGolemToken;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class DarksteelSplicer extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent(SubType.PHYREXIAN, "nontoken Phyrexian");
static {
filter.add(AnotherPredicate.instance);
}
private static final FilterPermanent filter2 = new FilterPermanent(SubType.GOLEM, "Golems");
public DarksteelSplicer(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{W}");
this.subtype.add(SubType.PHYREXIAN);
this.subtype.add(SubType.ARTIFICER);
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// Whenever Darksteel Splicer or another nontoken Phyrexian enters the battlefield under your control, create X 3/3 colorless Phyrexian Golem artifact creature tokens, where X is the number of opponents you have.
this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility(
new CreateTokenEffect(new PhyrexianGolemToken(), OpponentsCount.instance),
filter, false, true
));
// Golems you control have indestructible.
this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(
IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, filter2
)));
}
private DarksteelSplicer(final DarksteelSplicer card) {
super(card);
}
@Override
public DarksteelSplicer copy() {
return new DarksteelSplicer(this);
}
}

View file

@ -0,0 +1,81 @@
package mage.cards.e;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount;
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
import mage.abilities.keyword.BackupAbility;
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.filter.FilterCard;
import mage.filter.common.FilterPermanentCard;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class EmergentWoodwurm extends CardImpl {
private static final FilterCard filter = new FilterPermanentCard("permanent card with mana value X or less");
static {
filter.add(EmergentWoodwurmPredicate.instance);
}
private static final DynamicValue xValue = new SourcePermanentPowerCount(false);
public EmergentWoodwurm(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{G}");
this.subtype.add(SubType.WURM);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Backup 3
BackupAbility backupAbility = new BackupAbility(this, 3);
this.addAbility(backupAbility);
// Whenever this creature attacks, look at the top X cards of your library, where X is its power. You may put a permanent card with mana value X or less from among them onto the battlefield. Put the rest on the bottom of your library in a random order.
backupAbility.addAbility(new AttacksTriggeredAbility(new LookLibraryAndPickControllerEffect(
xValue, 1, filter, PutCards.BATTLEFIELD, PutCards.BOTTOM_RANDOM
)).setTriggerPhrase("Whenever this creature attacks, "));
}
private EmergentWoodwurm(final EmergentWoodwurm card) {
super(card);
}
@Override
public EmergentWoodwurm copy() {
return new EmergentWoodwurm(this);
}
}
enum EmergentWoodwurmPredicate implements ObjectSourcePlayerPredicate<Card> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<Card> input, Game game) {
return Optional
.of(input)
.map(ObjectSourcePlayer::getSource)
.map(ability -> ability.getSourcePermanentOrLKI(game))
.filter(Objects::nonNull)
.map(MageObject::getPower)
.map(MageInt::getValue)
.map(i -> input.getObject().getManaValue() <= i)
.orElse(false);
}
}

View file

@ -0,0 +1,59 @@
package mage.cards.h;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.ExileTopXMayPlayUntilEndOfTurnEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.target.common.TargetControlledPermanent;
import mage.target.common.TargetOpponent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class HedronDetonator extends CardImpl {
private static final FilterControlledPermanent filter = new FilterControlledArtifactPermanent("artifacts");
public HedronDetonator(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}");
this.subtype.add(SubType.GOBLIN);
this.subtype.add(SubType.ARTIFICER);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// Whenever an artifact enters the battlefield under your control, Hedron Detonator deals 1 damage to target opponent.
Ability ability = new EntersBattlefieldControlledTriggeredAbility(
new DamageTargetEffect(1), StaticFilters.FILTER_PERMANENT_ARTIFACT
);
ability.addTarget(new TargetOpponent());
this.addAbility(ability);
// {T}, Sacrifice two artifacts: Exile the top card of your library. You may play that card this turn.
ability = new SimpleActivatedAbility(new ExileTopXMayPlayUntilEndOfTurnEffect(1), new TapSourceCost());
ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(2, filter)));
this.addAbility(ability);
}
private HedronDetonator(final HedronDetonator card) {
super(card);
}
@Override
public HedronDetonator copy() {
return new HedronDetonator(this);
}
}

View file

@ -0,0 +1,104 @@
package mage.cards.m;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.abilities.effects.common.ExileTargetEffect;
import mage.abilities.keyword.BackupAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.permanent.AttackingPredicate;
import mage.filter.predicate.permanent.ModifiedPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTargets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class MirrorStyleMaster extends CardImpl {
public MirrorStyleMaster(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.WARRIOR);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Backup 1
BackupAbility backupAbility = new BackupAbility(this, 1);
this.addAbility(backupAbility);
// Whenever this creature attacks, for each attacking modified creature you control, create a tapped and attacking token that's a copy of that creature. Exile those tokens at end of combat.
backupAbility.addAbility(new AttacksTriggeredAbility(new MirrorStyleMasterEffect())
.setTriggerPhrase("Whenever this creature attacks, "));
}
private MirrorStyleMaster(final MirrorStyleMaster card) {
super(card);
}
@Override
public MirrorStyleMaster copy() {
return new MirrorStyleMaster(this);
}
}
class MirrorStyleMasterEffect extends OneShotEffect {
private static final FilterPermanent filter = new FilterControlledCreaturePermanent();
static {
filter.add(AttackingPredicate.instance);
filter.add(ModifiedPredicate.instance);
}
MirrorStyleMasterEffect() {
super(Outcome.Benefit);
staticText = "for each attacking modified creature you control, create a tapped and attacking token " +
"that's a copy of that creature. Exile those tokens at end of combat";
}
private MirrorStyleMasterEffect(final MirrorStyleMasterEffect effect) {
super(effect);
}
@Override
public MirrorStyleMasterEffect copy() {
return new MirrorStyleMasterEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
List<Permanent> permanents = new ArrayList<>();
for (Permanent permanent : game.getBattlefield().getActivePermanents(
filter, source.getControllerId(), source, game
)) {
CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(
null, null, false, 1, true, true
);
effect.setSavedPermanent(permanent);
effect.apply(game, source);
permanents.addAll(effect.getAddedPermanents());
}
game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility(
new ExileTargetEffect()
.setTargetPointer(new FixedTargets(permanents, game))
.setText("exile those tokens")
), source);
return true;
}
}

View file

@ -44,8 +44,8 @@ public final class RashmiAndRagavan extends CardImpl {
this.toughness = new MageInt(4); this.toughness = new MageInt(4);
// Whenever you cast your first spell during each of your turns, // Whenever you cast your first spell during each of your turns,
// exile the top card of target opponents library and create a Treasure token. // exile the top card of target opponent's library and create a Treasure token.
// Then you may cast the exiled card without paying its mana cost if its a spell with mana value // Then you may cast the exiled card without paying its mana cost if it's a spell with mana value
// less than the number of artifacts you control. // less than the number of artifacts you control.
// If you dont cast it this way, you may cast it this turn. // If you dont cast it this way, you may cast it this turn.
this.addAbility(new RashmiAndRagavanTriggeredAbility()); this.addAbility(new RashmiAndRagavanTriggeredAbility());
@ -97,9 +97,9 @@ class RashmiAndRagavanTriggeredAbility extends SpellCastControllerTriggeredAbili
@Override @Override
public String getRule() { public String getRule() {
return "Whenever you cast your first spell during each of your turns, exile the top card of target " return "Whenever you cast your first spell during each of your turns, exile the top card of target "
+ "opponents library and create a Treasure token. Then you may cast the exiled card without " + "opponent's library and create a Treasure token. Then you may cast the exiled card without "
+ "paying its mana cost if its a spell with mana value less than the number of artifacts you " + "paying its mana cost if it's a spell with mana value less than the number of artifacts you "
+ "control. If you dont cast it this way, you may cast it this turn."; + "control. If you don't cast it this way, you may cast it this turn.";
} }
} }
@ -107,9 +107,9 @@ class RashmiAndRagavanEffect extends OneShotEffect {
RashmiAndRagavanEffect() { RashmiAndRagavanEffect() {
super(Outcome.PlayForFree); super(Outcome.PlayForFree);
this.staticText = "exile the top card of target opponents library and create a Treasure token. " this.staticText = "exile the top card of target opponent's library and create a Treasure token. "
+ "Then you may cast the exiled card without paying its mana cost if its a spell with mana value " + "Then you may cast the exiled card without paying its mana cost if it's a spell with mana value "
+ "less than the number of artifacts you control. If you dont cast it this way, " + "less than the number of artifacts you control. If you don't cast it this way, "
+ "you may cast it this turn"; + "you may cast it this turn";
} }

View file

@ -0,0 +1,69 @@
package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BecomesTappedSourceTriggeredAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.IfAbilityHasResolvedXTimesEffect;
import mage.abilities.effects.common.UntapSourceEffect;
import mage.abilities.keyword.ConvokeAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterSpell;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.game.permanent.token.AngelToken;
import mage.game.permanent.token.RedHumanToken;
import mage.game.permanent.token.SpiritBlueToken;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class SaintTraftAndRemKarolus extends CardImpl {
private static final FilterSpell filter = new FilterSpell("a spell that has convoke");
static {
filter.add(new AbilityPredicate(ConvokeAbility.class));
}
public SaintTraftAndRemKarolus(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}{W}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.SPIRIT);
this.subtype.add(SubType.HUMAN);
this.power = new MageInt(3);
this.toughness = new MageInt(4);
// Whenever Saint Traft and Rem Karolus becomes tapped, create a 1/1 red Human creature token if this is the first time this ability has resolved this turn. If it's the second time, create a 1/1 blue Spirit creature token with flying. If it's the third time, create a 4/4 white Angel creature token with flying.
Ability ability = new BecomesTappedSourceTriggeredAbility(new IfAbilityHasResolvedXTimesEffect(
Outcome.PutCreatureInPlay, 1, new CreateTokenEffect(new RedHumanToken())
).setText("create a 1/1 red Human creature token if this is the first time this ability has resolved this turn"));
ability.addEffect(new IfAbilityHasResolvedXTimesEffect(
Outcome.PutCreatureInPlay, 2, new CreateTokenEffect(new SpiritBlueToken())
).setText("If it's the second time, create a 1/1 blue Spirit creature token with flying"));
ability.addEffect(new IfAbilityHasResolvedXTimesEffect(
Outcome.PutCreatureInPlay, 3, new CreateTokenEffect(new AngelToken())
).setText("If it's the third time, create a 4/4 white Angel creature token with flying"));
this.addAbility(ability);
// Whenever you cast a spell that has convoke, untap Saint Traft and Rem Karolus.
this.addAbility(new SpellCastControllerTriggeredAbility(new UntapSourceEffect(), filter, false));
}
private SaintTraftAndRemKarolus(final SaintTraftAndRemKarolus card) {
super(card);
}
@Override
public SaintTraftAndRemKarolus copy() {
return new SaintTraftAndRemKarolus(this);
}
}

View file

@ -1,15 +1,14 @@
package mage.cards.v; package mage.cards.v;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.common.SpellCastOpponentTriggeredAbility; import mage.abilities.common.SpellCastOpponentTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.SourceMatchesFilterCondition; import mage.abilities.condition.common.SourceMatchesFilterCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.FilterSpell;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
@ -22,14 +21,17 @@ import java.util.UUID;
*/ */
public final class VeiledSentry extends CardImpl { public final class VeiledSentry extends CardImpl {
private static final Condition condition = new SourceMatchesFilterCondition(StaticFilters.FILTER_PERMANENT_ENCHANTMENT);
public VeiledSentry(UUID ownerId, CardSetInfo setInfo) { public VeiledSentry(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}");
// When an opponent casts a spell, if Veiled Sentry is an enchantment, Veiled Sentry becomes an Illusion creature with power and toughness each equal to that spell's converted mana cost. // When an opponent casts a spell, if Veiled Sentry is an enchantment, Veiled Sentry becomes an Illusion creature with power and toughness each equal to that spell's converted mana cost.
TriggeredAbility ability = new SpellCastOpponentTriggeredAbility(Zone.BATTLEFIELD, new VeiledSentryEffect(), new FilterSpell(), false, SetTargetPointer.SPELL); this.addAbility(new ConditionalInterveningIfTriggeredAbility(
this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, new SourceMatchesFilterCondition(StaticFilters.FILTER_PERMANENT_ENCHANTMENT), new SpellCastOpponentTriggeredAbility(new VeiledSentryEffect(), false),
"Whenever an opponent casts a spell, if Veiled Sentry is an enchantment, Veil Sentry becomes an Illusion creature with power and toughness equal to that spell's mana value.")); condition, "Whenever an opponent casts a spell, if {this} is an enchantment, " +
"{this} becomes an Illusion creature with power and toughness equal to that spell's mana value."
));
} }
private VeiledSentry(final VeiledSentry card) { private VeiledSentry(final VeiledSentry card) {
@ -44,6 +46,8 @@ public final class VeiledSentry extends CardImpl {
class VeiledSentryEffect extends ContinuousEffectImpl { class VeiledSentryEffect extends ContinuousEffectImpl {
private int spellMV = 0;
public VeiledSentryEffect() { public VeiledSentryEffect() {
super(Duration.Custom, Outcome.BecomeCreature); super(Duration.Custom, Outcome.BecomeCreature);
staticText = "{this} becomes an Illusion creature with power and toughness equal to that spell's mana value"; staticText = "{this} becomes an Illusion creature with power and toughness equal to that spell's mana value";
@ -51,6 +55,7 @@ class VeiledSentryEffect extends ContinuousEffectImpl {
public VeiledSentryEffect(final VeiledSentryEffect effect) { public VeiledSentryEffect(final VeiledSentryEffect effect) {
super(effect); super(effect);
this.spellMV = effect.spellMV;
} }
@Override @Override
@ -59,30 +64,32 @@ class VeiledSentryEffect extends ContinuousEffectImpl {
} }
@Override @Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { public void init(Ability source, Game game) {
Permanent veiledSentry = game.getPermanent(source.getSourceId()); super.init(source, game);
Spell spellCast = game.getSpell(targetPointer.getFirst(game, source)); Spell spell = (Spell) getValue("spellCast");
if (spellCast != null) { if (spell != null) {
game.getState().setValue(source + "cmcSpell", spellCast.getManaValue()); spellMV = spell.getManaValue();
} }
if (veiledSentry == null) { }
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent == null) {
discard();
return false; return false;
} }
switch (layer) { switch (layer) {
case TypeChangingEffects_4: case TypeChangingEffects_4:
veiledSentry.removeAllCardTypes(game); permanent.removeAllCardTypes(game);
veiledSentry.removeAllSubTypes(game); permanent.removeAllSubTypes(game);
veiledSentry.addCardType(game, CardType.CREATURE); permanent.addCardType(game, CardType.CREATURE);
veiledSentry.addSubType(game, SubType.ILLUSION); permanent.addSubType(game, SubType.ILLUSION);
break; break;
case PTChangingEffects_7: case PTChangingEffects_7:
if (game.getState().getValue(source + "cmcSpell") != null) { if (sublayer == SubLayer.SetPT_7b) {
int cmc = (int) game.getState().getValue(source + "cmcSpell"); permanent.getPower().setModifiedBaseValue(spellMV);
if (sublayer == SubLayer.SetPT_7b) { permanent.getToughness().setModifiedBaseValue(spellMV);
veiledSentry.addPower(cmc);
veiledSentry.addToughness(cmc);
}
} }
} }
return true; return true;

View file

@ -72,6 +72,7 @@ public final class MarchOfTheMachineCommander extends ExpansionSet {
cards.add(new SetCardInfo("Commander's Sphere", 352, Rarity.COMMON, mage.cards.c.CommandersSphere.class)); cards.add(new SetCardInfo("Commander's Sphere", 352, Rarity.COMMON, mage.cards.c.CommandersSphere.class));
cards.add(new SetCardInfo("Conclave Mentor", 320, Rarity.UNCOMMON, mage.cards.c.ConclaveMentor.class)); cards.add(new SetCardInfo("Conclave Mentor", 320, Rarity.UNCOMMON, mage.cards.c.ConclaveMentor.class));
cards.add(new SetCardInfo("Conclave Tribunal", 178, Rarity.UNCOMMON, mage.cards.c.ConclaveTribunal.class)); cards.add(new SetCardInfo("Conclave Tribunal", 178, Rarity.UNCOMMON, mage.cards.c.ConclaveTribunal.class));
cards.add(new SetCardInfo("Conjurer's Mantle", 12, Rarity.RARE, mage.cards.c.ConjurersMantle.class));
cards.add(new SetCardInfo("Constable of the Realm", 179, Rarity.UNCOMMON, mage.cards.c.ConstableOfTheRealm.class)); cards.add(new SetCardInfo("Constable of the Realm", 179, Rarity.UNCOMMON, mage.cards.c.ConstableOfTheRealm.class));
cards.add(new SetCardInfo("Corpse Knight", 321, Rarity.UNCOMMON, mage.cards.c.CorpseKnight.class)); cards.add(new SetCardInfo("Corpse Knight", 321, Rarity.UNCOMMON, mage.cards.c.CorpseKnight.class));
cards.add(new SetCardInfo("Coveted Jewel", 353, Rarity.RARE, mage.cards.c.CovetedJewel.class)); cards.add(new SetCardInfo("Coveted Jewel", 353, Rarity.RARE, mage.cards.c.CovetedJewel.class));
@ -79,6 +80,8 @@ public final class MarchOfTheMachineCommander extends ExpansionSet {
cards.add(new SetCardInfo("Cultivate", 295, Rarity.COMMON, mage.cards.c.Cultivate.class)); cards.add(new SetCardInfo("Cultivate", 295, Rarity.COMMON, mage.cards.c.Cultivate.class));
cards.add(new SetCardInfo("Cultivator's Caravan", 354, Rarity.RARE, mage.cards.c.CultivatorsCaravan.class)); cards.add(new SetCardInfo("Cultivator's Caravan", 354, Rarity.RARE, mage.cards.c.CultivatorsCaravan.class));
cards.add(new SetCardInfo("Curse of Opulence", 274, Rarity.UNCOMMON, mage.cards.c.CurseOfOpulence.class)); cards.add(new SetCardInfo("Curse of Opulence", 274, Rarity.UNCOMMON, mage.cards.c.CurseOfOpulence.class));
cards.add(new SetCardInfo("Dance with Calamity", 29, Rarity.RARE, mage.cards.d.DanceWithCalamity.class));
cards.add(new SetCardInfo("Darksteel Splicer", 13, Rarity.RARE, mage.cards.d.DarksteelSplicer.class));
cards.add(new SetCardInfo("Death-Greeter's Champion", 30, Rarity.RARE, mage.cards.d.DeathGreetersChampion.class)); cards.add(new SetCardInfo("Death-Greeter's Champion", 30, Rarity.RARE, mage.cards.d.DeathGreetersChampion.class));
cards.add(new SetCardInfo("Deluxe Dragster", 21, Rarity.RARE, mage.cards.d.DeluxeDragster.class)); cards.add(new SetCardInfo("Deluxe Dragster", 21, Rarity.RARE, mage.cards.d.DeluxeDragster.class));
cards.add(new SetCardInfo("Despark", 322, Rarity.UNCOMMON, mage.cards.d.Despark.class)); cards.add(new SetCardInfo("Despark", 322, Rarity.UNCOMMON, mage.cards.d.Despark.class));
@ -90,6 +93,7 @@ public final class MarchOfTheMachineCommander extends ExpansionSet {
cards.add(new SetCardInfo("Echo Storm", 221, Rarity.RARE, mage.cards.e.EchoStorm.class)); cards.add(new SetCardInfo("Echo Storm", 221, Rarity.RARE, mage.cards.e.EchoStorm.class));
cards.add(new SetCardInfo("Elite Scaleguard", 181, Rarity.UNCOMMON, mage.cards.e.EliteScaleguard.class)); cards.add(new SetCardInfo("Elite Scaleguard", 181, Rarity.UNCOMMON, mage.cards.e.EliteScaleguard.class));
cards.add(new SetCardInfo("Elspeth, Sun's Champion", 182, Rarity.MYTHIC, mage.cards.e.ElspethSunsChampion.class)); cards.add(new SetCardInfo("Elspeth, Sun's Champion", 182, Rarity.MYTHIC, mage.cards.e.ElspethSunsChampion.class));
cards.add(new SetCardInfo("Emergent Woodwurm", 37, Rarity.RARE, mage.cards.e.EmergentWoodwurm.class));
cards.add(new SetCardInfo("Emeria Angel", 183, Rarity.RARE, mage.cards.e.EmeriaAngel.class)); cards.add(new SetCardInfo("Emeria Angel", 183, Rarity.RARE, mage.cards.e.EmeriaAngel.class));
cards.add(new SetCardInfo("Enduring Scalelord", 325, Rarity.UNCOMMON, mage.cards.e.EnduringScalelord.class)); cards.add(new SetCardInfo("Enduring Scalelord", 325, Rarity.UNCOMMON, mage.cards.e.EnduringScalelord.class));
cards.add(new SetCardInfo("Ephemeral Shields", 184, Rarity.COMMON, mage.cards.e.EphemeralShields.class)); cards.add(new SetCardInfo("Ephemeral Shields", 184, Rarity.COMMON, mage.cards.e.EphemeralShields.class));
@ -138,6 +142,7 @@ public final class MarchOfTheMachineCommander extends ExpansionSet {
cards.add(new SetCardInfo("Hamza, Guardian of Arashin", 327, Rarity.UNCOMMON, mage.cards.h.HamzaGuardianOfArashin.class)); cards.add(new SetCardInfo("Hamza, Guardian of Arashin", 327, Rarity.UNCOMMON, mage.cards.h.HamzaGuardianOfArashin.class));
cards.add(new SetCardInfo("Heaven // Earth", 328, Rarity.RARE, mage.cards.h.HeavenEarth.class)); cards.add(new SetCardInfo("Heaven // Earth", 328, Rarity.RARE, mage.cards.h.HeavenEarth.class));
cards.add(new SetCardInfo("Hedron Archive", 359, Rarity.UNCOMMON, mage.cards.h.HedronArchive.class)); cards.add(new SetCardInfo("Hedron Archive", 359, Rarity.UNCOMMON, mage.cards.h.HedronArchive.class));
cards.add(new SetCardInfo("Hedron Detonator", 31, Rarity.RARE, mage.cards.h.HedronDetonator.class));
cards.add(new SetCardInfo("Hellkite Igniter", 284, Rarity.RARE, mage.cards.h.HellkiteIgniter.class)); cards.add(new SetCardInfo("Hellkite Igniter", 284, Rarity.RARE, mage.cards.h.HellkiteIgniter.class));
cards.add(new SetCardInfo("Herald of Hoofbeats", 22, Rarity.RARE, mage.cards.h.HeraldOfHoofbeats.class)); cards.add(new SetCardInfo("Herald of Hoofbeats", 22, Rarity.RARE, mage.cards.h.HeraldOfHoofbeats.class));
cards.add(new SetCardInfo("Herald's Horn", 360, Rarity.UNCOMMON, mage.cards.h.HeraldsHorn.class)); cards.add(new SetCardInfo("Herald's Horn", 360, Rarity.UNCOMMON, mage.cards.h.HeraldsHorn.class));
@ -192,6 +197,7 @@ public final class MarchOfTheMachineCommander extends ExpansionSet {
cards.add(new SetCardInfo("Mikaeus, the Lunarch", 197, Rarity.RARE, mage.cards.m.MikaeusTheLunarch.class)); cards.add(new SetCardInfo("Mikaeus, the Lunarch", 197, Rarity.RARE, mage.cards.m.MikaeusTheLunarch.class));
cards.add(new SetCardInfo("Mind Stone", 364, Rarity.UNCOMMON, mage.cards.m.MindStone.class)); cards.add(new SetCardInfo("Mind Stone", 364, Rarity.UNCOMMON, mage.cards.m.MindStone.class));
cards.add(new SetCardInfo("Mindless Automaton", 365, Rarity.UNCOMMON, mage.cards.m.MindlessAutomaton.class)); cards.add(new SetCardInfo("Mindless Automaton", 365, Rarity.UNCOMMON, mage.cards.m.MindlessAutomaton.class));
cards.add(new SetCardInfo("Mirror-Style Master", 32, Rarity.RARE, mage.cards.m.MirrorStyleMaster.class));
cards.add(new SetCardInfo("Mortify", 337, Rarity.UNCOMMON, mage.cards.m.Mortify.class)); cards.add(new SetCardInfo("Mortify", 337, Rarity.UNCOMMON, mage.cards.m.Mortify.class));
cards.add(new SetCardInfo("Mossfire Valley", 414, Rarity.RARE, mage.cards.m.MossfireValley.class)); cards.add(new SetCardInfo("Mossfire Valley", 414, Rarity.RARE, mage.cards.m.MossfireValley.class));
cards.add(new SetCardInfo("Mosswort Bridge", 415, Rarity.RARE, mage.cards.m.MosswortBridge.class)); cards.add(new SetCardInfo("Mosswort Bridge", 415, Rarity.RARE, mage.cards.m.MosswortBridge.class));
@ -238,6 +244,7 @@ public final class MarchOfTheMachineCommander extends ExpansionSet {
cards.add(new SetCardInfo("Root Out", 311, Rarity.COMMON, mage.cards.r.RootOut.class)); cards.add(new SetCardInfo("Root Out", 311, Rarity.COMMON, mage.cards.r.RootOut.class));
cards.add(new SetCardInfo("Saheeli's Artistry", 234, Rarity.RARE, mage.cards.s.SaheelisArtistry.class)); cards.add(new SetCardInfo("Saheeli's Artistry", 234, Rarity.RARE, mage.cards.s.SaheelisArtistry.class));
cards.add(new SetCardInfo("Saheeli, Sublime Artificer", 338, Rarity.UNCOMMON, mage.cards.s.SaheeliSublimeArtificer.class)); cards.add(new SetCardInfo("Saheeli, Sublime Artificer", 338, Rarity.UNCOMMON, mage.cards.s.SaheeliSublimeArtificer.class));
cards.add(new SetCardInfo("Saint Traft and Rem Karolus", 9, Rarity.MYTHIC, mage.cards.s.SaintTraftAndRemKarolus.class));
cards.add(new SetCardInfo("Sandsteppe War Riders", 39, Rarity.RARE, mage.cards.s.SandsteppeWarRiders.class)); cards.add(new SetCardInfo("Sandsteppe War Riders", 39, Rarity.RARE, mage.cards.s.SandsteppeWarRiders.class));
cards.add(new SetCardInfo("Schema Thief", 24, Rarity.RARE, mage.cards.s.SchemaThief.class)); cards.add(new SetCardInfo("Schema Thief", 24, Rarity.RARE, mage.cards.s.SchemaThief.class));
cards.add(new SetCardInfo("Scrap Trawler", 373, Rarity.RARE, mage.cards.s.ScrapTrawler.class)); cards.add(new SetCardInfo("Scrap Trawler", 373, Rarity.RARE, mage.cards.s.ScrapTrawler.class));

View file

@ -1283,6 +1283,8 @@ public class VerifyCardDataTest {
if (token == null) { if (token == null) {
errorsList.add("Error: token must have default constructor with zero params: " + tokenClass.getName()); errorsList.add("Error: token must have default constructor with zero params: " + tokenClass.getName());
} else if (tokDataNamesIndex.getOrDefault(token.getName().replace(" Token", ""), "").isEmpty()) { } else if (tokDataNamesIndex.getOrDefault(token.getName().replace(" Token", ""), "").isEmpty()) {
// how-to fix: public token must be downloadable, so tok-data must contain miss set
// (also don't forget to add new set to scryfall download)
errorsList.add("Error: can't find data in card-pictures-tok.txt for token: " + tokenClass.getName() + " -> " + token.getName()); errorsList.add("Error: can't find data in card-pictures-tok.txt for token: " + tokenClass.getName() + " -> " + token.getName());
} }
} }
@ -1312,20 +1314,45 @@ public class VerifyCardDataTest {
} }
// set uses tokens, but tok data miss it // set uses tokens, but tok data miss it
setsWithTokens.forEach((setCode, sourceCards) -> { setsWithTokens.forEach((setCode, sourceCards) -> {
if (!tokDataTokensBySetIndex.containsKey(setCode)) { List<CardDownloadData> setTokens = tokDataTokensBySetIndex.getOrDefault(setCode, null);
if (setTokens == null) {
// it's not a problem -- just find set's cards without real tokens for image tests // it's not a problem -- just find set's cards without real tokens for image tests
// (most use cases: promo sets) // Possible reasons:
warningsList.add("info, set has cards with token abilities, but it haven't token data: " // - promo sets with cards without tokens (nothing to do with it)
// - miss set from tok-data (must add new set to tok-data and scryfall download)
warningsList.add("info, set's cards uses tokens but tok-data haven't it: "
+ setCode + " - " + sourceCards.stream().map(MageObject::getName).collect(Collectors.joining(", "))); + setCode + " - " + sourceCards.stream().map(MageObject::getName).collect(Collectors.joining(", ")));
} else {
// Card can be checked on scryfall like "set:set_code oracle:token_name oracle:token"
// Possible reasons for un-used tokens:
// - normal use case: tok-data contains wrong token data and must be removed
// - normal use case: card uses wrong rules text (must contain "create" and "token" words)
// - rare use case: un-implemented card that uses a token
setTokens.forEach(token -> {
if (token.getName().contains("Plane - ")) {
// cards don't put it to the game, so no related cards
return;
}
String needTokenName = token.getName()
.replace(" Token", "")
.replace("Emblem ", "");
// need add card name, so it will skip no name emblems like Sarkhan, the Dragonspeaker
if (sourceCards.stream()
.map(card -> card.getName() + " - " + String.join(", ", card.getRules()))
.noneMatch(s -> s.contains(needTokenName))) {
warningsList.add("info, tok-data has un-used tokens: "
+ token.getSet() + " - " + token.getName());
}
});
} }
}); });
// tok data have tokens, but cards from set are miss // tok data have tokens, but cards from set are miss
tokDataTokensBySetIndex.forEach((setCode, setTokens) -> { tokDataTokensBySetIndex.forEach((setCode, setTokens) -> {
if (!setsWithTokens.containsKey(setCode)) { if (!setsWithTokens.containsKey(setCode)) {
// possible reasons: // Possible reasons:
// - bad: outdated set code in tokens database (must use exists set codes, see additional check with token's codes) // - outdated set code in tokens database (must be fixed by new set code, another verify check it)
// - ok: promo set contains additional tokens for main set -- it's ok and must be ignored (example: Saproling in E02) // - promo set contains additional tokens for main set (it's ok and must be ignored, example: Saproling in E02)
warningsList.add("warning, tok data has set with tokens, but real set haven't cards with it: " warningsList.add("warning, tok-data has tokens, but real set haven't cards with it: "
+ setCode + " - " + setTokens.stream().map(CardDownloadData::getName).collect(Collectors.joining(", "))); + setCode + " - " + setTokens.stream().map(CardDownloadData::getName).collect(Collectors.joining(", ")));
} }
}); });

View file

@ -17,7 +17,7 @@ import java.util.Arrays;
public final class VoiceOfResurgenceToken extends TokenImpl { public final class VoiceOfResurgenceToken extends TokenImpl {
public VoiceOfResurgenceToken() { public VoiceOfResurgenceToken() {
super("Elemental Token", "X/X green and white Elemental creature with with \"This creature's power and toughness are each equal to the number of creatures you control."); super("Elemental Token", "green and white Elemental creature token with \"This creature's power and toughness are each equal to the number of creatures you control.");
setOriginalExpansionSetCode("DGM"); setOriginalExpansionSetCode("DGM");
cardType.add(CardType.CREATURE); cardType.add(CardType.CREATURE);
color.setGreen(true); color.setGreen(true);