Merge pull request 'master' (#21) from External/mage:master into master
All checks were successful
/ example-docker-compose (push) Successful in 18m25s

Reviewed-on: #21
This commit is contained in:
Failure 2025-03-15 22:36:02 -07:00
commit b8546215e4
72 changed files with 3047 additions and 463 deletions

View file

@ -4,8 +4,8 @@ import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.VehiclesBecomeArtifactCreatureEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
@ -25,7 +25,7 @@ public final class ArmedAndArmored extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}");
// Vehicles you control become artifact creatures until end of turn.
this.getSpellAbility().addEffect(new ArmedAndArmoredEffect());
this.getSpellAbility().addEffect(new VehiclesBecomeArtifactCreatureEffect(Duration.EndOfTurn));
// Choose a Dwarf you control. Attach any number of Equipment you control to it.
this.getSpellAbility().addEffect(new ArmedAndArmoredEquipEffect());
@ -41,41 +41,6 @@ public final class ArmedAndArmored extends CardImpl {
}
}
class ArmedAndArmoredEffect extends ContinuousEffectImpl {
ArmedAndArmoredEffect() {
super(Duration.EndOfTurn, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.BecomeCreature);
staticText = "Vehicles you control become artifact creatures until end of turn";
}
private ArmedAndArmoredEffect(final ArmedAndArmoredEffect effect) {
super(effect);
}
@Override
public ArmedAndArmoredEffect copy() {
return new ArmedAndArmoredEffect(this);
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) {
if (permanent != null && permanent.hasSubtype(SubType.VEHICLE, game)) {
if (sublayer == SubLayer.NA) {
permanent.addCardType(game, CardType.ARTIFACT);
permanent.addCardType(game, CardType.CREATURE);// TODO: Check if giving CREATURE Type is correct
}
}
}
return true;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
}
class ArmedAndArmoredEquipEffect extends OneShotEffect {
ArmedAndArmoredEquipEffect() {

View file

@ -1,9 +1,7 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.dynamicvalue.common.CreaturesYouControlDiedCount;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
@ -11,8 +9,6 @@ import mage.abilities.keyword.SpectacleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.game.Game;
import mage.watchers.common.CreaturesDiedWatcher;
import java.util.UUID;
@ -22,14 +18,14 @@ import java.util.UUID;
public final class BodyCount extends CardImpl {
private static final Hint hint = new ValueHint(
"Creatures that died under your control this turn", BodyCountValue.instance
"Creatures that died under your control this turn", CreaturesYouControlDiedCount.instance
);
public BodyCount(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}");
// Draw a card for each creature that died under your control this turn.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(BodyCountValue.instance));
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(CreaturesYouControlDiedCount.instance));
this.getSpellAbility().addHint(hint);
// Spectacle {B}
@ -44,30 +40,4 @@ public final class BodyCount extends CardImpl {
public BodyCount copy() {
return new BodyCount(this);
}
}
enum BodyCountValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game.getState()
.getWatcher(CreaturesDiedWatcher.class)
.getAmountOfCreaturesDiedThisTurnByController(sourceAbility.getControllerId());
}
@Override
public BodyCountValue copy() {
return this;
}
@Override
public String getMessage() {
return "creature that died under your control this turn";
}
@Override
public String toString() {
return "1";
}
}
}

View file

@ -3,8 +3,7 @@ package mage.cards.c;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.dynamicvalue.common.CreaturesYouControlDiedCount;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
@ -21,7 +20,6 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetAnyTarget;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.watchers.common.CreaturesDiedWatcher;
import java.util.UUID;
@ -33,7 +31,7 @@ public final class CallousSellSword extends AdventureCard {
private static final FilterAnyTarget filterSecondTarget = new FilterAnyTarget("any other target");
private static final Hint hint = new ValueHint(
"Creatures that died under your control this turn", CallousSellSwordValue.instance
"Creatures that died under your control this turn", CreaturesYouControlDiedCount.instance
);
public CallousSellSword(UUID ownerId, CardSetInfo setInfo) {
@ -48,7 +46,7 @@ public final class CallousSellSword extends AdventureCard {
this.addAbility(new EntersBattlefieldAbility(
new AddCountersSourceEffect(
CounterType.P1P1.createInstance(0),
CallousSellSwordValue.instance, true
CreaturesYouControlDiedCount.instance, true
).setText("with a +1/+1 counter on it for each creature that died under your control this turn.")
).addHint(hint));
@ -72,32 +70,6 @@ public final class CallousSellSword extends AdventureCard {
}
}
enum CallousSellSwordValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game.getState()
.getWatcher(CreaturesDiedWatcher.class)
.getAmountOfCreaturesDiedThisTurnByController(sourceAbility.getControllerId());
}
@Override
public CallousSellSwordValue copy() {
return this;
}
@Override
public String getMessage() {
return "creature that died under your control this turn";
}
@Override
public String toString() {
return "1";
}
}
class CallousSellSwordSacrificeFirstTargetEffect extends OneShotEffect {
CallousSellSwordSacrificeFirstTargetEffect() {

View file

@ -0,0 +1,123 @@
package mage.cards.c;
import java.util.UUID;
import mage.MageInt;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.TargetController;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.common.FilterEquipmentPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.PowerPredicate;
import mage.filter.predicate.permanent.AttackingPredicate;
import mage.filter.predicate.permanent.EnchantedPredicate;
import mage.filter.predicate.permanent.EquippedPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.InsectToken;
import mage.game.permanent.token.TreasureToken;
import mage.target.TargetPermanent;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.SourceMatchesFilterCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.Outcome;
/**
*
* @author balazskristof
*/
public final class CloudExSOLDIER extends CardImpl {
private static final FilterControlledArtifactPermanent filter = new FilterControlledArtifactPermanent("Equipment you control");
private static final FilterControlledCreaturePermanent filter2 = new FilterControlledCreaturePermanent("equipped attacking creature you control");
private static final FilterCreaturePermanent filter3 = new FilterCreaturePermanent();
static {
filter.add(SubType.EQUIPMENT.getPredicate());
filter2.add(Predicates.and(
AttackingPredicate.instance,
EquippedPredicate.instance
));
filter3.add(new PowerPredicate(ComparisonType.MORE_THAN, 6));
}
static {
filter.add(TargetController.YOU.getControllerPredicate());
}
public CloudExSOLDIER(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.SOLDIER);
this.subtype.add(SubType.MERCENARY);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Haste
this.addAbility(HasteAbility.getInstance());
// When Cloud enters, attach up to one target Equipment you control to it.
Ability ability = new EntersBattlefieldTriggeredAbility(new CloudExSOLDIEREntersEffect());
ability.addTarget(new TargetPermanent(0, 1, filter));
this.addAbility(ability);
// Whenever Cloud attacks, draw a card for each equipped attacking creature you control. Then if Cloud has power 7 or greater, create two Treasure tokens.
Ability ability2 = new AttacksTriggeredAbility(new DrawCardSourceControllerEffect(new PermanentsOnBattlefieldCount(filter2)));
ability2.addEffect(new ConditionalOneShotEffect(
new CreateTokenEffect(new TreasureToken(), 2),
new SourceMatchesFilterCondition(filter3),
"Then if {this} has power 7 or greater, create two Treasure tokens."
));
this.addAbility(ability2);
}
private CloudExSOLDIER(final CloudExSOLDIER card) {
super(card);
}
@Override
public CloudExSOLDIER copy() {
return new CloudExSOLDIER(this);
}
}
class CloudExSOLDIEREntersEffect extends OneShotEffect {
CloudExSOLDIEREntersEffect() {
super(Outcome.Benefit);
staticText = "attach up to one target Equipment you control to {this}";
}
private CloudExSOLDIEREntersEffect(final CloudExSOLDIEREntersEffect effect) {
super(effect);
}
@Override
public CloudExSOLDIEREntersEffect copy() {
return new CloudExSOLDIEREntersEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent equipment = game.getPermanent(this.getTargetPointer().getFirst(game, source));
Permanent creature = source.getSourcePermanentIfItStillExists(game);
return equipment != null && creature != null && creature.addAttachment(equipment.getId(), source, game);
}
}

View file

@ -80,7 +80,7 @@ class DelneyStreetwiseLookoutEffect extends ReplacementEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanent(event.getSourceId());
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
return permanent != null
&& permanent.isCreature(game)
&& permanent.isControlledBy(source.getControllerId())

View file

@ -118,6 +118,7 @@ class DracoplasmEffect extends ReplacementEffectImpl {
}
ContinuousEffect effect = new SetBasePowerToughnessSourceEffect(power, toughness, Duration.WhileOnBattlefield);
game.addEffect(effect, source);
this.discard(); // prevent multiple replacements e.g. on blink
return false;
}
}

View file

@ -0,0 +1,151 @@
package mage.cards.e;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.Condition;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalAsThoughEffect;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.constants.*;
import mage.abilities.keyword.ExhaustAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.counters.CounterType;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import mage.watchers.Watcher;
/**
*
* @author Jmlundeen
*/
public final class ElvishRefueler extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent();
private static final Hint hint = new ConditionHint(ActivatedExhaustCondition.instance,
"You haven't activated an exhaust ability this turn",
null,
"You have activated an exhaust ability this turn",
null,
true);
static {
filter.add(TargetController.YOU.getControllerPredicate());
}
public ElvishRefueler(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
this.subtype.add(SubType.ELF);
this.subtype.add(SubType.DRUID);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// During your turn, as long as you haven't activated an exhaust ability this turn, you may activate exhaust abilities as though they haven't been activated.
this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect(
new ElvishRefuelerEffect(filter), ActivatedExhaustCondition.instance)
.setText("During your turn, as long as you haven't activated an exhaust ability this turn, " +
"you may activate exhaust abilities as though they haven't been activated")
).addHint(hint), new ElvishRefuelerWatcher());
// Exhaust -- {1}{G}: Put a +1/+1 counter on this creature.
this.addAbility(new ExhaustAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new ManaCostsImpl<>("{1}{G}")));
}
private ElvishRefueler(final ElvishRefueler card) {
super(card);
}
@Override
public ElvishRefueler copy() {
return new ElvishRefueler(this);
}
}
enum ActivatedExhaustCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
ElvishRefuelerWatcher watcher = game.getState().getWatcher(ElvishRefuelerWatcher.class);
return watcher != null && !watcher.checkActivatedExhaust(source.getControllerId());
}
}
class ElvishRefuelerWatcher extends Watcher {
private final Map<UUID, Boolean> activatedExhaustAbilities = new HashMap<>();
public ElvishRefuelerWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.ACTIVATED_ABILITY) {
return;
}
StackObject stackObject = game.getStack().getStackObject(event.getSourceId());
if (stackObject != null && stackObject.getStackAbility() instanceof ExhaustAbility) {
activatedExhaustAbilities.put(event.getPlayerId(), true);
}
}
@Override
public void reset() {
super.reset();
activatedExhaustAbilities.clear();
}
public boolean checkActivatedExhaust(UUID playerId) {
return activatedExhaustAbilities.getOrDefault(playerId, false);
}
}
class ElvishRefuelerEffect extends AsThoughEffectImpl {
private final FilterPermanent filter;
ElvishRefuelerEffect(FilterPermanent filter) {
super(AsThoughEffectType.ALLOW_EXHAUST_PER_TURN, Duration.WhileOnBattlefield, Outcome.Benefit);
this.filter = filter;
staticText = "During your turn, as long as you haven't activated an exhaust ability this turn, " +
"you may activate exhaust abilities as though they haven't been activated";
}
ElvishRefuelerEffect(final ElvishRefuelerEffect effect) {
super(effect);
this.filter = effect.filter;
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public ElvishRefuelerEffect copy() {
return new ElvishRefuelerEffect(this);
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
ElvishRefuelerWatcher watcher = game.getState().getWatcher(ElvishRefuelerWatcher.class);
if (!game.isActivePlayer(affectedControllerId) || watcher == null || watcher.checkActivatedExhaust(affectedControllerId)) {
return false;
}
Permanent permanent = game.getPermanent(objectId);
return filter.match(permanent, source.getControllerId(), source, game);
}
}

View file

@ -0,0 +1,110 @@
package mage.cards.e;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardImpl;
import mage.cards.CardsImpl;
import mage.cards.CardSetInfo;
import mage.choices.FaceVillainousChoice;
import mage.choices.VillainousChoice;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import mage.MageObject;
import mage.util.CardUtil;
/**
*
* @author padfoot
*/
public final class EnsnaredByTheMara extends CardImpl {
public EnsnaredByTheMara(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}{R}");
// Each opponent faces a villainous choice -- They exile cards from the top of their library until they exile a nonland card, then you may cast that card without paying its mana cost, or that player exiles the top four cards of their library and Ensnared by the Mara deals damage equal to the total mana value of those exiled cards to that player.
this.getSpellAbility().addEffect(new EnsnaredByTheMaraEffect());
}
private EnsnaredByTheMara(final EnsnaredByTheMara card) {
super(card);
}
@Override
public EnsnaredByTheMara copy() {
return new EnsnaredByTheMara(this);
}
}
class EnsnaredByTheMaraEffect extends OneShotEffect {
private static final FaceVillainousChoice choice = new FaceVillainousChoice(
Outcome.PlayForFree, new EnsnaredByTheMaraFirstChoice(), new EnsnaredByTheMaraSecondChoice()
);
EnsnaredByTheMaraEffect() {
super(Outcome.Benefit);
staticText = "each opponent " + choice.generateRule();
}
private EnsnaredByTheMaraEffect(final EnsnaredByTheMaraEffect effect) {
super(effect);
}
@Override
public EnsnaredByTheMaraEffect copy() {
return new EnsnaredByTheMaraEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID playerId : game.getOpponents(source.getControllerId())) {
Player player = game.getPlayer(playerId);
choice.faceChoice(player, game, source);
}
return true;
}
}
class EnsnaredByTheMaraFirstChoice extends VillainousChoice {
EnsnaredByTheMaraFirstChoice() {
super("They exile cards from the top of their library until they exile a nonland card, then you may cast that card without paying its mana cost","Exile a card for {controller} to cast for free");
}
@Override
public boolean doChoice(Player player, Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
for (Card card : player.getLibrary().getCards(game)) {
player.moveCards(card, Zone.EXILED, source, game);
if (!card.isLand(game)) {
CardUtil.castSpellWithAttributesForFree(controller, source, game, card);
break;
}
}
return true;
}
}
class EnsnaredByTheMaraSecondChoice extends VillainousChoice {
EnsnaredByTheMaraSecondChoice() {
super("that player exiles the top four cards of their library and {this} deals damage equal to the total mana value of those exiled cards to that player","exile four cards and take damage");
}
@Override
public boolean doChoice(Player player, Game game, Ability source) {
int totalManaValue = 0;
Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 4));
player.moveCards(cards, Zone.EXILED, source, game);
totalManaValue = cards
.getCards(game)
.stream()
.mapToInt(MageObject::getManaValue)
.sum();
player.damage(totalManaValue, source, game);
return true;
}
}

View file

@ -6,14 +6,15 @@ import mage.ObjectColor;
import mage.abilities.costs.AlternativeCostSourceAbility;
import mage.abilities.costs.common.ExileFromHandCost;
import mage.abilities.effects.common.AddCombatAndMainPhaseEffect;
import mage.abilities.effects.common.UntapAllThatAttackedEffect;
import mage.abilities.effects.common.UntapAllEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.filter.predicate.permanent.AttackedThisTurnPredicate;
import mage.target.common.TargetCardInHand;
import mage.watchers.common.AttackedThisTurnWatcher;
/**
*
@ -22,9 +23,11 @@ import mage.watchers.common.AttackedThisTurnWatcher;
public final class FuryOfTheHorde extends CardImpl {
private static final FilterCard filter = new FilterCard("red cards");
private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("creatures that attacked this turn");
static {
filter.add(new ColorPredicate(ObjectColor.RED));
filter2.add(AttackedThisTurnPredicate.instance);
}
public FuryOfTheHorde(UUID ownerId, CardSetInfo setInfo) {
@ -34,7 +37,7 @@ public final class FuryOfTheHorde extends CardImpl {
this.addAbility(new AlternativeCostSourceAbility(new ExileFromHandCost(new TargetCardInHand(2, filter))));
// Untap all creatures that attacked this turn. After this main phase, there is an additional combat phase followed by an additional main phase.
this.getSpellAbility().addEffect(new UntapAllThatAttackedEffect());
this.getSpellAbility().addEffect(new UntapAllEffect(filter2));
this.getSpellAbility().addEffect(new AddCombatAndMainPhaseEffect());
}

View file

@ -0,0 +1,83 @@
package mage.cards.g;
import java.util.UUID;
import mage.ConditionalMana;
import mage.MageInt;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.mana.ConditionalColoredManaAbility;
import mage.abilities.mana.builder.ConditionalManaBuilder;
import mage.abilities.mana.conditional.ManaCondition;
import mage.constants.SubType;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.game.Game;
/**
*
* @author Jmlundeen
*/
public final class GuidelightOptimizer extends CardImpl {
public GuidelightOptimizer(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}");
this.subtype.add(SubType.ROBOT);
this.power = new MageInt(2);
this.toughness = new MageInt(1);
// {T}: Add {U}. Spend this mana only to cast an artifact spell or activate an ability.
this.addAbility(new ConditionalColoredManaAbility(Mana.BlueMana(1), new ArtifactOrActivatedManaBuilder()));
}
private GuidelightOptimizer(final GuidelightOptimizer card) {
super(card);
}
@Override
public GuidelightOptimizer copy() {
return new GuidelightOptimizer(this);
}
}
class ArtifactOrActivatedManaBuilder extends ConditionalManaBuilder {
@Override
public ConditionalMana build(Object... options) {
return new ArtifactOrActivatedConditionalMana(this.mana);
}
@Override
public String getRule() {
return "Spend this mana only to cast an artifact spells or activate an ability";
}
}
class ArtifactOrActivatedConditionalMana extends ConditionalMana {
public ArtifactOrActivatedConditionalMana(Mana mana) {
super(mana);
staticText = "Spend this mana only to cast an artifact spells or activate an ability";
addCondition(new ArtifactOrActivatedManaCondition());
}
}
class ArtifactOrActivatedManaCondition extends ManaCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
MageObject sourceObject = game.getObject(source);
return source != null
&& (source.isActivatedAbility() && !source.isActivated())
|| (sourceObject != null && sourceObject.isArtifact());
}
@Override
public boolean apply(Game game, Ability source, UUID originalId, mage.abilities.costs.Cost costsToPay) {
return apply(game, source);
}
}

View file

@ -0,0 +1,112 @@
package mage.cards.k;
import mage.MageInt;
import mage.abilities.BatchTriggeredAbility;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.CardsInExileCondition;
import mage.abilities.dynamicvalue.common.CardsInExileCount;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.LoseLifeSourceControllerEffect;
import mage.abilities.effects.common.combat.CantAttackBlockUnlessConditionSourceEffect;
import mage.abilities.keyword.IndestructibleAbility;
import mage.abilities.keyword.LifelinkAbility;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeBatchEvent;
import mage.game.events.ZoneChangeEvent;
import java.util.UUID;
/**
* @author Jmlundeen
*/
public final class KetramoseTheNewDawn extends CardImpl {
public KetramoseTheNewDawn(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{B}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.GOD);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Menace
this.addAbility(new MenaceAbility());
// Lifelink
this.addAbility(LifelinkAbility.getInstance());
// Indestructible
this.addAbility(IndestructibleAbility.getInstance());
// Ketramose can't attack or block unless there are seven or more cards in exile.
this.addAbility(new SimpleStaticAbility(
new CantAttackBlockUnlessConditionSourceEffect(new CardsInExileCondition(ComparisonType.OR_GREATER, 7))
).addHint(CardsInExileCount.ALL.getHint()));
// Whenever one or more cards are put into exile from graveyards and/or the battlefield during your turn, you draw a card and lose 1 life.
this.addAbility(new KetramoseTriggeredAbility());
}
private KetramoseTheNewDawn(final KetramoseTheNewDawn card) {
super(card);
}
@Override
public KetramoseTheNewDawn copy() {
return new KetramoseTheNewDawn(this);
}
}
class KetramoseTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility<ZoneChangeEvent> {
KetramoseTriggeredAbility() {
super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), false);
this.addEffect(new LoseLifeSourceControllerEffect(1));
}
private KetramoseTriggeredAbility(final KetramoseTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH;
}
@Override
public boolean checkEvent(ZoneChangeEvent event, Game game) {
if (event.getToZone() != Zone.EXILED) {
return false;
}
return event.getFromZone() == Zone.GRAVEYARD || event.getFromZone() == Zone.BATTLEFIELD;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return game.getActivePlayerId().equals(getControllerId())
&& !getFilteredEvents((ZoneChangeBatchEvent) event, game).isEmpty();
}
@Override
public TriggeredAbility copy()
{
return new KetramoseTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever one or more cards are put into exile from graveyards"
+ " and/or the battlefield during your turn, you draw a card and lose 1 life.";
}
}

View file

@ -0,0 +1,72 @@
package mage.cards.l;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.costs.CompositeCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.mana.AddManaOfAnyColorEffect;
import mage.abilities.keyword.ExhaustAbility;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.abilities.keyword.DoubleStrikeAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.target.common.TargetAnyTarget;
/**
*
* @author Jmlundeen
*/
public final class LootThePathfinder extends CardImpl {
public LootThePathfinder(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{U}{R}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.BEAST);
this.subtype.add(SubType.NOBLE);
this.power = new MageInt(2);
this.toughness = new MageInt(4);
// Double strike
this.addAbility(DoubleStrikeAbility.getInstance());
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// Haste
this.addAbility(HasteAbility.getInstance());
// Exhaust -- {G}, {T}: Add three mana of any one color.
Ability abilityOne = new ExhaustAbility(new AddManaOfAnyColorEffect(3), new ManaCostsImpl<>("{G}"));
abilityOne.addCost(new TapSourceCost());
this.addAbility(abilityOne);
// Exhaust -- {U}, {T}: Draw three cards.
Ability abilityTwo = new ExhaustAbility(new DrawCardSourceControllerEffect(3), new ManaCostsImpl<>("{U}"), false);
abilityTwo.addCost(new TapSourceCost());
this.addAbility(abilityTwo);
// Exhaust -- {R}, {T}: Loot deals 3 damage to any target.
Ability abilityThree = new ExhaustAbility(new DamageTargetEffect(3), new ManaCostsImpl<>("{R}"), false);
abilityThree.addCost(new TapSourceCost());
abilityThree.addTarget(new TargetAnyTarget());
this.addAbility(abilityThree);
}
private LootThePathfinder(final LootThePathfinder card) {
super(card);
}
@Override
public LootThePathfinder copy() {
return new LootThePathfinder(this);
}
}

View file

@ -1,26 +1,17 @@
package mage.cards.m;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.CreaturePutInYourGraveyardCondition;
import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect;
import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.target.common.TargetCardInYourGraveyard;
import mage.watchers.Watcher;
import mage.watchers.common.CreaturePutIntoGraveyardWatcher;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
@ -33,8 +24,8 @@ public final class MacabreReconstruction extends CardImpl {
// This spell costs {2} less to cast if a creature card was put into your graveyard from anywhere this turn.
this.addAbility(new SimpleStaticAbility(
Zone.ALL, new SpellCostReductionSourceEffect(2, MacabreReconstructionCondition.instance)
).setRuleAtTheTop(true).addHint(MacabreReconstructionCondition.getHint()), new MacabreReconstructionWatcher());
Zone.ALL, new SpellCostReductionSourceEffect(2, CreaturePutInYourGraveyardCondition.instance)
).setRuleAtTheTop(true).addHint(CreaturePutInYourGraveyardCondition.getHint()), new CreaturePutIntoGraveyardWatcher());
// Return up to two target creature cards from your graveyard to your hand.
this.getSpellAbility().addEffect(new ReturnFromGraveyardToHandTargetEffect());
@ -52,58 +43,3 @@ public final class MacabreReconstruction extends CardImpl {
return new MacabreReconstruction(this);
}
}
enum MacabreReconstructionCondition implements Condition {
instance;
private static final Hint hint = new ConditionHint(
instance, "A card was put into your graveyard this turn"
);
public static Hint getHint() {
return hint;
}
@Override
public boolean apply(Game game, Ability source) {
return MacabreReconstructionWatcher.checkPlayer(source.getControllerId(), game);
}
@Override
public String toString() {
return "a creature card was put into your graveyard from anywhere this turn";
}
}
class MacabreReconstructionWatcher extends Watcher {
private final Set<UUID> set = new HashSet<>();
MacabreReconstructionWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.ZONE_CHANGE) {
return;
}
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (Zone.GRAVEYARD.match(zEvent.getToZone())) {
set.add(game.getOwnerId(zEvent.getTargetId()));
}
}
@Override
public void reset() {
super.reset();
set.clear();
}
static boolean checkPlayer(UUID playerId, Game game) {
return game
.getState()
.getWatcher(MacabreReconstructionWatcher.class)
.set
.contains(playerId);
}
}

View file

@ -6,7 +6,7 @@ import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect;
import mage.abilities.keyword.TrampleAbility;
@ -15,6 +15,8 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil;
@ -52,10 +54,10 @@ public final class MinionOfTheWastes extends CardImpl {
}
}
class MinionOfTheWastesEffect extends OneShotEffect {
class MinionOfTheWastesEffect extends ReplacementEffectImpl {
MinionOfTheWastesEffect() {
super(Outcome.LoseLife);
super(Duration.EndOfGame, Outcome.LoseLife);
staticText = "pay any amount of life";
}
@ -69,10 +71,20 @@ class MinionOfTheWastesEffect extends OneShotEffect {
}
@Override
public boolean apply(Game game, Ability source) {
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return event.getTargetId().equals(source.getSourceId());
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget();
Player controller = game.getPlayer(source.getControllerId());
Permanent permanent = game.getPermanentEntering(source.getSourceId());
if (controller == null || permanent == null) {
if (creature == null || controller == null) {
return false;
}
int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", game);
@ -84,9 +96,10 @@ class MinionOfTheWastesEffect extends OneShotEffect {
game.informPlayers((sourceCard != null ? sourceCard.getLogName() : "") + ": " + controller.getLogName() +
" pays " + payAmount + " life");
game.addEffect(new SetBasePowerToughnessSourceEffect(
payAmount, payAmount, Duration.Custom
payAmount, payAmount, Duration.WhileOnBattlefield
), source);
permanent.addInfo("life paid", CardUtil.addToolTipMarkTags("Life paid: " + payAmount), game);
return true;
creature.addInfo("life paid", CardUtil.addToolTipMarkTags("Life paid: " + payAmount), game);
this.discard(); // prevent multiple replacements e.g. on blink
return false;
}
}

View file

@ -0,0 +1,100 @@
package mage.cards.m;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SacrificeSourceTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.CompositeCost;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.ControllerSpeedCount;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.SacrificeEffect;
import mage.abilities.keyword.StartYourEnginesAbility;
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.predicate.Predicates;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
/**
*
* @author Jmlundeen
*/
public final class MomentumBreaker extends CardImpl {
public MomentumBreaker(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}");
// Start your engines!
this.addAbility(new StartYourEnginesAbility());
// When this enchantment enters, each opponent sacrifices a creature or Vehicle of their choice. Each opponent who can't discards a card.
this.addAbility(new EntersBattlefieldTriggeredAbility(new MomentumBreakerEffect()));
// {2}, Sacrifice this enchantment: You gain life equal to your speed.
Effect gainLifeEffect = new GainLifeEffect(ControllerSpeedCount.instance).setText("You gain life equal to your speed");
Ability ability = new SimpleActivatedAbility(gainLifeEffect,new ManaCostsImpl<>("{2}"));
ability.addCost(new SacrificeSourceCost());
this.addAbility(ability);
}
private MomentumBreaker(final MomentumBreaker card) {
super(card);
}
@Override
public MomentumBreaker copy() {
return new MomentumBreaker(this);
}
}
class MomentumBreakerEffect extends OneShotEffect {
MomentumBreakerEffect() {
super(Outcome.Benefit);
this.staticText = "each opponent sacrifices a creature or Vehicle of their choice. " +
"Each opponent who can't discards a card.";
}
private MomentumBreakerEffect(final MomentumBreakerEffect effect) {
super(effect);
}
@Override
public MomentumBreakerEffect copy() {
return new MomentumBreakerEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
game.getOpponents(source.getControllerId()).forEach(opponent -> {
Player player = game.getPlayer(opponent);
if (player != null) {
FilterPermanent filter = new FilterPermanent("creature or Vehicle");
filter.add(Predicates.or(
CardType.CREATURE.getPredicate(),
SubType.VEHICLE.getPredicate()
));
filter.add(new ControllerIdPredicate(opponent));
Effect effect = new SacrificeEffect(filter, 1, null);
effect.setTargetPointer(new FixedTarget(player.getId(), game));
if (!effect.apply(game, source)) {
player.discard(1, false, false, source, game);
}
}
});
return true;
}
}

View file

@ -1,14 +1,13 @@
package mage.cards.m;
import mage.MageInt;
import mage.abilities.common.ZoneChangeTriggeredAbility;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.TurnPhase;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.GreenDogToken;
@ -41,10 +40,11 @@ public final class MongrelPack extends CardImpl {
}
}
class MongrelPackAbility extends ZoneChangeTriggeredAbility {
class MongrelPackAbility extends DiesSourceTriggeredAbility {
MongrelPackAbility() {
super(Zone.BATTLEFIELD, Zone.GRAVEYARD, new CreateTokenEffect(new GreenDogToken(), 4), "When {this} dies during combat, ", false);
super(new CreateTokenEffect(new GreenDogToken(), 4));
setTriggerPhrase("When {this} dies during combat, ");
}
private MongrelPackAbility(MongrelPackAbility ability) {
@ -58,11 +58,6 @@ class MongrelPackAbility extends ZoneChangeTriggeredAbility {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (super.checkTrigger(event, game)) {
if (game.getTurnPhaseType() == TurnPhase.COMBAT) {
return true;
}
}
return false;
return game.getTurnPhaseType() == TurnPhase.COMBAT && super.checkTrigger(event, game);
}
}

View file

@ -9,7 +9,7 @@ import mage.abilities.costs.Cost;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.dynamicvalue.common.CardsInAllGraveyardsCount;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect;
import mage.abilities.keyword.TrampleAbility;
@ -22,6 +22,8 @@ import mage.filter.FilterPermanent;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.filter.predicate.permanent.TokenPredicate;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil;
@ -58,7 +60,7 @@ public final class NamelessRace extends CardImpl {
}
}
class NamelessRaceEffect extends OneShotEffect {
class NamelessRaceEffect extends ReplacementEffectImpl {
private static final FilterPermanent filter
= new FilterPermanent("white nontoken permanents your opponents control");
@ -74,7 +76,7 @@ class NamelessRaceEffect extends OneShotEffect {
}
NamelessRaceEffect() {
super(Outcome.LoseLife);
super(Duration.EndOfGame, Outcome.LoseLife);
staticText = "pay any amount of life. The amount you pay can't be more than " +
"the total number of white nontoken permanents your opponents control " +
"plus the total number of white cards in their graveyards";
@ -90,10 +92,20 @@ class NamelessRaceEffect extends OneShotEffect {
}
@Override
public boolean apply(Game game, Ability source) {
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return event.getTargetId().equals(source.getSourceId());
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget();
Player controller = game.getPlayer(source.getControllerId());
Permanent permanent = game.getPermanentEntering(source.getSourceId());
if (controller == null || permanent == null) {
if (creature == null || controller == null) {
return false;
}
int permanentsInPlay = new PermanentsOnBattlefieldCount(filter).calculate(game, source, null);
@ -108,9 +120,10 @@ class NamelessRaceEffect extends OneShotEffect {
game.informPlayers((sourceCard != null ? sourceCard.getLogName() : "") + ": " + controller.getLogName() +
" pays " + payAmount + " life");
game.addEffect(new SetBasePowerToughnessSourceEffect(
payAmount, payAmount, Duration.Custom
payAmount, payAmount, Duration.WhileOnBattlefield
), source);
permanent.addInfo("life paid", CardUtil.addToolTipMarkTags("Life paid: " + payAmount), game);
return true;
creature.addInfo("life paid", CardUtil.addToolTipMarkTags("Life paid: " + payAmount), game);
this.discard(); // prevent multiple replacements e.g. on blink
return false;
}
}

View file

@ -1,11 +1,8 @@
package mage.cards.n;
import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.mana.ColoredManaCost;
import mage.abilities.common.DiesCreatureTriggeredAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
import mage.abilities.keyword.HasteAbility;
@ -14,11 +11,12 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.ColoredManaSymbol;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import java.util.UUID;
/**
*
@ -26,6 +24,12 @@ import mage.game.events.ZoneChangeEvent;
*/
public final class NetherTraitor extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent();
static {
filter.add(AnotherPredicate.instance);
filter.add(TargetController.YOU.getOwnerPredicate());
}
public NetherTraitor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{B}{B}");
this.subtype.add(SubType.SPIRIT);
@ -39,7 +43,11 @@ public final class NetherTraitor extends CardImpl {
this.addAbility(ShadowAbility.getInstance());
// Whenever another creature is put into your graveyard from the battlefield, you may pay {B}. If you do, return Nether Traitor from your graveyard to the battlefield.
this.addAbility(new NetherTraitorTriggeredAbility());
this.addAbility(new DiesCreatureTriggeredAbility(Zone.GRAVEYARD, new DoIfCostPaid(
new ReturnSourceFromGraveyardToBattlefieldEffect(),
new ManaCostsImpl<>("{B}")
), false, filter, false
).setTriggerPhrase("Whenever another creature is put into your graveyard from the battlefield, "));
}
private NetherTraitor(final NetherTraitor card) {
@ -52,53 +60,3 @@ public final class NetherTraitor extends CardImpl {
}
}
class NetherTraitorTriggeredAbility extends TriggeredAbilityImpl {
NetherTraitorTriggeredAbility(){
super(Zone.GRAVEYARD, new DoIfCostPaid(new ReturnSourceFromGraveyardToBattlefieldEffect(), new ColoredManaCost(ColoredManaSymbol.B)));
setLeavesTheBattlefieldTrigger(true);
}
private NetherTraitorTriggeredAbility(final NetherTraitorTriggeredAbility ability) {
super(ability);
}
@Override
public NetherTraitorTriggeredAbility copy(){
return new NetherTraitorTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
for (Zone z : Zone.values()) {
if (game.checkShortLivingLKI(sourceId, z) && z != Zone.GRAVEYARD) {
return false;
}
}
if (zEvent.isDiesEvent()) {
if (zEvent.getTarget() != null &&
zEvent.getTarget().isOwnedBy(this.getControllerId()) &&
zEvent.getTarget().isCreature(game)&&
!zEvent.getTarget().getId().equals(this.getSourceId())) {
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever another creature is put into your graveyard from the battlefield, you may pay {B}. If you do, return {this} from your graveyard to the battlefield.";
}
@Override
public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game);
}
}

View file

@ -0,0 +1,97 @@
package mage.cards.o;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawDiscardTargetEffect;
import mage.abilities.effects.common.LookAtTargetPlayerHandEffect;
import mage.abilities.effects.common.discard.DiscardAndDrawThatManyEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.abilities.keyword.LifelinkAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.keyword.WardAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.TargetPlayer;
import mage.target.common.TargetCardInHand;
/**
*
* @author Jmlundeen
*/
public final class OildeepGearhulk extends CardImpl {
public OildeepGearhulk(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{U}{U}{B}{B}");
this.subtype.add(SubType.CONSTRUCT);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Lifelink
this.addAbility(LifelinkAbility.getInstance());
// Ward {1}
this.addAbility(new WardAbility(new ManaCostsImpl<>("{1}")));
// When this creature enters, look at target player's hand. You may choose a card from it. If you do, that player discards that card, then draws a card.
Ability ability = new EntersBattlefieldTriggeredAbility(new OildeepGearhulkEffect());
ability.addTarget(new TargetPlayer());
this.addAbility(ability);
}
private OildeepGearhulk(final OildeepGearhulk card) {
super(card);
}
@Override
public OildeepGearhulk copy() {
return new OildeepGearhulk(this);
}
}
class OildeepGearhulkEffect extends OneShotEffect {
public OildeepGearhulkEffect() {
super(Outcome.Benefit);
staticText = "look at target player's hand. You may choose a card from it. If you do, that player discards that card, then draws a card";
}
public OildeepGearhulkEffect(final OildeepGearhulkEffect effect) {
super(effect);
}
@Override
public OildeepGearhulkEffect copy() {
return new OildeepGearhulkEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Player targetPlayer = game.getPlayer(source.getFirstTarget());
if (controller == null || targetPlayer == null) {
return false;
}
controller.lookAtCards(targetPlayer.getName() + " Hand", targetPlayer.getHand(), game);
TargetCard chosenCard = new TargetCardInHand(0, 1, new FilterCard("card to discard"));
if (controller.choose(Outcome.Discard, targetPlayer.getHand(), chosenCard, source, game)) {
Card card = game.getCard(chosenCard.getFirstTarget());
targetPlayer.discard(card, false, source, game);
targetPlayer.drawCards(1, source, game);
return true;
}
return false;
}
}

View file

@ -0,0 +1,48 @@
package mage.cards.o;
import java.util.UUID;
import mage.abilities.condition.common.MyTurnCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.common.AddCombatAndMainPhaseEffect;
import mage.abilities.effects.common.UntapAllControllerEffect;
import mage.abilities.keyword.FreerunningAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.permanent.AttackedThisTurnPredicate;
/**
*
* @author balazskristof
*/
public final class OverpoweringAttack extends CardImpl {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent();
static {
filter.add(AttackedThisTurnPredicate.instance);
}
public OverpoweringAttack(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{R}");
// Freerunning {2}{R}
this.addAbility(new FreerunningAbility("{2}{R}"));
// Untap all creatures you control that attacked this turn. If it's your main phase, there is an additional combat phase after this phase, followed by an additional main phase.
this.getSpellAbility().addEffect(new UntapAllControllerEffect(filter, "untap all creatures you control that attacked this turn"));
this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new AddCombatAndMainPhaseEffect(), MyTurnCondition.instance, "If it's your main phase, there is an additional combat phase after this phase, followed by an additional main phase."));
}
private OverpoweringAttack(final OverpoweringAttack card) {
super(card);
}
@Override
public OverpoweringAttack copy() {
return new OverpoweringAttack(this);
}
}

View file

@ -0,0 +1,45 @@
package mage.cards.p;
import mage.MageInt;
import mage.abilities.dynamicvalue.common.CreaturesYouControlDiedCount;
import mage.abilities.effects.common.counter.AddCountersAllEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import java.util.UUID;
public class PriestOfTheCrossing extends CardImpl {
public PriestOfTheCrossing(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
this.addSubType(SubType.ZOMBIE);
this.addSubType(SubType.BIRD);
this.addSubType(SubType.CLERIC);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Flying
this.addAbility(FlyingAbility.getInstance());
// At the beginning of each end step, put X +1/+1 counters on each creature you control, where X is the number of creatures that died under your control this turn.
this.addAbility(new BeginningOfEndStepTriggeredAbility(
new AddCountersAllEffect(CounterType.P1P1.createInstance(), CreaturesYouControlDiedCount.instance, StaticFilters.FILTER_CONTROLLED_CREATURE)
.setText("put X +1/+1 counters on each creature you control, where X is the number of creatures that died under your control this turn")));
}
public PriestOfTheCrossing(PriestOfTheCrossing card) {
super(card);
}
@Override
public PriestOfTheCrossing copy() {
return new PriestOfTheCrossing(this);
}
}

View file

@ -0,0 +1,108 @@
package mage.cards.p;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.*;
import mage.abilities.effects.common.continuous.BecomesSubtypeAllEffect;
import mage.abilities.effects.common.continuous.CreaturesBecomeOtherTypeEffect;
import mage.abilities.effects.common.continuous.GainAbilityAllEffect;
import mage.abilities.effects.common.continuous.VehiclesBecomeArtifactCreatureEffect;
import mage.abilities.keyword.HasteAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.MageObjectReferencePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTargets;
/**
*
* @author Jmlundeen
*/
public final class PushTheLimit extends CardImpl {
private static final FilterPermanent filterCreatures = new FilterControlledPermanent("Creatures you control");
public PushTheLimit(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{R}{R}");
// Return all Mount and Vehicle cards from your graveyard to the battlefield. Sacrifice them at the beginning of the next end step.
this.getSpellAbility().addEffect(new PushTheLimitEffect());
// Vehicles you control become artifact creatures until end of turn. Creatures you control gain haste until end of turn.
this.getSpellAbility().addEffect(new VehiclesBecomeArtifactCreatureEffect(Duration.EndOfTurn)
.concatBy("<br>"));
this.getSpellAbility().addEffect(new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, filterCreatures));
}
private PushTheLimit(final PushTheLimit card) {
super(card);
}
@Override
public PushTheLimit copy() {
return new PushTheLimit(this);
}
}
class PushTheLimitEffect extends OneShotEffect {
private static final FilterCard filter = new FilterCard("Mount and Vehicle cards");
static {
filter.add(Predicates.or(
SubType.MOUNT.getPredicate(),
SubType.VEHICLE.getPredicate()
));
}
public PushTheLimitEffect() {
super(Outcome.PutCreatureInPlay);
staticText = "return all " + filter.getMessage() + " from your graveyard to the battlefield. " +
"Sacrifice them at the beginning of the next end step.";
}
public PushTheLimitEffect(final PushTheLimitEffect effect) {
super(effect);
}
@Override
public PushTheLimitEffect copy() {
return new PushTheLimitEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Set<Card> cards = controller.getGraveyard().getCards(filter, source.getControllerId(), source, game);
boolean result = controller.moveCards(cards, Zone.BATTLEFIELD, source, game,
false, false, false, null);
if (result) {
List<Permanent> permanentsToSac = cards.stream()
.map(card -> game.getPermanent(card.getId()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
Effect sacrificeEffect = new SacrificeTargetEffect("sacrifice them", source.getControllerId());
sacrificeEffect.setTargetPointer(new FixedTargets(permanentsToSac, game));
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(sacrificeEffect), source);
}
return result;
}
}

View file

@ -0,0 +1,88 @@
package mage.cards.r;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.costs.common.SacrificeXTargetCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.ChoiceColor;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetPlayer;
/**
*
* @author Jmlundeen
*/
public final class RadiantLotus extends CardImpl {
private static final FilterPermanent filter = new FilterControlledArtifactPermanent("artifacts");
public RadiantLotus(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}");
// {T}, Sacrifice one or more artifacts: Choose a color. Target player adds three mana of the chosen color for each artifact sacrificed this way.
Ability ability = new SimpleActivatedAbility(new RadiantLotusEffect(), new TapSourceCost());
ability.addCost(new SacrificeXTargetCost(filter, true, 1).setText("Sacrifice one or more artifacts"));
ability.addTarget(new TargetPlayer());
this.addAbility(ability);
}
private RadiantLotus(final RadiantLotus card) {
super(card);
}
@Override
public RadiantLotus copy() {
return new RadiantLotus(this);
}
}
class RadiantLotusEffect extends OneShotEffect {
public RadiantLotusEffect() {
super(Outcome.AIDontUseIt);
staticText = "Choose a color. Target player adds three mana of the chosen color for each artifact sacrificed this way";
}
public RadiantLotusEffect(final RadiantLotusEffect effect) {
super(effect);
}
@Override
public RadiantLotusEffect copy() {
return new RadiantLotusEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
int manaCount = source.getCosts().stream()
.filter(SacrificeTargetCost.class::isInstance)
.mapToInt(cost -> ((SacrificeTargetCost) cost).getPermanents().size() * 3)
.sum();
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
String mes = String.format("Select a color of mana to add %d of it", manaCount);
ChoiceColor choice = new ChoiceColor(true, mes, game.getObject(source));
if (controller.choose(outcome, choice, game)) {
Player player = game.getPlayer(source.getFirstTarget());
if (choice.getColor() != null && player != null) {
player.getManaPool().addMana(choice.getMana(manaCount), game, source);
return true;
}
}
}
return false;
}
}

View file

@ -0,0 +1,103 @@
package mage.cards.r;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.Condition;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.PowerPredicate;
import mage.game.Game;
import mage.game.permanent.token.BirdToken;
import mage.game.permanent.token.MonasteryMentorToken;
import mage.game.stack.Spell;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.watchers.common.SpellsCastWatcher;
/**
* @author balazskristof
*/
public final class RallyTheMonastery extends CardImpl {
private static final Hint hint = new ConditionHint(
RallyTheMonasteryCondition.instance, "You've cast a spell this turn"
);
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power 4 or greater");
static {
filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3));
}
public RallyTheMonastery(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{W}");
// This spell costs {2} less to cast if you've cast another spell this turn.
this.addAbility(new SimpleStaticAbility(
Zone.ALL,
new SpellCostReductionSourceEffect(2, RallyTheMonasteryCondition.instance).setCanWorksOnStackOnly(true)
).setRuleAtTheTop(true).addHint(hint));
// Choose one
this.getSpellAbility().getModes().setMinModes(1);
this.getSpellAbility().getModes().setMaxModes(1);
// Create two 1/1 white Monk creature tokens with prowess.
this.getSpellAbility().addEffect(new CreateTokenEffect(new MonasteryMentorToken(), 2));
// Up to two target creatures you control each get +2/+2 until end of turn.
Mode mode = new Mode(new BoostTargetEffect(2, 2));
mode.addTarget(new TargetControlledCreaturePermanent(0, 2, StaticFilters.FILTER_CONTROLLED_CREATURES, false));
this.getSpellAbility().getModes().addMode(mode);
// Destroy target creature with power 4 or greater.
Mode mode2 = new Mode(new DestroyTargetEffect());
mode2.addTarget(new TargetPermanent(filter));
this.getSpellAbility().getModes().addMode(mode2);
}
private RallyTheMonastery(final RallyTheMonastery card) {
super(card);
}
@Override
public RallyTheMonastery copy() {
return new RallyTheMonastery(this);
}
}
enum RallyTheMonasteryCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class);
if (watcher == null) {
return false;
}
List<Spell> spells = watcher.getSpellsCastThisTurn(source.getControllerId());
return spells != null && spells
.stream()
.filter(Objects::nonNull)
.anyMatch(spell -> !spell.getSourceId().equals(source.getSourceId()));
}
@Override
public String toString() {
return "you've cast another spell this turn";
}
}

View file

@ -3,11 +3,12 @@ package mage.cards.r;
import java.util.UUID;
import mage.abilities.effects.common.AddCombatAndMainPhaseEffect;
import mage.abilities.effects.common.UntapAllThatAttackedEffect;
import mage.abilities.effects.common.UntapAllEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.watchers.common.AttackedThisTurnWatcher;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.AttackedThisTurnPredicate;
/**
*
@ -15,11 +16,17 @@ import mage.watchers.common.AttackedThisTurnWatcher;
*/
public final class RelentlessAssault extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures that attacked this turn");
static {
filter.add(AttackedThisTurnPredicate.instance);
}
public RelentlessAssault(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{R}{R}");
// Untap all creatures that attacked this turn. After this main phase, there is an additional combat phase followed by an additional main phase.
this.getSpellAbility().addEffect(new UntapAllThatAttackedEffect());
this.getSpellAbility().addEffect(new UntapAllEffect(filter));
this.getSpellAbility().addEffect(new AddCombatAndMainPhaseEffect());
}

View file

@ -1,10 +1,9 @@
package mage.cards.s;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount;
import mage.abilities.effects.Effect;
import mage.abilities.dynamicvalue.common.CreaturesYouControlDiedCount;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.LoseLifeOpponentsEffect;
import mage.abilities.effects.common.SacrificeAllEffect;
@ -13,8 +12,6 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.watchers.common.CreaturesDiedWatcher;
import java.util.UUID;
@ -37,7 +34,7 @@ public final class SeasonOfLoss extends CardImpl {
this.getSpellAbility().getModes().getMode().withPawPrintValue(1);
// {P}{P} -- Draw a card for each creature you controlled that died this turn.
Mode mode2 = new Mode(new DrawCardSourceControllerEffect(SeasonOfLossValue.instance));
Mode mode2 = new Mode(new DrawCardSourceControllerEffect(CreaturesYouControlDiedCount.instance));
this.getSpellAbility().addMode(mode2.withPawPrintValue(2));
// {P}{P}{P} -- Each opponent loses X life, where X is the number of creature cards in your graveyard.
@ -56,31 +53,4 @@ public final class SeasonOfLoss extends CardImpl {
public SeasonOfLoss copy() {
return new SeasonOfLoss(this);
}
}
// Based on CallousSellSwordValue
enum SeasonOfLossValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game.getState()
.getWatcher(CreaturesDiedWatcher.class)
.getAmountOfCreaturesDiedThisTurnByController(sourceAbility.getControllerId());
}
@Override
public SeasonOfLossValue copy() {
return this;
}
@Override
public String getMessage() {
return "creature that died under your control this turn";
}
@Override
public String toString() {
return "1";
}
}
}

View file

@ -0,0 +1,87 @@
package mage.cards.s;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.SetBasePowerToughnessAllEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.ExhaustAbility;
import mage.constants.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author Jmlundeen
*/
public final class SitaVarmaMaskedRacer extends CardImpl {
public SitaVarmaMaskedRacer(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.ROGUE);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// Exhaust -- {X}{G}{G}{U}: Put X +1/+1 counters on Sita Varma. Then you may have the base power and toughness of each other creature you control become equal to Sita Varma's power until end of turn.
Cost cost = new ManaCostsImpl<>("{X}{G}{G}{U}");
Effect effect = new AddCountersSourceEffect(CounterType.P1P1.createInstance(), GetXValue.instance);
Ability ability = new ExhaustAbility(effect, cost);
ability.addEffect(new SitaVarmaMaskedRacerMayEffect());
this.addAbility(ability);
}
private SitaVarmaMaskedRacer(final SitaVarmaMaskedRacer card) {
super(card);
}
@Override
public SitaVarmaMaskedRacer copy() {
return new SitaVarmaMaskedRacer(this);
}
}
class SitaVarmaMaskedRacerMayEffect extends OneShotEffect {
private static final String choiceText = "Have the base power and toughness of each other creature you control" +
" become equal to Sita Varma's power until end of turn?";
public SitaVarmaMaskedRacerMayEffect() {
super(Outcome.Benefit);
this.staticText = "Then you may have the base power and toughness of each other creature you control become equal to Sita Varma's power until end of turn.";
}
public SitaVarmaMaskedRacerMayEffect(final SitaVarmaMaskedRacerMayEffect effect) {
super(effect);
}
@Override
public SitaVarmaMaskedRacerMayEffect copy() {
return new SitaVarmaMaskedRacerMayEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null || !controller.chooseUse(outcome, choiceText, source, game)) {
return false;
}
ContinuousEffect setBasePowerToughnessEffect = new SetBasePowerToughnessAllEffect(SourcePermanentPowerValue.ALLOW_NEGATIVE,
SourcePermanentPowerValue.ALLOW_NEGATIVE, Duration.EndOfTurn,
StaticFilters.FILTER_OTHER_CONTROLLED_CREATURES);
game.addEffect(setBasePowerToughnessEffect, source);
return true;
}
}

View file

@ -0,0 +1,90 @@
package mage.cards.s;
import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.ChooseACardNameEffect;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.abilities.effects.common.cost.SpellsCostIncreasingAllEffect;
import mage.constants.*;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.CrewAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.game.Game;
import mage.util.CardUtil;
/**
*
* @author Jmlundeen
*/
public final class SkyseersChariot extends CardImpl {
public SkyseersChariot(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}");
this.subtype.add(SubType.VEHICLE);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Flying
this.addAbility(FlyingAbility.getInstance());
// As this Vehicle enters, choose a nonland card name.
this.addAbility(new AsEntersBattlefieldAbility(new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.NON_LAND_NAME)));
// Activated abilities of sources with the chosen name cost {2} more to activate.
this.addAbility(new SimpleStaticAbility(new SkyseersChariotEffect()));
// Crew 2
this.addAbility(new CrewAbility(2));
}
private SkyseersChariot(final SkyseersChariot card) {
super(card);
}
@Override
public SkyseersChariot copy() {
return new SkyseersChariot(this);
}
}
class SkyseersChariotEffect extends CostModificationEffectImpl {
SkyseersChariotEffect() {
super(Duration.WhileOnBattlefield, Outcome.Detriment, CostModificationType.INCREASE_COST);
staticText = "Activated abilities of sources with the chosen name cost {2} more to activate";
}
private SkyseersChariotEffect(final SkyseersChariotEffect effect) {
super(effect);
}
@Override
public SkyseersChariotEffect copy() {
return new SkyseersChariotEffect(this);
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
CardUtil.increaseCost(abilityToModify, 2);
return true;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
MageObject activatedSource = game.getObject(abilityToModify.getSourceId());
if (activatedSource == null) {
return false;
}
String chosenName = (String) game.getState().getValue(
source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY
);
return CardUtil.haveSameNames(activatedSource, chosenName, game)
&& abilityToModify.isActivatedAbility();
}
}

View file

@ -0,0 +1,95 @@
package mage.cards.s;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.RevealCardsFromLibraryUntilEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.ExhaustAbility;
import mage.cards.*;
import mage.constants.*;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.DeathtouchAbility;
import mage.counters.CounterType;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author Jmlundeen
*/
public final class SkyserpentSeeker extends CardImpl {
public SkyserpentSeeker(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}");
this.subtype.add(SubType.SNAKE);
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Deathtouch
this.addAbility(DeathtouchAbility.getInstance());
// Exhaust -- {4}: Reveal cards from the top of your library until you reveal two land cards. Put those land cards onto the battlefield tapped and the rest on the bottom of your library in a random order. Put a +1/+1 counter on this creature.
Ability ability = (new ExhaustAbility(new SkyserpentSeekerEffect(), new ManaCostsImpl<>("{4}")));
ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(), StaticValue.get(1)));
this.addAbility(ability);
}
private SkyserpentSeeker(final SkyserpentSeeker card) {
super(card);
}
@Override
public SkyserpentSeeker copy() {
return new SkyserpentSeeker(this);
}
}
class SkyserpentSeekerEffect extends OneShotEffect {
SkyserpentSeekerEffect() {
super(Outcome.PutLandInPlay);
this.staticText = "Reveal cards from the top of your library until you reveal two land cards. " +
"Put those land cards onto the battlefield tapped and the rest on the bottom of your library in a random order";
}
private SkyserpentSeekerEffect(final SkyserpentSeekerEffect effect) {
super(effect);
}
@Override
public SkyserpentSeekerEffect copy() {
return new SkyserpentSeekerEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Cards revealedCards = new CardsImpl();
Cards lands = new CardsImpl();
for (Card card : controller.getLibrary().getCards(game)) {
if (card.isLand()) {
lands.add(card);
if (lands.size() == 2) {
break;
}
}
revealedCards.add(card);
}
controller.revealCards(source, revealedCards, game);
PutCards.BATTLEFIELD_TAPPED.moveCards(controller, lands, source, game);
PutCards.BOTTOM_RANDOM.moveCards(controller, revealedCards, source, game);
return true;
}
}

View file

@ -6,6 +6,7 @@ import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.abilities.effects.common.continuous.VehiclesBecomeArtifactCreatureEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
@ -22,7 +23,7 @@ public final class StartYourEngines extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}");
// Vehicles you control becomes artifact creatures until end of turn.
Effect effect = new StartYourEnginesEffect();
Effect effect = new VehiclesBecomeArtifactCreatureEffect(Duration.EndOfTurn);
this.getSpellAbility().addEffect(effect);
// Creatures you control get +2/+0 until end of turn.
@ -38,39 +39,3 @@ public final class StartYourEngines extends CardImpl {
return new StartYourEngines(this);
}
}
class StartYourEnginesEffect extends ContinuousEffectImpl {
StartYourEnginesEffect() {
super(Duration.EndOfTurn, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.BecomeCreature);
staticText = "Vehicles you control become artifact creatures until end of turn";
}
private StartYourEnginesEffect(final StartYourEnginesEffect effect) {
super(effect);
}
@Override
public StartYourEnginesEffect copy() {
return new StartYourEnginesEffect(this);
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) {
if (permanent != null && permanent.hasSubtype(SubType.VEHICLE, game)) {
if (sublayer == SubLayer.NA) {
permanent.addCardType(game, CardType.ARTIFACT);
permanent.addCardType(game, CardType.CREATURE);// TODO: Check if giving CREATURE Type is correct
}
}
}
return true;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
}

View file

@ -0,0 +1,104 @@
package mage.cards.s;
import java.util.Map;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.constants.*;
import mage.filter.common.FilterCreaturePermanent;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.game.Game;
import mage.game.permanent.token.Token;
import mage.game.events.CreateTokenEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.token.ThopterColorlessToken;
/**
* @author grimreap124
*/
public final class StridehangarAutomaton extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.THOPTER, "Thopters");
public StridehangarAutomaton(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}");
this.subtype.add(SubType.CONSTRUCT);
this.power = new MageInt(1);
this.toughness = new MageInt(4);
// Thopters you control get +1/+1.
this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(
1, 1, Duration.WhileOnBattlefield, filter
)));
// If one or more artifact tokens would be created under your control, those tokens plus an additional 1/1 colorless Thopter artifact creature token with flying are created instead.
this.addAbility(new SimpleStaticAbility(new StridehangarAutomatonReplacementEffect()));
}
private StridehangarAutomaton(final StridehangarAutomaton card) {
super(card);
}
@Override
public StridehangarAutomaton copy() {
return new StridehangarAutomaton(this);
}
}
class StridehangarAutomatonReplacementEffect extends ReplacementEffectImpl {
StridehangarAutomatonReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
this.staticText = "If one or more artifact tokens would be created under your control, those tokens plus an additional 1/1 colorless Thopter artifact creature token with flying are created instead";
}
private StridehangarAutomatonReplacementEffect(final StridehangarAutomatonReplacementEffect effect) {
super(effect);
}
@Override
public StridehangarAutomatonReplacementEffect copy() {
return new StridehangarAutomatonReplacementEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.CREATE_TOKEN;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (source.isControlledBy(event.getPlayerId())) {
for (Map.Entry<Token, Integer> entry : ((CreateTokenEvent) event).getTokens().entrySet()) {
if (entry.getValue() > 0 && entry.getKey().isArtifact(game)) {
return true;
}
}
}
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
if (event instanceof CreateTokenEvent) {
CreateTokenEvent tokenEvent = (CreateTokenEvent) event;
ThopterColorlessToken thopterToken = null;
Map<Token, Integer> tokens = tokenEvent.getTokens();
for (Map.Entry<Token, Integer> entry : tokens.entrySet()) {
if (entry.getKey() instanceof ThopterColorlessToken) {
thopterToken = (ThopterColorlessToken) entry.getKey();
}
}
if (thopterToken == null) {
thopterToken = new ThopterColorlessToken();
}
tokens.put(thopterToken, tokens.getOrDefault(thopterToken, 0) + 1);
}
return false;
}
}

View file

@ -13,7 +13,6 @@ import mage.constants.SubType;
import mage.counters.CounterType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.card.FaceDownPredicate;
import java.util.UUID;
@ -24,10 +23,6 @@ public final class SumalaSentry extends CardImpl {
private static final FilterPermanent filter = new FilterControlledPermanent("a face-down permanent you control");
static {
filter.add(FaceDownPredicate.instance);
}
public SumalaSentry(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}");

View file

@ -0,0 +1,43 @@
package mage.cards.s;
import java.util.UUID;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.effects.common.ExileSpellWithTimeCountersEffect;
import mage.abilities.effects.common.LoseLifeTargetControllerEffect;
import mage.abilities.keyword.SuspendAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.target.common.TargetOpponentsCreaturePermanent;
/**
*
* @author padfoothelix
*/
public final class SuspendedSentence extends CardImpl {
public SuspendedSentence(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}");
// Destroy target creature an opponent controls. That player loses 3 life. Exile Suspended Sentence with three time counters on it.
this.getSpellAbility().addEffect(new DestroyTargetEffect());
this.getSpellAbility().addEffect(new LoseLifeTargetControllerEffect(3).setText("That player loses 3 life"));
this.getSpellAbility().addTarget(new TargetOpponentsCreaturePermanent());
this.getSpellAbility().addEffect(new ExileSpellWithTimeCountersEffect(3));
// Suspend 3--{1}{B}
this.addAbility(new SuspendAbility(3, new ManaCostsImpl<>("{1}{B}"), this));
}
private SuspendedSentence(final SuspendedSentence card) {
super(card);
}
@Override
public SuspendedSentence copy() {
return new SuspendedSentence(this);
}
}

View file

@ -51,7 +51,7 @@ public final class TerraHeraldOfHope extends CardImpl {
ReflexiveTriggeredAbility reflexiveAbility = new ReflexiveTriggeredAbility(
new ReturnFromGraveyardToBattlefieldTargetEffect(true), false
);
ability.addTarget(new TargetCardInYourGraveyard(filter));
reflexiveAbility.addTarget(new TargetCardInYourGraveyard(filter));
this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(
new DoWhenCostPaid(reflexiveAbility, new GenericManaCost(2), "Pay {2}?")
));

View file

@ -100,7 +100,7 @@ class TheAethersparkEffect extends RestrictionEffect {
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
return Optional
.ofNullable(source.getControllerId())
.ofNullable(source.getSourceId())
.map(game::getPermanent)
.map(Permanent::getAttachedTo)
.map(game::getPermanent)

View file

@ -0,0 +1,110 @@
package mage.cards.t;
import mage.abilities.Ability;
import mage.abilities.common.SagaAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DestroyAllEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
import mage.constants.*;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreatureCard;
import mage.filter.StaticFilters;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import java.util.*;
/**
*
* @author padfoothelix
*/
public final class TheNightOfTheDoctor extends CardImpl {
private static final FilterCard filter = new FilterCreatureCard("legendary creature card");
static {
filter.add(SuperType.LEGENDARY.getPredicate());
}
public TheNightOfTheDoctor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{W}{W}");
this.subtype.add(SubType.SAGA);
// (As this Saga enters and after your draw step, add a lore counter. Sacrifice after II.)
SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_II);
// I -- Destroy all creatures.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES));
// II -- Return target legendary creature card from your graveyard to the battlefield. Put your choice of a first strike, vigilance, or lifelink counter on it.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new TheNightOfTheDoctorEffect(), new TargetCardInYourGraveyard(filter));
this.addAbility(sagaAbility);
}
private TheNightOfTheDoctor(final TheNightOfTheDoctor card) {
super(card);
}
@Override
public TheNightOfTheDoctor copy() {
return new TheNightOfTheDoctor(this);
}
}
class TheNightOfTheDoctorEffect extends OneShotEffect {
private static final Set<String> choices = new LinkedHashSet<>(Arrays.asList(
"First strike", "Vigilance", "Lifelink"
));
TheNightOfTheDoctorEffect() {
super(Outcome.PutCardInPlay);
this.staticText = "Return target legendary creature card from your graveyard to the battlefield. Put your choice of a first strike, vigilance, or lifelink counter on it";
}
private TheNightOfTheDoctorEffect(final TheNightOfTheDoctorEffect effect) {
super(effect);
}
@Override
public TheNightOfTheDoctorEffect copy() {
return new TheNightOfTheDoctorEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (card == null || !controller.moveCards(card, Zone.BATTLEFIELD, source, game)) {
return false;
}
Permanent permanent = game.getPermanent(card.getId());
if (permanent == null) {
return false;
}
Choice choice = new ChoiceImpl(true);
choice.setMessage("Choose first strike, vigilance, or lifelink counter");
choice.setChoices(choices);
controller.choose(outcome, choice, game);
String chosen = choice.getChoice();
if (chosen != null) {
permanent.addCounters(
CounterType.findByName(chosen.toLowerCase(Locale.ENGLISH)).createInstance(),
source.getControllerId(), source, game
);
}
return true;
}
}

View file

@ -0,0 +1,104 @@
package mage.cards.t;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect;
import mage.abilities.effects.common.combat.GoadAttachedEffect;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.target.common.TargetCreaturePermanent;
import mage.target.TargetPermanent;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
import java.util.UUID;
/**
*
* @author padfoot
*/
public final class TheSoundOfDrums extends CardImpl {
public TheSoundOfDrums(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}");
this.subtype.add(SubType.AURA);
// Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
this.addAbility(new EnchantAbility(auraTarget));
// Enchanted creature is goaded.
this.addAbility(new SimpleStaticAbility(new GoadAttachedEffect().setText("enchanted creature is goaded")));
// If enchanted creature would deal combat damage to a permanent or player, it deals double that damage instead.
this.addAbility(new SimpleStaticAbility(new TheSoundOfDrumsEffect()));
// {2}{R}: Return The Sound of Drums from your graveyard to your hand.
this.addAbility(new SimpleActivatedAbility(
Zone.GRAVEYARD,
new ReturnSourceFromGraveyardToHandEffect(),
new ManaCostsImpl<>("{2}{R}")
));
}
private TheSoundOfDrums(final TheSoundOfDrums card) {
super(card);
}
@Override
public TheSoundOfDrums copy() {
return new TheSoundOfDrums(this);
}
}
class TheSoundOfDrumsEffect extends ReplacementEffectImpl {
TheSoundOfDrumsEffect() {
super(Duration.WhileOnBattlefield, Outcome.Damage);
staticText = "If enchanted creature would deal combat damage to a permanent or player, it deals double that damage instead";
}
private TheSoundOfDrumsEffect(final TheSoundOfDrumsEffect effect) {
super(effect);
}
@Override
public TheSoundOfDrumsEffect copy() {
return new TheSoundOfDrumsEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
event.setAmount(CardUtil.overflowMultiply(event.getAmount(), 2));
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
switch (event.getType()) {
case DAMAGE_PLAYER:
case DAMAGE_PERMANENT:
return true;
default:
return false;
}
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Permanent enchantment = game.getPermanent(source.getSourceId());
return enchantment != null
&& ((DamageEvent) event).isCombatDamage()
&& enchantment.isAttachedTo(event.getSourceId());
}
}

View file

@ -0,0 +1,70 @@
package mage.cards.t;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.abilities.effects.common.ReturnFromYourGraveyardToBattlefieldAllEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCardInYourGraveyard;
/**
*
* @author Jmlundeen
*/
public final class TuneUp extends CardImpl {
public TuneUp(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}");
// Return target artifact card from your graveyard to the battlefield. If it's a Vehicle, it becomes an artifact creature.
this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect());
this.getSpellAbility().addEffect(new TuneUpEffect());
this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_ARTIFACT_FROM_YOUR_GRAVEYARD));
}
private TuneUp(final TuneUp card) {
super(card);
}
@Override
public TuneUp copy() {
return new TuneUp(this);
}
}
class TuneUpEffect extends OneShotEffect {
TuneUpEffect() {
super(Outcome.BecomeCreature);
this.staticText = "If it's a Vehicle, it becomes an artifact creature";
}
private TuneUpEffect(final TuneUpEffect effect) {
super(effect);
}
@Override
public TuneUpEffect copy() {
return new TuneUpEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getFirstTarget());
if (permanent != null && permanent.hasSubtype(SubType.VEHICLE, game)) {
permanent.addCardType(CardType.CREATURE);
return true;
}
return false;
}
}

View file

@ -0,0 +1,61 @@
package mage.cards.u;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
import mage.abilities.effects.common.MillCardsControllerEffect;
import mage.abilities.effects.common.combat.AttackIfAbleTargetRandomOpponentSourceEffect;
import mage.abilities.effects.common.continuous.BoostSourceEffect;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.abilities.keyword.IndestructibleAbility;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
/**
*
* @author inuenc
*/
public final class UrsineMonstrosity extends CardImpl {
public UrsineMonstrosity(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
this.subtype.add(SubType.BEAR);
this.subtype.add(SubType.MUTANT);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Trample
this.addAbility(TrampleAbility.getInstance());
// At the beginning of combat on your turn, mill a card and choose an opponent at random. Ursine Monstrosity attacks that player this combat if able. Until end of turn, Ursine Monstrosity gains indestructible and gets +1/+1 for each card type among cards in your graveyard.
BeginningOfCombatTriggeredAbility ability = new BeginningOfCombatTriggeredAbility(
new MillCardsControllerEffect(1)
);
ability.addEffect(new AttackIfAbleTargetRandomOpponentSourceEffect()
.concatBy("and")
);
ability.addEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn)
.setText("Until end of turn, {this} gains indestructible")
);
ability.addEffect(new BoostSourceEffect(CardTypesInGraveyardCount.YOU, CardTypesInGraveyardCount.YOU, Duration.EndOfTurn)
.setText("gets +1/+1 for each card type among cards in your graveyard.")
.concatBy("and")
);
this.addAbility(ability.addHint(CardTypesInGraveyardCount.YOU.getHint()));
}
private UrsineMonstrosity(final UrsineMonstrosity card) {
super(card);
}
@Override
public UrsineMonstrosity copy() {
return new UrsineMonstrosity(this);
}
}

View file

@ -4,10 +4,11 @@ import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.PayEnergyCost;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.SacrificeTargetEffect;
import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HexproofBaseAbility;
@ -137,13 +138,10 @@ class VolatileStormdrakeEffect extends OneShotEffect {
game.addEffect(effect, source);
game.processAction();
controller.addCounters(CounterType.ENERGY.createInstance(4), controller.getId(), source, game);
Cost cost = new PayEnergyCost(targetPermanent.getManaValue());
if (cost.canPay(source, source, controller.getId(), game) &&
controller.chooseUse(Outcome.Benefit, "Pay " + cost.getText() + " to prevent sacrifice?", source, game) &&
cost.pay(source, game, source, controller.getId(), false)) {
return true;
}
targetPermanent.sacrifice(source, game);
new DoIfCostPaid(
null, new SacrificeTargetEffect("", controller.getId()),
new PayEnergyCost(targetPermanent.getManaValue()), true
).apply(game, source);
return true;
}
}

View file

@ -2,13 +2,15 @@
package mage.cards.w;
import java.util.UUID;
import mage.abilities.effects.common.AddCombatAndMainPhaseEffect;
import mage.abilities.effects.common.UntapAllThatAttackedEffect;
import mage.abilities.effects.common.UntapAllEffect;
import mage.abilities.keyword.RetraceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.watchers.common.AttackedThisTurnWatcher;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.AttackedThisTurnPredicate;
/**
*
@ -16,11 +18,17 @@ import mage.watchers.common.AttackedThisTurnWatcher;
*/
public final class WavesOfAggression extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures that attacked this turn");
static {
filter.add(AttackedThisTurnPredicate.instance);
}
public WavesOfAggression(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{R/W}{R/W}");
// Untap all creatures that attacked this turn. After this main phase, there is an additional combat phase followed by an additional main phase.
this.getSpellAbility().addEffect(new UntapAllThatAttackedEffect());
this.getSpellAbility().addEffect(new UntapAllEffect(filter));
this.getSpellAbility().addEffect(new AddCombatAndMainPhaseEffect());
// Retrace
this.addAbility(new RetraceAbility(this));

View file

@ -1,29 +1,26 @@
package mage.cards.w;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.constants.*;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.permanent.TappedPredicate;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetControlledPermanent;
import mage.target.common.TargetSacrifice;
import java.util.UUID;
/**
*
@ -54,7 +51,7 @@ public final class WoodElemental extends CardImpl {
}
}
class WoodElementalEffect extends OneShotEffect {
class WoodElementalEffect extends ReplacementEffectImpl {
private static final FilterControlledPermanent filter = new FilterControlledPermanent("untapped Forests you control");
@ -64,7 +61,7 @@ class WoodElementalEffect extends OneShotEffect {
}
public WoodElementalEffect() {
super(Outcome.Sacrifice);
super(Duration.EndOfGame, Outcome.Sacrifice);
staticText = "sacrifice any number of untapped Forests";
}
@ -78,27 +75,39 @@ class WoodElementalEffect extends OneShotEffect {
}
@Override
public boolean apply(Game game, Ability source) {
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return event.getTargetId().equals(source.getSourceId());
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget();
Player controller = game.getPlayer(source.getControllerId());
Card sourceCard = game.getCard(source.getSourceId());
if (controller != null && sourceCard != null) {
Target target = new TargetControlledPermanent(0, Integer.MAX_VALUE, filter, true);
if (target.canChoose(source.getControllerId(), source, game)
&& controller.chooseTarget(Outcome.Detriment, target, source, game)) {
if (!target.getTargets().isEmpty()) {
int sacrificedForests = target.getTargets().size();
game.informPlayers(controller.getLogName() + " sacrifices " + sacrificedForests + " untapped Forests for " + sourceCard.getLogName());
for (UUID targetId : target.getTargets()) {
Permanent targetPermanent = game.getPermanent(targetId);
if (targetPermanent != null) {
targetPermanent.sacrifice(source, game);
}
}
game.addEffect(new SetBasePowerToughnessSourceEffect(sacrificedForests, sacrificedForests, Duration.Custom), source);
return true;
}
if (creature == null || controller == null) {
return false;
}
Target target = new TargetSacrifice(0, Integer.MAX_VALUE, filter);
if (!target.canChoose(source.getControllerId(), source, game)) {
return false;
}
controller.choose(Outcome.Detriment, target, source, game);
if (target.getTargets().isEmpty()) {
return false;
}
int value = 0;
for (UUID targetId : target.getTargets()) {
Permanent targetCreature = game.getPermanent(targetId);
if (targetCreature != null && targetCreature.sacrifice(source, game)) {
value++;
}
}
game.addEffect(new SetBasePowerToughnessSourceEffect(value, value, Duration.WhileOnBattlefield), source);
this.discard(); // prevent multiple replacements e.g. on blink
return false;
}
}

View file

@ -0,0 +1,94 @@
package mage.cards.y;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.DamagePlayersEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.hint.ConditionHint;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.constants.*;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.filter.FilterSpell;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.watchers.common.PlayerLostLifeWatcher;
/**
*
* @author inuenc
*/
public final class YshtolaNightsBlessed extends CardImpl {
private static final FilterSpell filter = new FilterSpell("a noncreature spell with mana value 3 or greater");
static {
filter.add(Predicates.not(CardType.CREATURE.getPredicate()));
filter.add(new ManaValuePredicate(ComparisonType.MORE_THAN, 2));
}
public YshtolaNightsBlessed(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{U}{B}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.CAT);
this.subtype.add(SubType.WARLOCK);
this.power = new MageInt(2);
this.toughness = new MageInt(4);
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// At the beginning of each end step, if a player lost 4 or more life this turn, you draw a card.
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
new BeginningOfEndStepTriggeredAbility(
TargetController.ANY,
new DrawCardSourceControllerEffect(1),
false
), YshtolaNightsBlessedCondition.instance, "At the beginning of each end step, " +
"if a player lost 4 or more life this turn, you draw a card."
).addHint(new ConditionHint(YshtolaNightsBlessedCondition.instance, "A player lost 4 or more life this turn")));
// Whenever you cast a noncreature spell with mana value 3 or greater, Y'shtola deals 2 damage to each opponent and you gain 2 life.
Ability ability = new SpellCastControllerTriggeredAbility(
new DamagePlayersEffect(
2, TargetController.OPPONENT, "Y'shtola"),
filter, false
);
ability.addEffect(new GainLifeEffect(2).setText("and you gain 2 life"));
this.addAbility(ability);
}
private YshtolaNightsBlessed(final YshtolaNightsBlessed card) {
super(card);
}
@Override
public YshtolaNightsBlessed copy() {
return new YshtolaNightsBlessed(this);
}
}
enum YshtolaNightsBlessedCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
PlayerLostLifeWatcher watcher = game.getState().getWatcher(PlayerLostLifeWatcher.class);
if (watcher == null) {
return false;
}
return game
.getState()
.getPlayersInRange(source.getControllerId(), game)
.stream()
.anyMatch(uuid -> watcher.getLifeLost(uuid) > 3);
}
}

View file

@ -107,6 +107,7 @@ public final class Aetherdrift extends ExpansionSet {
cards.add(new SetCardInfo("Dune Drifter", 200, Rarity.UNCOMMON, mage.cards.d.DuneDrifter.class));
cards.add(new SetCardInfo("Dynamite Diver", 123, Rarity.COMMON, mage.cards.d.DynamiteDiver.class));
cards.add(new SetCardInfo("Earthrumbler", 160, Rarity.UNCOMMON, mage.cards.e.Earthrumbler.class));
cards.add(new SetCardInfo("Elvish Refueler", 161, Rarity.UNCOMMON, mage.cards.e.ElvishRefueler.class));
cards.add(new SetCardInfo("Embalmed Ascendant", 201, Rarity.UNCOMMON, mage.cards.e.EmbalmedAscendant.class));
cards.add(new SetCardInfo("Endrider Catalyzer", 124, Rarity.COMMON, mage.cards.e.EndriderCatalyzer.class));
cards.add(new SetCardInfo("Endrider Spikespitter", 125, Rarity.UNCOMMON, mage.cards.e.EndriderSpikespitter.class));
@ -140,6 +141,7 @@ public final class Aetherdrift extends ExpansionSet {
cards.add(new SetCardInfo("Grim Bauble", 88, Rarity.COMMON, mage.cards.g.GrimBauble.class));
cards.add(new SetCardInfo("Grim Javelineer", 89, Rarity.COMMON, mage.cards.g.GrimJavelineer.class));
cards.add(new SetCardInfo("Guardian Sunmare", 15, Rarity.RARE, mage.cards.g.GuardianSunmare.class));
cards.add(new SetCardInfo("Guidelight Optimizer", 45, Rarity.COMMON, mage.cards.g.GuidelightOptimizer.class));
cards.add(new SetCardInfo("Guidelight Pathmaker", 206, Rarity.UNCOMMON, mage.cards.g.GuidelightPathmaker.class));
cards.add(new SetCardInfo("Guidelight Synergist", 16, Rarity.UNCOMMON, mage.cards.g.GuidelightSynergist.class));
cards.add(new SetCardInfo("Haunt the Network", 207, Rarity.UNCOMMON, mage.cards.h.HauntTheNetwork.class));
@ -157,6 +159,10 @@ public final class Aetherdrift extends ExpansionSet {
cards.add(new SetCardInfo("Jungle Hollow", 256, Rarity.COMMON, mage.cards.j.JungleHollow.class));
cards.add(new SetCardInfo("Kalakscion, Hunger Tyrant", 93, Rarity.UNCOMMON, mage.cards.k.KalakscionHungerTyrant.class));
cards.add(new SetCardInfo("Keen Buccaneer", 48, Rarity.COMMON, mage.cards.k.KeenBuccaneer.class));
cards.add(new SetCardInfo("Ketramose, the New Dawn", 209, Rarity.MYTHIC, mage.cards.k.KetramoseTheNewDawn.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ketramose, the New Dawn", 350, Rarity.MYTHIC, mage.cards.k.KetramoseTheNewDawn.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ketramose, the New Dawn", 482, Rarity.MYTHIC, mage.cards.k.KetramoseTheNewDawn.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ketramose, the New Dawn", 549, Rarity.MYTHIC, mage.cards.k.KetramoseTheNewDawn.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kickoff Celebrations", 135, Rarity.COMMON, mage.cards.k.KickoffCelebrations.class));
cards.add(new SetCardInfo("Lagorin, Soul of Alacria", 211, Rarity.UNCOMMON, mage.cards.l.LagorinSoulOfAlacria.class));
cards.add(new SetCardInfo("Leonin Surveyor", 18, Rarity.COMMON, mage.cards.l.LeoninSurveyor.class));
@ -165,6 +171,11 @@ public final class Aetherdrift extends ExpansionSet {
cards.add(new SetCardInfo("Lightshield Parry", 19, Rarity.COMMON, mage.cards.l.LightshieldParry.class));
cards.add(new SetCardInfo("Lightwheel Enhancements", 20, Rarity.COMMON, mage.cards.l.LightwheelEnhancements.class));
cards.add(new SetCardInfo("Locust Spray", 95, Rarity.UNCOMMON, mage.cards.l.LocustSpray.class));
cards.add(new SetCardInfo("Loot, the Pathfinder", 212, Rarity.MYTHIC, mage.cards.l.LootThePathfinder.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Loot, the Pathfinder", 391, Rarity.MYTHIC, mage.cards.l.LootThePathfinder.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Loot, the Pathfinder", 404, Rarity.MYTHIC, mage.cards.l.LootThePathfinder.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Loot, the Pathfinder", 414, Rarity.MYTHIC, mage.cards.l.LootThePathfinder.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Loot, the Pathfinder", 484, Rarity.MYTHIC, mage.cards.l.LootThePathfinder.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Lotusguard Disciple", 21, Rarity.COMMON, mage.cards.l.LotusguardDisciple.class));
cards.add(new SetCardInfo("Loxodon Surveyor", 167, Rarity.COMMON, mage.cards.l.LoxodonSurveyor.class));
cards.add(new SetCardInfo("Lumbering Worldwagon", 168, Rarity.RARE, mage.cards.l.LumberingWorldwagon.class));
@ -186,6 +197,7 @@ public final class Aetherdrift extends ExpansionSet {
cards.add(new SetCardInfo("Migrating Ketradon", 170, Rarity.COMMON, mage.cards.m.MigratingKetradon.class));
cards.add(new SetCardInfo("Mindspring Merfolk", 51, Rarity.RARE, mage.cards.m.MindspringMerfolk.class));
cards.add(new SetCardInfo("Molt Tender", 171, Rarity.UNCOMMON, mage.cards.m.MoltTender.class));
cards.add(new SetCardInfo("Momentum Breaker", 97, Rarity.UNCOMMON, mage.cards.m.MomentumBreaker.class));
cards.add(new SetCardInfo("Monument to Endurance", 237, Rarity.RARE, mage.cards.m.MonumentToEndurance.class));
cards.add(new SetCardInfo("Mountain", 286, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Mu Yanling, Wind Rider", 52, Rarity.MYTHIC, mage.cards.m.MuYanlingWindRider.class));
@ -194,6 +206,10 @@ public final class Aetherdrift extends ExpansionSet {
cards.add(new SetCardInfo("Nesting Bot", 22, Rarity.UNCOMMON, mage.cards.n.NestingBot.class));
cards.add(new SetCardInfo("Night Market", 258, Rarity.COMMON, mage.cards.n.NightMarket.class));
cards.add(new SetCardInfo("Nimble Thopterist", 53, Rarity.COMMON, mage.cards.n.NimbleThopterist.class));
cards.add(new SetCardInfo("Oildeep Gearhulk", 215, Rarity.MYTHIC, mage.cards.o.OildeepGearhulk.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Oildeep Gearhulk", 351, Rarity.MYTHIC, mage.cards.o.OildeepGearhulk.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Oildeep Gearhulk", 487, Rarity.MYTHIC, mage.cards.o.OildeepGearhulk.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Oildeep Gearhulk", 550, Rarity.MYTHIC, mage.cards.o.OildeepGearhulk.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ooze Patrol", 172, Rarity.UNCOMMON, mage.cards.o.OozePatrol.class));
cards.add(new SetCardInfo("Outpace Oblivion", 139, Rarity.UNCOMMON, mage.cards.o.OutpaceOblivion.class));
cards.add(new SetCardInfo("Oviya, Automech Artisan", 173, Rarity.RARE, mage.cards.o.OviyaAutomechArtisan.class));
@ -209,9 +225,15 @@ public final class Aetherdrift extends ExpansionSet {
cards.add(new SetCardInfo("Pothole Mole", 176, Rarity.COMMON, mage.cards.p.PotholeMole.class));
cards.add(new SetCardInfo("Pride of the Road", 24, Rarity.UNCOMMON, mage.cards.p.PrideOfTheRoad.class));
cards.add(new SetCardInfo("Prowcatcher Specialist", 142, Rarity.COMMON, mage.cards.p.ProwcatcherSpecialist.class));
cards.add(new SetCardInfo("Push the Limit", 143, Rarity.UNCOMMON, mage.cards.p.PushTheLimit.class));
cards.add(new SetCardInfo("Pyrewood Gearhulk", 216, Rarity.MYTHIC, mage.cards.p.PyrewoodGearhulk.class));
cards.add(new SetCardInfo("Quag Feast", 100, Rarity.RARE, mage.cards.q.QuagFeast.class));
cards.add(new SetCardInfo("Racers' Scoreboard", 239, Rarity.UNCOMMON, mage.cards.r.RacersScoreboard.class));
cards.add(new SetCardInfo("Radiant Lotus", 240, Rarity.MYTHIC, mage.cards.r.RadiantLotus.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Radiant Lotus", 395, Rarity.MYTHIC, mage.cards.r.RadiantLotus.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Radiant Lotus", 406, Rarity.MYTHIC, mage.cards.r.RadiantLotus.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Radiant Lotus", 416, Rarity.MYTHIC, mage.cards.r.RadiantLotus.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Radiant Lotus", 500, Rarity.MYTHIC, mage.cards.r.RadiantLotus.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Rangers' Aetherhive", 217, Rarity.UNCOMMON, mage.cards.r.RangersAetherhive.class));
cards.add(new SetCardInfo("Rangers' Refueler", 55, Rarity.UNCOMMON, mage.cards.r.RangersRefueler.class));
cards.add(new SetCardInfo("Reckless Velocitaur", 144, Rarity.UNCOMMON, mage.cards.r.RecklessVelocitaur.class));
@ -249,8 +271,17 @@ public final class Aetherdrift extends ExpansionSet {
cards.add(new SetCardInfo("Scrounging Skyray", 60, Rarity.UNCOMMON, mage.cards.s.ScroungingSkyray.class));
cards.add(new SetCardInfo("Shefet Archfiend", 104, Rarity.UNCOMMON, mage.cards.s.ShefetArchfiend.class));
cards.add(new SetCardInfo("Silken Strength", 180, Rarity.COMMON, mage.cards.s.SilkenStrength.class));
cards.add(new SetCardInfo("Sita Varma, Masked Racer", 223, Rarity.RARE, mage.cards.s.SitaVarmaMaskedRacer.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Sita Varma, Masked Racer", 368, Rarity.RARE, mage.cards.s.SitaVarmaMaskedRacer.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Sita Varma, Masked Racer", 493, Rarity.RARE, mage.cards.s.SitaVarmaMaskedRacer.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Skybox Ferry", 243, Rarity.COMMON, mage.cards.s.SkyboxFerry.class));
cards.add(new SetCardInfo("Skycrash", 146, Rarity.UNCOMMON, mage.cards.s.Skycrash.class));
cards.add(new SetCardInfo("Skyseer's Chariot", 28, Rarity.RARE, mage.cards.s.SkyseersChariot.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Skyseer's Chariot", 296, Rarity.RARE, mage.cards.s.SkyseersChariot.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Skyseer's Chariot", 432, Rarity.RARE, mage.cards.s.SkyseersChariot.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Skyseer's Chariot", 518, Rarity.RARE, mage.cards.s.SkyseersChariot.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Skyserpent Seeker", 224, Rarity.UNCOMMON, mage.cards.s.SkyserpentSeeker.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Skyserpent Seeker", 420, Rarity.UNCOMMON, mage.cards.s.SkyserpentSeeker.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Skystreak Engineer", 61, Rarity.COMMON, mage.cards.s.SkystreakEngineer.class));
cards.add(new SetCardInfo("Slick Imitator", 62, Rarity.UNCOMMON, mage.cards.s.SlickImitator.class));
cards.add(new SetCardInfo("Spectacular Pileup", 29, Rarity.RARE, mage.cards.s.SpectacularPileup.class, NON_FULL_USE_VARIOUS));
@ -293,6 +324,8 @@ public final class Aetherdrift extends ExpansionSet {
cards.add(new SetCardInfo("Tranquil Cove", 267, Rarity.COMMON, mage.cards.t.TranquilCove.class));
cards.add(new SetCardInfo("Transit Mage", 70, Rarity.UNCOMMON, mage.cards.t.TransitMage.class));
cards.add(new SetCardInfo("Trip Up", 71, Rarity.COMMON, mage.cards.t.TripUp.class));
cards.add(new SetCardInfo("Tune Up", 33, Rarity.UNCOMMON, mage.cards.t.TuneUp.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Tune Up", 417, Rarity.UNCOMMON, mage.cards.t.TuneUp.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Tyrox, Saurid Tyrant", 149, Rarity.UNCOMMON, mage.cards.t.TyroxSauridTyrant.class));
cards.add(new SetCardInfo("Unstoppable Plan", 72, Rarity.RARE, mage.cards.u.UnstoppablePlan.class));
cards.add(new SetCardInfo("Unswerving Sloth", 34, Rarity.UNCOMMON, mage.cards.u.UnswervingSloth.class));

View file

@ -125,6 +125,8 @@ public final class AetherdriftCommander extends ExpansionSet {
cards.add(new SetCardInfo("Pia and Kiran Nalaar", 105, Rarity.RARE, mage.cards.p.PiaAndKiranNalaar.class));
cards.add(new SetCardInfo("Plague Belcher", 97, Rarity.RARE, mage.cards.p.PlagueBelcher.class));
cards.add(new SetCardInfo("Prairie Stream", 167, Rarity.RARE, mage.cards.p.PrairieStream.class));
cards.add(new SetCardInfo("Priest of the Crossing", 6, Rarity.RARE, mage.cards.p.PriestOfTheCrossing.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Priest of the Crossing", 22, Rarity.RARE, mage.cards.p.PriestOfTheCrossing.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Prophet of the Scarab", 9, Rarity.RARE, mage.cards.p.ProphetOfTheScarab.class));
cards.add(new SetCardInfo("Pull from Tomorrow", 81, Rarity.RARE, mage.cards.p.PullFromTomorrow.class));
cards.add(new SetCardInfo("Reality Shift", 39, Rarity.UNCOMMON, mage.cards.r.RealityShift.class));
@ -145,6 +147,7 @@ public final class AetherdriftCommander extends ExpansionSet {
cards.add(new SetCardInfo("Solemn Simulacrum", 138, Rarity.RARE, mage.cards.s.SolemnSimulacrum.class));
cards.add(new SetCardInfo("Soul-Guide Lantern", 139, Rarity.UNCOMMON, mage.cards.s.SoulGuideLantern.class));
cards.add(new SetCardInfo("Spire of Industry", 172, Rarity.RARE, mage.cards.s.SpireOfIndustry.class));
cards.add(new SetCardInfo("Stridehangar Automaton", 19, Rarity.RARE, mage.cards.s.StridehangarAutomaton.class));
cards.add(new SetCardInfo("Sulfur Falls", 173, Rarity.RARE, mage.cards.s.SulfurFalls.class));
cards.add(new SetCardInfo("Sunken Hollow", 174, Rarity.RARE, mage.cards.s.SunkenHollow.class));
cards.add(new SetCardInfo("Swords to Plowshares", 37, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class));

View file

@ -221,8 +221,8 @@ public final class AssassinsCreed extends ExpansionSet {
cards.add(new SetCardInfo("Murder", 92, Rarity.UNCOMMON, mage.cards.m.Murder.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Nurturing Peatland", 114, Rarity.RARE, mage.cards.n.NurturingPeatland.class));
cards.add(new SetCardInfo("Origin of the Hidden Ones", 36, Rarity.UNCOMMON, mage.cards.o.OriginOfTheHiddenOnes.class));
//cards.add(new SetCardInfo("Overpowering Attack", 218, Rarity.UNCOMMON, mage.cards.o.OverpoweringAttack.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("Overpowering Attack", 37, Rarity.UNCOMMON, mage.cards.o.OverpoweringAttack.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Overpowering Attack", 218, Rarity.UNCOMMON, mage.cards.o.OverpoweringAttack.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Overpowering Attack", 37, Rarity.UNCOMMON, mage.cards.o.OverpoweringAttack.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Palazzo Archers", 222, Rarity.UNCOMMON, mage.cards.p.PalazzoArchers.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Palazzo Archers", 42, Rarity.UNCOMMON, mage.cards.p.PalazzoArchers.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Path to Exile", 178, Rarity.UNCOMMON, mage.cards.p.PathToExile.class, NON_FULL_USE_VARIOUS));

View file

@ -291,10 +291,10 @@ public final class DoctorWho extends ExpansionSet {
cards.add(new SetCardInfo("Ecstatic Beauty", 688, Rarity.RARE, mage.cards.e.EcstaticBeauty.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ecstatic Beauty", 83, Rarity.RARE, mage.cards.e.EcstaticBeauty.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ecstatic Beauty", 974, Rarity.RARE, mage.cards.e.EcstaticBeauty.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("Ensnared by the Mara", 384, Rarity.RARE, mage.cards.e.EnsnaredByTheMara.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("Ensnared by the Mara", 689, Rarity.RARE, mage.cards.e.EnsnaredByTheMara.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("Ensnared by the Mara", 84, Rarity.RARE, mage.cards.e.EnsnaredByTheMara.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("Ensnared by the Mara", 975, Rarity.RARE, mage.cards.e.EnsnaredByTheMara.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ensnared by the Mara", 384, Rarity.RARE, mage.cards.e.EnsnaredByTheMara.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ensnared by the Mara", 689, Rarity.RARE, mage.cards.e.EnsnaredByTheMara.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ensnared by the Mara", 84, Rarity.RARE, mage.cards.e.EnsnaredByTheMara.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ensnared by the Mara", 975, Rarity.RARE, mage.cards.e.EnsnaredByTheMara.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("Everybody Lives!", 18, Rarity.RARE, mage.cards.e.EverybodyLives.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("Everybody Lives!", 338, Rarity.RARE, mage.cards.e.EverybodyLives.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("Everybody Lives!", 623, Rarity.RARE, mage.cards.e.EverybodyLives.class, NON_FULL_USE_VARIOUS));
@ -988,8 +988,8 @@ public final class DoctorWho extends ExpansionSet {
//cards.add(new SetCardInfo("The Moment", 459, Rarity.RARE, mage.cards.t.TheMoment.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("The Moment", 785, Rarity.RARE, mage.cards.t.TheMoment.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("The Moonbase", 591, Rarity.COMMON, mage.cards.t.TheMoonbase.class));
//cards.add(new SetCardInfo("The Night of the Doctor", 24, Rarity.RARE, mage.cards.t.TheNightOfTheDoctor.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("The Night of the Doctor", 629, Rarity.RARE, mage.cards.t.TheNightOfTheDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Night of the Doctor", 24, Rarity.RARE, mage.cards.t.TheNightOfTheDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Night of the Doctor", 629, Rarity.RARE, mage.cards.t.TheNightOfTheDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Ninth Doctor", "560z", Rarity.RARE, mage.cards.t.TheNinthDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Ninth Doctor", 1023, Rarity.RARE, mage.cards.t.TheNinthDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Ninth Doctor", 1151, Rarity.RARE, mage.cards.t.TheNinthDoctor.class, NON_FULL_USE_VARIOUS));
@ -1031,10 +1031,10 @@ public final class DoctorWho extends ExpansionSet {
cards.add(new SetCardInfo("The Sixth Doctor", 443, Rarity.RARE, mage.cards.t.TheSixthDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Sixth Doctor", 557, Rarity.RARE, mage.cards.t.TheSixthDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Sixth Doctor", 764, Rarity.RARE, mage.cards.t.TheSixthDoctor.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("The Sound of Drums", 391, Rarity.RARE, mage.cards.t.TheSoundOfDrums.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("The Sound of Drums", 702, Rarity.RARE, mage.cards.t.TheSoundOfDrums.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("The Sound of Drums", 97, Rarity.RARE, mage.cards.t.TheSoundOfDrums.class, NON_FULL_USE_VARIOUS));
//cards.add(new SetCardInfo("The Sound of Drums", 982, Rarity.RARE, mage.cards.t.TheSoundOfDrums.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Sound of Drums", 391, Rarity.RARE, mage.cards.t.TheSoundOfDrums.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Sound of Drums", 702, Rarity.RARE, mage.cards.t.TheSoundOfDrums.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Sound of Drums", 97, Rarity.RARE, mage.cards.t.TheSoundOfDrums.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Sound of Drums", 982, Rarity.RARE, mage.cards.t.TheSoundOfDrums.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Tenth Doctor", "561z", Rarity.MYTHIC, mage.cards.t.TheTenthDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Tenth Doctor", 1037, Rarity.MYTHIC, mage.cards.t.TheTenthDoctor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("The Tenth Doctor", 1152, Rarity.MYTHIC, mage.cards.t.TheTenthDoctor.class, NON_FULL_USE_VARIOUS));

View file

@ -246,6 +246,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet {
cards.add(new SetCardInfo("Stitcher's Supplier", 157, Rarity.UNCOMMON, mage.cards.s.StitchersSupplier.class));
cards.add(new SetCardInfo("Stormfist Crusader", 234, Rarity.RARE, mage.cards.s.StormfistCrusader.class));
cards.add(new SetCardInfo("Sulfurous Springs", 301, Rarity.RARE, mage.cards.s.SulfurousSprings.class));
cards.add(new SetCardInfo("Suspended Sentence", 25, Rarity.RARE, mage.cards.s.SuspendedSentence.class));
cards.add(new SetCardInfo("Suspicious Bookcase", 95, Rarity.UNCOMMON, mage.cards.s.SuspiciousBookcase.class));
cards.add(new SetCardInfo("Swords to Plowshares", 106, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class));
cards.add(new SetCardInfo("Syr Konrad, the Grim", 158, Rarity.UNCOMMON, mage.cards.s.SyrKonradTheGrim.class));
@ -289,6 +290,8 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet {
cards.add(new SetCardInfo("Trygon Predator", 238, Rarity.UNCOMMON, mage.cards.t.TrygonPredator.class));
cards.add(new SetCardInfo("Twilight Mire", 320, Rarity.RARE, mage.cards.t.TwilightMire.class));
cards.add(new SetCardInfo("Underground River", 321, Rarity.RARE, mage.cards.u.UndergroundRiver.class));
cards.add(new SetCardInfo("Ursine Monstrosity", 36, Rarity.RARE, mage.cards.u.UrsineMonstrosity.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ursine Monstrosity", 63, Rarity.RARE, mage.cards.u.UrsineMonstrosity.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Utter End", 91, Rarity.RARE, mage.cards.u.UtterEnd.class));
cards.add(new SetCardInfo("Valgavoth, Harrower of Souls", 6, Rarity.MYTHIC, mage.cards.v.ValgavothHarrowerOfSouls.class));
cards.add(new SetCardInfo("Vault of Whispers", 322, Rarity.COMMON, mage.cards.v.VaultOfWhispers.class));

View file

@ -19,6 +19,14 @@ public final class FinalFantasyCommander extends ExpansionSet {
super("Final Fantasy Commander", "FIC", ExpansionSet.buildDate(2025, 6, 13), SetType.SUPPLEMENTAL);
this.hasBasicLands = false;
cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 2, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 168, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 202, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 221, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Terra, Herald of Hope", 4, Rarity.MYTHIC, mage.cards.t.TerraHeraldOfHope.class));
cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 7, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 191, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 215, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 226, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS));
}
}

View file

@ -361,7 +361,7 @@ public class SecretLairDrop extends ExpansionSet {
cards.add(new SetCardInfo("Mountain", 362, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Forest", 363, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Swords to Plowshares", 364, Rarity.RARE, mage.cards.s.SwordsToPlowshares.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Grim Tutor", 365, Rarity.RARE, mage.cards.g.GrimTutor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Grim Tutor", 365, Rarity.MYTHIC, mage.cards.g.GrimTutor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Blood Moon", 366, Rarity.RARE, mage.cards.b.BloodMoon.class));
cards.add(new SetCardInfo("Cut // Ribbons", 367, Rarity.RARE, mage.cards.c.CutRibbons.class));
cards.add(new SetCardInfo("Teferi's Puzzle Box", 368, Rarity.RARE, mage.cards.t.TeferisPuzzleBox.class));

View file

@ -24,6 +24,7 @@ public final class TarkirDragonstorm extends ExpansionSet {
cards.add(new SetCardInfo("Inevitable Defeat", 194, Rarity.RARE, mage.cards.i.InevitableDefeat.class));
cards.add(new SetCardInfo("Mox Jasper", 246, Rarity.MYTHIC, mage.cards.m.MoxJasper.class));
cards.add(new SetCardInfo("Narset, Jeskai Waymaster", 209, Rarity.RARE, mage.cards.n.NarsetJeskaiWaymaster.class));
cards.add(new SetCardInfo("Rally the Monastery", 19, Rarity.UNCOMMON, mage.cards.r.RallyTheMonastery.class));
cards.add(new SetCardInfo("Shiko, Paragon of the Way", 223, Rarity.MYTHIC, mage.cards.s.ShikoParagonOfTheWay.class));
cards.add(new SetCardInfo("Skirmish Rhino", 224, Rarity.UNCOMMON, mage.cards.s.SkirmishRhino.class));
cards.add(new SetCardInfo("Smile at Death", 24, Rarity.MYTHIC, mage.cards.s.SmileAtDeath.class));

View file

@ -0,0 +1,56 @@
package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
702.49. Ninjutsu
702.49a. Ninjutsu is an activated ability that functions only while the card with ninjutsu is in a player's hand.
"Ninjutsu [cost]" means "[Cost], Reveal this card from your hand, Return an unblocked attacking creature you control
to its owner's hand: Put this card onto the battlefield from your hand tapped and attacking."
702.49b. The card with ninjutsu remains revealed from the time the ability is announced until the ability leaves the stack.
702.49c. A ninjutsu ability may be activated only while a creature on the battlefield is unblocked (see rule 509.1h).
The creature with ninjutsu is put onto the battlefield unblocked. It will be attacking the same player, planeswalker,
or battle as the creature that was returned to its owner's hand.
702.49d. Commander ninjutsu is a variant of the ninjutsu ability that also functions while the card with commander
ninjutsu is in the command zone. "Commander ninjutsu [cost]" means "[Cost], Reveal this card from your hand or
from the command zone, Return an unblocked attacking creature you control to its owner's hand: Put this card onto
the battlefield tapped and attacking."
*/
public class NinjutsuTest extends CardTestPlayerBase {
private static final String drake = "Seacoast Drake"; // 1/3 flying
private static final String crab = "Jwari Scuttler"; // 2/3
private static final String shinobi = "Moonblade Shinobi"; // 3/2, Ninjutsu 2U, on combat damage to player create 1/1 Illusion
@Test
public void testMultipleUsage() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
addCard(Zone.BATTLEFIELD, playerA, drake);
addCard(Zone.BATTLEFIELD, playerA, crab);
addCard(Zone.HAND, playerA, shinobi);
attack(1, playerA, drake, playerB);
attack(1, playerA, crab, playerB);
activateAbility(1, PhaseStep.DECLARE_BLOCKERS, playerA, "Ninjutsu");
setChoice(playerA, drake);
activateAbility(1, PhaseStep.DECLARE_BLOCKERS, playerA, "Ninjutsu"); // while above ability on stack
setChoice(playerA, crab);
// result, both drake and crab in hand, shinobi on battlefield
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_COMBAT);
execute();
assertLife(playerB, 17);
assertPowerToughness(playerA, shinobi, 3, 2);
assertPermanentCount(playerA, "Illusion Token", 1);
assertHandCount(playerA, drake, 1);
assertHandCount(playerA, crab, 1);
assertTappedCount("Island", true, 6); // activated twice
}
}

View file

@ -98,4 +98,31 @@ public class DoIfCostPaidTest extends CardTestPlayerBase {
execute();
}
@Test
public void testCannotPay() {
String thirst = "Thirst for Meaning"; // Draw three cards. Then discard two cards unless you discard an enchantment card.
skipInitShuffling();
addCard(Zone.LIBRARY, playerA, "Craw Wurm");
addCard(Zone.LIBRARY, playerA, "Runeclaw Bear");
addCard(Zone.LIBRARY, playerA, "Glory Seeker");
addCard(Zone.HAND, playerA, thirst);
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, thirst);
// can't discard enchantment, so must discard two
setChoice(playerA, "Runeclaw Bear");
setChoice(playerA, "Glory Seeker");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, 3);
assertGraveyardCount(playerA, thirst, 1);
assertGraveyardCount(playerA, "Runeclaw Bear", 1);
assertGraveyardCount(playerA, "Glory Seeker", 1);
assertHandCount(playerA, 1);
assertHandCount(playerA, "Craw Wurm", 1);
}
}

View file

@ -135,4 +135,46 @@ public class GainProtectionTest extends CardTestPlayerBase {
assertPermanentCount(playerB, "Pentarch Ward", 1);
assertHandCount(playerB, 3);
}
// reported issue #13419
@Test
public void testChoMannosBlessingContagion() {
String soltari = "Soltari Visionary";
// Shadow (This creature can block or be blocked by only creatures with shadow.)
// Whenever this creature deals damage to a player, destroy target enchantment that player controls.
String contagion = "Contagion";
// You may pay 1 life and exile a black card from your hand rather than pay this spells mana cost.
// Distribute two -2/-1 counters among one or two target creatures.
String cmb = "Cho-Manno's Blessing";
// Flash
// Enchant creature
// As this Aura enters, choose a color.
// Enchanted creature has protection from the chosen color. This effect doesnt remove this Aura.
addCard(Zone.BATTLEFIELD, playerA, soltari);
addCard(Zone.HAND, playerB, contagion);
addCard(Zone.HAND, playerB, "Warpath Ghoul"); // black card
addCard(Zone.HAND, playerA, cmb);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, contagion, soltari);
setChoice(playerB, "Cast with alternative cost");
setChoice(playerB, "Warpath Ghoul");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cmb, soltari, contagion); // in response
setChoice(playerA, "Black");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 19);
assertExileCount(playerB, "Warpath Ghoul", 1);
assertGraveyardCount(playerB, contagion, 1);
assertPermanentCount(playerA, soltari, 1);
assertPermanentCount(playerA, cmb, 1);
assertAttachedTo(playerA, cmb, soltari, true);
}
}

View file

@ -0,0 +1,60 @@
package org.mage.test.cards.single.dft;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author jimga150
*/
public class ElvishRefuelerTest extends CardTestPlayerBase {
@Test
public void testExhaustPrevention() {
// During your turn, as long as you havent activated an exhaust ability this turn,
// you may activate exhaust abilities as though they havent been activated.
String refueler = "Elvish Refueler";
addCard(Zone.BATTLEFIELD, playerA, refueler);
addCard(Zone.HAND, playerA, refueler);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 7);
// Should be able to activate refueler's exhaust ability
checkPlayableAbility("Should be able to activate exhaust",
1, PhaseStep.PRECOMBAT_MAIN, playerA, "Exhaust", true);
// Activate refueler's exhaust ability
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Exhaust");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
// Should not be able to activate refueler's exhaust ability
checkPlayableAbility("Should not be able to activate exhaust after activating",
1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Exhaust", false);
// Casting a second refueler should not allow activating the exhaust ability
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, refueler);
waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN);
// Activating second refueler's exhaust ability
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Exhaust");
waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN);
// Should be no exhaust left
checkPlayableAbility("All exhausts should be used from both refuelers",
1, PhaseStep.END_TURN, playerA, "Exhaust", false);
// Confirm on opponent's turn that exhaust is still not available
checkPlayableAbility("Opponent's turn, effect should not apply",
2, PhaseStep.END_TURN, playerA, "Exhaust", false);
// Should be able to activate refueler's exhaust ability on next turn
checkPlayableAbility("Should be able to activate exhaust on our next turn",
3, PhaseStep.PRECOMBAT_MAIN, playerA, "Exhaust", true);
// Activate refueler's exhaust ability
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Exhaust");
waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN);
// Only one exhaust should have been available
checkPlayableAbility("Already activated exhaust, should not be able to activate again",
3, PhaseStep.PRECOMBAT_MAIN, playerA, "Exhaust", false);
setStopAt(3, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
}
}

View file

@ -2,7 +2,6 @@ package org.mage.test.cards.single.ncc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -63,7 +62,6 @@ public class RainOfRichesTest extends CardTestPlayerBase {
}
@Test
@Ignore("Does not work until actions are processed between the last mana being paid and the spell being cast.")
public void test_Cast_Two_Using_Treasures() {
setStrictChooseMode(true);
skipInitShuffling();
@ -91,7 +89,6 @@ public class RainOfRichesTest extends CardTestPlayerBase {
}
@Test
@Ignore("Does not work until actions are processed between the last mana being paid and the spell being cast.")
public void test_Cast_SomethingElse_Then_Cast_Using_Treasure() {
setStrictChooseMode(true);

View file

@ -0,0 +1,57 @@
package org.mage.test.cards.single.tmp;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author xenohedron
*/
public class MinionOfTheWastesTest extends CardTestPlayerBase {
private static final String minion = "Minion of the Wastes";
/* Trample
* As this creature enters, pay any amount of life.
* Minion of the Wastess power and toughness are each equal to the life paid as it entered.
*/
@Test
public void testSimple() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
addCard(Zone.HAND, playerA, minion);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, minion);
setChoice(playerA, "X=3");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_TURN);
execute();
assertLife(playerA, 17);
assertPowerToughness(playerA, minion, 3, 3);
}
@Test
public void testFlicker() {
addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 7);
addCard(Zone.HAND, playerA, minion);
addCard(Zone.HAND, playerA, "Cloudshift"); // flicker
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, minion);
setChoice(playerA, "X=3");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cloudshift", minion);
setChoice(playerA, "X=2");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_TURN);
execute();
assertLife(playerA, 15);
assertPowerToughness(playerA, minion, 2, 2);
}
// Note similar cards: Wood Elemental, Nameless Race, Dracoplasm
}

View file

@ -0,0 +1,67 @@
package org.mage.test.cards.single.tmp;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author xenohedron
*/
public class MongrelPackTest extends CardTestPlayerBase {
private static final String mp = "Mongrel Pack"; // 4/1
// When this creature dies during combat, create four 1/1 green Dog creature tokens.
private static final String wurm = "Craw Wurm"; // 6/4
private static final String bb = "Blood Bairn"; // sac outlet
@Test
public void testDuringCombat() {
addCard(Zone.BATTLEFIELD, playerA, mp);
addCard(Zone.BATTLEFIELD, playerB, wurm);
attack(1, playerA, mp, playerB);
block(1, playerB, wurm, mp);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, mp, 1);
assertGraveyardCount(playerB, wurm, 1);
assertPermanentCount(playerA, "Dog Token", 4);
}
@Test
public void testNotDuringCombat() {
addCard(Zone.BATTLEFIELD, playerA, mp);
addCard(Zone.BATTLEFIELD, playerA, bb);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice");
setChoice(playerA, mp);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, mp, 1);
assertPermanentCount(playerA, "Dog Token", 0);
}
@Test
public void testDuringCombatNotFromCombat() {
addCard(Zone.BATTLEFIELD, playerA, mp);
addCard(Zone.BATTLEFIELD, playerA, bb);
activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Sacrifice");
setChoice(playerA, mp);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, mp, 1);
assertPermanentCount(playerA, "Dog Token", 4);
}
}

View file

@ -0,0 +1,118 @@
package org.mage.test.cards.single.tsp;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author xenohedron
*/
public class NetherTraitorTest extends CardTestPlayerBase {
private static final String nt = "Nether Traitor";
/* Haste; shadow
Whenever another creature is put into your graveyard from the battlefield, you may pay {B}.
If you do, return this card from your graveyard to the battlefield.
*/
private static final String gb = "Goblin Bombardment"; // Sacrifice a creature: 1 damage to any target
private static final String wg = "Warpath Ghoul"; // 3/2
@Test
public void testNetherTraitorBattlefieldGhoulDies() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.BATTLEFIELD, playerA, nt);
addCard(Zone.BATTLEFIELD, playerA, gb);
addCard(Zone.BATTLEFIELD, playerA, wg);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice");
setChoice(playerA, wg); // to sac
addTarget(playerA, playerB); // 1 damage
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 19);
assertGraveyardCount(playerA, wg, 1);
assertPermanentCount(playerA, nt, 1);
assertTapped("Swamp", false);
}
@Test
public void testNetherTraitorBattlefieldAndDies() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.BATTLEFIELD, playerA, nt);
addCard(Zone.BATTLEFIELD, playerA, gb);
addCard(Zone.BATTLEFIELD, playerA, wg);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice");
setChoice(playerA, nt); // to sac
addTarget(playerA, playerB); // 1 damage
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 19);
assertGraveyardCount(playerA, nt, 1);
assertPermanentCount(playerA, wg, 1);
assertTapped("Swamp", false);
}
@Test
public void testNetherTraitorGraveyardGhoulDies() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.GRAVEYARD, playerA, nt);
addCard(Zone.BATTLEFIELD, playerA, gb);
addCard(Zone.BATTLEFIELD, playerA, wg);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice");
setChoice(playerA, wg); // to sac
addTarget(playerA, playerB); // 1 damage
setChoice(playerA, true); // yes to pay B and return
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 19);
assertGraveyardCount(playerA, wg, 1);
assertPermanentCount(playerA, nt, 1);
assertTapped("Swamp", true);
}
/* Ruling:
If Nether Traitor and another creature are put into your graveyard at the same time,
Nether Traitor's ability won't trigger.
This is because it must be in your graveyard before the creature dies in order for its ability
that returns it to the battlefield to trigger.
*/
@Test
public void testNetherTraitorGhoulBothDie() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.BATTLEFIELD, playerA, nt);
addCard(Zone.BATTLEFIELD, playerA, "Tooth and Claw");
// Sacrifice two creatures: Create a 3/1 red Beast creature token named Carnivore.
addCard(Zone.BATTLEFIELD, playerA, wg);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice two");
setChoice(playerA, wg + "^" + nt); // to sac
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 20);
assertPermanentCount(playerA, "Carnivore", 1);
assertGraveyardCount(playerA, wg, 1);
assertGraveyardCount(playerA, nt, 1);
assertTapped("Swamp", false);
}
}

View file

@ -0,0 +1,65 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.CardsInExileCount;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.constants.ComparisonType;
import mage.game.Game;
import mage.util.CardUtil;
/**
* Cards in exile condition
*
* @author Jmlundeen
*/
public class CardsInExileCondition implements Condition
{
private final ComparisonType type;
private final int count;
private final DynamicValue cardsInExileCount;
public CardsInExileCondition(ComparisonType type, int count)
{
this(type, count, CardsInExileCount.ALL);
}
public CardsInExileCondition(ComparisonType type, int count, DynamicValue cardsInExileCount)
{
this.type = type;
this.count = count;
this.cardsInExileCount = cardsInExileCount;
}
@Override
public boolean apply(Game game, Ability source)
{
int exileCards = cardsInExileCount.calculate(game, source, null);
return ComparisonType.compare(exileCards, type, count);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("there are ");
String countString = CardUtil.numberToText(count);
switch (type) {
case MORE_THAN:
sb.append("more than ").append(countString).append(" ");
break;
case FEWER_THAN:
sb.append("fewer than ").append(countString).append(" ");
break;
case OR_LESS:
sb.append(countString).append(" or less ");
break;
case OR_GREATER:
sb.append(countString).append(" or more ");
break;
default:
throw new IllegalArgumentException("comparison rules for " + type + " missing");
}
sb.append("cards in exile");
return sb.toString();
}
}

View file

@ -0,0 +1,81 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.cards.Card;
import mage.game.Game;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Stream;
/**
* @author Jmlundeen
*/
public enum CardsInExileCount implements DynamicValue {
YOU("you"),
ALL("all players"),
OPPONENTS("your opponents'");
private final String message;
private final ValueHint hint;
CardsInExileCount(String message) {
this.message = "The number of cards owned by " + message + " in exile";
this.hint = new ValueHint(this.message, this);
}
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return getExileCards(game, sourceAbility)
.mapToInt(x -> 1)
.sum();
}
@Override
public CardsInExileCount copy() {
return this;
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return message;
}
public Hint getHint() {
return hint;
}
public Stream<Card> getExileCards(Game game, Ability ability) {
Collection<UUID> playerIds;
switch (this) {
case YOU:
playerIds = Collections.singletonList(ability.getControllerId());
break;
case OPPONENTS:
playerIds = game.getOpponents(ability.getControllerId());
break;
case ALL:
playerIds = game.getState().getPlayersInRange(ability.getControllerId(), game);
break;
default:
throw new IllegalArgumentException("Wrong code usage: miss implementation for " + this);
}
return playerIds.stream()
.map(game::getPlayer)
.filter(Objects::nonNull)
.map(player -> game.getExile().getAllCards(game, player.getId()))
.flatMap(Collection::stream)
.filter(Objects::nonNull);
}
}

View file

@ -0,0 +1,33 @@
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.watchers.common.CreaturesDiedWatcher;
public enum CreaturesYouControlDiedCount implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game.getState()
.getWatcher(CreaturesDiedWatcher.class)
.getAmountOfCreaturesDiedThisTurnByController(sourceAbility.getControllerId());
}
@Override
public CreaturesYouControlDiedCount copy() {
return this;
}
@Override
public String getMessage() {
return "creature that died under your control this turn";
}
@Override
public String toString() {
return "1";
}
}

View file

@ -98,16 +98,12 @@ public class DoIfCostPaid extends OneShotEffect {
if (player == null || mageObject == null) {
return false;
}
// nothing to pay (do not support mana cost - it's true all the time)
if (!this.cost.canPay(source, source, player.getId(), game)) {
return false;
}
String message = CardUtil.replaceSourceName(makeChooseText(game, source), mageObject.getName());
Outcome payOutcome = executingEffects.getOutcome(source, this.outcome);
// nothing to pay (do not support mana cost - it's true all the time)
boolean canPay = cost.canPay(source, source, player.getId(), game);
boolean didPay = false;
if (!optional || player.chooseUse(payOutcome, message, source, game)) {
if (canPay && (!optional || player.chooseUse(payOutcome, message, source, game))) {
cost.clearPaid();
int bookmark = game.bookmarkState();
if (cost.pay(source, game, source, player.getId(), false)) {

View file

@ -1,48 +0,0 @@
package mage.abilities.effects.common;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.watchers.common.AttackedThisTurnWatcher;
import java.util.Set;
/**
* @author LevelX2
*/
public class UntapAllThatAttackedEffect extends OneShotEffect {
public UntapAllThatAttackedEffect() {
super(Outcome.Benefit);
staticText = "Untap all creatures that attacked this turn";
}
protected UntapAllThatAttackedEffect(final UntapAllThatAttackedEffect effect) {
super(effect);
}
@Override
public UntapAllThatAttackedEffect copy() {
return new UntapAllThatAttackedEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class);
if (watcher != null) {
Set<MageObjectReference> attackedThisTurn = watcher.getAttackedThisTurnCreatures();
for (MageObjectReference mor : attackedThisTurn) {
Permanent permanent = mor.getPermanent(game);
if (permanent != null && permanent.isCreature(game)) {
permanent.untap(game);
}
}
return true;
}
return false;
}
}

View file

@ -0,0 +1,42 @@
package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
public class VehiclesBecomeArtifactCreatureEffect extends ContinuousEffectImpl {
public VehiclesBecomeArtifactCreatureEffect(Duration duration) {
super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.BecomeCreature);
staticText = "Vehicles you control become artifact creatures until end of turn";
}
private VehiclesBecomeArtifactCreatureEffect(final VehiclesBecomeArtifactCreatureEffect effect) {
super(effect);
}
@Override
public VehiclesBecomeArtifactCreatureEffect copy() {
return new VehiclesBecomeArtifactCreatureEffect(this);
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) {
if (permanent != null && permanent.hasSubtype(SubType.VEHICLE, game)) {
if (sublayer == SubLayer.NA) {
permanent.addCardType(game, CardType.ARTIFACT);
permanent.addCardType(game, CardType.CREATURE);// TODO: Check if giving CREATURE Type is correct
}
}
}
return true;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
}

View file

@ -3,20 +3,36 @@ package mage.abilities.keyword;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.effects.Effect;
import mage.constants.AsThoughEffectType;
import mage.constants.Zone;
import mage.game.Game;
/**
* @author TheElk801
*/
public class ExhaustAbility extends ActivatedAbilityImpl {
private boolean withReminderText = true;
public ExhaustAbility(Effect effect, Cost cost) {
super(Zone.BATTLEFIELD, effect, cost);
}
public ExhaustAbility(Effect effect, Cost cost, boolean withReminderText) {
super(Zone.BATTLEFIELD, effect, cost);
this.setRuleVisible(false);
this.withReminderText = withReminderText;
}
private ExhaustAbility(final ExhaustAbility ability) {
super(ability);
this.maxActivationsPerGame = 1;
this.withReminderText = ability.withReminderText;
}
public ExhaustAbility withReminderText(boolean withReminderText) {
this.withReminderText = withReminderText;
return this;
}
@Override
@ -24,8 +40,23 @@ public class ExhaustAbility extends ActivatedAbilityImpl {
return new ExhaustAbility(this);
}
@Override
public boolean hasMoreActivationsThisTurn(Game game) {
ActivationInfo info = getActivationInfo(game);
if (info != null && info.totalActivations >= maxActivationsPerGame) {
boolean canActivate = !game.getContinuousEffects()
.asThough(sourceId, AsThoughEffectType.ALLOW_EXHAUST_PER_TURN, this, controllerId, game)
.isEmpty();
if (canActivate) {
return true;
}
}
return super.hasMoreActivationsThisTurn(game);
}
@Override
public String getRule() {
return "Exhaust &mdash; " + super.getRule() + " <i>(Activate each exhaust ability only once.)</i>";
return "Exhaust &mdash; " + super.getRule() +
(withReminderText ? " <i>(Activate each exhaust ability only once.)</i>" : "");
}
}

View file

@ -54,7 +54,7 @@ public class NinjutsuAbility extends ActivatedAbilityImpl {
}
public NinjutsuAbility(Cost cost, boolean commander) {
super(commander ? Zone.ALL : Zone.HAND, new NinjutsuEffect(), cost);
super(commander ? Zone.ALL : Zone.HAND, new NinjutsuEffect(commander), cost);
this.addCost(new RevealNinjutsuCardCost(commander));
this.addCost(new ReturnAttackerToHandTargetCost());
this.commander = commander;
@ -84,14 +84,18 @@ public class NinjutsuAbility extends ActivatedAbilityImpl {
class NinjutsuEffect extends OneShotEffect {
public NinjutsuEffect() {
private final boolean commander;
NinjutsuEffect(boolean commander) {
super(Outcome.PutCreatureInPlay);
this.commander = commander;
this.staticText = "Put this card onto the battlefield "
+ "from your hand tapped and attacking";
}
protected NinjutsuEffect(final NinjutsuEffect effect) {
private NinjutsuEffect(final NinjutsuEffect effect) {
super(effect);
this.commander = effect.commander;
}
@Override
@ -102,11 +106,12 @@ class NinjutsuEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
Card card = game.getCard(source.getSourceId());
if (controller == null || card == null) {
return false;
}
Card card = game.getCard(source.getSourceId());
if (card != null) {
Zone cardZone = game.getState().getZone(card.getId());
if (cardZone == Zone.HAND || (commander && cardZone == Zone.COMMAND)) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null);
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
@ -144,6 +149,7 @@ class ReturnAttackerToHandTargetCost extends CostImpl {
public ReturnAttackerToHandTargetCost(ReturnAttackerToHandTargetCost cost) {
super(cost);
this.defendingPlayerId = cost.defendingPlayerId;
}
@Override

View file

@ -59,7 +59,10 @@ public enum AsThoughEffectType {
//
// ALLOW_FORETELL_ANYTIME:
// For Cosmos Charger effect
ALLOW_FORETELL_ANYTIME;
ALLOW_FORETELL_ANYTIME,
// ALLOW_EXHAUST_ACTIVE_ABILITY:
// Elvish Refueler effect allows Exhaust on your turn as though it hasn't been activated
ALLOW_EXHAUST_PER_TURN(true, false);
private final boolean needAffectedAbility; // mark what AsThough check must be called for specific ability, not full object (example: spell check)
private final boolean needPlayCardAbility; // mark what AsThough check must be called for play/cast abilities

View file

@ -0,0 +1,25 @@
package mage.filter.predicate.permanent;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.watchers.common.AttackedThisTurnWatcher;
/**
*
* @author balazskristof
*/
public enum AttackedThisTurnPredicate implements Predicate<Permanent> {
instance;
@Override
public boolean apply(Permanent input, Game game) {
AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class);
return watcher != null && watcher.checkIfAttacked(input, game);
}
@Override
public String toString() {
return "attacked this turn";
}
}

View file

@ -10,7 +10,7 @@ import mage.constants.SubType;
public final class CarnivoreToken extends TokenImpl {
public CarnivoreToken() {
super("Carnivore Token", "3/1 red Beast creature token");
super("Carnivore", "3/1 red Beast creature token");
cardType.add(CardType.CREATURE);
color.setRed(true);
subtype.add(SubType.BEAST);

View file

@ -1346,6 +1346,7 @@ public abstract class PlayerImpl implements Player, Serializable {
castEvent.setZone(fromZone);
game.fireEvent(castEvent);
if (spell.activate(game, allowedIdentifiers, noMana)) {
game.processAction();
GameEvent castedEvent = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST,
ability.getId(), ability, playerId, approvingObject);
castedEvent.setZone(fromZone);

View file

@ -57197,6 +57197,7 @@ Chaos, the Endless|Final Fantasy|221|U||Legendary Creature - Demon|5|5|Flying$Wh
Sin, Spira's Punishment|Final Fantasy|242|R|{4}{B}{G}{U}|Legendary Creature - Leviathan Avatar|7|7|Flying$Whenever Sin enters or attacks, exile a permanent card from your graveyard at random, then create a tapped token that's a copy of that card. If the exiled card is a land card, repeat this process.|
Cloud, Planet's Champion|Final Fantasy|552|M|{3}{R}{W}|Legendary Creature - Human Soldier Mercenary|4|4|During your turn, as long as Cloud is equipped, it has double strike and indestructible.$Equip abilities you activate that target Cloud cost {2} less to activate.|
Sephiroth, Planet's Heir|Final Fantasy|553|M|{4}{U}{B}|Legendary Creature - Human Avatar Soldier|4|4|Vigilance$When Sephiroth enters, creatures your opponents control get -2/-2 until end of turn.$Whenever a creature an opponent controls dies, put a +1/+1 counter on Sephiroth.|
Rally the Monastery|Tarkir: Dragonstorm|19|U|{3}{W}|Instant|||This spell costs {2} less to cast if you've cast another spell this turn.$Choose one —$• Create two 1/1 white Monk creature tokens with prowess.$• Up to two target creatures you control each get +2/+2 until end of turn.$• Destroy target creature with power 4 or greater.|
Smile at Death|Tarkir: Dragonstorm|24|M|{3}{W}{W}|Enchantment|||At the beginning of your upkeep, return up to two target creature cards with power 2 or less from your graveyard to the battlefield. Put a +1/+1 counter on each of those creatures.|
Sarkhan, Dragon Ascendant|Tarkir: Dragonstorm|118|R|{1}{R}|Legendary Creature - Human Druid|2|2|When Sarkhan enters, you may behold a Dragon. If you do, create a Treasure token.$Whenever a Dragon you control enters, put a +1/+1 counter on Sarkhan. Until end of turn, Sarkhan becomes a Dragon in addition to its other types and gains flying.|
Stormscale Scion|Tarkir: Dragonstorm|123|M|{4}{R}{R}|Creature - Dragon|4|4|Flying$Other Dragons you control get +1/+1.$Storm|